diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 9973cfb120..1faffbbdf7 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -6,25 +6,20 @@ src/components/structures/RoomView.js src/components/structures/ScrollPanel.js src/components/structures/SearchBox.js src/components/structures/UploadBar.js -src/components/views/avatars/BaseAvatar.js src/components/views/avatars/MemberAvatar.js src/components/views/create_room/RoomAlias.js src/components/views/dialogs/SetPasswordDialog.js -src/components/views/dialogs/UnknownDeviceDialog.js src/components/views/elements/AddressSelector.js src/components/views/elements/DirectorySearchBox.js src/components/views/elements/MemberEventListSummary.js src/components/views/elements/UserSelector.js -src/components/views/globals/MatrixToolbar.js src/components/views/globals/NewVersionBar.js -src/components/views/globals/UpdateCheckBar.js src/components/views/messages/MFileBody.js src/components/views/messages/TextualBody.js src/components/views/room_settings/ColorSettings.js src/components/views/rooms/Autocomplete.js src/components/views/rooms/AuxPanel.js src/components/views/rooms/LinkPreviewWidget.js -src/components/views/rooms/MemberDeviceInfo.js src/components/views/rooms/MemberInfo.js src/components/views/rooms/MemberList.js src/components/views/rooms/RoomList.js diff --git a/.eslintrc.js b/.eslintrc.js index 6a0576c58a..069a67e511 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -11,111 +11,36 @@ const path = require('path'); const matrixJsSdkPath = path.join(path.dirname(require.resolve('matrix-js-sdk')), '..'); module.exports = { + extends: ["matrix-org", "matrix-org/react-legacy"], parser: "babel-eslint", - extends: [matrixJsSdkPath + "/.eslintrc.js"], - plugins: [ - "react", - "react-hooks", - "flowtype", - "babel" - ], + + env: { + browser: true, + node: true, + }, globals: { LANGUAGES_FILE: "readonly", }, - env: { - es6: true, - }, - parserOptions: { - ecmaFeatures: { - jsx: true, - legacyDecorators: true, - } - }, rules: { - // eslint's built in no-invalid-this rule breaks with class properties - "no-invalid-this": "off", - // so we replace it with a version that is class property aware - "babel/no-invalid-this": "error", - - // We appear to follow this most of the time, so let's enforce it instead - // of occasionally following it (or catching it in review) - "keyword-spacing": "error", - - /** react **/ - // This just uses the react plugin to help eslint known when - // variables have been used in JSX - "react/jsx-uses-vars": "error", - // Don't mark React as unused if we're using JSX - "react/jsx-uses-react": "error", - - // bind or arrow function in props causes performance issues - // (but we currently use them in some places) - // It's disabled here, but we should using it sparingly. - "react/jsx-no-bind": "off", - "react/jsx-key": ["error"], - - // Components in JSX should always be defined. - "react/jsx-no-undef": "error", - - // Assert no spacing in JSX curly brackets - // - // - // https://github.com/yannickcr/eslint-plugin-react/blob/HEAD/docs/rules/jsx-curly-spacing.md - // - // Disabled for now - if anything we'd like to *enforce* spacing in JSX - // curly brackets for legibility, but in practice it's not clear that the - // consistency particularly improves legibility here. --Matthew - // - // "react/jsx-curly-spacing": ["error", {"when": "never", "children": {"when": "always"}}], - - // Assert spacing before self-closing JSX tags, and no spacing before or - // after the closing slash, and no spacing after the opening bracket of - // the opening tag or closing tag. - // - // https://github.com/yannickcr/eslint-plugin-react/blob/HEAD/docs/rules/jsx-tag-spacing.md - "react/jsx-tag-spacing": ["error"], - - /** flowtype **/ - "flowtype/require-parameter-type": ["warn", { - "excludeArrowFunctions": true, - }], - "flowtype/define-flow-type": "warn", - "flowtype/require-return-type": ["warn", - "always", - { - "annotateUndefined": "never", - "excludeArrowFunctions": true, - } - ], - "flowtype/space-after-type-colon": ["warn", "always"], - "flowtype/space-before-type-colon": ["warn", "never"], - - /* - * things that are errors in the js-sdk config that the current - * code does not adhere to, turned down to warn - */ - "max-len": ["warn", { - // apparently people believe the length limit shouldn't apply - // to JSX. - ignorePattern: '^\\s*<', - ignoreComments: true, - ignoreRegExpLiterals: true, - code: 120, - }], - "valid-jsdoc": ["warn"], - "new-cap": ["warn"], - "key-spacing": ["warn"], - "prefer-const": ["warn"], - - // crashes currently: https://github.com/eslint/eslint/issues/6274 - "generator-star-spacing": "off", - - "react-hooks/rules-of-hooks": "error", - "react-hooks/exhaustive-deps": "warn", - }, - settings: { - flowtype: { - onlyFilesWithFlowAnnotation: true - }, + // Things we do that break the ideal style + "no-constant-condition": "off", + "prefer-promise-reject-errors": "off", + "no-async-promise-executor": "off", + "quotes": "off", + "indent": "off", }, + + overrides: [{ + files: ["src/**/*.{ts, tsx}"], + "extends": ["matrix-org/ts"], + "rules": { + // We disable this while we're transitioning + "@typescript-eslint/no-explicit-any": "off", + // We'd rather not do this but we do + "@typescript-eslint/ban-ts-comment": "off", + + "quotes": "off", + "no-extra-boolean-cast": "off", + } + }], }; diff --git a/CHANGELOG.md b/CHANGELOG.md index 02c085d0b5..e08b2ad612 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,1467 @@ +Changes in [2.10.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.10.1) (2020-07-16) +===================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.10.0...v2.10.1) + + * Post-launch Element Web polish + [\#5002](https://github.com/matrix-org/matrix-react-sdk/pull/5002) + * Move e2e icon + [\#4992](https://github.com/matrix-org/matrix-react-sdk/pull/4992) + * Wire up new room list breadcrumbs as an ARIA Toolbar + [\#4976](https://github.com/matrix-org/matrix-react-sdk/pull/4976) + * Fix Room Tile Icon to not ignore DMs in other tags + [\#4999](https://github.com/matrix-org/matrix-react-sdk/pull/4999) + * Fix filtering by community not showing DM rooms with community members + [\#4997](https://github.com/matrix-org/matrix-react-sdk/pull/4997) + * Fix enter in new room list filter breaking things + [\#4996](https://github.com/matrix-org/matrix-react-sdk/pull/4996) + * Notify left panel of resizing when it is collapsed&expanded + [\#4995](https://github.com/matrix-org/matrix-react-sdk/pull/4995) + * When removing a filter condition, try recalculate in case it wasn't last + [\#4994](https://github.com/matrix-org/matrix-react-sdk/pull/4994) + * Create a generic ARIA toolbar component + [\#4975](https://github.com/matrix-org/matrix-react-sdk/pull/4975) + * Fix /op Slash Command + [\#4604](https://github.com/matrix-org/matrix-react-sdk/pull/4604) + * Fix copy button in share dialog + [\#4998](https://github.com/matrix-org/matrix-react-sdk/pull/4998) + * Add tooltip to Room Tile Icon + [\#4987](https://github.com/matrix-org/matrix-react-sdk/pull/4987) + * Fix names jumping on hover in irc layout + [\#4991](https://github.com/matrix-org/matrix-react-sdk/pull/4991) + * check that encryptionInfo.sender is set + [\#4988](https://github.com/matrix-org/matrix-react-sdk/pull/4988) + * Update help link + [\#4986](https://github.com/matrix-org/matrix-react-sdk/pull/4986) + * Update cover photo link + [\#4985](https://github.com/matrix-org/matrix-react-sdk/pull/4985) + +Changes in [2.10.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.10.0) (2020-07-15) +===================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.9.0...v2.10.0) + + * Incorporate new toasts into end-to-end tests + [\#4983](https://github.com/matrix-org/matrix-react-sdk/pull/4983) + * Fix TS lint errors + [\#4982](https://github.com/matrix-org/matrix-react-sdk/pull/4982) + * Fix js lint errors after rebrand merge + [\#4981](https://github.com/matrix-org/matrix-react-sdk/pull/4981) + * Fix style lint + [\#4980](https://github.com/matrix-org/matrix-react-sdk/pull/4980) + * Fix alignment of login/syncing spinner + [\#4979](https://github.com/matrix-org/matrix-react-sdk/pull/4979) + * De labs font-scaling + [\#4899](https://github.com/matrix-org/matrix-react-sdk/pull/4899) + * Remove debug logging from new room list + [\#4972](https://github.com/matrix-org/matrix-react-sdk/pull/4972) + * Tweak sticky header hiding to avoid pop + [\#4974](https://github.com/matrix-org/matrix-react-sdk/pull/4974) + * Fix show-all keyboard focus regression + [\#4973](https://github.com/matrix-org/matrix-react-sdk/pull/4973) + * Clean up TODOs, comments, and imports in the new room list + [\#4970](https://github.com/matrix-org/matrix-react-sdk/pull/4970) + * Make EffectiveMembership utils generic + [\#4971](https://github.com/matrix-org/matrix-react-sdk/pull/4971) + * Update sticky headers when breadcrumbs pop in or out + [\#4969](https://github.com/matrix-org/matrix-react-sdk/pull/4969) + * Fix show less button occluding the last tile + [\#4967](https://github.com/matrix-org/matrix-react-sdk/pull/4967) + * Ensure breadcrumbs don't keep turning themselves back on + [\#4968](https://github.com/matrix-org/matrix-react-sdk/pull/4968) + * Update top vs. bottom sticky styles separately + [\#4966](https://github.com/matrix-org/matrix-react-sdk/pull/4966) + * Ensure RoomListStore2 gets reset when the client becomes invalidated + [\#4965](https://github.com/matrix-org/matrix-react-sdk/pull/4965) + * Add fade to show more button on room list + [\#4963](https://github.com/matrix-org/matrix-react-sdk/pull/4963) + * Fix extra room tiles being rendered on smaller sublists + [\#4964](https://github.com/matrix-org/matrix-react-sdk/pull/4964) + * Ensure tag changes (leaving rooms) causes rooms to move between lists + [\#4962](https://github.com/matrix-org/matrix-react-sdk/pull/4962) + * Fix badges for font size 20 + [\#4958](https://github.com/matrix-org/matrix-react-sdk/pull/4958) + * Fix default sorting mechanics for new room list + [\#4960](https://github.com/matrix-org/matrix-react-sdk/pull/4960) + * Fix room sub list header collapse/jump interactions on bottom-most sublist + [\#4961](https://github.com/matrix-org/matrix-react-sdk/pull/4961) + * Fix room tile context menu for Historical rooms + [\#4959](https://github.com/matrix-org/matrix-react-sdk/pull/4959) + * "ignore"/"unignore" commands: validate user ID + [\#4895](https://github.com/matrix-org/matrix-react-sdk/pull/4895) + * Stop classname from overwritting baseavatar's + [\#4957](https://github.com/matrix-org/matrix-react-sdk/pull/4957) + * Remove redundant scroll-margins and fix RoomTile wrongly scrolling + [\#4952](https://github.com/matrix-org/matrix-react-sdk/pull/4952) + * Fix RoomAvatar viewAvatarOnClick to work on actual avatars instead of + default ones + [\#4953](https://github.com/matrix-org/matrix-react-sdk/pull/4953) + * Be consistent with the at-room pill avatar configurability + [\#4955](https://github.com/matrix-org/matrix-react-sdk/pull/4955) + * Room List v2 Enter in the filter field should select the first result + [\#4954](https://github.com/matrix-org/matrix-react-sdk/pull/4954) + * Enable the new room list by default + [\#4919](https://github.com/matrix-org/matrix-react-sdk/pull/4919) + * Convert ImportanceAlgorithm over to using NotificationColor instead + [\#4949](https://github.com/matrix-org/matrix-react-sdk/pull/4949) + * Internalize algorithm updates in the new room list store + [\#4951](https://github.com/matrix-org/matrix-react-sdk/pull/4951) + * Remove now-dead code from sublist resizing + [\#4950](https://github.com/matrix-org/matrix-react-sdk/pull/4950) + * Ensure triggered updates get fired for filters in the new room list + [\#4948](https://github.com/matrix-org/matrix-react-sdk/pull/4948) + * Handle off-cycle filtering updates in the new room list + [\#4947](https://github.com/matrix-org/matrix-react-sdk/pull/4947) + * Make the show more button do a clean cut on the room list while transparent + [\#4941](https://github.com/matrix-org/matrix-react-sdk/pull/4941) + * Stop safari from aggressively shrinking flex items + [\#4945](https://github.com/matrix-org/matrix-react-sdk/pull/4945) + * Fix search padding + [\#4946](https://github.com/matrix-org/matrix-react-sdk/pull/4946) + * Reduce event loop load caused by duplicate calculations in the new room list + [\#4943](https://github.com/matrix-org/matrix-react-sdk/pull/4943) + * Add an option to disable room list logging, and improve logging + [\#4944](https://github.com/matrix-org/matrix-react-sdk/pull/4944) + * Scroll fade for breadcrumbs + [\#4942](https://github.com/matrix-org/matrix-react-sdk/pull/4942) + * Auto expand room list on search + [\#4927](https://github.com/matrix-org/matrix-react-sdk/pull/4927) + * Fix rough badge alignment for community invite tiles again + [\#4939](https://github.com/matrix-org/matrix-react-sdk/pull/4939) + * Improve safety of new rooms in the room list + [\#4940](https://github.com/matrix-org/matrix-react-sdk/pull/4940) + * Don't destroy room notification states when replacing them + [\#4938](https://github.com/matrix-org/matrix-react-sdk/pull/4938) + * Move irc layout option to advanced + [\#4937](https://github.com/matrix-org/matrix-react-sdk/pull/4937) + * Potential solution to supporting transparent 'show more' buttons + [\#4932](https://github.com/matrix-org/matrix-react-sdk/pull/4932) + * Improve performance and stability in sticky headers for new room list + [\#4931](https://github.com/matrix-org/matrix-react-sdk/pull/4931) + * Move and improve notification state handling + [\#4935](https://github.com/matrix-org/matrix-react-sdk/pull/4935) + * Move list layout management to its own store + [\#4934](https://github.com/matrix-org/matrix-react-sdk/pull/4934) + * Noop first breadcrumb + [\#4933](https://github.com/matrix-org/matrix-react-sdk/pull/4933) + * Highlight "Jump to Bottom" badge when appropriate + [\#4892](https://github.com/matrix-org/matrix-react-sdk/pull/4892) + * Don't render the context menu within its trigger otherwise unhandled clicks + bubble + [\#4930](https://github.com/matrix-org/matrix-react-sdk/pull/4930) + * Protect rooms from getting lost due to complex transitions + [\#4929](https://github.com/matrix-org/matrix-react-sdk/pull/4929) + * Hide archive button + [\#4928](https://github.com/matrix-org/matrix-react-sdk/pull/4928) + * Enable options to favourite and low priority rooms + [\#4920](https://github.com/matrix-org/matrix-react-sdk/pull/4920) + * Move voip previews to bottom right corner + [\#4904](https://github.com/matrix-org/matrix-react-sdk/pull/4904) + * Focus room filter on openSearch + [\#4923](https://github.com/matrix-org/matrix-react-sdk/pull/4923) + * Swap out the resizer lib for something more stable in the new room list + [\#4924](https://github.com/matrix-org/matrix-react-sdk/pull/4924) + * Add wrapper to room list so sticky headers don't need a background + [\#4912](https://github.com/matrix-org/matrix-react-sdk/pull/4912) + * New room list view_room show_room_tile support + [\#4908](https://github.com/matrix-org/matrix-react-sdk/pull/4908) + * Convert Context Menu to TypeScript + [\#4871](https://github.com/matrix-org/matrix-react-sdk/pull/4871) + * Use html innerText for org.matrix.custom.html m.room.message room list + previews + [\#4925](https://github.com/matrix-org/matrix-react-sdk/pull/4925) + * Fix MELS summary of 3pid invite revocations + [\#4913](https://github.com/matrix-org/matrix-react-sdk/pull/4913) + * Fix sticky headers being left on display:none if they change too quickly + [\#4926](https://github.com/matrix-org/matrix-react-sdk/pull/4926) + * Fix gaps under resize handle + [\#4922](https://github.com/matrix-org/matrix-react-sdk/pull/4922) + * Fix DM handling in new room list + [\#4921](https://github.com/matrix-org/matrix-react-sdk/pull/4921) + * Respect and fix understanding of legacy options in new room list + [\#4918](https://github.com/matrix-org/matrix-react-sdk/pull/4918) + * Ensure DMs are not lost in the new room list, and clean up tag logging + [\#4916](https://github.com/matrix-org/matrix-react-sdk/pull/4916) + * Mute "Unknown room caused setting update" spam + [\#4915](https://github.com/matrix-org/matrix-react-sdk/pull/4915) + * Remove comment claiming encrypted rooms are handled incorrectly in the new + room list + [\#4917](https://github.com/matrix-org/matrix-react-sdk/pull/4917) + * Try using requestAnimationFrame if available for sticky headers + [\#4914](https://github.com/matrix-org/matrix-react-sdk/pull/4914) + * Show more/Show less keep focus in a relevant place + [\#4911](https://github.com/matrix-org/matrix-react-sdk/pull/4911) + * Change orange to our orange and do some lints + [\#4910](https://github.com/matrix-org/matrix-react-sdk/pull/4910) + * New Room List implement view_room_delta for keyboard shortcuts + [\#4900](https://github.com/matrix-org/matrix-react-sdk/pull/4900) + * New Room List accessibility + [\#4896](https://github.com/matrix-org/matrix-react-sdk/pull/4896) + * Improve room safety in the new room list + [\#4905](https://github.com/matrix-org/matrix-react-sdk/pull/4905) + * Fix a number of issues with the new room list's invites + [\#4906](https://github.com/matrix-org/matrix-react-sdk/pull/4906) + * Decrease default visible rooms down to 5 + [\#4907](https://github.com/matrix-org/matrix-react-sdk/pull/4907) + * swap order of context menu buttons so it does not jump when muted + [\#4909](https://github.com/matrix-org/matrix-react-sdk/pull/4909) + * Fix some room list sticky header instabilities + [\#4901](https://github.com/matrix-org/matrix-react-sdk/pull/4901) + * null-guard against groups with a null name in new Room List + [\#4903](https://github.com/matrix-org/matrix-react-sdk/pull/4903) + * Allow vertical scrolling on the new room list breadcrumbs + [\#4902](https://github.com/matrix-org/matrix-react-sdk/pull/4902) + * Convert things to Typescript, including languageHandler + [\#4883](https://github.com/matrix-org/matrix-react-sdk/pull/4883) + * Fix minor issues with the badges in the new room list + [\#4894](https://github.com/matrix-org/matrix-react-sdk/pull/4894) + * Radio button outline fixes including for new room list context menu + [\#4893](https://github.com/matrix-org/matrix-react-sdk/pull/4893) + * First step towards a11y in the new room list + [\#4882](https://github.com/matrix-org/matrix-react-sdk/pull/4882) + * Fix theme selector clicks bubbling out and causing context menu to float + away + [\#4891](https://github.com/matrix-org/matrix-react-sdk/pull/4891) + * Revert "Remove a bunch of noisy logging from the room list" + [\#4890](https://github.com/matrix-org/matrix-react-sdk/pull/4890) + * Remove duplicate compact settings, handle device level updates + [\#4888](https://github.com/matrix-org/matrix-react-sdk/pull/4888) + * fix notifications icons some more + [\#4887](https://github.com/matrix-org/matrix-react-sdk/pull/4887) + * Remove a bunch of noisy logging from the room list + [\#4886](https://github.com/matrix-org/matrix-react-sdk/pull/4886) + * Fix bell icon mismatch on room tile between hover and context menu + [\#4884](https://github.com/matrix-org/matrix-react-sdk/pull/4884) + * Add a null guard for message event previews + [\#4885](https://github.com/matrix-org/matrix-react-sdk/pull/4885) + * Enable the new room list by default and trigger an initial render + [\#4881](https://github.com/matrix-org/matrix-react-sdk/pull/4881) + * Fix selection states of room tiles in the new room list + [\#4879](https://github.com/matrix-org/matrix-react-sdk/pull/4879) + * Update mute icon behaviour for new room list designs + [\#4876](https://github.com/matrix-org/matrix-react-sdk/pull/4876) + * Fix alignment of avatars on community invites + [\#4878](https://github.com/matrix-org/matrix-react-sdk/pull/4878) + * Don't include empty badge container in minimized view + [\#4880](https://github.com/matrix-org/matrix-react-sdk/pull/4880) + * Fix alignment of dot badges in new room list + [\#4877](https://github.com/matrix-org/matrix-react-sdk/pull/4877) + * Reorganize and match new room list badges to old list behaviour + [\#4861](https://github.com/matrix-org/matrix-react-sdk/pull/4861) + * Implement breadcrumb notifications and scrolling + [\#4862](https://github.com/matrix-org/matrix-react-sdk/pull/4862) + * Add click-to-jump on badge in the room sublist header + [\#4875](https://github.com/matrix-org/matrix-react-sdk/pull/4875) + * Room List v2 context menu interactions + [\#4870](https://github.com/matrix-org/matrix-react-sdk/pull/4870) + * Wedge community invites into the new room list + [\#4874](https://github.com/matrix-org/matrix-react-sdk/pull/4874) + * Check whether crypto is enabled in room recovery reminder + [\#4873](https://github.com/matrix-org/matrix-react-sdk/pull/4873) + * Fix room list 2's room tile wrapping wrongly + [\#4872](https://github.com/matrix-org/matrix-react-sdk/pull/4872) + * Hide scrollbar without pixel jumping + [\#4863](https://github.com/matrix-org/matrix-react-sdk/pull/4863) + * Room Tile context menu, notifications, indicator and placement + [\#4858](https://github.com/matrix-org/matrix-react-sdk/pull/4858) + * Improve resizing interactions in the new room list + [\#4865](https://github.com/matrix-org/matrix-react-sdk/pull/4865) + * Disable use of account-level ordering options in new room list + [\#4866](https://github.com/matrix-org/matrix-react-sdk/pull/4866) + * Remove context menu on invites in new room list + [\#4867](https://github.com/matrix-org/matrix-react-sdk/pull/4867) + * Fix reaction event crashes in message previews + [\#4868](https://github.com/matrix-org/matrix-react-sdk/pull/4868) + +Changes in [2.9.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.9.0) (2020-07-03) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.9.0-rc.1...v2.9.0) + + * Upgrade to JS SDK 7.1.0 + * Remove duplicate compact settings, handle device level updates + [\#4889](https://github.com/matrix-org/matrix-react-sdk/pull/4889) + +Changes in [2.9.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.9.0-rc.1) (2020-07-01) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.8.1...v2.9.0-rc.1) + + * Upgrade to JS SDK 7.1.0-rc.1 + * Update from Weblate + [\#4869](https://github.com/matrix-org/matrix-react-sdk/pull/4869) + * Fix a number of proliferation issues in the new room list + [\#4828](https://github.com/matrix-org/matrix-react-sdk/pull/4828) + * Fix jumping to read marker for events without tiles + [\#4860](https://github.com/matrix-org/matrix-react-sdk/pull/4860) + * De-duplicate rooms from the room autocomplete provider + [\#4859](https://github.com/matrix-org/matrix-react-sdk/pull/4859) + * Add file upload button to recovery key input + [\#4847](https://github.com/matrix-org/matrix-react-sdk/pull/4847) + * Implement new design on security setup & login + [\#4831](https://github.com/matrix-org/matrix-react-sdk/pull/4831) + * Fix /join slash command via servers including room id as a via + [\#4856](https://github.com/matrix-org/matrix-react-sdk/pull/4856) + * Add Generic Expiring Toast and timing hooks + [\#4855](https://github.com/matrix-org/matrix-react-sdk/pull/4855) + * Fix Room Custom Sounds regression and make ProgressBar relevant again + [\#4846](https://github.com/matrix-org/matrix-react-sdk/pull/4846) + * Including start_sso and start_cas in redirect loop prevention + [\#4854](https://github.com/matrix-org/matrix-react-sdk/pull/4854) + * Clean up TODO comments for new room list + [\#4850](https://github.com/matrix-org/matrix-react-sdk/pull/4850) + * Show timestamp of redaction on hover + [\#4622](https://github.com/matrix-org/matrix-react-sdk/pull/4622) + * Remove the DM button from new room tiles + [\#4849](https://github.com/matrix-org/matrix-react-sdk/pull/4849) + * Hide room list show less button if it would do nothing + [\#4848](https://github.com/matrix-org/matrix-react-sdk/pull/4848) + * Improve message preview copy in new room list + [\#4823](https://github.com/matrix-org/matrix-react-sdk/pull/4823) + * Allow the tag panel to be disabled in the new room list + [\#4844](https://github.com/matrix-org/matrix-react-sdk/pull/4844) + * Make the whole user row clickable in the new room list + [\#4843](https://github.com/matrix-org/matrix-react-sdk/pull/4843) + * Add a new spinner design behind a labs flag + [\#4842](https://github.com/matrix-org/matrix-react-sdk/pull/4842) + * ts-ignore because something is made of fail + [\#4845](https://github.com/matrix-org/matrix-react-sdk/pull/4845) + * Fix Welcome.html CAS and SSO URLs not working + [\#4838](https://github.com/matrix-org/matrix-react-sdk/pull/4838) + * More small tweaks in preparation for Notifications rework + [\#4835](https://github.com/matrix-org/matrix-react-sdk/pull/4835) + * Iterate on the new room list resize handle + [\#4840](https://github.com/matrix-org/matrix-react-sdk/pull/4840) + * Update sublists for new hover states + [\#4837](https://github.com/matrix-org/matrix-react-sdk/pull/4837) + * Tweak parts of the new room list design + [\#4839](https://github.com/matrix-org/matrix-react-sdk/pull/4839) + * Implement new resize handle for dogfooding + [\#4836](https://github.com/matrix-org/matrix-react-sdk/pull/4836) + * Hide app badge count for hidden upgraded rooms (non-highlight) + [\#4834](https://github.com/matrix-org/matrix-react-sdk/pull/4834) + * Move compact modern layout checkbox to 'advanced' + [\#4822](https://github.com/matrix-org/matrix-react-sdk/pull/4822) + * Allow the user to resize the new sublists to 1 tile + [\#4825](https://github.com/matrix-org/matrix-react-sdk/pull/4825) + * Make LoggedInView a real component because it uses shouldComponentUpdate + [\#4832](https://github.com/matrix-org/matrix-react-sdk/pull/4832) + * Small tweaks in preparation for Notifications rework + [\#4829](https://github.com/matrix-org/matrix-react-sdk/pull/4829) + * Remove extraneous debug from the new left panel + [\#4826](https://github.com/matrix-org/matrix-react-sdk/pull/4826) + * Fix icons in the new user menu not showing up + [\#4824](https://github.com/matrix-org/matrix-react-sdk/pull/4824) + * Fix sticky room disappearing/jumping in search results + [\#4817](https://github.com/matrix-org/matrix-react-sdk/pull/4817) + * Show cross-signing / secret storage reset button in more cases + [\#4821](https://github.com/matrix-org/matrix-react-sdk/pull/4821) + * Use theme-capable icons in the user menu + [\#4819](https://github.com/matrix-org/matrix-react-sdk/pull/4819) + * Font support in custom themes + [\#4814](https://github.com/matrix-org/matrix-react-sdk/pull/4814) + * Decrease margin between new sublists + [\#4816](https://github.com/matrix-org/matrix-react-sdk/pull/4816) + * Update profile information in User Menu and truncate where needed + [\#4818](https://github.com/matrix-org/matrix-react-sdk/pull/4818) + * Fix MessageActionBar in irc layout + [\#4802](https://github.com/matrix-org/matrix-react-sdk/pull/4802) + * Mark messages with a black shield if the megolm session isn't trusted + [\#4797](https://github.com/matrix-org/matrix-react-sdk/pull/4797) + * Custom font selection + [\#4761](https://github.com/matrix-org/matrix-react-sdk/pull/4761) + * Use the correct timeline reference for message previews + [\#4812](https://github.com/matrix-org/matrix-react-sdk/pull/4812) + * Fix read receipt handling in the new room list + [\#4811](https://github.com/matrix-org/matrix-react-sdk/pull/4811) + * Improve unread/badge states in new room list (mk II) + [\#4805](https://github.com/matrix-org/matrix-react-sdk/pull/4805) + * Only fire setting changes for changed settings + [\#4803](https://github.com/matrix-org/matrix-react-sdk/pull/4803) + * Trigger room-specific watchers whenever a higher level change happens + [\#4804](https://github.com/matrix-org/matrix-react-sdk/pull/4804) + * Have the theme switcher set the device-level theme to match settings + [\#4810](https://github.com/matrix-org/matrix-react-sdk/pull/4810) + * Fix layout of minimized view for new room list + [\#4808](https://github.com/matrix-org/matrix-react-sdk/pull/4808) + * Fix sticky headers over/under extending themselves in the new room list + [\#4809](https://github.com/matrix-org/matrix-react-sdk/pull/4809) + * Update read receipt remainder for internal font size change + [\#4806](https://github.com/matrix-org/matrix-react-sdk/pull/4806) + * Fix some appearance tab crash and implement style nits + [\#4801](https://github.com/matrix-org/matrix-react-sdk/pull/4801) + * Add message preview for font slider + [\#4770](https://github.com/matrix-org/matrix-react-sdk/pull/4770) + * Add layout options to the appearance tab + [\#4773](https://github.com/matrix-org/matrix-react-sdk/pull/4773) + * Update from Weblate + [\#4800](https://github.com/matrix-org/matrix-react-sdk/pull/4800) + * Support accounts with cross signing but no SSSS + [\#4717](https://github.com/matrix-org/matrix-react-sdk/pull/4717) + * Look for existing verification requests after login + [\#4762](https://github.com/matrix-org/matrix-react-sdk/pull/4762) + * Add a checkpoint to index newly encrypted rooms. + [\#4611](https://github.com/matrix-org/matrix-react-sdk/pull/4611) + * Add support to paginate search results when using Seshat. + [\#4705](https://github.com/matrix-org/matrix-react-sdk/pull/4705) + * User versions in the event index. + [\#4788](https://github.com/matrix-org/matrix-react-sdk/pull/4788) + * Fix crash when filtering new room list too fast + [\#4796](https://github.com/matrix-org/matrix-react-sdk/pull/4796) + * hide search results from unknown rooms + [\#4795](https://github.com/matrix-org/matrix-react-sdk/pull/4795) + * Mark the new room list as ready for general testing + [\#4794](https://github.com/matrix-org/matrix-react-sdk/pull/4794) + * Extend QueryMatcher's sorting heuristic + [\#4784](https://github.com/matrix-org/matrix-react-sdk/pull/4784) + * Lint ts semicolons (aka. The great semicolon migration) + [\#4791](https://github.com/matrix-org/matrix-react-sdk/pull/4791) + * Revert "Use recovery keys over passphrases" + [\#4790](https://github.com/matrix-org/matrix-react-sdk/pull/4790) + * Clear `top` when not sticking headers to the top + [\#4783](https://github.com/matrix-org/matrix-react-sdk/pull/4783) + * Don't show a 'show less' button when it's impossible to collapse + [\#4785](https://github.com/matrix-org/matrix-react-sdk/pull/4785) + * Fix show less/more button occluding the list automatically + [\#4786](https://github.com/matrix-org/matrix-react-sdk/pull/4786) + * Improve room switching in the new room list + [\#4787](https://github.com/matrix-org/matrix-react-sdk/pull/4787) + * Remove labs option to cache 'passphrase' + [\#4789](https://github.com/matrix-org/matrix-react-sdk/pull/4789) + * Remove escape backslashes in non-Markdown messages + [\#4694](https://github.com/matrix-org/matrix-react-sdk/pull/4694) + +Changes in [2.8.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.8.1) (2020-06-29) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.8.0...v2.8.1) + + * Support accounts with cross signing but no SSSS + [\#4852](https://github.com/matrix-org/matrix-react-sdk/pull/4852) + +Changes in [2.8.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.8.0) (2020-06-23) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.8.0-rc.1...v2.8.0) + + * Upgrade to JS SDK 7.0.0 + * Update read receipt remainder for internal font size change + [\#4807](https://github.com/matrix-org/matrix-react-sdk/pull/4807) + * Revert "Use recovery keys over passphrases" + [\#4793](https://github.com/matrix-org/matrix-react-sdk/pull/4793) + +Changes in [2.8.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.8.0-rc.1) (2020-06-17) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.7.2...v2.8.0-rc.1) + + * Upgrade to JS SDK 7.0.0-rc.1 + * Fix Styled Checkbox and Radio Button disabled state + [\#4778](https://github.com/matrix-org/matrix-react-sdk/pull/4778) + * clean up and fix the isMasterRuleEnabled logic + [\#4782](https://github.com/matrix-org/matrix-react-sdk/pull/4782) + * Fix case-sensitivity of /me to match rest of slash commands + [\#4763](https://github.com/matrix-org/matrix-react-sdk/pull/4763) + * Add a 'show less' button to the new room list + [\#4765](https://github.com/matrix-org/matrix-react-sdk/pull/4765) + * Update from Weblate + [\#4781](https://github.com/matrix-org/matrix-react-sdk/pull/4781) + * Sticky and collapsing headers for new room list + [\#4758](https://github.com/matrix-org/matrix-react-sdk/pull/4758) + * Make the room list labs setting reload on change + [\#4780](https://github.com/matrix-org/matrix-react-sdk/pull/4780) + * Handle/hide old rooms in the room list + [\#4767](https://github.com/matrix-org/matrix-react-sdk/pull/4767) + * Add some media queries to improve UI on mobile (#3991) + [\#4656](https://github.com/matrix-org/matrix-react-sdk/pull/4656) + * Match fuzzy filtering a bit more reliably in the new room list + [\#4769](https://github.com/matrix-org/matrix-react-sdk/pull/4769) + * Improve Field ts definitions some more + [\#4777](https://github.com/matrix-org/matrix-react-sdk/pull/4777) + * Fix alignment of checkboxes in new room list's context menu + [\#4776](https://github.com/matrix-org/matrix-react-sdk/pull/4776) + * Fix Field ts def, fix LocalEchoWrapper and NotificationsEnabledController + [\#4775](https://github.com/matrix-org/matrix-react-sdk/pull/4775) + * Add presence indicators and globes to new room list + [\#4774](https://github.com/matrix-org/matrix-react-sdk/pull/4774) + * Include the sticky room when filtering in the new room list + [\#4772](https://github.com/matrix-org/matrix-react-sdk/pull/4772) + * Add a home button to the new room list menu when available + [\#4771](https://github.com/matrix-org/matrix-react-sdk/pull/4771) + * use group layout for search results + [\#4764](https://github.com/matrix-org/matrix-react-sdk/pull/4764) + * Fix m.id.phone spec compliance + [\#4757](https://github.com/matrix-org/matrix-react-sdk/pull/4757) + * User Info default power levels for ban/kick/redact to 50 as per spec + [\#4759](https://github.com/matrix-org/matrix-react-sdk/pull/4759) + * Match new room list's text search to old room list + [\#4768](https://github.com/matrix-org/matrix-react-sdk/pull/4768) + * Fix ordering of recent rooms in the new room list + [\#4766](https://github.com/matrix-org/matrix-react-sdk/pull/4766) + * Change theme selector to use new styled radio buttons + [\#4731](https://github.com/matrix-org/matrix-react-sdk/pull/4731) + * Use recovery keys over passphrases + [\#4686](https://github.com/matrix-org/matrix-react-sdk/pull/4686) + * Update from Weblate + [\#4760](https://github.com/matrix-org/matrix-react-sdk/pull/4760) + * Initial dark theme support for new room list + [\#4756](https://github.com/matrix-org/matrix-react-sdk/pull/4756) + * Support per-list options and algorithms on the new room list + [\#4754](https://github.com/matrix-org/matrix-react-sdk/pull/4754) + * Send read marker updates immediately after moving visually + [\#4755](https://github.com/matrix-org/matrix-react-sdk/pull/4755) + * Add a minimized view to the new room list + [\#4753](https://github.com/matrix-org/matrix-react-sdk/pull/4753) + * Fix e2e icon alignment in irc-layout + [\#4752](https://github.com/matrix-org/matrix-react-sdk/pull/4752) + * Add some resource leak protection to new room list badges + [\#4750](https://github.com/matrix-org/matrix-react-sdk/pull/4750) + * Fix read-receipt alignment + [\#4747](https://github.com/matrix-org/matrix-react-sdk/pull/4747) + * Show message previews on the new room list tiles + [\#4751](https://github.com/matrix-org/matrix-react-sdk/pull/4751) + * Fix various layout concerns with the new room list + [\#4749](https://github.com/matrix-org/matrix-react-sdk/pull/4749) + * Prioritize text on the clipboard over file + [\#4748](https://github.com/matrix-org/matrix-react-sdk/pull/4748) + * Move Settings flag to ts + [\#4729](https://github.com/matrix-org/matrix-react-sdk/pull/4729) + * Add a context menu to rooms in the new room list + [\#4743](https://github.com/matrix-org/matrix-react-sdk/pull/4743) + * Add hover states and basic context menu to new room list + [\#4742](https://github.com/matrix-org/matrix-react-sdk/pull/4742) + * Update resize handle for new designs in new room list + [\#4741](https://github.com/matrix-org/matrix-react-sdk/pull/4741) + * Improve general stability in the new room list + [\#4740](https://github.com/matrix-org/matrix-react-sdk/pull/4740) + * Reimplement breadcrumbs for new room list + [\#4735](https://github.com/matrix-org/matrix-react-sdk/pull/4735) + * Add styled radio buttons + [\#4744](https://github.com/matrix-org/matrix-react-sdk/pull/4744) + * Hide checkbox tick on dark backgrounds + [\#4730](https://github.com/matrix-org/matrix-react-sdk/pull/4730) + * Make checkboxes a11y friendly + [\#4746](https://github.com/matrix-org/matrix-react-sdk/pull/4746) + * EventIndex: Store and restore the encryption info for encrypted events. + [\#4738](https://github.com/matrix-org/matrix-react-sdk/pull/4738) + * Use IDestroyable instead of IDisposable + [\#4739](https://github.com/matrix-org/matrix-react-sdk/pull/4739) + * Add/improve badge counts in new room list + [\#4734](https://github.com/matrix-org/matrix-react-sdk/pull/4734) + * Convert FormattingUtils to TypeScript and add badge utility function + [\#4732](https://github.com/matrix-org/matrix-react-sdk/pull/4732) + * Add filtering and exploring to the new room list + [\#4736](https://github.com/matrix-org/matrix-react-sdk/pull/4736) + * Support prioritized room list filters + [\#4737](https://github.com/matrix-org/matrix-react-sdk/pull/4737) + * Clean up font scaling appearance + [\#4733](https://github.com/matrix-org/matrix-react-sdk/pull/4733) + * Add user menu to new room list + [\#4722](https://github.com/matrix-org/matrix-react-sdk/pull/4722) + * New room list basic styling and layout + [\#4711](https://github.com/matrix-org/matrix-react-sdk/pull/4711) + * Fix read receipt overlap + [\#4727](https://github.com/matrix-org/matrix-react-sdk/pull/4727) + * Load correct default font size + [\#4726](https://github.com/matrix-org/matrix-react-sdk/pull/4726) + * send state of lowBandwidth in rageshakes + [\#4724](https://github.com/matrix-org/matrix-react-sdk/pull/4724) + * Change internal font size from from 15 to 10 + [\#4725](https://github.com/matrix-org/matrix-react-sdk/pull/4725) + * Upgrade deps + [\#4723](https://github.com/matrix-org/matrix-react-sdk/pull/4723) + * Ensure active Jitsi conference is closed on widget pop-out + [\#4444](https://github.com/matrix-org/matrix-react-sdk/pull/4444) + * Introduce sticky rooms to the new room list + [\#4720](https://github.com/matrix-org/matrix-react-sdk/pull/4720) + * Handle remaining cases for room updates in new room list + [\#4721](https://github.com/matrix-org/matrix-react-sdk/pull/4721) + * Allow searching the emoji picker using other emoji + [\#4719](https://github.com/matrix-org/matrix-react-sdk/pull/4719) + * New room list scrolling and resizing + [\#4697](https://github.com/matrix-org/matrix-react-sdk/pull/4697) + * Don't show FormatBar if composer is empty + [\#4696](https://github.com/matrix-org/matrix-react-sdk/pull/4696) + * Split the left panel into new and old for new room list designs + [\#4687](https://github.com/matrix-org/matrix-react-sdk/pull/4687) + * Fix compact layout regression + [\#4712](https://github.com/matrix-org/matrix-react-sdk/pull/4712) + * fix emoji in safari + [\#4710](https://github.com/matrix-org/matrix-react-sdk/pull/4710) + * Fix not being able to dismiss new login toasts + [\#4709](https://github.com/matrix-org/matrix-react-sdk/pull/4709) + * Fix exceptions from Tooltip + [\#4708](https://github.com/matrix-org/matrix-react-sdk/pull/4708) + * Stop removing variation selector from quick reactions + [\#4707](https://github.com/matrix-org/matrix-react-sdk/pull/4707) + * Tidy up continuation algorithm and make it work for hidden profile changes + [\#4704](https://github.com/matrix-org/matrix-react-sdk/pull/4704) + * Profile settings should never show a disambiguated display name + [\#4699](https://github.com/matrix-org/matrix-react-sdk/pull/4699) + * Prevent (double) 4S bootstrap from RestoreKeyBackupDialog + [\#4701](https://github.com/matrix-org/matrix-react-sdk/pull/4701) + * Stop checkbox styling bleeding through room address selector + [\#4691](https://github.com/matrix-org/matrix-react-sdk/pull/4691) + * Center HeaderButtons + [\#4695](https://github.com/matrix-org/matrix-react-sdk/pull/4695) + * Add .well-known option to control default e2ee behaviour + [\#4605](https://github.com/matrix-org/matrix-react-sdk/pull/4605) + * Add max-width to right and left panels + [\#4692](https://github.com/matrix-org/matrix-react-sdk/pull/4692) + * Fix login loop where the sso flow returns to `#/login` + [\#4685](https://github.com/matrix-org/matrix-react-sdk/pull/4685) + * Don't clear MAU toasts when a successful sync comes in + [\#4690](https://github.com/matrix-org/matrix-react-sdk/pull/4690) + * Add initial filtering support to new room list + [\#4681](https://github.com/matrix-org/matrix-react-sdk/pull/4681) + * Bubble up a decline-to-render of verification events to outside wrapper + [\#4664](https://github.com/matrix-org/matrix-react-sdk/pull/4664) + * upgrade to twemoji 13.0.0 + [\#4672](https://github.com/matrix-org/matrix-react-sdk/pull/4672) + * Apply FocusLock to ImageView to capture Escape handling + [\#4666](https://github.com/matrix-org/matrix-react-sdk/pull/4666) + * Fix the 'complete security' screen + [\#4689](https://github.com/matrix-org/matrix-react-sdk/pull/4689) + * add null-guard for Autocomplete containerRef + [\#4688](https://github.com/matrix-org/matrix-react-sdk/pull/4688) + * Remove legacy codepaths for Unknown Device Error (UDE/UDD) handling + [\#4660](https://github.com/matrix-org/matrix-react-sdk/pull/4660) + * Remove feature_cross_signing + [\#4655](https://github.com/matrix-org/matrix-react-sdk/pull/4655) + * Autocomplete: use scrollIntoView for auto-scroll to fix it + [\#4670](https://github.com/matrix-org/matrix-react-sdk/pull/4670) + +Changes in [2.7.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.7.2) (2020-06-16) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.7.1...v2.7.2) + + * Upgrade to JS SDK 6.2.2 + +Changes in [2.7.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.7.1) (2020-06-05) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.7.0...v2.7.1) + + * Upgrade to JS SDK 6.2.1 + * Fix exceptions from Tooltip + [\#4716](https://github.com/matrix-org/matrix-react-sdk/pull/4716) + * Fix not being able to dismiss new login toasts + [\#4715](https://github.com/matrix-org/matrix-react-sdk/pull/4715) + * Fix compact layout regression + [\#4714](https://github.com/matrix-org/matrix-react-sdk/pull/4714) + +Changes in [2.7.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.7.0) (2020-06-04) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.7.0-rc.2...v2.7.0) + + * Upgrade to JS SDK 6.2.0 + * Prevent (double) 4S bootstrap from RestoreKeyBackupDialog + [\#4703](https://github.com/matrix-org/matrix-react-sdk/pull/4703) + * Fix checkbox bleed + [\#4702](https://github.com/matrix-org/matrix-react-sdk/pull/4702) + * Fix login loop where the sso flow returns to `#/login` to release + [\#4693](https://github.com/matrix-org/matrix-react-sdk/pull/4693) + +Changes in [2.7.0-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.7.0-rc.2) (2020-06-02) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.7.0-rc.1...v2.7.0-rc.2) + + * Rewire the Sticker button to be an Emoji Picker + [\#3747](https://github.com/matrix-org/matrix-react-sdk/pull/3747) + +Changes in [2.7.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.7.0-rc.1) (2020-06-02) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.6.1...v2.7.0-rc.1) + + * Upgrade to JS SDK 6.2.0-rc.1 + * Update from Weblate + [\#4683](https://github.com/matrix-org/matrix-react-sdk/pull/4683) + * Make auth argument in the register request compliant with r0.6.0 + [\#4347](https://github.com/matrix-org/matrix-react-sdk/pull/4347) + * Revert "Prevent PersistedElements overflowing scrolled areas" + [\#4682](https://github.com/matrix-org/matrix-react-sdk/pull/4682) + * Remove unused TagPanelButtons + [\#4680](https://github.com/matrix-org/matrix-react-sdk/pull/4680) + * Pass roomId to IRCTimelineProfileResizer + [\#4679](https://github.com/matrix-org/matrix-react-sdk/pull/4679) + * Remove logging to console for irc name resize + [\#4678](https://github.com/matrix-org/matrix-react-sdk/pull/4678) + * Use arrow functions instead of binding `this` + [\#4677](https://github.com/matrix-org/matrix-react-sdk/pull/4677) + * Increase specificity of compact layout selectors + [\#4675](https://github.com/matrix-org/matrix-react-sdk/pull/4675) + * Create and use stylised checkboxes + [\#4665](https://github.com/matrix-org/matrix-react-sdk/pull/4665) + * useIRCLayout moved to props + [\#4676](https://github.com/matrix-org/matrix-react-sdk/pull/4676) + * Fix paste image to upload + [\#4674](https://github.com/matrix-org/matrix-react-sdk/pull/4674) + * Fix FilePanel and NotificationsPanel regression + [\#4647](https://github.com/matrix-org/matrix-react-sdk/pull/4647) + * Allow deferring of Update Toast until the next morning + [\#4669](https://github.com/matrix-org/matrix-react-sdk/pull/4669) + * Give contextual feedback for manual update check instead of banner + [\#4668](https://github.com/matrix-org/matrix-react-sdk/pull/4668) + * Dialog wrap title instead of taking same space as the close/cancel button + [\#4659](https://github.com/matrix-org/matrix-react-sdk/pull/4659) + * Update Modular hosting link + [\#4627](https://github.com/matrix-org/matrix-react-sdk/pull/4627) + * Fix field placeholder regression + [\#4663](https://github.com/matrix-org/matrix-react-sdk/pull/4663) + * Fix/document a number of UIA oddities + [\#4667](https://github.com/matrix-org/matrix-react-sdk/pull/4667) + * Stop copy icon repeating weirdly + [\#4662](https://github.com/matrix-org/matrix-react-sdk/pull/4662) + * Try and fix the Notifier race + [\#4661](https://github.com/matrix-org/matrix-react-sdk/pull/4661) + * set the client's pickle key if the platform can store one + [\#4657](https://github.com/matrix-org/matrix-react-sdk/pull/4657) + * Migrate Banners to Toasts + [\#4624](https://github.com/matrix-org/matrix-react-sdk/pull/4624) + * Move Appearance tab to ts + [\#4658](https://github.com/matrix-org/matrix-react-sdk/pull/4658) + * Fix room alias lookup vs peeking race condition + [\#4606](https://github.com/matrix-org/matrix-react-sdk/pull/4606) + * Fix encryption icon miss-alignment + [\#4651](https://github.com/matrix-org/matrix-react-sdk/pull/4651) + * Fix sublist sizing regression + [\#4649](https://github.com/matrix-org/matrix-react-sdk/pull/4649) + * Fix lines overflowing room list width + [\#4650](https://github.com/matrix-org/matrix-react-sdk/pull/4650) + * Remove the keyshare dialog + [\#4648](https://github.com/matrix-org/matrix-react-sdk/pull/4648) + * Update badge counts in new room list as needed + [\#4654](https://github.com/matrix-org/matrix-react-sdk/pull/4654) + * EventIndex: Handle invalid m.room.redaction events correctly. + [\#4653](https://github.com/matrix-org/matrix-react-sdk/pull/4653) + * EventIndex: Print out the checkpoint if there was an error during a crawl + [\#4652](https://github.com/matrix-org/matrix-react-sdk/pull/4652) + * Move Field to Typescript + [\#4635](https://github.com/matrix-org/matrix-react-sdk/pull/4635) + * Use connection error to detect network problem + [\#4646](https://github.com/matrix-org/matrix-react-sdk/pull/4646) + * Revert default font size to 15px + [\#4641](https://github.com/matrix-org/matrix-react-sdk/pull/4641) + * Add logging when room join fails + [\#4645](https://github.com/matrix-org/matrix-react-sdk/pull/4645) + * Remove EncryptedEventDialog + [\#4644](https://github.com/matrix-org/matrix-react-sdk/pull/4644) + * Migrate Toasts to Typescript and to granular priority system + [\#4618](https://github.com/matrix-org/matrix-react-sdk/pull/4618) + * Update Crypto Store Too New copy + [\#4632](https://github.com/matrix-org/matrix-react-sdk/pull/4632) + * MemberAvatar should not have its own letter fallback, it should use + BaseAvatar + [\#4643](https://github.com/matrix-org/matrix-react-sdk/pull/4643) + * Fix media upload issues with abort and status bar + [\#4630](https://github.com/matrix-org/matrix-react-sdk/pull/4630) + * fix viewGroup to actually show the group if possible + [\#4633](https://github.com/matrix-org/matrix-react-sdk/pull/4633) + * Update confirm passphrase copy + [\#4634](https://github.com/matrix-org/matrix-react-sdk/pull/4634) + * Improve accessibility of the emoji picker + [\#4636](https://github.com/matrix-org/matrix-react-sdk/pull/4636) + * Fix Emoji Picker footer being too small if text overflows + [\#4631](https://github.com/matrix-org/matrix-react-sdk/pull/4631) + * Improve style of toasts to match Figma + [\#4613](https://github.com/matrix-org/matrix-react-sdk/pull/4613) + * Iterate toast count indicator more logically + [\#4620](https://github.com/matrix-org/matrix-react-sdk/pull/4620) + * Fix reacting to redactions + [\#4626](https://github.com/matrix-org/matrix-react-sdk/pull/4626) + * Fix sentMessageAndIsAlone by dispatching `message_sent` more consistently + [\#4628](https://github.com/matrix-org/matrix-react-sdk/pull/4628) + * Update from Weblate + [\#4640](https://github.com/matrix-org/matrix-react-sdk/pull/4640) + * Replace `alias` with `address` in copy for consistency + [\#4402](https://github.com/matrix-org/matrix-react-sdk/pull/4402) + * Convert MatrixClientPeg to TypeScript + [\#4638](https://github.com/matrix-org/matrix-react-sdk/pull/4638) + * Fix BaseAvatar wrongly retrying urls + [\#4629](https://github.com/matrix-org/matrix-react-sdk/pull/4629) + * Fix event highlights not being updated to reflect edits + [\#4637](https://github.com/matrix-org/matrix-react-sdk/pull/4637) + * Calculate badges in the new room list more reliably + [\#4625](https://github.com/matrix-org/matrix-react-sdk/pull/4625) + * Transition BaseAvatar to hooks + [\#4101](https://github.com/matrix-org/matrix-react-sdk/pull/4101) + * Convert BasePlatform and BaseEventIndexManager to Typescript + [\#4614](https://github.com/matrix-org/matrix-react-sdk/pull/4614) + * Fix: Tag_DM is not defined + [\#4619](https://github.com/matrix-org/matrix-react-sdk/pull/4619) + * Fix visibility of message timestamps + [\#4615](https://github.com/matrix-org/matrix-react-sdk/pull/4615) + * Rewrite the room list store + [\#4253](https://github.com/matrix-org/matrix-react-sdk/pull/4253) + * Update code style to mention switch statements + [\#4610](https://github.com/matrix-org/matrix-react-sdk/pull/4610) + * Fix key backup restore with SSSS + [\#4612](https://github.com/matrix-org/matrix-react-sdk/pull/4612) + * Handle null tokens in the crawler loop. + [\#4608](https://github.com/matrix-org/matrix-react-sdk/pull/4608) + * Font scaling settings and slider + [\#4424](https://github.com/matrix-org/matrix-react-sdk/pull/4424) + * Prevent PersistedElements overflowing scrolled areas + [\#4494](https://github.com/matrix-org/matrix-react-sdk/pull/4494) + * IRC ui layout + [\#4531](https://github.com/matrix-org/matrix-react-sdk/pull/4531) + * Remove SSSS key upgrade check from rageshake + [\#4607](https://github.com/matrix-org/matrix-react-sdk/pull/4607) + * Label the create room button better than "Add room" + [\#4603](https://github.com/matrix-org/matrix-react-sdk/pull/4603) + * Convert the dispatcher to TypeScript + [\#4593](https://github.com/matrix-org/matrix-react-sdk/pull/4593) + * Consolidate password/passphrase fields into a component & add dynamic colour + to progress + [\#4599](https://github.com/matrix-org/matrix-react-sdk/pull/4599) + * UserView, show Welcome page in the mid panel instead of empty space + [\#4590](https://github.com/matrix-org/matrix-react-sdk/pull/4590) + * Update from Weblate + [\#4601](https://github.com/matrix-org/matrix-react-sdk/pull/4601) + * Make email auth component fail better if server claims email isn't validated + [\#4600](https://github.com/matrix-org/matrix-react-sdk/pull/4600) + * Add new keyboard shortcuts for jump to unread and upload file + [\#4588](https://github.com/matrix-org/matrix-react-sdk/pull/4588) + * accept and linkify local domains like those from mDNS + [\#4594](https://github.com/matrix-org/matrix-react-sdk/pull/4594) + * Revert "ImageView make clicking off it easier" + [\#4586](https://github.com/matrix-org/matrix-react-sdk/pull/4586) + * wrap node-qrcode in a React FC and use it for ShareDialog + [\#4394](https://github.com/matrix-org/matrix-react-sdk/pull/4394) + * Pass screenAfterLogin through SSO in the callback url + [\#4585](https://github.com/matrix-org/matrix-react-sdk/pull/4585) + * Remove debugging that causes email addresses to load forever + [\#4597](https://github.com/matrix-org/matrix-react-sdk/pull/4597) + +Changes in [2.6.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.6.1) (2020-05-22) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.6.0...v2.6.1) + + * Fix key backup restore with SSSS + [\#4617](https://github.com/matrix-org/matrix-react-sdk/pull/4617) + * Remove SSSS key upgrade check from rageshake + [\#4616](https://github.com/matrix-org/matrix-react-sdk/pull/4616) + +Changes in [2.6.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.6.0) (2020-05-19) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.6.0-rc.1...v2.6.0) + + * Upgrade to JS SDK 6.1.0 + * Revert "ImageView make clicking off it easier" + [\#4602](https://github.com/matrix-org/matrix-react-sdk/pull/4602) + * Remove debugging that causes email addresses to load forever (to release) + [\#4598](https://github.com/matrix-org/matrix-react-sdk/pull/4598) + +Changes in [2.6.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.6.0-rc.1) (2020-05-14) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.5.0...v2.6.0-rc.1) + + * Upgrade to JS SDK 6.1.0-rc.1 + * Update from Weblate + [\#4596](https://github.com/matrix-org/matrix-react-sdk/pull/4596) + * Fix message edits dialog being wrong and sometimes crashing + [\#4595](https://github.com/matrix-org/matrix-react-sdk/pull/4595) + * Acquire a new session before enacting deactivation + [\#4584](https://github.com/matrix-org/matrix-react-sdk/pull/4584) + * Remove UI for upgrading 4S to symmetric encryption + [\#4581](https://github.com/matrix-org/matrix-react-sdk/pull/4581) + * Add copy to SSO prompts during cross-signing setup + [\#4555](https://github.com/matrix-org/matrix-react-sdk/pull/4555) + * Re-fix OpenID requests from widgets + [\#4592](https://github.com/matrix-org/matrix-react-sdk/pull/4592) + * Fix persistent widgets on desktop / http + [\#4591](https://github.com/matrix-org/matrix-react-sdk/pull/4591) + * Updated link and added:Yarn two is not yet used. + [\#4589](https://github.com/matrix-org/matrix-react-sdk/pull/4589) + * Fix topic dialog not supporting escape as it didn't have a "Close" + [\#4578](https://github.com/matrix-org/matrix-react-sdk/pull/4578) + * Default to public room when creating room from room directory + [\#4579](https://github.com/matrix-org/matrix-react-sdk/pull/4579) + * Replace png flags and add Kosovo to country code dropdown + [\#4576](https://github.com/matrix-org/matrix-react-sdk/pull/4576) + * Rename `trash (custom).svg` as electron doesn't like paths with spaces + [\#4583](https://github.com/matrix-org/matrix-react-sdk/pull/4583) + * Fix sign in / up links on previewed rooms + [\#4582](https://github.com/matrix-org/matrix-react-sdk/pull/4582) + * Avoid soft crash if unknown device in verification + [\#4580](https://github.com/matrix-org/matrix-react-sdk/pull/4580) + * Add slash commands /query and /msg to match IRC + [\#4568](https://github.com/matrix-org/matrix-react-sdk/pull/4568) + * Send cross-signing debug booleans over rageshake + [\#4570](https://github.com/matrix-org/matrix-react-sdk/pull/4570) + * Prompt user to specify an alternate server if theirs has registration off + [\#4575](https://github.com/matrix-org/matrix-react-sdk/pull/4575) + * Don't try and redact redactions for "Remove recent messages" + [\#4573](https://github.com/matrix-org/matrix-react-sdk/pull/4573) + * View Source should target the replacing event rather than the root one + [\#4571](https://github.com/matrix-org/matrix-react-sdk/pull/4571) + * Fix passphrase reset in key backup restore dialog + [\#4569](https://github.com/matrix-org/matrix-react-sdk/pull/4569) + * Ensure key backup gets dealt with correctly during secret storage reset + [\#4556](https://github.com/matrix-org/matrix-react-sdk/pull/4556) + * Fix crash for broken invites + [\#4565](https://github.com/matrix-org/matrix-react-sdk/pull/4565) + * Fix rageshake with no matrix client + [\#4572](https://github.com/matrix-org/matrix-react-sdk/pull/4572) + * Update from Weblate + [\#4567](https://github.com/matrix-org/matrix-react-sdk/pull/4567) + * Bring back UnknownBody for UISIs + [\#4564](https://github.com/matrix-org/matrix-react-sdk/pull/4564) + * clear tag panel selection if the community selected is left + [\#4559](https://github.com/matrix-org/matrix-react-sdk/pull/4559) + * Close ImageView when redacting + [\#4560](https://github.com/matrix-org/matrix-react-sdk/pull/4560) + * Redesign redactions + [\#4484](https://github.com/matrix-org/matrix-react-sdk/pull/4484) + * Don't try to reload profile information when closing the user panel + [\#4547](https://github.com/matrix-org/matrix-react-sdk/pull/4547) + * Fix right panel hiding when viewing room member + [\#4558](https://github.com/matrix-org/matrix-react-sdk/pull/4558) + * Don't erase password confirm on registration error + [\#4540](https://github.com/matrix-org/matrix-react-sdk/pull/4540) + * Add a loading state for email addresses/phone numbers in settings + [\#4557](https://github.com/matrix-org/matrix-react-sdk/pull/4557) + * set the meta tag for theme-color to the same theme css background + [\#4554](https://github.com/matrix-org/matrix-react-sdk/pull/4554) + * Update Invite Dialog copy to include email addresses + [\#4497](https://github.com/matrix-org/matrix-react-sdk/pull/4497) + * Fix slider toggle regression. + [\#4546](https://github.com/matrix-org/matrix-react-sdk/pull/4546) + * Fix a crash where a name could unexpectedly be an empty list + [\#4552](https://github.com/matrix-org/matrix-react-sdk/pull/4552) + * Solves communities can be dragged from context menu + [\#4492](https://github.com/matrix-org/matrix-react-sdk/pull/4492) + * Remove prefixes for composer avatar urls + [\#4553](https://github.com/matrix-org/matrix-react-sdk/pull/4553) + * Fix reply RR spacing getting doubled + [\#4541](https://github.com/matrix-org/matrix-react-sdk/pull/4541) + * Differentiate copy for own untrusted device dialog + [\#4549](https://github.com/matrix-org/matrix-react-sdk/pull/4549) + * EventIndex: Reduce the logging the event index is producing. + [\#4548](https://github.com/matrix-org/matrix-react-sdk/pull/4548) + * Increase rageshake size limit to 5mb + [\#4543](https://github.com/matrix-org/matrix-react-sdk/pull/4543) + * Update from Weblate + [\#4542](https://github.com/matrix-org/matrix-react-sdk/pull/4542) + * Guard against race when waiting for cross-signing to be ready + [\#4539](https://github.com/matrix-org/matrix-react-sdk/pull/4539) + * Wait for user to be verified in e2e setup + [\#4537](https://github.com/matrix-org/matrix-react-sdk/pull/4537) + * Convert MatrixChat to a TypeScript class + [\#4462](https://github.com/matrix-org/matrix-react-sdk/pull/4462) + * Mark room as read when escape is pressed + [\#4271](https://github.com/matrix-org/matrix-react-sdk/pull/4271) + * Only show key backup reminder when confirmed by server to be missing + [\#4534](https://github.com/matrix-org/matrix-react-sdk/pull/4534) + * Add device name to unverified session toast + [\#4535](https://github.com/matrix-org/matrix-react-sdk/pull/4535) + * Show progress when loading keys + [\#4507](https://github.com/matrix-org/matrix-react-sdk/pull/4507) + * Fix device verification toasts not disappearing + [\#4532](https://github.com/matrix-org/matrix-react-sdk/pull/4532) + * Update toast copy again + [\#4529](https://github.com/matrix-org/matrix-react-sdk/pull/4529) + * Re-apply theme after login + [\#4518](https://github.com/matrix-org/matrix-react-sdk/pull/4518) + * Reduce maximum width of toasts & allow multiple lines + [\#4525](https://github.com/matrix-org/matrix-react-sdk/pull/4525) + * Treat sessions that are there when we log in as old + [\#4524](https://github.com/matrix-org/matrix-react-sdk/pull/4524) + * Allow resetting storage from the access dialog + [\#4521](https://github.com/matrix-org/matrix-react-sdk/pull/4521) + * Update (bulk) unverified device toast copy + [\#4522](https://github.com/matrix-org/matrix-react-sdk/pull/4522) + * Make new device toasts appear above review toasts + [\#4519](https://github.com/matrix-org/matrix-react-sdk/pull/4519) + * Separate toasts for existing & new device verification + [\#4511](https://github.com/matrix-org/matrix-react-sdk/pull/4511) + * Slightly darker toggle off bg color + [\#4477](https://github.com/matrix-org/matrix-react-sdk/pull/4477) + * Fix pill vertical align + [\#4514](https://github.com/matrix-org/matrix-react-sdk/pull/4514) + * Fix set up encryption toast to use "set up" as action + [\#4502](https://github.com/matrix-org/matrix-react-sdk/pull/4502) + * Don't enable e2ee when inviting a 3pid + [\#4509](https://github.com/matrix-org/matrix-react-sdk/pull/4509) + * Fix internal link styling in Security Settings + [\#4510](https://github.com/matrix-org/matrix-react-sdk/pull/4510) + * Small custom theming fixes + [\#4508](https://github.com/matrix-org/matrix-react-sdk/pull/4508) + * Fix scaling issues + [\#4355](https://github.com/matrix-org/matrix-react-sdk/pull/4355) + * Aggregate device verify toasts + [\#4506](https://github.com/matrix-org/matrix-react-sdk/pull/4506) + * Support setting username and avatar colors in custom themes + [\#4503](https://github.com/matrix-org/matrix-react-sdk/pull/4503) + * only clear on continuations where the clear isn't done by SenderProfile + [\#4501](https://github.com/matrix-org/matrix-react-sdk/pull/4501) + * cap width of editable item list item to leave space for its X button + [\#4495](https://github.com/matrix-org/matrix-react-sdk/pull/4495) + * Add a link from settings / devices to your user profile + [\#4498](https://github.com/matrix-org/matrix-react-sdk/pull/4498) + * Update from Weblate + [\#4496](https://github.com/matrix-org/matrix-react-sdk/pull/4496) + * Make icon change in SetupEncryptionDialog + [\#4485](https://github.com/matrix-org/matrix-react-sdk/pull/4485) + * Remove invite only padlocks feature flag + [\#4487](https://github.com/matrix-org/matrix-react-sdk/pull/4487) + * Fix incorrect toast if security setup skipped + [\#4486](https://github.com/matrix-org/matrix-react-sdk/pull/4486) + * Revert "Update emojibase for fixed emoji codepoints and Emoji 13 support" + [\#4482](https://github.com/matrix-org/matrix-react-sdk/pull/4482) + * Fix widget URL templating (again) + [\#4481](https://github.com/matrix-org/matrix-react-sdk/pull/4481) + * Fix recovery link on login verification flow + [\#4479](https://github.com/matrix-org/matrix-react-sdk/pull/4479) + * Make avatars in pills occupy the entire space using cropping + [\#4476](https://github.com/matrix-org/matrix-react-sdk/pull/4476) + * Use WidgetType more often to avoid breaking new sticker pickers + [\#4458](https://github.com/matrix-org/matrix-react-sdk/pull/4458) + * Update logging for unmanaged widgets, and add TODO comments for other areas + [\#4460](https://github.com/matrix-org/matrix-react-sdk/pull/4460) + * Fix OpenID requests from widgets + [\#4459](https://github.com/matrix-org/matrix-react-sdk/pull/4459) + * Take encrypted message search out of labs + [\#4467](https://github.com/matrix-org/matrix-react-sdk/pull/4467) + * Fix BigEmoji for replies + [\#4475](https://github.com/matrix-org/matrix-react-sdk/pull/4475) + * Update login security copy and design to match Figma + [\#4472](https://github.com/matrix-org/matrix-react-sdk/pull/4472) + * Fix i18n of SSO UIA copy in Deactivate Account Dialog + [\#4471](https://github.com/matrix-org/matrix-react-sdk/pull/4471) + * Assert type of domNode as HTMLElement to fix build + [\#4470](https://github.com/matrix-org/matrix-react-sdk/pull/4470) + * Unignored in settings + [\#4466](https://github.com/matrix-org/matrix-react-sdk/pull/4466) + * Skip auth flow test for signing upload when password present + [\#4464](https://github.com/matrix-org/matrix-react-sdk/pull/4464) + * If user cannot set email during registration don't tell them to + [\#4461](https://github.com/matrix-org/matrix-react-sdk/pull/4461) + * Fix post-ts autocomplete, it is not null + [\#4463](https://github.com/matrix-org/matrix-react-sdk/pull/4463) + * Convert autocomplete stuff to TypeScript + [\#4452](https://github.com/matrix-org/matrix-react-sdk/pull/4452) + * Add a back button to the devtools verifications panel + [\#4455](https://github.com/matrix-org/matrix-react-sdk/pull/4455) + * Fix: wait until cross-signing keys are fetched to show verify button + [\#4456](https://github.com/matrix-org/matrix-react-sdk/pull/4456) + * Handle load error in create secret storage dialog + [\#4451](https://github.com/matrix-org/matrix-react-sdk/pull/4451) + * Allow iframes and Jitsi URLs in /addwidget + [\#4382](https://github.com/matrix-org/matrix-react-sdk/pull/4382) + * Support m.jitsi-typed widgets as Jitsi widgets + [\#4379](https://github.com/matrix-org/matrix-react-sdk/pull/4379) + * Don't recheck DeviceListener until after initial sync is finished + [\#4450](https://github.com/matrix-org/matrix-react-sdk/pull/4450) + * Fix CSS class in ButtonPlaceholder + [\#4449](https://github.com/matrix-org/matrix-react-sdk/pull/4449) + * Password Login make sure tab takes user to password field + [\#4441](https://github.com/matrix-org/matrix-react-sdk/pull/4441) + * Network Dropdown fix things not scrolling properly + [\#4439](https://github.com/matrix-org/matrix-react-sdk/pull/4439) + * ImageView make clicking off it easier + [\#4448](https://github.com/matrix-org/matrix-react-sdk/pull/4448) + * Add slash command to send a rageshake + [\#4443](https://github.com/matrix-org/matrix-react-sdk/pull/4443) + * EventIndex: Filter out events that don't have a propper content value. + [\#4446](https://github.com/matrix-org/matrix-react-sdk/pull/4446) + * Revert "Fix Filepanel scroll position state lost when room is changed" + [\#4445](https://github.com/matrix-org/matrix-react-sdk/pull/4445) + * Update seshat copy to remove trailing full stop + [\#4442](https://github.com/matrix-org/matrix-react-sdk/pull/4442) + * Fix Filepanel scroll position state lost when room is changed + [\#4388](https://github.com/matrix-org/matrix-react-sdk/pull/4388) + * Fix end-to-end tests for end-to-end encryption verification + [\#4436](https://github.com/matrix-org/matrix-react-sdk/pull/4436) + * Don't explode if the e2e test directory exists when crashing + [\#4437](https://github.com/matrix-org/matrix-react-sdk/pull/4437) + * Bump https-proxy-agent from 2.2.1 to 2.2.4 in /test/end-to-end-tests + [\#4430](https://github.com/matrix-org/matrix-react-sdk/pull/4430) + * Minor updates to e2e test instructions on Windows + [\#4432](https://github.com/matrix-org/matrix-react-sdk/pull/4432) + * Fix typo + [\#4435](https://github.com/matrix-org/matrix-react-sdk/pull/4435) + * Catch errors sooner so users can recover more easily + [\#4122](https://github.com/matrix-org/matrix-react-sdk/pull/4122) + * Rageshake: remind user of unsupported browser and send modernizr report + [\#4381](https://github.com/matrix-org/matrix-react-sdk/pull/4381) + * Design tweaks for DM Room Tiles + [\#4338](https://github.com/matrix-org/matrix-react-sdk/pull/4338) + * Don't break spills over multiple lines, ellipsis them at max-1-line + [\#4434](https://github.com/matrix-org/matrix-react-sdk/pull/4434) + * Turn the end-to-end tests back on and fix the lazy-loading tests + [\#4433](https://github.com/matrix-org/matrix-react-sdk/pull/4433) + * Fix key backup debug panel + [\#4431](https://github.com/matrix-org/matrix-react-sdk/pull/4431) + * Convert cross-signing feature flag to setting + [\#4416](https://github.com/matrix-org/matrix-react-sdk/pull/4416) + * Make RoomPublishSetting import-skinnable + [\#4428](https://github.com/matrix-org/matrix-react-sdk/pull/4428) + * Iterate cross-signing copy + [\#4425](https://github.com/matrix-org/matrix-react-sdk/pull/4425) + * Fix: ensure twemoji font is loaded when showing SAS emojis + [\#4422](https://github.com/matrix-org/matrix-react-sdk/pull/4422) + * Revert "Fix: load Twemoji before login so complete security gets the right + emojis during SAS" + [\#4421](https://github.com/matrix-org/matrix-react-sdk/pull/4421) + * Fix: load Twemoji before login so complete security gets the right emojis + during SAS + [\#4419](https://github.com/matrix-org/matrix-react-sdk/pull/4419) + * consolidate and fix copy to clipboard + [\#4410](https://github.com/matrix-org/matrix-react-sdk/pull/4410) + * Fix Message Context Menu options not displaying: block + [\#4418](https://github.com/matrix-org/matrix-react-sdk/pull/4418) + * Fix pills being broken by unescaped characters + [\#4411](https://github.com/matrix-org/matrix-react-sdk/pull/4411) + +Changes in [2.5.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.5.0) (2020-05-05) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.5.0-rc.6...v2.5.0) + + * Upgrade to JS SDK 6.0.0 + * EventIndex: Reduce the logging the event index is producing. + [\#4551](https://github.com/matrix-org/matrix-react-sdk/pull/4551) + * Differentiate copy for own untrusted device dialog + [\#4550](https://github.com/matrix-org/matrix-react-sdk/pull/4550) + * More detailed progress for key backup progress + [\#4545](https://github.com/matrix-org/matrix-react-sdk/pull/4545) + * Increase rageshake size limit to 5mb + [\#4544](https://github.com/matrix-org/matrix-react-sdk/pull/4544) + +Changes in [2.5.0-rc.6](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.5.0-rc.6) (2020-05-01) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.5.0-rc.5...v2.5.0-rc.6) + + * Upgrade to JS SDK 6.0.0-rc.2 + * Wait for user to be verified in e2e setup + [\#4538](https://github.com/matrix-org/matrix-react-sdk/pull/4538) + * Add device name to unverified session toast + [\#4536](https://github.com/matrix-org/matrix-react-sdk/pull/4536) + +Changes in [2.5.0-rc.5](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.5.0-rc.5) (2020-04-30) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.5.0-rc.4...v2.5.0-rc.5) + + * Upgrade to JS SDK 6.0.0-rc.1 + * Fix device verification toasts not disappearing + [\#4533](https://github.com/matrix-org/matrix-react-sdk/pull/4533) + * Allow resetting storage from the access dialog + [\#4526](https://github.com/matrix-org/matrix-react-sdk/pull/4526) + * Update toast copy again + [\#4530](https://github.com/matrix-org/matrix-react-sdk/pull/4530) + * Reduce maximum width of toasts & allow multiple lines + [\#4528](https://github.com/matrix-org/matrix-react-sdk/pull/4528) + * Treat sessions that are there when we log in as old + [\#4527](https://github.com/matrix-org/matrix-react-sdk/pull/4527) + * Update (bulk) unverified device toast copy + [\#4523](https://github.com/matrix-org/matrix-react-sdk/pull/4523) + * Make new device toasts appear above review toasts + [\#4520](https://github.com/matrix-org/matrix-react-sdk/pull/4520) + * Separate toasts for existing & new device verification + [\#4517](https://github.com/matrix-org/matrix-react-sdk/pull/4517) + * Aggregate device verify toasts + [\#4516](https://github.com/matrix-org/matrix-react-sdk/pull/4516) + * Fix set up encryption toast to use "set up" as action + [\#4515](https://github.com/matrix-org/matrix-react-sdk/pull/4515) + * Fix internal link styling in Security Settings + [\#4512](https://github.com/matrix-org/matrix-react-sdk/pull/4512) + * Don't enable e2ee when inviting a 3pid + [\#4513](https://github.com/matrix-org/matrix-react-sdk/pull/4513) + * only clear on continuations where the clear isn't done by SenderProfile + [\#4505](https://github.com/matrix-org/matrix-react-sdk/pull/4505) + * cap width of editable item list item to leave space for its X button + [\#4504](https://github.com/matrix-org/matrix-react-sdk/pull/4504) + * Add a link from settings / devices to your user profile + [\#4499](https://github.com/matrix-org/matrix-react-sdk/pull/4499) + * Make icon change in SetupEncryptionDialog + [\#4490](https://github.com/matrix-org/matrix-react-sdk/pull/4490) + * Remove invite only padlocks feature flag for release + [\#4488](https://github.com/matrix-org/matrix-react-sdk/pull/4488) + * Fix incorrect toast if security setup skipped + [\#4489](https://github.com/matrix-org/matrix-react-sdk/pull/4489) + * Revert "Update emojibase for fixed emoji codepoints and Emoji 13 support" + [\#4483](https://github.com/matrix-org/matrix-react-sdk/pull/4483) + * Fix recovery link on login verification flow + [\#4480](https://github.com/matrix-org/matrix-react-sdk/pull/4480) + +Changes in [2.5.0-rc.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.5.0-rc.4) (2020-04-23) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.5.0-rc.3...v2.5.0-rc.4) + + * Upgrade to JS SDK 5.3.1-rc.4 + * Take encrypted message search out of labs for release + [\#4468](https://github.com/matrix-org/matrix-react-sdk/pull/4468) + * Update login security copy and design to match Figma [to release] + [\#4474](https://github.com/matrix-org/matrix-react-sdk/pull/4474) + * Fix i18n of SSO UIA copy in Deactivate Account Dialog on release + [\#4473](https://github.com/matrix-org/matrix-react-sdk/pull/4473) + * Skip auth flow test for signing upload when password present + [\#4465](https://github.com/matrix-org/matrix-react-sdk/pull/4465) + * Fix: wait until cross-signing keys are fetched to show verify button + [\#4457](https://github.com/matrix-org/matrix-react-sdk/pull/4457) + * Handle load error in create secret storage dialog + [\#4454](https://github.com/matrix-org/matrix-react-sdk/pull/4454) + * Don't recheck DeviceListener until after initial sync is finished + [\#4450](https://github.com/matrix-org/matrix-react-sdk/pull/4450) + * EventIndex: Filter out events that don't have a propper content value. + [\#4447](https://github.com/matrix-org/matrix-react-sdk/pull/4447) + +Changes in [2.5.0-rc.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.5.0-rc.3) (2020-04-17) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.5.0-rc.2...v2.5.0-rc.3) + + * Upgrade to JS SDK 5.3.1-rc.3 + +Changes in [2.5.0-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.5.0-rc.2) (2020-04-16) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.5.0-rc.1...v2.5.0-rc.2) + + * Upgrade to JS SDK 5.3.1-rc.2 + * [Release] Convert cross-signing flag to a setting + [\#4429](https://github.com/matrix-org/matrix-react-sdk/pull/4429) + * Iterate cross-signing copy + [\#4426](https://github.com/matrix-org/matrix-react-sdk/pull/4426) + * Fix: ensure twemoji font is loaded when showing SAS emojis + [\#4423](https://github.com/matrix-org/matrix-react-sdk/pull/4423) + +Changes in [2.5.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.5.0-rc.1) (2020-04-15) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.4.0-rc.1...v2.5.0-rc.1) + + * Upgrade to JS SDK 5.3.1-rc.1 + * null-guard MatrixClientPeg in RoomViewStore + [\#4415](https://github.com/matrix-org/matrix-react-sdk/pull/4415) + * Fix: prevent spurious notifications from indexer + [\#4414](https://github.com/matrix-org/matrix-react-sdk/pull/4414) + * Login block on initialSync with spinners + [\#4413](https://github.com/matrix-org/matrix-react-sdk/pull/4413) + * Allow network dropdown to be scrollable and fix context menu padding calc + [\#4408](https://github.com/matrix-org/matrix-react-sdk/pull/4408) + * Remove end-to-end message info option when cross-signing is used + [\#4412](https://github.com/matrix-org/matrix-react-sdk/pull/4412) + * Minimize widgets by default + [\#4378](https://github.com/matrix-org/matrix-react-sdk/pull/4378) + * Add comments to highlight where we'll need m.widget support + [\#4380](https://github.com/matrix-org/matrix-react-sdk/pull/4380) + * Fix: dont try to enable 4S if cross-signing is disabled + [\#4407](https://github.com/matrix-org/matrix-react-sdk/pull/4407) + * Fix: don't confuse user with spinner during complete security step + [\#4406](https://github.com/matrix-org/matrix-react-sdk/pull/4406) + * Fix: avoid potential crash during certain verification paths + [\#4405](https://github.com/matrix-org/matrix-react-sdk/pull/4405) + * Add riot-desktop shortcuts for forward/back matching browsers&slack + [\#4392](https://github.com/matrix-org/matrix-react-sdk/pull/4392) + * Convert LoggedInView to an ES6 PureComponent Class & TypeScript + [\#4398](https://github.com/matrix-org/matrix-react-sdk/pull/4398) + * Fix width of MVideoBody in FilePanel + [\#4396](https://github.com/matrix-org/matrix-react-sdk/pull/4396) + * Remove unused react-addons-css-transition-group + [\#4397](https://github.com/matrix-org/matrix-react-sdk/pull/4397) + * Fix emoji tooltip flickering + [\#4395](https://github.com/matrix-org/matrix-react-sdk/pull/4395) + * Pass along key backup for bootstrap + [\#4374](https://github.com/matrix-org/matrix-react-sdk/pull/4374) + * Fix create room dialog e2ee private room setting + [\#4403](https://github.com/matrix-org/matrix-react-sdk/pull/4403) + * Sort emoji by shortcodes for autocomplete primarily for :-1 and :+1 + [\#4391](https://github.com/matrix-org/matrix-react-sdk/pull/4391) + * Fix invalid commands when figuring out whether to set isTyping + [\#4390](https://github.com/matrix-org/matrix-react-sdk/pull/4390) + * op/deop return error if trying to affect an unknown user + [\#4389](https://github.com/matrix-org/matrix-react-sdk/pull/4389) + * Composer pills respect showPillAvatar setting + [\#4384](https://github.com/matrix-org/matrix-react-sdk/pull/4384) + * Only send typing notification when composing commands which send messages + [\#4385](https://github.com/matrix-org/matrix-react-sdk/pull/4385) + * Reverse order of they match/they don't match buttons + [\#4386](https://github.com/matrix-org/matrix-react-sdk/pull/4386) + * Use singular text on 'delete sessions' button for SSO + [\#4383](https://github.com/matrix-org/matrix-react-sdk/pull/4383) + * Pass widget data through from sticker picker + [\#4377](https://github.com/matrix-org/matrix-react-sdk/pull/4377) + * Obliterate widgets when they are minimized + [\#4376](https://github.com/matrix-org/matrix-react-sdk/pull/4376) + * Fix image thumbnail width when read receipts are hidden + [\#4370](https://github.com/matrix-org/matrix-react-sdk/pull/4370) + * Add toggle for e2ee when creating private room + [\#4362](https://github.com/matrix-org/matrix-react-sdk/pull/4362) + * Fix logging for failed searches + [\#4372](https://github.com/matrix-org/matrix-react-sdk/pull/4372) + * Ensure UI is updated when cross-signing gets disabled + [\#4369](https://github.com/matrix-org/matrix-react-sdk/pull/4369) + * Retry the request for the master key from SSSS on login + [\#4371](https://github.com/matrix-org/matrix-react-sdk/pull/4371) + * Upgrade deps + [\#4365](https://github.com/matrix-org/matrix-react-sdk/pull/4365) + * App load tweaks, i18n and localStorage + [\#4367](https://github.com/matrix-org/matrix-react-sdk/pull/4367) + * Fix encoding of widget arguments + [\#4366](https://github.com/matrix-org/matrix-react-sdk/pull/4366) + +Changes in [2.4.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.4.0-rc.1) (2020-04-08) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.3.1...v2.4.0-rc.1) + + * Upgrade to JS SDK to 5.3.0-rc.1 + * EventIndex: Log if we had all events in a checkpoint but are continuing. + [\#4363](https://github.com/matrix-org/matrix-react-sdk/pull/4363) + * Update from Weblate + [\#4364](https://github.com/matrix-org/matrix-react-sdk/pull/4364) + * Support deactivating your account with SSO + [\#4356](https://github.com/matrix-org/matrix-react-sdk/pull/4356) + * Add debug status for cached backup key format + [\#4359](https://github.com/matrix-org/matrix-react-sdk/pull/4359) + * Fix composer placeholder not updating + [\#4361](https://github.com/matrix-org/matrix-react-sdk/pull/4361) + * Fix sas verification buttons to match figma + [\#4358](https://github.com/matrix-org/matrix-react-sdk/pull/4358) + * Don't show fallback text for verification requests + [\#4345](https://github.com/matrix-org/matrix-react-sdk/pull/4345) + * Fix share dialog correctly + [\#4360](https://github.com/matrix-org/matrix-react-sdk/pull/4360) + * Use singular copy when only deleting one device + [\#4357](https://github.com/matrix-org/matrix-react-sdk/pull/4357) + * Deem m.sticker events as actionable for reacting + [\#4288](https://github.com/matrix-org/matrix-react-sdk/pull/4288) + * Don't show spinner over encryption setup dialogs + [\#4354](https://github.com/matrix-org/matrix-react-sdk/pull/4354) + * Support Jitsi information from client .well-known + [\#4348](https://github.com/matrix-org/matrix-react-sdk/pull/4348) + * Add new default home page fallback + [\#4350](https://github.com/matrix-org/matrix-react-sdk/pull/4350) + * Check more account data in toast listener + [\#4351](https://github.com/matrix-org/matrix-react-sdk/pull/4351) + * Don't try to send presence updates until the client is started + [\#4353](https://github.com/matrix-org/matrix-react-sdk/pull/4353) + * Fix copy button on code blocks when there is no code tag just pre + [\#4352](https://github.com/matrix-org/matrix-react-sdk/pull/4352) + * Clear sessionStorage on sign out + [\#4346](https://github.com/matrix-org/matrix-react-sdk/pull/4346) + * Re-request room keys after auth + [\#4341](https://github.com/matrix-org/matrix-react-sdk/pull/4341) + * Update emojibase for fixed emoji codepoints and Emoji 13 support + [\#4344](https://github.com/matrix-org/matrix-react-sdk/pull/4344) + * App load order tweaks for code splitting + [\#4343](https://github.com/matrix-org/matrix-react-sdk/pull/4343) + * Fix alignment of e2e icon in userinfo and expose full displayname in title + [\#4312](https://github.com/matrix-org/matrix-react-sdk/pull/4312) + * Adjust copy & UX for self-verification + [\#4342](https://github.com/matrix-org/matrix-react-sdk/pull/4342) + * QR code reciprocation + [\#4334](https://github.com/matrix-org/matrix-react-sdk/pull/4334) + * Fix Hangul typing does not work properly + [\#4339](https://github.com/matrix-org/matrix-react-sdk/pull/4339) + * Fix: dismiss setup encryption toast if cross-signing is ready + [\#4336](https://github.com/matrix-org/matrix-react-sdk/pull/4336) + * Fix read marker visibility for grouped events + [\#4340](https://github.com/matrix-org/matrix-react-sdk/pull/4340) + * Make all 'font-size's and 'line-height's rem + [\#4305](https://github.com/matrix-org/matrix-react-sdk/pull/4305) + * Fix spurious extra devices on registration + [\#4337](https://github.com/matrix-org/matrix-react-sdk/pull/4337) + * Fix the edit messager composer + [\#4333](https://github.com/matrix-org/matrix-react-sdk/pull/4333) + * Fix Room Settings Dialog Notifications tab icon + [\#4321](https://github.com/matrix-org/matrix-react-sdk/pull/4321) + * Fix various cases of React warnings by silencing them + [\#4331](https://github.com/matrix-org/matrix-react-sdk/pull/4331) + * Only apply padding to standard textual buttons (kind buttons) + [\#4332](https://github.com/matrix-org/matrix-react-sdk/pull/4332) + * Use console.log in place of console.warn for less warnings + [\#4330](https://github.com/matrix-org/matrix-react-sdk/pull/4330) + * Revert componentDidMount changes on breadcrumbs + [\#4329](https://github.com/matrix-org/matrix-react-sdk/pull/4329) + * Use new method for checking secret storage key + [\#4309](https://github.com/matrix-org/matrix-react-sdk/pull/4309) + * Label and use UNSAFE_componentWillMount to minimize warnings + [\#4315](https://github.com/matrix-org/matrix-react-sdk/pull/4315) + * Fix a number of minor code quality issues + [\#4314](https://github.com/matrix-org/matrix-react-sdk/pull/4314) + * Use componentDidMount in place of componentWillMount where possible + [\#4313](https://github.com/matrix-org/matrix-react-sdk/pull/4313) + * EventIndex: Mark the initial checkpoints for a full crawl. + [\#4325](https://github.com/matrix-org/matrix-react-sdk/pull/4325) + * Fix UserInfo e2e buttons to match Figma + [\#4320](https://github.com/matrix-org/matrix-react-sdk/pull/4320) + * Only auto-scroll to RoomTile when clicking on RoomTile or via shortcuts + [\#4316](https://github.com/matrix-org/matrix-react-sdk/pull/4316) + * Support SSO for interactive authentication + [\#4292](https://github.com/matrix-org/matrix-react-sdk/pull/4292) + * Fix /invite Slash Command + [\#4328](https://github.com/matrix-org/matrix-react-sdk/pull/4328) + * Fix jitsi popout URL + [\#4326](https://github.com/matrix-org/matrix-react-sdk/pull/4326) + * Use our own jitsi widget for the popout URL + [\#4323](https://github.com/matrix-org/matrix-react-sdk/pull/4323) + * Fix popout support for jitsi widgets + [\#4319](https://github.com/matrix-org/matrix-react-sdk/pull/4319) + * Fix: legacy verify user throwing error + [\#4318](https://github.com/matrix-org/matrix-react-sdk/pull/4318) + * Document settingDefaults + [\#3046](https://github.com/matrix-org/matrix-react-sdk/pull/3046) + * Fix Ctrl+/ for Finnish keyboard where it includes Shift + [\#4317](https://github.com/matrix-org/matrix-react-sdk/pull/4317) + * Rework SlashCommands to better expose aliases + [\#4302](https://github.com/matrix-org/matrix-react-sdk/pull/4302) + * Fix EventListSummary when RR rendering is disabled + [\#4311](https://github.com/matrix-org/matrix-react-sdk/pull/4311) + * Update link to css location. + [\#4299](https://github.com/matrix-org/matrix-react-sdk/pull/4299) + * Fix peeking keeping two timeline update mechanisms in play + [\#4310](https://github.com/matrix-org/matrix-react-sdk/pull/4310) + * Pass new secret storage key to bootstrap path + [\#4308](https://github.com/matrix-org/matrix-react-sdk/pull/4308) + * Show red shield for users that become unverified + [\#4303](https://github.com/matrix-org/matrix-react-sdk/pull/4303) + * Accessibility fixed for Event List Summary and Composer Format Bar + [\#4295](https://github.com/matrix-org/matrix-react-sdk/pull/4295) + * Support $riot: Templates for SSO/CAS urls in the welcome.html page + [\#4279](https://github.com/matrix-org/matrix-react-sdk/pull/4279) + * Added the /html command + [\#4296](https://github.com/matrix-org/matrix-react-sdk/pull/4296) + * EventIndex: Better logging on how many events are added. + [\#4301](https://github.com/matrix-org/matrix-react-sdk/pull/4301) + * Field: mark id as optional in propTypes + [\#4307](https://github.com/matrix-org/matrix-react-sdk/pull/4307) + * Fix view community link icon contrast + [\#4254](https://github.com/matrix-org/matrix-react-sdk/pull/4254) + * Remove underscore from Jitsi conference names + [\#4304](https://github.com/matrix-org/matrix-react-sdk/pull/4304) + * Refactor shield display logic; changed rules for DMs + [\#4290](https://github.com/matrix-org/matrix-react-sdk/pull/4290) + * Fix: bring back global thin scrollbars + [\#4300](https://github.com/matrix-org/matrix-react-sdk/pull/4300) + * Keyboard shortcuts: Escape cancel reply and fix Ctrl+K + [\#4297](https://github.com/matrix-org/matrix-react-sdk/pull/4297) + * Field: make id optional, generate one if not provided + [\#4298](https://github.com/matrix-org/matrix-react-sdk/pull/4298) + * Fix ugly scrollbars in TabbedView (settings), emojipicker and widgets + [\#4293](https://github.com/matrix-org/matrix-react-sdk/pull/4293) + * Rename secret storage force-reset variable to avoid confusion + [\#4274](https://github.com/matrix-org/matrix-react-sdk/pull/4274) + * Fix: can't dismiss unverified session toast when encryption hasn't been + upgraded + [\#4291](https://github.com/matrix-org/matrix-react-sdk/pull/4291) + * Blank out UserInfo avatar when changing between members + [\#4289](https://github.com/matrix-org/matrix-react-sdk/pull/4289) + * Add cancel button to verification panel + [\#4283](https://github.com/matrix-org/matrix-react-sdk/pull/4283) + * Show ongoing verification request straight away when navigating to member + [\#4284](https://github.com/matrix-org/matrix-react-sdk/pull/4284) + * Fix: allow scrolling while window is not focused & remove scrollbar hack + [\#4276](https://github.com/matrix-org/matrix-react-sdk/pull/4276) + * Show whether backup key is cached + [\#4287](https://github.com/matrix-org/matrix-react-sdk/pull/4287) + * Rename unverified session toast + [\#4285](https://github.com/matrix-org/matrix-react-sdk/pull/4285) + * Fix: pick last active DM for verification request + [\#4286](https://github.com/matrix-org/matrix-react-sdk/pull/4286) + * Fix formatBar not hidden after highlight and backspacing some text + [\#4269](https://github.com/matrix-org/matrix-react-sdk/pull/4269) + +Changes in [2.3.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.3.1) (2020-04-01) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.3.0...v2.3.1) + + * Fix jitsi popout URL + [\#4327](https://github.com/matrix-org/matrix-react-sdk/pull/4327) + * Remove underscore from Jitsi conference names + [\#4324](https://github.com/matrix-org/matrix-react-sdk/pull/4324) + * Fix popout support for jitsi widgets + [\#4322](https://github.com/matrix-org/matrix-react-sdk/pull/4322) + Changes in [2.3.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.3.0) (2020-03-30) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.3.0-rc.1...v2.3.0) diff --git a/README.md b/README.md index d6fd6db1b7..5f5da9a40d 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Code should be committed as follows: * In practice, `matrix-react-sdk` is still evolving so fast that the maintenance burden of customising and overriding these components for Riot can seriously impede development. So right now, there should be very few (if any) customisations for Riot. - * CSS: https://github.com/vector-im/riot-web/tree/master/src/skins/vector/css/matrix-react-sdk + * CSS: https://github.com/matrix-org/matrix-react-sdk/tree/master/res/css * Theme specific CSS & resources: https://github.com/matrix-org/matrix-react-sdk/tree/master/res/themes React components in matrix-react-sdk are come in two different flavours: @@ -133,8 +133,10 @@ Development Ensure you have the latest LTS version of Node.js installed. -Using `yarn` instead of `npm` is recommended. Please see the Yarn [install -guide](https://yarnpkg.com/docs/install/) if you do not have it already. +Using `yarn` instead of `npm` is recommended. Please see the Yarn 1 [install +guide](https://classic.yarnpkg.com/docs/install) if you do not have it +already. This project has not yet been migrated to Yarn 2, so please ensure +`yarn --version` shows a version from the 1.x series. `matrix-react-sdk` depends on `matrix-js-sdk`. To make use of changes in the latter and to ensure tests run against the develop branch of `matrix-js-sdk`, diff --git a/code_style.md b/code_style.md index 3ad0d38873..fe04d2cc3d 100644 --- a/code_style.md +++ b/code_style.md @@ -4,7 +4,7 @@ Matrix JavaScript/ECMAScript Style Guide The intention of this guide is to make Matrix's JavaScript codebase clean, consistent with other popular JavaScript styles and consistent with the rest of the Matrix codebase. For reference, the Matrix Python style guide can be found -at https://github.com/matrix-org/synapse/blob/master/docs/code_style.rst +at https://github.com/matrix-org/synapse/blob/master/docs/code_style.md This document reflects how we would like Matrix JavaScript code to look, with acknowledgement that a significant amount of code is written to older @@ -17,7 +17,7 @@ writing in modern ECMAScript and using a transpile step to generate the file that applications can then include. There are significant benefits in being able to use modern ECMAScript, although the tooling for doing so can be awkward for library code, especially with regard to translating source maps and line -number throgh from the original code to the final application. +number through from the original code to the final application. General Style ------------- @@ -151,6 +151,7 @@ General Style Don't set things to undefined. Reserve that value to mean "not yet set to anything." Boolean objects are verboten. - Use JSDoc +- Use switch-case statements where there are 5 or more branches running against the same variable. ECMAScript ---------- diff --git a/docs/img/RoomListStore2.png b/docs/img/RoomListStore2.png new file mode 100644 index 0000000000..9952d1c910 Binary files /dev/null and b/docs/img/RoomListStore2.png differ diff --git a/docs/room-list-store.md b/docs/room-list-store.md new file mode 100644 index 0000000000..53f0527209 --- /dev/null +++ b/docs/room-list-store.md @@ -0,0 +1,151 @@ +# Room list sorting + +It's so complicated it needs its own README. + +![](img/RoomListStore2.png) + +Legend: +* Orange = External event. +* Purple = Deterministic flow. +* Green = Algorithm definition. +* Red = Exit condition/point. +* Blue = Process definition. + +## Algorithms involved + +There's two main kinds of algorithms involved in the room list store: list ordering and tag sorting. +Throughout the code an intentional decision has been made to call them the List Algorithm and Sorting +Algorithm respectively. The list algorithm determines the primary ordering of a given tag whereas the +tag sorting defines how rooms within that tag get sorted, at the discretion of the list ordering. + +Behaviour of the overall room list (sticky rooms, etc) are determined by the generically-named Algorithm +class. Here is where much of the coordination from the room list store is done to figure out which list +algorithm to call, instead of having all the logic in the room list store itself. + + +Tag sorting is effectively the comparator supplied to the list algorithm. This gives the list algorithm +the power to decide when and how to apply the tag sorting, if at all. For example, the importance algorithm, +later described in this document, heavily uses the list ordering behaviour to break the tag into categories. +Each category then gets sorted by the appropriate tag sorting algorithm. + +### Tag sorting algorithm: Alphabetical + +When used, rooms in a given tag will be sorted alphabetically, where the alphabet's order is a problem +for the browser. All we do is a simple string comparison and expect the browser to return something +useful. + +### Tag sorting algorithm: Manual + +Manual sorting makes use of the `order` property present on all tags for a room, per the +[Matrix specification](https://matrix.org/docs/spec/client_server/r0.6.0#room-tagging). Smaller values +of `order` cause rooms to appear closer to the top of the list. + +### Tag sorting algorithm: Recent + +Rooms get ordered by the timestamp of the most recent useful message. Usefulness is yet another algorithm +in the room list system which determines whether an event type is capable of bubbling up in the room list. +Normally events like room messages, stickers, and room security changes will be considered useful enough +to cause a shift in time. + +Note that this is reliant on the event timestamps of the most recent message. Because Matrix is eventually +consistent this means that from time to time a room might plummet or skyrocket across the tag due to the +timestamp contained within the event (generated server-side by the sender's server). + +### List ordering algorithm: Natural + +This is the easiest of the algorithms to understand because it does essentially nothing. It imposes no +behavioural changes over the tag sorting algorithm and is by far the simplest way to order a room list. +Historically, it's been the only option in Riot and extremely common in most chat applications due to +its relative deterministic behaviour. + +### List ordering algorithm: Importance + +On the other end of the spectrum, this is the most complicated algorithm which exists. There's major +behavioural changes, and the tag sorting algorithm gets selectively applied depending on circumstances. + +Each tag which is not manually ordered gets split into 4 sections or "categories". Manually ordered tags +simply get the manual sorting algorithm applied to them with no further involvement from the importance +algorithm. There are 4 categories: Red, Grey, Bold, and Idle. Each has their own definition based off +relative (perceived) importance to the user: + +* **Red**: The room has unread mentions waiting for the user. +* **Grey**: The room has unread notifications waiting for the user. Notifications are simply unread + messages which cause a push notification or badge count. Typically, this is the default as rooms get + set to 'All Messages'. +* **Bold**: The room has unread messages waiting for the user. Essentially this is a grey room without + a badge/notification count (or 'Mentions Only'/'Muted'). +* **Idle**: No useful (see definition of useful above) activity has occurred in the room since the user + last read it. + +Conveniently, each tag gets ordered by those categories as presented: red rooms appear above grey, grey +above bold, etc. + +Once the algorithm has determined which rooms belong in which categories, the tag sorting algorithm +gets applied to each category in a sub-list fashion. This should result in the red rooms (for example) +being sorted alphabetically amongst each other as well as the grey rooms sorted amongst each other, but +collectively the tag will be sorted into categories with red being at the top. + +## Sticky rooms + +When the user visits a room, that room becomes 'sticky' in the list, regardless of ordering algorithm. +From a code perspective, the underlying algorithm is not aware of a sticky room and instead the base class +manages which room is sticky. This is to ensure that all algorithms handle it the same. + +The sticky flag is simply to say it will not move higher or lower down the list while it is active. For +example, if using the importance algorithm, the room would naturally become idle once viewed and thus +would normally fly down the list out of sight. The sticky room concept instead holds it in place, never +letting it fly down until the user moves to another room. + +Only one room can be sticky at a time. Room updates around the sticky room will still hold the sticky +room in place. The best example of this is the importance algorithm: if the user has 3 red rooms and +selects the middle room, they will see exactly one room above their selection at all times. If they +receive another notification which causes the room to move into the topmost position, the room that was +above the sticky room will move underneath to allow for the new room to take the top slot, maintaining +the sticky room's position. + +Though only applicable to the importance algorithm, the sticky room is not aware of category boundaries +and thus the user can see a shift in what kinds of rooms move around their selection. An example would +be the user having 4 red rooms, the user selecting the third room (leaving 2 above it), and then having +the rooms above it read on another device. This would result in 1 red room and 1 other kind of room +above the sticky room as it will try to maintain 2 rooms above the sticky room. + +An exception for the sticky room placement is when there's suddenly not enough rooms to maintain the placement +exactly. This typically happens if the user selects a room and leaves enough rooms where it cannot maintain +the N required rooms above the sticky room. In this case, the sticky room will simply decrease N as needed. +The N value will never increase while selection remains unchanged: adding a bunch of rooms after having +put the sticky room in a position where it's had to decrease N will not increase N. + +## Responsibilities of the store + +The store is responsible for the ordering, upkeep, and tracking of all rooms. The room list component simply gets +an object containing the tags it needs to worry about and the rooms within. The room list component will +decide which tags need rendering (as it commonly filters out empty tags in most cases), and will deal with +all kinds of filtering. + +## Filtering + +Filters are provided to the store as condition classes, which are then passed along to the algorithm +implementations. The implementations then get to decide how to actually filter the rooms, however in +practice the base `Algorithm` class deals with the filtering in a more optimized/generic way. + +The results of filters get cached to avoid needlessly iterating over potentially thousands of rooms, +as the old room list store does. When a filter condition changes, it emits an update which (in this +case) the `Algorithm` class will pick up and act accordingly. Typically, this also means filtering a +minor subset where possible to avoid over-iterating rooms. + +All filter conditions are considered "stable" by the consumers, meaning that the consumer does not +expect a change in the condition unless the condition says it has changed. This is intentional to +maintain the caching behaviour described above. + +## Class breakdowns + +The `RoomListStore` is the major coordinator of various algorithm implementations, which take care +of the various `ListAlgorithm` and `SortingAlgorithm` options. The `Algorithm` class is responsible +for figuring out which tags get which rooms, as Matrix specifies them as a reverse map: tags get +defined on rooms and are not defined as a collection of rooms (unlike how they are presented to the +user). Various list-specific utilities are also included, though they are expected to move somewhere +more general when needed. For example, the `membership` utilities could easily be moved elsewhere +as needed. + +The various bits throughout the room list store should also have jsdoc of some kind to help describe +what they do and how they work. diff --git a/docs/settings.md b/docs/settings.md index 9b780c27c9..46e4a68fdb 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -51,6 +51,17 @@ Settings are the different options a user may set or experience in the applicati } ``` +Settings that support the config level can be set in the config file under the `settingDefaults` key (note that some settings, like the "theme" setting, are special cased in the config file): +```json +{ + ... + "settingDefaults": { + "settingName": true + }, + ... +} +``` + ### Getting values for a setting After importing `SettingsStore`, simply make a call to `SettingsStore.getValue`. The `roomId` parameter should always diff --git a/package.json b/package.json index 07ed0ccf78..4b9d612444 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "2.3.0", + "version": "2.10.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -45,9 +45,8 @@ "start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:all", "start:all": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n build,reskindex \"yarn start:build\" \"yarn reskindex:watch\"", "start:build": "babel src -w -s -d lib --verbose --extensions \".ts,.js\"", - "lint": "yarn lint:types && yarn lint:ts && yarn lint:js && yarn lint:style", + "lint": "yarn lint:types && yarn lint:js && yarn lint:style", "lint:js": "eslint --max-warnings 0 --ignore-path .eslintignore.errorfiles src test", - "lint:ts": "tslint --project ./tsconfig.json -t stylish", "lint:types": "tsc --noEmit --jsx react", "lint:style": "stylelint 'res/css/**/*.scss'", "test": "jest", @@ -55,6 +54,7 @@ }, "dependencies": { "@babel/runtime": "^7.8.3", + "await-lock": "^2.0.1", "blueimp-canvas-to-blob": "^3.5.0", "browser-encrypt-attachment": "^0.3.0", "browser-request": "^0.3.3", @@ -64,8 +64,8 @@ "create-react-class": "^15.6.0", "diff-dom": "^4.1.3", "diff-match-patch": "^1.0.4", - "emojibase-data": "^4.0.2", - "emojibase-regex": "^3.0.0", + "emojibase-data": "^5.0.1", + "emojibase-regex": "^4.0.1", "escape-html": "^1.0.3", "file-saver": "^1.3.3", "filesize": "3.5.6", @@ -82,17 +82,18 @@ "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "minimist": "^1.2.0", "pako": "^1.0.5", + "parse5": "^5.1.1", "png-chunks-extract": "^1.0.0", "project-name-generator": "^2.1.7", "prop-types": "^15.5.8", "qrcode": "^1.4.4", - "qrcode-react": "^0.1.16", "qs": "^6.6.0", + "re-resizable": "^6.5.2", "react": "^16.9.0", - "react-addons-css-transition-group": "15.6.2", "react-beautiful-dnd": "^4.0.1", "react-dom": "^16.9.0", "react-focus-lock": "^2.2.1", + "react-transition-group": "^4.4.1", "resize-observer-polyfill": "^1.5.0", "sanitize-html": "^1.18.4", "tar-js": "^0.3.0", @@ -119,15 +120,29 @@ "@babel/register": "^7.7.4", "@peculiar/webcrypto": "^1.0.22", "@types/classnames": "^2.2.10", - "@types/react": "16.9", + "@types/counterpart": "^0.18.1", + "@types/flux": "^3.1.9", + "@types/linkifyjs": "^2.1.3", + "@types/lodash": "^4.14.152", + "@types/modernizr": "^3.5.3", + "@types/node": "^12.12.41", + "@types/qrcode": "^1.3.4", + "@types/react": "^16.9", + "@types/react-dom": "^16.9.8", + "@types/react-transition-group": "^4.4.0", + "@types/sanitize-html": "^1.23.3", + "@types/zxcvbn": "^4.4.0", + "@typescript-eslint/eslint-plugin": "^3.4.0", + "@typescript-eslint/parser": "^3.4.0", "babel-eslint": "^10.0.3", "babel-jest": "^24.9.0", "chokidar": "^3.3.1", "concurrently": "^4.0.1", "enzyme": "^3.10.0", "enzyme-adapter-react-16": "^1.15.1", - "eslint": "^5.12.0", + "eslint": "7.3.1", "eslint-config-google": "^0.7.1", + "eslint-config-matrix-org": "^0.1.2", "eslint-plugin-babel": "^5.2.1", "eslint-plugin-flowtype": "^2.30.0", "eslint-plugin-jest": "^23.0.4", @@ -138,6 +153,7 @@ "flow-parser": "^0.57.3", "glob": "^5.0.14", "jest": "^24.9.0", + "jest-canvas-mock": "^2.2.0", "lolex": "^5.1.2", "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.2", @@ -147,7 +163,6 @@ "stylelint": "^9.10.1", "stylelint-config-standard": "^18.2.0", "stylelint-scss": "^3.9.0", - "tslint": "^5.20.1", "typescript": "^3.7.3", "walk": "^2.3.9", "webpack": "^4.20.2", @@ -157,6 +172,9 @@ "testMatch": [ "/test/**/*-test.js" ], + "setupFiles": [ + "jest-canvas-mock" + ], "setupFilesAfterEnv": [ "/test/setupTests.js" ], diff --git a/res/css/_common.scss b/res/css/_common.scss index ad64aced50..f2d3a0e54b 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -16,6 +16,12 @@ See the License for the specific language governing permissions and limitations under the License. */ +@import "./_font-sizes.scss"; + +:root { + font-size: 10px; +} + html { /* hack to stop overscroll bounce on OSX and iOS. N.B. Breaks things when we have legitimate horizontal overscroll */ @@ -25,7 +31,7 @@ html { body { font-family: $font-family; - font-size: 15px; + font-size: $font-15px; background-color: $primary-bg-color; color: $primary-fg-color; border: 0px; @@ -60,7 +66,7 @@ b { h2 { color: $primary-fg-color; font-weight: 400; - font-size: 18px; + font-size: $font-18px; margin-top: 16px; margin-bottom: 16px; } @@ -76,7 +82,7 @@ input[type=search], input[type=password] { padding: 9px; font-family: $font-family; - font-size: 14px; + font-size: $font-14px; font-weight: 600; min-width: 0; } @@ -154,9 +160,8 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { background-color: transparent; color: $input-darker-fg-color; border-radius: 4px; - border: 1px solid $dialog-close-fg-color; - // these things should probably not be defined - // globally + border: 1px solid rgba($primary-fg-color, .1); + // these things should probably not be defined globally margin: 9px; flex: 0 0 auto; } @@ -169,7 +174,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text]::placeholder, :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search]::placeholder, .mx_textinput input::placeholder { - color: $roomsublist-label-fg-color; + color: rgba($input-darker-fg-color, .75); } } @@ -221,7 +226,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { } #mx_theme_tertiaryAccentColor { - color: $roomsublist-label-bg-color; + color: $tertiary-accent-color; } /* Expected z-indexes for dialogs: @@ -253,12 +258,12 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { color: $light-fg-color; z-index: 4012; font-weight: 300; - font-size: 15px; + font-size: $font-15px; position: relative; padding: 25px 30px 30px 30px; max-height: 80%; box-shadow: 2px 15px 30px 0 $dialog-shadow-color; - border-radius: 4px; + border-radius: 8px; overflow-y: auto; } @@ -313,7 +318,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { } .mx_Dialog_titleImage { - vertical-align: middle; + vertical-align: sub; width: 25px; height: 25px; margin-left: -2px; @@ -321,14 +326,17 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { } .mx_Dialog_title { - font-size: 22px; - line-height: 36px; + font-size: $font-22px; + line-height: $font-36px; color: $dialog-title-fg-color; } .mx_Dialog_header.mx_Dialog_headerWithButton > .mx_Dialog_title { text-align: center; } +.mx_Dialog_header.mx_Dialog_headerWithCancel > .mx_Dialog_title { + margin-right: 20px; // leave space for the 'X' cancel button +} .mx_Dialog_title.danger { color: $warning-color; @@ -350,7 +358,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { .mx_Dialog_content { margin: 24px 0 68px; - font-size: 14px; + font-size: $font-14px; color: $primary-fg-color; word-wrap: break-word; } @@ -419,6 +427,10 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { border-radius: 8px; padding: 0px; box-shadow: none; + + /* Don't show scroll-bars on spinner dialogs */ + overflow-x: hidden; + overflow-y: hidden; } // TODO: Review mx_GeneralButton usage to see if it can use a different class @@ -446,7 +458,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { } .mx_TextInputDialog_input { - font-size: 15px; + font-size: $font-15px; border-radius: 3px; border: 1px solid $input-border-color; padding: 9px; @@ -572,3 +584,111 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { // So it fits in the space provided by the page max-width: 120px; } + +// A context menu that largely fits the | [icon] [label] | format. +.mx_IconizedContextMenu { + min-width: 146px; + + .mx_IconizedContextMenu_optionList { + & > * { + padding-left: 20px; + padding-right: 20px; + } + + // the notFirst class is for cases where the optionList might be under a header of sorts. + &:nth-child(n + 2), .mx_IconizedContextMenu_optionList_notFirst { + // This is a bit of a hack when we could just use a simple border-top property, + // however we have a (kinda) good reason for doing it this way: we need opacity. + // To get the right color, we need an opacity modifier which means we have to work + // around the problem. PostCSS doesn't support the opacity() function, and if we + // use something like postcss-functions we quickly run into an issue where the + // function we would define gets passed a CSS variable for custom themes, which + // can't be converted easily even when considering https://stackoverflow.com/a/41265350/7037379 + // + // Therefore, we just hack in a line and border the thing ourselves + &::before { + border-top: 1px solid $primary-fg-color; + opacity: 0.1; + content: ''; + + // Counteract the padding problems (width: 100% ignores the 40px padding, + // unless we position it absolutely then it does the right thing). + width: 100%; + position: absolute; + left: 0; + } + } + + // round the top corners of the top button for the hover effect to be bounded + &:first-child .mx_AccessibleButton:first-child { + border-radius: 8px 8px 0 0; // radius matches .mx_ContextualMenu + } + + // round the bottom corners of the bottom button for the hover effect to be bounded + &:last-child .mx_AccessibleButton:last-child { + border-radius: 0 0 8px 8px; // radius matches .mx_ContextualMenu + } + + .mx_AccessibleButton { + // pad the inside of the button so that the hover background is padded too + padding-top: 12px; + padding-bottom: 12px; + text-decoration: none; + color: $primary-fg-color; + font-size: $font-15px; + line-height: $font-24px; + + // Create a flexbox to more easily define the list items + display: flex; + align-items: center; + + &:hover { + background-color: $menu-selected-color; + } + + img, .mx_IconizedContextMenu_icon { // icons + width: 16px; + min-width: 16px; + max-width: 16px; + } + + span.mx_IconizedContextMenu_label { // labels + padding-left: 14px; + width: 100%; + flex: 1; + + // Ellipsize any text overflow + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + } + } + + &.mx_IconizedContextMenu_compact { + .mx_IconizedContextMenu_optionList > * { + padding: 8px 16px 8px 11px; + } + } +} + +@define-mixin ProgressBarColour $colour { + color: $colour; + &::-moz-progress-bar { + background-color: $colour; + } + &::-webkit-progress-value { + background-color: $colour; + } +} + +@define-mixin ProgressBarBorderRadius $radius { + border-radius: $radius; + &::-moz-progress-bar { + border-radius: $radius; + } + &::-webkit-progress-bar, + &::-webkit-progress-value { + border-radius: $radius; + } +} diff --git a/res/css/_components.scss b/res/css/_components.scss index b959b1f1cd..23e4af780a 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -1,5 +1,6 @@ // autogenerated by rethemendex.sh @import "./_common.scss"; +@import "./_font-sizes.scss"; @import "./structures/_AutoHideScrollbar.scss"; @import "./structures/_CompatibilityPage.scss"; @import "./structures/_ContextualMenu.scss"; @@ -17,16 +18,16 @@ @import "./structures/_NotificationPanel.scss"; @import "./structures/_RightPanel.scss"; @import "./structures/_RoomDirectory.scss"; +@import "./structures/_RoomSearch.scss"; @import "./structures/_RoomStatusBar.scss"; -@import "./structures/_RoomSubList.scss"; @import "./structures/_RoomView.scss"; @import "./structures/_ScrollPanel.scss"; @import "./structures/_SearchBox.scss"; @import "./structures/_TabbedView.scss"; @import "./structures/_TagPanel.scss"; @import "./structures/_ToastContainer.scss"; -@import "./structures/_TopLeftMenuButton.scss"; @import "./structures/_UploadBar.scss"; +@import "./structures/_UserMenu.scss"; @import "./structures/_ViewSource.scss"; @import "./structures/auth/_CompleteSecurity.scss"; @import "./structures/auth/_Login.scss"; @@ -40,11 +41,14 @@ @import "./views/auth/_CountryDropdown.scss"; @import "./views/auth/_InteractiveAuthEntryComponents.scss"; @import "./views/auth/_LanguageSelector.scss"; +@import "./views/auth/_PassphraseField.scss"; @import "./views/auth/_ServerConfig.scss"; @import "./views/auth/_ServerTypeSelector.scss"; @import "./views/auth/_Welcome.scss"; @import "./views/avatars/_BaseAvatar.scss"; +@import "./views/avatars/_DecoratedRoomAvatar.scss"; @import "./views/avatars/_MemberStatusMessageAvatar.scss"; +@import "./views/avatars/_PulsedAvatar.scss"; @import "./views/context_menus/_MessageContextMenu.scss"; @import "./views/context_menus/_RoomTileContextMenu.scss"; @import "./views/context_menus/_StatusMessageContextMenu.scss"; @@ -59,15 +63,14 @@ @import "./views/dialogs/_CreateGroupDialog.scss"; @import "./views/dialogs/_CreateRoomDialog.scss"; @import "./views/dialogs/_DeactivateAccountDialog.scss"; -@import "./views/dialogs/_DeviceVerifyDialog.scss"; @import "./views/dialogs/_DevtoolsDialog.scss"; -@import "./views/dialogs/_EncryptedEventDialog.scss"; @import "./views/dialogs/_GroupAddressPicker.scss"; @import "./views/dialogs/_IncomingSasDialog.scss"; @import "./views/dialogs/_InviteDialog.scss"; @import "./views/dialogs/_KeyboardShortcutsDialog.scss"; @import "./views/dialogs/_MessageEditHistoryDialog.scss"; @import "./views/dialogs/_NewSessionReviewDialog.scss"; +@import "./views/dialogs/_RebrandDialog.scss"; @import "./views/dialogs/_RoomSettingsDialog.scss"; @import "./views/dialogs/_RoomSettingsDialogBridges.scss"; @import "./views/dialogs/_RoomUpgradeDialog.scss"; @@ -80,7 +83,6 @@ @import "./views/dialogs/_SlashCommandHelpDialog.scss"; @import "./views/dialogs/_TabbedIntegrationManagerDialog.scss"; @import "./views/dialogs/_TermsDialog.scss"; -@import "./views/dialogs/_UnknownDeviceDialog.scss"; @import "./views/dialogs/_UploadConfirmDialog.scss"; @import "./views/dialogs/_UserSettingsDialog.scss"; @import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss"; @@ -103,16 +105,19 @@ @import "./views/elements/_IconButton.scss"; @import "./views/elements/_ImageView.scss"; @import "./views/elements/_InlineSpinner.scss"; -@import "./views/elements/_InteractiveTooltip.scss"; @import "./views/elements/_ManageIntegsButton.scss"; @import "./views/elements/_PowerSelector.scss"; @import "./views/elements/_ProgressBar.scss"; +@import "./views/elements/_QRCode.scss"; @import "./views/elements/_ReplyThread.scss"; @import "./views/elements/_ResizeHandle.scss"; @import "./views/elements/_RichText.scss"; @import "./views/elements/_RoleButton.scss"; @import "./views/elements/_RoomAliasField.scss"; +@import "./views/elements/_Slider.scss"; @import "./views/elements/_Spinner.scss"; +@import "./views/elements/_StyledCheckbox.scss"; +@import "./views/elements/_StyledRadioButton.scss"; @import "./views/elements/_SyntaxHighlight.scss"; @import "./views/elements/_TextWithTooltip.scss"; @import "./views/elements/_ToggleSwitch.scss"; @@ -120,7 +125,6 @@ @import "./views/elements/_TooltipButton.scss"; @import "./views/elements/_Validation.scss"; @import "./views/emojipicker/_EmojiPicker.scss"; -@import "./views/globals/_MatrixToolbar.scss"; @import "./views/groups/_GroupPublicityToggle.scss"; @import "./views/groups/_GroupRoomList.scss"; @import "./views/groups/_GroupUserSettings.scss"; @@ -132,12 +136,13 @@ @import "./views/messages/_MNoticeBody.scss"; @import "./views/messages/_MStickerBody.scss"; @import "./views/messages/_MTextBody.scss"; +@import "./views/messages/_MVideoBody.scss"; @import "./views/messages/_MessageActionBar.scss"; @import "./views/messages/_MessageTimestamp.scss"; @import "./views/messages/_MjolnirBody.scss"; @import "./views/messages/_ReactionsRow.scss"; @import "./views/messages/_ReactionsRowButton.scss"; -@import "./views/messages/_ReactionsRowButtonTooltip.scss"; +@import "./views/messages/_RedactedBody.scss"; @import "./views/messages/_RoomAvatarEvent.scss"; @import "./views/messages/_SenderProfile.scss"; @import "./views/messages/_TextualEvent.scss"; @@ -157,31 +162,32 @@ @import "./views/rooms/_EditMessageComposer.scss"; @import "./views/rooms/_EntityTile.scss"; @import "./views/rooms/_EventTile.scss"; -@import "./views/rooms/_InviteOnlyIcon.scss"; +@import "./views/rooms/_GroupLayout.scss"; +@import "./views/rooms/_IRCLayout.scss"; @import "./views/rooms/_JumpToBottomButton.scss"; @import "./views/rooms/_LinkPreviewWidget.scss"; -@import "./views/rooms/_MemberDeviceInfo.scss"; @import "./views/rooms/_MemberInfo.scss"; @import "./views/rooms/_MemberList.scss"; @import "./views/rooms/_MessageComposer.scss"; @import "./views/rooms/_MessageComposerFormatBar.scss"; +@import "./views/rooms/_NotificationBadge.scss"; @import "./views/rooms/_PinnedEventTile.scss"; @import "./views/rooms/_PinnedEventsPanel.scss"; @import "./views/rooms/_PresenceLabel.scss"; @import "./views/rooms/_ReplyPreview.scss"; @import "./views/rooms/_RoomBreadcrumbs.scss"; -@import "./views/rooms/_RoomDropTarget.scss"; @import "./views/rooms/_RoomHeader.scss"; @import "./views/rooms/_RoomList.scss"; @import "./views/rooms/_RoomPreviewBar.scss"; @import "./views/rooms/_RoomRecoveryReminder.scss"; +@import "./views/rooms/_RoomSublist.scss"; @import "./views/rooms/_RoomTile.scss"; +@import "./views/rooms/_RoomTileIcon.scss"; @import "./views/rooms/_RoomUpgradeWarningBar.scss"; @import "./views/rooms/_SearchBar.scss"; @import "./views/rooms/_SendMessageComposer.scss"; @import "./views/rooms/_Stickers.scss"; @import "./views/rooms/_TopUnreadMessagesBar.scss"; -@import "./views/rooms/_UserOnlineDot.scss"; @import "./views/rooms/_WhoIsTypingTile.scss"; @import "./views/settings/_AvatarSetting.scss"; @import "./views/settings/_CrossSigningPanel.scss"; @@ -195,10 +201,12 @@ @import "./views/settings/_ProfileSettings.scss"; @import "./views/settings/_SetIdServer.scss"; @import "./views/settings/_SetIntegrationManager.scss"; +@import "./views/settings/_UpdateCheckButton.scss"; @import "./views/settings/tabs/_SettingsTab.scss"; @import "./views/settings/tabs/room/_GeneralRoomSettingsTab.scss"; @import "./views/settings/tabs/room/_RolesRoomSettingsTab.scss"; @import "./views/settings/tabs/room/_SecurityRoomSettingsTab.scss"; +@import "./views/settings/tabs/user/_AppearanceUserSettingsTab.scss"; @import "./views/settings/tabs/user/_GeneralUserSettingsTab.scss"; @import "./views/settings/tabs/user/_HelpUserSettingsTab.scss"; @import "./views/settings/tabs/user/_MjolnirUserSettingsTab.scss"; @@ -208,6 +216,6 @@ @import "./views/settings/tabs/user/_VoiceUserSettingsTab.scss"; @import "./views/terms/_InlineTermsAgreement.scss"; @import "./views/verification/_VerificationShowSas.scss"; +@import "./views/voip/_CallContainer.scss"; @import "./views/voip/_CallView.scss"; -@import "./views/voip/_IncomingCallbox.scss"; @import "./views/voip/_VideoView.scss"; diff --git a/res/css/_font-sizes.scss b/res/css/_font-sizes.scss new file mode 100644 index 0000000000..caa3a452b0 --- /dev/null +++ b/res/css/_font-sizes.scss @@ -0,0 +1,73 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +$font-1px: 0.1rem; +$font-1-5px: 0.15rem; +$font-2px: 0.2rem; +$font-3px: 0.3rem; +$font-4px: 0.4rem; +$font-5px: 0.5rem; +$font-6px: 0.6rem; +$font-7px: 0.7rem; +$font-8px: 0.8rem; +$font-9px: 0.9rem; +$font-10px: 1.0rem; +$font-10-4px: 1.04rem; +$font-11px: 1.1rem; +$font-12px: 1.2rem; +$font-13px: 1.3rem; +$font-14px: 1.4rem; +$font-15px: 1.5rem; +$font-16px: 1.6rem; +$font-17px: 1.7rem; +$font-18px: 1.8rem; +$font-19px: 1.9rem; +$font-20px: 2.0rem; +$font-21px: 2.1rem; +$font-22px: 2.2rem; +$font-23px: 2.3rem; +$font-24px: 2.4rem; +$font-25px: 2.5rem; +$font-26px: 2.6rem; +$font-27px: 2.7rem; +$font-28px: 2.8rem; +$font-29px: 2.9rem; +$font-30px: 3.0rem; +$font-31px: 3.1rem; +$font-32px: 3.2rem; +$font-33px: 3.3rem; +$font-34px: 3.4rem; +$font-35px: 3.5rem; +$font-36px: 3.6rem; +$font-37px: 3.7rem; +$font-38px: 3.8rem; +$font-39px: 3.9rem; +$font-40px: 4.0rem; +$font-41px: 4.1rem; +$font-42px: 4.2rem; +$font-43px: 4.3rem; +$font-44px: 4.4rem; +$font-45px: 4.5rem; +$font-46px: 4.6rem; +$font-47px: 4.7rem; +$font-48px: 4.8rem; +$font-49px: 4.9rem; +$font-50px: 5.0rem; +$font-51px: 5.1rem; +$font-52px: 5.2rem; +$font-78px: 7.8rem; +$font-88px: 8.8rem; +$font-400px: 40rem; diff --git a/res/css/structures/_ContextualMenu.scss b/res/css/structures/_ContextualMenu.scss index fa2d87029d..658033339a 100644 --- a/res/css/structures/_ContextualMenu.scss +++ b/res/css/structures/_ContextualMenu.scss @@ -31,12 +31,12 @@ limitations under the License. } .mx_ContextualMenu { - border-radius: 4px; + border-radius: 8px; box-shadow: 4px 4px 12px 0 $menu-box-shadow-color; background-color: $menu-bg-color; color: $primary-fg-color; position: absolute; - font-size: 14px; + font-size: $font-14px; z-index: 5001; } diff --git a/res/css/structures/_CreateRoom.scss b/res/css/structures/_CreateRoom.scss index 10f9e23a02..e859beb20e 100644 --- a/res/css/structures/_CreateRoom.scss +++ b/res/css/structures/_CreateRoom.scss @@ -26,7 +26,7 @@ limitations under the License. border-radius: 3px; border: 1px solid $strong-input-border-color; font-weight: 300; - font-size: 13px; + font-size: $font-13px; padding: 9px; margin-top: 6px; } diff --git a/res/css/structures/_CustomRoomTagPanel.scss b/res/css/structures/_CustomRoomTagPanel.scss index 1fb18ec41e..135a51c7cd 100644 --- a/res/css/structures/_CustomRoomTagPanel.scss +++ b/res/css/structures/_CustomRoomTagPanel.scss @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// TODO: Update design for custom tags to match new designs + .mx_LeftPanel_tagPanelContainer { display: flex; flex-direction: column; @@ -50,7 +52,7 @@ limitations under the License. background-color: $accent-color-alt; width: 5px; position: absolute; - left: -15px; + left: -9px; border-radius: 0 3px 3px 0; - top: 2px; // 10 [padding-top] - (56 - 40)/2 + top: 12px; // just feels right (see comment above about designs needing to be updated) } diff --git a/res/css/structures/_FilePanel.scss b/res/css/structures/_FilePanel.scss index 87e885e668..859ee28035 100644 --- a/res/css/structures/_FilePanel.scss +++ b/res/css/structures/_FilePanel.scss @@ -49,7 +49,7 @@ limitations under the License. .mx_FilePanel .mx_EventTile .mx_MFileBody_download { display: flex; - font-size: 14px; + font-size: $font-14px; color: $event-timestamp-color; } @@ -60,7 +60,7 @@ limitations under the License. .mx_FilePanel .mx_EventTile .mx_MImageBody_size { flex: 1 0 0; - font-size: 11px; + font-size: $font-11px; text-align: right; white-space: nowrap; } @@ -80,7 +80,7 @@ limitations under the License. flex: 1 1 auto; line-height: initial; padding: 0px; - font-size: 11px; + font-size: $font-11px; opacity: 1.0; color: $event-timestamp-color; } @@ -90,7 +90,7 @@ limitations under the License. text-align: right; visibility: visible; position: initial; - font-size: 11px; + font-size: $font-11px; opacity: 1.0; color: $event-timestamp-color; } diff --git a/res/css/structures/_GroupView.scss b/res/css/structures/_GroupView.scss index 72a1132c15..2350d9f28a 100644 --- a/res/css/structures/_GroupView.scss +++ b/res/css/structures/_GroupView.scss @@ -29,12 +29,12 @@ limitations under the License. align-items: center; display: flex; padding-bottom: 10px; + padding-left: 19px; } .mx_GroupView_header_view { border-bottom: 1px solid $primary-hairline-color; padding-bottom: 0px; - padding-left: 19px; padding-right: 8px; } @@ -63,11 +63,11 @@ limitations under the License. } .mx_GroupHeader_editButton::before { - mask-image: url('$(res)/img/feather-customised/settings.svg'); + mask-image: url('$(res)/img/element-icons/settings.svg'); } .mx_GroupHeader_shareButton::before { - mask-image: url('$(res)/img/icons-share.svg'); + mask-image: url('$(res)/img/element-icons/room/share.svg'); } .mx_GroupView_hostingSignup img { @@ -134,7 +134,7 @@ limitations under the License. overflow: hidden; color: $primary-fg-color; font-weight: bold; - font-size: 22px; + font-size: $font-22px; padding-left: 19px; padding-right: 16px; /* why isn't text-overflow working? */ @@ -148,7 +148,7 @@ limitations under the License. max-height: 42px; color: $settings-grey-fg-color; font-weight: 300; - font-size: 13px; + font-size: $font-13px; padding-left: 19px; margin-right: 16px; overflow: hidden; @@ -182,6 +182,7 @@ limitations under the License. .mx_GroupView_body { flex-grow: 1; + margin: 0 24px; } .mx_GroupView_rooms { @@ -196,7 +197,7 @@ limitations under the License. text-transform: uppercase; color: $h3-color; font-weight: 600; - font-size: 13px; + font-size: $font-13px; margin-bottom: 10px; } @@ -226,7 +227,7 @@ limitations under the License. .mx_GroupView_rooms_header_addRow_label { display: inline-block; vertical-align: top; - line-height: 24px; + line-height: $font-24px; padding-left: 28px; color: $accent-color; } @@ -250,6 +251,7 @@ limitations under the License. .mx_GroupView_membershipSubSection { justify-content: space-between; display: flex; + padding-bottom: 8px; } .mx_GroupView_membershipSubSection .mx_Spinner { @@ -258,7 +260,7 @@ limitations under the License. .mx_GroupView_membershipSection_description { /* To match textButton */ - line-height: 34px; + line-height: $font-34px; } .mx_GroupView_membershipSection_description .mx_BaseAvatar { diff --git a/res/css/structures/_HeaderButtons.scss b/res/css/structures/_HeaderButtons.scss index eef7653b24..9ef40e9d6a 100644 --- a/res/css/structures/_HeaderButtons.scss +++ b/res/css/structures/_HeaderButtons.scss @@ -22,7 +22,7 @@ limitations under the License. content: ""; background-color: $header-divider-color; opacity: 0.5; - margin: 0 15px; + margin: 6px 8px; border-radius: 1px; width: 1px; } diff --git a/res/css/structures/_HomePage.scss b/res/css/structures/_HomePage.scss index 3aa80f6f59..04527bff48 100644 --- a/res/css/structures/_HomePage.scss +++ b/res/css/structures/_HomePage.scss @@ -23,3 +23,84 @@ limitations under the License. margin-left: auto; margin-right: auto; } + +.mx_HomePage_default { + text-align: center; + + .mx_HomePage_default_wrapper { + padding: 25vh 0 12px; + } + + img { + height: 48px; + } + + h1 { + font-weight: 600; + font-size: $font-32px; + line-height: $font-44px; + margin-bottom: 4px; + } + + h4 { + margin-top: 4px; + font-weight: 600; + font-size: $font-18px; + line-height: $font-25px; + color: $muted-fg-color; + } + + .mx_HomePage_default_buttons { + margin: 80px auto 0; + width: fit-content; + + .mx_AccessibleButton { + padding: 73px 8px 15px; // top: 20px top padding + 40px icon + 13px margin + + width: 104px; // 120px - 2* 8px + margin: 0 39px; // 55px - 2* 8px + position: relative; + display: inline-block; + border-radius: 8px; + vertical-align: top; + word-break: break-word; + + font-weight: 600; + font-size: $font-15px; + line-height: $font-20px; + color: $muted-fg-color; + + &:hover { + color: $accent-color; + background: rgba($accent-color, 0.06); + + &::before { + background-color: $accent-color; + } + } + + &::before { + top: 20px; + left: 40px; // (120px-40px)/2 + width: 40px; + height: 40px; + + content: ''; + position: absolute; + background-color: $muted-fg-color; + mask-repeat: no-repeat; + mask-size: contain; + } + + &.mx_HomePage_button_sendDm::before { + mask-image: url('$(res)/img/feather-customised/message-circle.svg'); + } + &.mx_HomePage_button_explore::before { + mask-image: url('$(res)/img/feather-customised/explore.svg'); + } + &.mx_HomePage_button_createGroup::before { + mask-image: url('$(res)/img/feather-customised/group.svg'); + } + } + } +} diff --git a/res/css/structures/_LeftPanel.scss b/res/css/structures/_LeftPanel.scss index 85fdfa092d..1673092c9e 100644 --- a/res/css/structures/_LeftPanel.scss +++ b/res/css/structures/_LeftPanel.scss @@ -1,6 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2018 New Vector Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,163 +14,182 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_LeftPanel_container { - display: flex; - /* LeftPanel 260px */ - min-width: 260px; - flex: 0 0 auto; -} - -.mx_LeftPanel_container.collapsed { - min-width: unset; - /* Collapsed LeftPanel 50px */ - flex: 0 0 50px; -} - -.mx_LeftPanel_container.collapsed.mx_LeftPanel_container_hasTagPanel { - /* TagPanel 70px + Collapsed LeftPanel 50px */ - flex: 0 0 120px; -} - -.mx_LeftPanel_tagPanelContainer { - flex: 0 0 70px; - height: 100%; -} - -.mx_LeftPanel_hideButton { - position: absolute; - top: 10px; - right: 0px; - padding: 8px; - cursor: pointer; -} +$tagPanelWidth: 56px; // only applies in this file, used for calculations .mx_LeftPanel { - flex: 1; - overflow-x: hidden; - display: flex; - flex-direction: column; - min-height: 0; -} + background-color: $roomlist-bg-color; + min-width: 260px; + max-width: 50%; -.mx_LeftPanel .mx_AppTile_mini { - height: 132px; -} - -.mx_LeftPanel .mx_RoomList_scrollbar { - order: 1; - - flex: 1 1 0; - - overflow-y: auto; - z-index: 6; -} - -.mx_LeftPanel .mx_BottomLeftMenu { - order: 3; - - border-top: 1px solid $panel-divider-color; - margin-left: 16px; /* gutter */ - margin-right: 16px; /* gutter */ - flex: 0 0 60px; - z-index: 1; -} - -.mx_LeftPanel_container.collapsed .mx_BottomLeftMenu { - flex: 0 0 160px; - margin-bottom: 9px; -} - -.mx_LeftPanel .mx_BottomLeftMenu_options { - margin-top: 18px; -} - -.mx_BottomLeftMenu_options object { - pointer-events: none; -} - -.mx_BottomLeftMenu_options > div { - display: inline-block; -} - -.mx_BottomLeftMenu_options .mx_RoleButton { - margin-left: 0px; - margin-right: 10px; - height: 30px; -} - -.mx_BottomLeftMenu_options .mx_BottomLeftMenu_settings { - float: right; -} - -.mx_BottomLeftMenu_options .mx_BottomLeftMenu_settings .mx_RoleButton { - margin-right: 0px; -} - -.mx_LeftPanel_container.collapsed .mx_BottomLeftMenu_settings { - float: none; -} - -.mx_MatrixChat_useCompactLayout { - .mx_LeftPanel .mx_BottomLeftMenu { - flex: 0 0 50px; - } - - .mx_LeftPanel_container.collapsed .mx_BottomLeftMenu { - flex: 0 0 160px; - } - - .mx_LeftPanel .mx_BottomLeftMenu_options { - margin-top: 12px; - } -} - -.mx_LeftPanel_exploreAndFilterRow { + // Create a row-based flexbox for the TagPanel and the room list display: flex; - .mx_SearchBox { - flex: 1 1 0; - min-width: 0; - margin: 4px 9px 1px 9px; - } -} + .mx_LeftPanel_tagPanelContainer { + flex-grow: 0; + flex-shrink: 0; + flex-basis: $tagPanelWidth; + height: 100%; -.mx_LeftPanel_explore { - flex: 0 0 50%; - overflow: hidden; - transition: flex-basis 0.2s; - box-sizing: border-box; + // Create another flexbox so the TagPanel fills the container + display: flex; - &.mx_LeftPanel_explore_hidden { - flex-basis: 0; + // TagPanel handles its own CSS } - .mx_AccessibleButton { - font-size: 14px; - margin: 4px 0 1px 9px; - padding: 9px; - padding-left: 42px; - font-weight: 600; - color: $notice-secondary-color; - position: relative; - border-radius: 4px; + &:not(.mx_LeftPanel_hasTagPanel) { + .mx_LeftPanel_roomListContainer { + width: 100%; + } + } - &:hover { - background-color: $primary-bg-color; + // Note: The 'room list' in this context is actually everything that isn't the tag + // panel, such as the menu options, breadcrumbs, filtering, etc + .mx_LeftPanel_roomListContainer { + width: calc(100% - $tagPanelWidth); + background-color: $roomlist-bg-color; + + // Create another flexbox (this time a column) for the room list components + display: flex; + flex-direction: column; + + .mx_LeftPanel_userHeader { + /* 12px top, 12px sides, 20px bottom (using 13px bottom to account + * for internal whitespace in the breadcrumbs) + */ + padding: 12px; + flex-shrink: 0; // to convince safari's layout engine the flexbox is fine + + // Create another flexbox column for the rows to stack within + display: flex; + flex-direction: column; } - &::before { - cursor: pointer; - mask: url('$(res)/img/explore.svg'); - mask-repeat: no-repeat; - mask-position: center center; - content: ""; - left: 14px; - top: 10px; - width: 16px; - height: 16px; - background-color: $notice-secondary-color; - position: absolute; + .mx_LeftPanel_breadcrumbsContainer { + overflow-y: hidden; + overflow-x: scroll; + margin: 12px 12px 0 12px; + flex: 0 0 auto; + // Create yet another flexbox, this time within the row, to ensure items stay + // aligned correctly. This is also a row-based flexbox. + display: flex; + align-items: center; + + &.mx_IndicatorScrollbar_leftOverflow { + mask-image: linear-gradient(90deg, transparent, black 5%); + } + + &.mx_IndicatorScrollbar_rightOverflow { + mask-image: linear-gradient(90deg, black, black 95%, transparent); + } + + &.mx_IndicatorScrollbar_rightOverflow.mx_IndicatorScrollbar_leftOverflow { + mask-image: linear-gradient(90deg, transparent, black 5%, black 95%, transparent); + } + } + + .mx_LeftPanel_filterContainer { + margin-left: 12px; + margin-right: 12px; + + flex-shrink: 0; // to convince safari's layout engine the flexbox is fine + + // Create a flexbox to organize the inputs + display: flex; + align-items: center; + + .mx_RoomSearch_expanded + .mx_LeftPanel_exploreButton { + // Cheaty way to return the occupied space to the filter input + flex-basis: 0; + margin: 0; + width: 0; + + // Don't forget to hide the masked ::before icon, + // using display:none or visibility:hidden would break accessibility + &::before { + content: none; + } + } + + .mx_LeftPanel_exploreButton { + width: 28px; + height: 28px; + border-radius: 20px; + background-color: $roomlist-button-bg-color; + position: relative; + margin-left: 8px; + + &::before { + content: ''; + position: absolute; + top: 6px; + left: 6px; + width: 16px; + height: 16px; + mask-image: url('$(res)/img/feather-customised/compass.svg'); + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background: $primary-fg-color; + } + } + } + + .mx_LeftPanel_roomListWrapper { + // Create a flexbox to ensure the containing items cause appropriate overflow. + display: flex; + + flex-grow: 1; + overflow: hidden; + min-height: 0; + margin-top: 10px; // so we're not up against the search/filter + + &.mx_LeftPanel_roomListWrapper_stickyBottom { + padding-bottom: 32px; + } + + &.mx_LeftPanel_roomListWrapper_stickyTop { + padding-top: 32px; + } + } + + .mx_LeftPanel_actualRoomListContainer { + flex-grow: 1; // fill the available space + overflow-y: auto; + width: 100%; + max-width: 100%; + position: relative; // for sticky headers + + // Create a flexbox to trick the layout engine + display: flex; + } + } + + // These styles override the defaults for the minimized (66px) layout + &.mx_LeftPanel_minimized { + min-width: unset; + + // We have to forcefully set the width to override the resizer's style attribute. + &.mx_LeftPanel_hasTagPanel { + width: calc(68px + $tagPanelWidth) !important; + } + &:not(.mx_LeftPanel_hasTagPanel) { + width: 68px !important; + } + + .mx_LeftPanel_roomListContainer { + width: 68px; + + .mx_LeftPanel_filterContainer { + // Organize the flexbox into a centered column layout + flex-direction: column; + justify-content: center; + + .mx_LeftPanel_exploreButton { + margin-left: 0; + margin-top: 8px; + background-color: transparent; + } + } } } } diff --git a/res/css/structures/_MatrixChat.scss b/res/css/structures/_MatrixChat.scss index c5a5d50068..88b29a96e8 100644 --- a/res/css/structures/_MatrixChat.scss +++ b/res/css/structures/_MatrixChat.scss @@ -41,10 +41,6 @@ limitations under the License. height: 40px; } -.mx_MatrixChat_toolbarShowing { - height: auto; -} - .mx_MatrixChat { width: 100%; height: 100%; @@ -70,7 +66,7 @@ limitations under the License. } /* not the left panel, and not the resize handle, so the roomview/groupview/... */ -.mx_MatrixChat > :not(.mx_LeftPanel_container):not(.mx_ResizeHandle) { +.mx_MatrixChat > :not(.mx_LeftPanel):not(.mx_ResizeHandle) { background-color: $primary-bg-color; flex: 1 1 0; diff --git a/res/css/structures/_MyGroups.scss b/res/css/structures/_MyGroups.scss index 36150c33a5..73f1332cd0 100644 --- a/res/css/structures/_MyGroups.scss +++ b/res/css/structures/_MyGroups.scss @@ -105,7 +105,7 @@ limitations under the License. .mx_MyGroups_placeholder { background-color: $info-plinth-bg-color; color: $info-plinth-fg-color; - line-height: 400px; + line-height: $font-400px; border-radius: 10px; text-align: center; } @@ -149,11 +149,11 @@ limitations under the License. .mx_GroupTile_profile .mx_GroupTile_name { margin: 0px; - font-size: 15px; + font-size: $font-15px; } .mx_GroupTile_profile .mx_GroupTile_groupId { - font-size: 13px; + font-size: $font-13px; opacity: 0.7; } @@ -161,7 +161,7 @@ limitations under the License. display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; - font-size: 13px; + font-size: $font-13px; max-height: 36px; overflow: hidden; } diff --git a/res/css/structures/_NotificationPanel.scss b/res/css/structures/_NotificationPanel.scss index c9e0261ec9..561ab1446f 100644 --- a/res/css/structures/_NotificationPanel.scss +++ b/res/css/structures/_NotificationPanel.scss @@ -39,7 +39,7 @@ limitations under the License. .mx_NotificationPanel .mx_EventTile_roomName { font-weight: bold; - font-size: 14px; + font-size: $font-14px; } .mx_NotificationPanel .mx_EventTile_roomName a { @@ -54,7 +54,7 @@ limitations under the License. .mx_NotificationPanel .mx_EventTile .mx_SenderProfile, .mx_NotificationPanel .mx_EventTile .mx_MessageTimestamp { color: $primary-fg-color; - font-size: 12px; + font-size: $font-12px; display: inline; padding-left: 0px; } @@ -63,6 +63,10 @@ limitations under the License. padding-left: 32px; padding-top: 8px; position: relative; + + a { + display: flex; + } } .mx_NotificationPanel .mx_EventTile_roomName a, diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index 3c373e8883..2fe7aac3b2 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -20,8 +20,16 @@ limitations under the License. flex: 0 0 auto; position: relative; min-width: 264px; + max-width: 50%; display: flex; flex-direction: column; + border-radius: 8px; + margin: 5px; + padding: 4px 0; + + .mx_RoomView_MessageList { + padding: 14px 18px; // top and bottom is 4px smaller to balance with the padding set above + } } .mx_RightPanel_header { @@ -43,22 +51,20 @@ limitations under the License. .mx_RightPanel_headerButton { cursor: pointer; flex: 0 0 auto; - vertical-align: top; - margin-left: 5px; - margin-right: 5px; - text-align: center; - border-bottom: 2px solid transparent; - height: 20px; - width: 20px; + margin-left: 1px; + margin-right: 1px; + height: 32px; + width: 32px; position: relative; + border-radius: 100%; &::before { content: ''; position: absolute; - top: 0; - left: 0; - height: 20px; - width: 20px; + top: 4px; // center with parent of 32px + left: 4px; // center with parent of 32px + height: 24px; + width: 24px; background-color: $rightpanel-button-color; mask-repeat: no-repeat; mask-size: contain; @@ -66,37 +72,50 @@ limitations under the License. } .mx_RightPanel_membersButton::before { - mask-image: url('$(res)/img/feather-customised/user.svg'); + mask-image: url('$(res)/img/element-icons/room/members.svg'); + mask-position: center; } .mx_RightPanel_filesButton::before { - mask-image: url('$(res)/img/feather-customised/files.svg'); + mask-image: url('$(res)/img/element-icons/room/files.svg'); + mask-position: center; } .mx_RightPanel_notifsButton::before { - mask-image: url('$(res)/img/feather-customised/notifications.svg'); + mask-image: url('$(res)/img/element-icons/notifications.svg'); + mask-position: center; } .mx_RightPanel_groupMembersButton::before { - mask-image: url('$(res)/img/icons-people.svg'); + mask-image: url('$(res)/img/element-icons/community-members.svg'); + mask-position: center; } .mx_RightPanel_roomsButton::before { - mask-image: url('$(res)/img/icons-room-nobg.svg'); + mask-image: url('$(res)/img/element-icons/community-rooms.svg'); + mask-position: center; } -.mx_RightPanel_headerButton_highlight::after { - content: ''; - position: absolute; - bottom: -6px; - left: 0; - right: 0; - height: 2px; - background-color: $button-bg-color; +.mx_RightPanel_headerButton_highlight { + background: rgba($accent-color, 0.25); + // make the icon the accent color too + &::before { + background-color: $accent-color; + } +} + +.mx_RightPanel_headerButton:not(.mx_RightPanel_headerButton_highlight) { + &:hover { + background: rgba($accent-color, 0.1); + + &::before { + background-color: $accent-color; + } + } } .mx_RightPanel_headerButton_badge { - font-size: 8px; + font-size: $font-8px; border-radius: 8px; color: $accent-fg-color; background-color: $accent-color; diff --git a/res/css/structures/_RoomDirectory.scss b/res/css/structures/_RoomDirectory.scss index f3a7b0e243..e0814182f5 100644 --- a/res/css/structures/_RoomDirectory.scss +++ b/res/css/structures/_RoomDirectory.scss @@ -64,7 +64,7 @@ limitations under the License. } .mx_RoomDirectory_table { - font-size: 12px; + font-size: $font-12px; color: $primary-fg-color; width: 100%; text-align: left; @@ -112,7 +112,7 @@ limitations under the License. .mx_RoomDirectory_name { display: inline-block; - font-size: 18px; + font-size: $font-18px; font-weight: 600; } @@ -124,7 +124,7 @@ limitations under the License. border-radius: 10px; display: inline-block; height: 20px; - line-height: 20px; + line-height: $font-20px; padding: 0 5px; color: $accent-fg-color; background-color: $rte-room-pill-color; @@ -136,7 +136,7 @@ limitations under the License. } .mx_RoomDirectory_alias { - font-size: 12px; + font-size: $font-12px; color: $settings-grey-fg-color; } @@ -150,7 +150,7 @@ limitations under the License. } .mx_RoomDirectory > span { - font-size: 15px; + font-size: $font-15px; margin-top: 0; .mx_AccessibleButton { diff --git a/res/css/structures/_RoomSearch.scss b/res/css/structures/_RoomSearch.scss new file mode 100644 index 0000000000..39a3dee30b --- /dev/null +++ b/res/css/structures/_RoomSearch.scss @@ -0,0 +1,81 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Note: this component expects to be contained within a flexbox +.mx_RoomSearch { + flex: 1; + border-radius: 20px; + background-color: $roomlist-button-bg-color; + height: 28px; + padding: 2px; + + // Create a flexbox for the icons (easier to manage) + display: flex; + align-items: center; + + .mx_RoomSearch_icon { + width: 16px; + height: 16px; + mask: url('$(res)/img/feather-customised/search-input.svg'); + mask-repeat: no-repeat; + background: $primary-fg-color; + margin-left: 7px; + } + + .mx_RoomSearch_input { + border: none !important; // !important to override default app-wide styles + flex: 1 !important; // !important to override default app-wide styles + color: $primary-fg-color !important; // !important to override default app-wide styles + padding: 0; + height: 100%; + width: 100%; + font-size: $font-12px; + line-height: $font-16px; + + &:not(.mx_RoomSearch_inputExpanded)::placeholder { + color: $primary-fg-color !important; // !important to override default app-wide styles + } + } + + &.mx_RoomSearch_expanded { + .mx_RoomSearch_clearButton { + width: 16px; + height: 16px; + mask-image: url('$(res)/img/feather-customised/x.svg'); + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background: $primary-fg-color; + margin-right: 8px; + } + } + + .mx_RoomSearch_clearButton { + width: 0; + height: 0; + } + + &.mx_RoomSearch_minimized { + border-radius: 32px; + height: auto; + width: auto; + padding: 8px; + + .mx_RoomSearch_icon { + margin-left: 0; + } + } +} diff --git a/res/css/structures/_RoomStatusBar.scss b/res/css/structures/_RoomStatusBar.scss index 090a40235f..cd4390ee5c 100644 --- a/res/css/structures/_RoomStatusBar.scss +++ b/res/css/structures/_RoomStatusBar.scss @@ -32,7 +32,7 @@ limitations under the License. .mx_RoomStatusBar_callBar { height: 50px; - line-height: 50px; + line-height: $font-50px; } .mx_RoomStatusBar_placeholderIndicator span { @@ -94,7 +94,7 @@ limitations under the License. border-radius: 40px; width: 24px; height: 24px; - line-height: 24px; + line-height: $font-24px; font-size: 0.8em; vertical-align: top; text-align: center; @@ -132,7 +132,7 @@ limitations under the License. .mx_RoomStatusBar_connectionLostBar_desc { color: $primary-fg-color; - font-size: 13px; + font-size: $font-13px; opacity: 0.5; padding-bottom: 20px; } @@ -145,7 +145,7 @@ limitations under the License. .mx_RoomStatusBar_typingBar { height: 50px; - line-height: 50px; + line-height: $font-50px; color: $primary-fg-color; opacity: 0.5; @@ -155,7 +155,7 @@ limitations under the License. .mx_RoomStatusBar_isAlone { height: 50px; - line-height: 50px; + line-height: $font-50px; color: $primary-fg-color; opacity: 0.5; @@ -174,11 +174,11 @@ limitations under the License. .mx_RoomStatusBar_callBar { height: 40px; - line-height: 40px; + line-height: $font-40px; } .mx_RoomStatusBar_typingBar { height: 40px; - line-height: 40px; + line-height: $font-40px; } } diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss deleted file mode 100644 index 1934e681c2..0000000000 --- a/res/css/structures/_RoomSubList.scss +++ /dev/null @@ -1,187 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/* a word of explanation about the flex-shrink values employed here: - there are 3 priotized categories of screen real-estate grabbing, - each with a flex-shrink difference of 4 order of magnitude, - so they ideally wouldn't affect each other. - lowest category: .mx_RoomSubList - flex-shrink: 10000000 - distribute size of items within the same category by their size - middle category: .mx_RoomSubList.resized-sized - flex-shrink: 1000 - applied when using the resizer, will have a max-height set to it, - to limit the size - highest category: .mx_RoomSubList.resized-all - flex-shrink: 1 - small flex-shrink value (1), is only added if you can drag the resizer so far - so in practice you can only assign this category if there is enough space. -*/ - -.mx_RoomSubList { - display: flex; - flex-direction: column; -} - - -.mx_RoomSubList_nonEmpty .mx_AutoHideScrollbar_offset { - padding-bottom: 4px; -} - -.mx_RoomSubList_labelContainer { - display: flex; - flex-direction: row; - align-items: center; - flex: 0 0 auto; - margin: 0 8px; - padding: 0 8px; - height: 36px; -} - -.mx_RoomSubList_labelContainer.focus-visible:focus-within { - background-color: $roomtile-focused-bg-color; -} - -.mx_RoomSubList_label { - flex: 1; - cursor: pointer; - display: flex; - align-items: center; - padding: 0 6px; -} - -.mx_RoomSubList_label > span { - flex: 1 1 auto; - text-transform: uppercase; - color: $roomsublist-label-fg-color; - font-weight: 700; - font-size: 12px; - margin-left: 8px; -} - -.mx_RoomSubList_badge > div { - flex: 0 0 auto; - border-radius: 8px; - font-weight: 600; - font-size: 12px; - padding: 0 5px; - color: $roomtile-badge-fg-color; - background-color: $roomtile-name-color; - cursor: pointer; -} - -.mx_RoomSubList_addRoom, .mx_RoomSubList_badge { - margin-left: 7px; -} - -.mx_RoomSubList_addRoom { - background-color: $roomheader-addroom-bg-color; - border-radius: 10px; // 16/2 + 2 padding - height: 16px; - flex: 0 0 16px; - position: relative; - - &::before { - background-color: $roomheader-addroom-fg-color; - mask: url('$(res)/img/icons-room-add.svg'); - mask-repeat: no-repeat; - mask-position: center; - content: ''; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - } -} - -.mx_RoomSubList_badgeHighlight > div { - color: $accent-fg-color; - background-color: $warning-color; -} - -.mx_RoomSubList_chevron { - pointer-events: none; - mask: url('$(res)/img/feather-customised/dropdown-arrow.svg'); - mask-repeat: no-repeat; - transition: transform 0.2s ease-in; - width: 10px; - height: 6px; - margin-left: 2px; - background-color: $roomsublist-label-fg-color; -} - -.mx_RoomSubList_chevronDown { - transform: rotateZ(0deg); -} - -.mx_RoomSubList_chevronUp { - transform: rotateZ(180deg); -} - -.mx_RoomSubList_chevronRight { - transform: rotateZ(-90deg); -} - -.mx_RoomSubList_scroll { - /* let rooms list grab as much space as it needs (auto), - potentially overflowing and showing a scrollbar */ - flex: 0 1 auto; - padding: 0 8px; -} - -.collapsed { - .mx_RoomSubList_scroll { - padding: 0; - } - - .mx_RoomSubList_labelContainer { - margin-right: 8px; - margin-left: 2px; - padding: 0; - } - - .mx_RoomSubList_addRoom { - margin-left: 3px; - margin-right: 10px; - } - - .mx_RoomSubList_label > span { - display: none; - } -} - -// overflow indicators -.mx_RoomSubList:not(.resized-all) > .mx_RoomSubList_scroll { - &.mx_IndicatorScrollbar_topOverflow::before { - position: sticky; - content: ""; - top: 0; - left: 0; - right: 0; - height: 8px; - z-index: 100; - display: block; - pointer-events: none; - transition: background-image 0.1s ease-in; - background: linear-gradient(to top, $panel-gradient); - } - - - &.mx_IndicatorScrollbar_topOverflow { - margin-top: -8px; - } -} diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 5e826306c6..3b60c4e62b 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -23,7 +23,7 @@ limitations under the License. .mx_RoomView_fileDropTarget { min-width: 0px; width: 100%; - font-size: 18px; + font-size: $font-18px; text-align: center; pointer-events: none; @@ -186,7 +186,7 @@ limitations under the License. .mx_RoomView_empty { flex: 1 1 auto; - font-size: 13px; + font-size: $font-13px; padding-left: 3em; padding-right: 3em; margin-right: 20px; @@ -261,7 +261,7 @@ hr.mx_RoomView_myReadMarker { .mx_RoomView_voipButton { float: right; margin-right: 13px; - margin-top: 10px; + margin-top: 13px; cursor: pointer; } diff --git a/res/css/structures/_TabbedView.scss b/res/css/structures/_TabbedView.scss index 7904df5a82..4a4bb125a3 100644 --- a/res/css/structures/_TabbedView.scss +++ b/res/css/structures/_TabbedView.scss @@ -39,7 +39,7 @@ limitations under the License. cursor: pointer; display: block; border-radius: 3px; - font-size: 14px; + font-size: $font-14px; min-height: 24px; // use min-height instead of height to allow the label to overflow a bit margin-bottom: 6px; position: relative; diff --git a/res/css/structures/_TagPanel.scss b/res/css/structures/_TagPanel.scss index 472831c0d9..78e8326772 100644 --- a/res/css/structures/_TagPanel.scss +++ b/res/css/structures/_TagPanel.scss @@ -33,7 +33,7 @@ limitations under the License. .mx_TagPanel .mx_TagPanel_clearButton_container { /* Constant height within flex mx_TagPanel */ height: 70px; - width: 60px; + width: 56px; flex: none; @@ -51,7 +51,7 @@ limitations under the License. .mx_TagPanel .mx_TagPanel_divider { height: 0px; - width: 42px; + width: 34px; border-bottom: 1px solid $panel-divider-color; display: none; } @@ -66,15 +66,13 @@ limitations under the License. flex-direction: column; align-items: center; - height: 100%; + padding-top: 6px; } .mx_TagPanel .mx_TagPanel_tagTileContainer > div { - height: 40px; - padding: 10px 0 9px 0; + margin: 6px 0; } .mx_TagPanel .mx_TagTile { - margin: 9px 0; // opacity: 0.5; position: relative; } @@ -86,8 +84,8 @@ limitations under the License. .mx_TagPanel .mx_TagTile_plus { margin-bottom: 12px; - height: 40px; - width: 40px; + height: 32px; + width: 32px; border-radius: 20px; background-color: $roomheader-addroom-bg-color; position: relative; @@ -110,13 +108,13 @@ limitations under the License. .mx_TagPanel .mx_TagTile.mx_TagTile_selected::before { content: ''; - height: 56px; + height: calc(100% + 16px); background-color: $accent-color; width: 5px; position: absolute; left: -15px; border-radius: 0 3px 3px 0; - top: -8px; // (56 - 40)/2 + top: -8px; // (16px from height / 2) } .mx_TagPanel .mx_TagTile.mx_AccessibleButton:focus { @@ -137,9 +135,9 @@ limitations under the License. top: -8px; border-radius: 8px; background-color: $neutral-badge-color; - color: #ffffff; + color: #000; font-weight: 600; - font-size: 10px; + font-size: $font-10px; text-align: center; padding-top: 1px; padding-left: 4px; @@ -157,9 +155,9 @@ limitations under the License. border-radius: 8px; color: $accent-fg-color; font-weight: 600; - font-size: 14px; + font-size: $font-14px; padding: 0 5px; - background-color: $roomtile-name-color; + background-color: $muted-fg-color; } .mx_TagTile_badgeHighlight { diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss index d1687743d6..e798e4ac52 100644 --- a/res/css/structures/_ToastContainer.scss +++ b/res/css/structures/_ToastContainer.scss @@ -28,8 +28,8 @@ limitations under the License. margin: 0 4px; grid-row: 2 / 4; grid-column: 1; - background-color: white; - box-shadow: 0px 4px 12px $menu-box-shadow-color; + background-color: $dark-panel-bg-color; + box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); border-radius: 8px; } @@ -37,26 +37,28 @@ limitations under the License. grid-row: 1 / 3; grid-column: 1; color: $primary-fg-color; - background-color: $primary-bg-color; - box-shadow: 0px 4px 12px $menu-box-shadow-color; + background-color: $dark-panel-bg-color; + box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); border-radius: 8px; overflow: hidden; display: grid; - grid-template-columns: 20px 1fr; - column-gap: 10px; + grid-template-columns: 22px 1fr; + column-gap: 8px; row-gap: 4px; padding: 8px; - padding-right: 16px; &.mx_Toast_hasIcon { - &::after { + &::before, &::after { content: ""; width: 22px; height: 22px; grid-column: 1; grid-row: 1; mask-size: 100%; + mask-position: center; mask-repeat: no-repeat; + background-size: 100%; + background-repeat: no-repeat; } &.mx_Toast_icon_verification::after { @@ -64,21 +66,63 @@ limitations under the License. background-color: $primary-fg-color; } - &.mx_Toast_icon_verification_warning::after { - background-image: url("$(res)/img/e2e/warning.svg"); + &.mx_Toast_icon_verification_warning { + // white infill for the hollow svg mask + &::before { + background-color: #ffffff; + mask-image: url('$(res)/img/e2e/normal.svg'); + mask-size: 90%; + } + + &::after { + mask-image: url("$(res)/img/e2e/warning.svg"); + background-color: $notice-primary-color; + } } - h2, .mx_Toast_body { + &.mx_Toast_icon_element_logo::after { + background-image: url("$(res)/img/element-logo.svg"); + } + + .mx_Toast_title, .mx_Toast_body { grid-column: 2; } } + &:not(.mx_Toast_hasIcon) { + padding-left: 12px; - h2 { - grid-column: 1 / 3; - grid-row: 1; - margin: 0; - font-size: 15px; - font-weight: 600; + .mx_Toast_title { + grid-column: 1 / -1; + } + } + + .mx_Toast_title, + .mx_Toast_description { + padding-right: 8px; + } + + .mx_Toast_title { + width: 100%; + box-sizing: border-box; + + h2 { + grid-column: 1 / 3; + grid-row: 1; + margin: 0; + font-size: $font-15px; + font-weight: 600; + display: inline; + width: auto; + vertical-align: middle; + } + + span { + padding-left: 8px; + float: right; + font-size: $font-12px; + line-height: $font-22px; + color: $muted-fg-color; + } } .mx_Toast_body { @@ -87,20 +131,34 @@ limitations under the License. } .mx_Toast_buttons { + float: right; display: flex; + + .mx_FormButton { + min-width: 96px; + box-sizing: border-box; + } } .mx_Toast_description { - max-width: 400px; + max-width: 272px; overflow: hidden; - white-space: nowrap; text-overflow: ellipsis; margin: 4px 0 11px 0; - font-size: 12px; + font-size: $font-12px; + + .mx_AccessibleButton_kind_link { + font-size: inherit; + padding: 0; + } + + a { + text-decoration: none; + } } .mx_Toast_deviceID { - font-size: 10px; + font-size: $font-10px; } } } diff --git a/res/css/structures/_TopLeftMenuButton.scss b/res/css/structures/_TopLeftMenuButton.scss deleted file mode 100644 index ee03978f18..0000000000 --- a/res/css/structures/_TopLeftMenuButton.scss +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright 2018 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_TopLeftMenuButton { - flex: 0 0 52px; - border-bottom: 1px solid $panel-divider-color; - color: $topleftmenu-color; - background-color: $primary-bg-color; - display: flex; - align-items: center; - min-width: 0; - padding: 0 4px; - overflow: hidden; -} - -.mx_TopLeftMenuButton .mx_BaseAvatar { - margin: 0 7px; -} - -.mx_TopLeftMenuButton_name { - margin: 0 7px; - font-size: 18px; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - font-weight: 600; -} - -.mx_TopLeftMenuButton_chevron { - margin: 0 7px; - mask: url('$(res)/img/feather-customised/dropdown-arrow.svg'); - mask-repeat: no-repeat; - width: 10px; - height: 6px; - background-color: $roomsublist-label-fg-color; -} diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss new file mode 100644 index 0000000000..81a10ee1d0 --- /dev/null +++ b/res/css/structures/_UserMenu.scss @@ -0,0 +1,201 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_UserMenu { + + // to make the ... button sort of aligned with the explore button below + padding-right: 6px; + + .mx_UserMenu_headerButtons { + width: 16px; + height: 16px; + position: relative; + display: block; + + &::before { + content: ''; + width: 16px; + height: 16px; + position: absolute; + top: 0; + left: 0; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background: $primary-fg-color; + mask-image: url('$(res)/img/element-icons/context-menu.svg'); + } + } + + .mx_UserMenu_row { + // Create a row-based flexbox to ensure items stay aligned correctly. + display: flex; + align-items: center; + + .mx_UserMenu_userAvatarContainer { + position: relative; // to make default avatars work + margin-right: 8px; + height: 32px; // to remove the unknown 4px gap the browser puts below it + + .mx_UserMenu_userAvatar { + border-radius: 32px; // should match avatar size + object-fit: cover; + } + } + + .mx_UserMenu_userName { + font-weight: 600; + font-size: $font-15px; + line-height: $font-20px; + flex: 1; + + // Ellipsize any text overflow + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + .mx_UserMenu_headerButtons { + // No special styles: the rest of the layout happens to make it work. + } + } + + &.mx_UserMenu_minimized { + .mx_UserMenu_userHeader { + .mx_UserMenu_row { + justify-content: center; + } + + .mx_UserMenu_userAvatarContainer { + margin-right: 0; + } + } + } +} + +.mx_UserMenu_contextMenu { + width: 247px; + + .mx_UserMenu_contextMenu_redRow { + .mx_AccessibleButton { + padding-top: 16px; + padding-bottom: 16px; + color: $warning-color !important; // !important to override styles from context menu + } + + .mx_IconizedContextMenu_icon::before { + background-color: $warning-color; + } + } + + .mx_UserMenu_contextMenu_header { + padding: 20px; + + // Create a flexbox to organize the header a bit easier + display: flex; + align-items: center; + + .mx_UserMenu_contextMenu_name { + // Create another flexbox of columns to handle large user IDs + display: flex; + flex-direction: column; + width: calc(100% - 40px); // 40px = 32px theme button + 8px margin to theme button + + * { + // Automatically grow all subelements to fit the container + flex: 1; + width: 100%; + + // Ellipsize any text overflow + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + .mx_UserMenu_contextMenu_displayName { + font-weight: bold; + font-size: $font-15px; + line-height: $font-20px; + } + + .mx_UserMenu_contextMenu_userId { + font-size: $font-15px; + line-height: $font-24px; + } + } + + .mx_UserMenu_contextMenu_themeButton { + min-width: 32px; + max-width: 32px; + width: 32px; + height: 32px; + margin-left: 8px; + border-radius: 32px; + background-color: $theme-button-bg-color; + cursor: pointer; + + // to make alignment easier, create flexbox for the image + display: flex; + align-items: center; + justify-content: center; + } + } + + .mx_IconizedContextMenu_icon { + width: 16px; + height: 16px; + display: block; + + &::before { + content: ''; + width: 16px; + height: 16px; + display: block; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background: $primary-fg-color; + } + } + + .mx_UserMenu_iconHome::before { + mask-image: url('$(res)/img/element-icons/roomlist/home.svg'); + } + + .mx_UserMenu_iconBell::before { + mask-image: url('$(res)/img/element-icons/notifications.svg'); + } + + .mx_UserMenu_iconLock::before { + mask-image: url('$(res)/img/element-icons/security.svg'); + } + + .mx_UserMenu_iconSettings::before { + mask-image: url('$(res)/img/element-icons/settings.svg'); + } + + .mx_UserMenu_iconArchive::before { + mask-image: url('$(res)/img/element-icons/roomlist/archived.svg'); + } + + .mx_UserMenu_iconMessage::before { + mask-image: url('$(res)/img/element-icons/roomlist/feedback.svg'); + } + + .mx_UserMenu_iconSignOut::before { + mask-image: url('$(res)/img/element-icons/leave.svg'); + } +} diff --git a/res/css/structures/_ViewSource.scss b/res/css/structures/_ViewSource.scss index b908861c6f..421d1f03cd 100644 --- a/res/css/structures/_ViewSource.scss +++ b/res/css/structures/_ViewSource.scss @@ -29,7 +29,7 @@ limitations under the License. .mx_ViewSource pre { text-align: left; - font-size: 12px; + font-size: $font-12px; padding: 0.5em 1em 0.5em 1em; word-wrap: break-word; white-space: pre-wrap; diff --git a/res/css/structures/auth/_CompleteSecurity.scss b/res/css/structures/auth/_CompleteSecurity.scss index 601492d43c..f742be70e4 100644 --- a/res/css/structures/auth/_CompleteSecurity.scss +++ b/res/css/structures/auth/_CompleteSecurity.scss @@ -26,6 +26,50 @@ limitations under the License. position: relative; } +.mx_CompleteSecurity_clients { + width: max-content; + margin: 36px auto 0; + + .mx_CompleteSecurity_clients_desktop, .mx_CompleteSecurity_clients_mobile { + position: relative; + width: 160px; + text-align: center; + padding-top: 64px; + display: inline-block; + + &::before { + content: ''; + position: absolute; + height: 48px; + width: 48px; + left: 56px; + top: 0; + background-color: $muted-fg-color; + mask-repeat: no-repeat; + mask-size: contain; + } + } + + .mx_CompleteSecurity_clients_desktop { + margin-right: 56px; + } + + .mx_CompleteSecurity_clients_desktop::before { + mask-image: url('$(res)/img/feather-customised/monitor.svg'); + } + + .mx_CompleteSecurity_clients_mobile::before { + mask-image: url('$(res)/img/feather-customised/smartphone.svg'); + } + + p { + margin-top: 16px; + font-size: $font-12px; + color: $muted-fg-color; + text-align: center; + } +} + .mx_CompleteSecurity_heroIcon { width: 128px; height: 128px; @@ -34,7 +78,7 @@ limitations under the License. } .mx_CompleteSecurity_body { - font-size: 15px; + font-size: $font-15px; } .mx_CompleteSecurity_waiting { @@ -44,6 +88,7 @@ limitations under the License. .mx_CompleteSecurity_actionRow { display: flex; justify-content: flex-end; + margin-top: $font-28px; .mx_AccessibleButton { margin-inline-start: 18px; diff --git a/res/css/structures/auth/_Login.scss b/res/css/structures/auth/_Login.scss index 4ce90cc6bd..02436833a2 100644 --- a/res/css/structures/auth/_Login.scss +++ b/res/css/structures/auth/_Login.scss @@ -89,3 +89,13 @@ limitations under the License. .mx_Login_underlinedServerName { border-bottom: 1px dashed $accent-color; } + +div.mx_AccessibleButton_kind_link.mx_Login_forgot { + // style it as a link + font-size: inherit; + padding: 0; + + &.mx_AccessibleButton_disabled { + cursor: not-allowed; + } +} diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index 7c5b008535..0ba0d10e06 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -17,7 +17,7 @@ limitations under the License. .mx_AuthBody { width: 500px; - font-size: 12px; + font-size: $font-12px; color: $authpage-secondary-color; background-color: $authpage-body-bg-color; border-radius: 0 4px 4px 0; @@ -25,14 +25,14 @@ limitations under the License. box-sizing: border-box; h2 { - font-size: 24px; + font-size: $font-24px; font-weight: 600; margin-top: 8px; color: $authpage-primary-color; } h3 { - font-size: 14px; + font-size: $font-14px; font-weight: 600; color: $authpage-primary-color; } @@ -98,7 +98,7 @@ limitations under the License. .mx_AuthBody_editServerDetails { padding-left: 1em; - font-size: 12px; + font-size: $font-12px; font-weight: normal; } @@ -119,6 +119,29 @@ limitations under the License. margin-right: 0; } +.mx_AuthBody_paddedFooter { + height: 80px; // height of the submit button + register link + padding-top: 28px; + text-align: center; + + .mx_AuthBody_paddedFooter_title { + margin-top: 16px; + font-size: $font-15px; + line-height: $font-24px; + + .mx_InlineSpinner img { + vertical-align: sub; + margin-right: 5px; + } + } + + .mx_AuthBody_paddedFooter_subtitle { + margin-top: 8px; + font-size: $font-10px; + line-height: $font-14px; + } +} + .mx_AuthBody_changeFlow { display: block; text-align: center; @@ -129,26 +152,11 @@ limitations under the License. margin: 1em 0; } -.mx_AuthBody_passwordScore { - width: 100%; - appearance: none; - height: 4px; - border: 0; - border-radius: 2px; - position: absolute; - top: -12px; - - &::-moz-progress-bar { - border-radius: 2px; - background-color: $accent-color; - } - - &::-webkit-progress-bar, - &::-webkit-progress-value { - border-radius: 2px; - } - - &::-webkit-progress-value { - background-color: $accent-color; +@media only screen and (max-width: 480px) { + .mx_AuthBody { + border-radius: 4px; + width: auto; + max-width: 500px; + padding: 10px; } } diff --git a/res/css/views/auth/_AuthButtons.scss b/res/css/views/auth/_AuthButtons.scss index 553adeee14..8deb0f80ac 100644 --- a/res/css/views/auth/_AuthButtons.scss +++ b/res/css/views/auth/_AuthButtons.scss @@ -43,7 +43,7 @@ limitations under the License. cursor: pointer; - font-size: 15px; + font-size: $font-15px; padding: 0 11px; word-break: break-word; } diff --git a/res/css/views/auth/_AuthFooter.scss b/res/css/views/auth/_AuthFooter.scss index ab169a6898..0bc2743d54 100644 --- a/res/css/views/auth/_AuthFooter.scss +++ b/res/css/views/auth/_AuthFooter.scss @@ -17,7 +17,7 @@ limitations under the License. .mx_AuthFooter { text-align: center; width: 100%; - font-size: 14px; + font-size: $font-14px; opacity: 0.72; padding: 20px 0; background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.8)); diff --git a/res/css/views/auth/_AuthHeader.scss b/res/css/views/auth/_AuthHeader.scss index b3d07b1925..b1372affee 100644 --- a/res/css/views/auth/_AuthHeader.scss +++ b/res/css/views/auth/_AuthHeader.scss @@ -21,3 +21,9 @@ limitations under the License. padding: 25px 40px; box-sizing: border-box; } + +@media only screen and (max-width: 480px) { + .mx_AuthHeader { + display: none; + } +} diff --git a/res/css/views/auth/_AuthHeaderLogo.scss b/res/css/views/auth/_AuthHeaderLogo.scss index 091fb0197b..917dcabf67 100644 --- a/res/css/views/auth/_AuthHeaderLogo.scss +++ b/res/css/views/auth/_AuthHeaderLogo.scss @@ -23,3 +23,9 @@ limitations under the License. .mx_AuthHeaderLogo img { width: 100%; } + +@media only screen and (max-width: 480px) { + .mx_AuthHeaderLogo { + display: none; + } +} diff --git a/res/css/views/auth/_AuthPage.scss b/res/css/views/auth/_AuthPage.scss index 8ef48b6265..e3409792f0 100644 --- a/res/css/views/auth/_AuthPage.scss +++ b/res/css/views/auth/_AuthPage.scss @@ -29,3 +29,9 @@ limitations under the License. box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.33); background-color: $authpage-modal-bg-color; } + +@media only screen and (max-width: 480px) { + .mx_AuthPage_modal { + margin-top: 0; + } +} diff --git a/res/css/views/auth/_CompleteSecurityBody.scss b/res/css/views/auth/_CompleteSecurityBody.scss index c7860fbe74..46b7abe2cc 100644 --- a/res/css/views/auth/_CompleteSecurityBody.scss +++ b/res/css/views/auth/_CompleteSecurityBody.scss @@ -24,13 +24,13 @@ limitations under the License. box-sizing: border-box; h2 { - font-size: 24px; + font-size: $font-24px; font-weight: 600; margin-top: 0; } h3 { - font-size: 14px; + font-size: $font-14px; font-weight: 600; } diff --git a/res/css/views/auth/_InteractiveAuthEntryComponents.scss b/res/css/views/auth/_InteractiveAuthEntryComponents.scss index 85007aeecb..05cddf2c48 100644 --- a/res/css/views/auth/_InteractiveAuthEntryComponents.scss +++ b/res/css/views/auth/_InteractiveAuthEntryComponents.scss @@ -60,3 +60,14 @@ limitations under the License. .mx_InteractiveAuthEntryComponents_passwordSection { width: 300px; } + +.mx_InteractiveAuthEntryComponents_sso_buttons { + display: flex; + flex-direction: row; + justify-content: flex-end; + margin-top: 20px; + + .mx_AccessibleButton { + margin-left: 5px; + } +} diff --git a/res/css/views/auth/_LanguageSelector.scss b/res/css/views/auth/_LanguageSelector.scss index 6f7eac0cf6..781561f876 100644 --- a/res/css/views/auth/_LanguageSelector.scss +++ b/res/css/views/auth/_LanguageSelector.scss @@ -20,7 +20,7 @@ limitations under the License. .mx_AuthBody_language .mx_Dropdown_input { border: none; - font-size: 14px; + font-size: $font-14px; font-weight: 600; color: $authpage-lang-color; } diff --git a/res/css/views/auth/_PassphraseField.scss b/res/css/views/auth/_PassphraseField.scss new file mode 100644 index 0000000000..bf8e7f4438 --- /dev/null +++ b/res/css/views/auth/_PassphraseField.scss @@ -0,0 +1,37 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +$PassphraseStrengthHigh: $accent-color; +$PassphraseStrengthMedium: $username-variant5-color; +$PassphraseStrengthLow: $notice-primary-color; + +progress.mx_PassphraseField_progress { + appearance: none; + width: 100%; + border: 0; + height: 4px; + position: absolute; + top: -12px; + + @mixin ProgressBarBorderRadius "2px"; + @mixin ProgressBarColour $PassphraseStrengthLow; + &[value="2"], &[value="3"] { + @mixin ProgressBarColour $PassphraseStrengthMedium; + } + &[value="4"] { + @mixin ProgressBarColour $PassphraseStrengthHigh; + } +} diff --git a/res/css/views/auth/_ServerTypeSelector.scss b/res/css/views/auth/_ServerTypeSelector.scss index ed781726b7..fbd3d2655d 100644 --- a/res/css/views/auth/_ServerTypeSelector.scss +++ b/res/css/views/auth/_ServerTypeSelector.scss @@ -65,5 +65,5 @@ limitations under the License. } .mx_ServerTypeSelector_description { - font-size: 10px; + font-size: $font-10px; } diff --git a/res/css/views/avatars/_DecoratedRoomAvatar.scss b/res/css/views/avatars/_DecoratedRoomAvatar.scss new file mode 100644 index 0000000000..48d72131b5 --- /dev/null +++ b/res/css/views/avatars/_DecoratedRoomAvatar.scss @@ -0,0 +1,34 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// XXX: We shouldn't be using TemporaryTile anywhere - delete it. +.mx_DecoratedRoomAvatar, .mx_TemporaryTile { + position: relative; + + .mx_RoomTileIcon { + position: absolute; + bottom: 0; + right: 0; + } + + .mx_NotificationBadge, .mx_RoomTile_badgeContainer { + position: absolute; + top: 0; + right: 0; + height: 18px; + width: 18px; + } +} diff --git a/res/css/views/avatars/_MemberStatusMessageAvatar.scss b/res/css/views/avatars/_MemberStatusMessageAvatar.scss index c101a5d8a8..975b4e5ce9 100644 --- a/res/css/views/avatars/_MemberStatusMessageAvatar.scss +++ b/res/css/views/avatars/_MemberStatusMessageAvatar.scss @@ -17,7 +17,7 @@ limitations under the License. .mx_MessageComposer_avatar .mx_BaseAvatar { padding: 2px; border: 1px solid transparent; - border-radius: 15px; + border-radius: 100%; } .mx_MessageComposer_avatar .mx_BaseAvatar_initial { diff --git a/res/css/views/avatars/_PulsedAvatar.scss b/res/css/views/avatars/_PulsedAvatar.scss new file mode 100644 index 0000000000..ce9e3382ab --- /dev/null +++ b/res/css/views/avatars/_PulsedAvatar.scss @@ -0,0 +1,30 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_PulsedAvatar { + @keyframes shadow-pulse { + 0% { + box-shadow: 0 0 0 0px rgba($accent-color, 0.2); + } + 100% { + box-shadow: 0 0 0 6px rgba($accent-color, 0); + } + } + + img { + animation: shadow-pulse 1s infinite; + } +} diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss index d15d566bdb..2ecb93e734 100644 --- a/res/css/views/context_menus/_MessageContextMenu.scss +++ b/res/css/views/context_menus/_MessageContextMenu.scss @@ -19,6 +19,7 @@ limitations under the License. } .mx_MessageContextMenu_field { + display: block; padding: 3px 6px 3px 6px; cursor: pointer; white-space: nowrap; diff --git a/res/css/views/context_menus/_RoomTileContextMenu.scss b/res/css/views/context_menus/_RoomTileContextMenu.scss index 308cecfe1e..9697ac9bef 100644 --- a/res/css/views/context_menus/_RoomTileContextMenu.scss +++ b/res/css/views/context_menus/_RoomTileContextMenu.scss @@ -38,7 +38,7 @@ limitations under the License. white-space: nowrap; display: flex; align-items: center; - line-height: 16px; + line-height: $font-16px; } .mx_RoomTileContextMenu_tag_field.mx_RoomTileContextMenu_tag_fieldSet { diff --git a/res/css/views/context_menus/_StatusMessageContextMenu.scss b/res/css/views/context_menus/_StatusMessageContextMenu.scss index 2c8d608950..fceb7fba34 100644 --- a/res/css/views/context_menus/_StatusMessageContextMenu.scss +++ b/res/css/views/context_menus/_StatusMessageContextMenu.scss @@ -44,7 +44,7 @@ input.mx_StatusMessageContextMenu_message { .mx_StatusMessageContextMenu_clear { @mixin mx_DialogButton; align-self: start; - font-size: 12px; + font-size: $font-12px; padding: 6px 1em; border: 1px solid transparent; margin-right: 10px; diff --git a/res/css/views/context_menus/_TagTileContextMenu.scss b/res/css/views/context_menus/_TagTileContextMenu.scss index 46b279ce2d..8929c8906e 100644 --- a/res/css/views/context_menus/_TagTileContextMenu.scss +++ b/res/css/views/context_menus/_TagTileContextMenu.scss @@ -15,25 +15,31 @@ limitations under the License. */ .mx_TagTileContextMenu_item { - padding-top: 8px; + padding: 8px; padding-right: 20px; - padding-bottom: 8px; cursor: pointer; white-space: nowrap; display: flex; align-items: center; - line-height: 16px; + line-height: $font-16px; } -.mx_TagTileContextMenu_item object { - pointer-events: none; +.mx_TagTileContextMenu_item::before { + content: ''; + height: 15px; + width: 15px; + background-color: currentColor; + mask-repeat: no-repeat; + mask-size: contain; + margin-right: 8px; } +.mx_TagTileContextMenu_viewCommunity::before { + mask-image: url('$(res)/img/element-icons/view-community.svg'); +} -.mx_TagTileContextMenu_item_icon { - padding-right: 8px; - padding-left: 4px; - display: inline-block; +.mx_TagTileContextMenu_hideCommunity::before { + mask-image: url('$(res)/img/element-icons/hide.svg'); } .mx_TagTileContextMenu_separator { diff --git a/res/css/views/context_menus/_TopLeftMenu.scss b/res/css/views/context_menus/_TopLeftMenu.scss index ed0d0106bc..e0f5dd47bd 100644 --- a/res/css/views/context_menus/_TopLeftMenu.scss +++ b/res/css/views/context_menus/_TopLeftMenu.scss @@ -19,12 +19,12 @@ limitations under the License. border-radius: 4px; .mx_TopLeftMenu_greyedText { - font-size: 12px; + font-size: $font-12px; opacity: 0.5; } .mx_TopLeftMenu_upgradeLink { - font-size: 12px; + font-size: $font-12px; img { margin-left: 5px; @@ -72,10 +72,10 @@ limitations under the License. .mx_AccessibleButton::after { mask-repeat: no-repeat; mask-position: 0 center; - mask-size: 16px; + mask-size: $font-16px; position: absolute; - width: 16px; - height: 16px; + width: $font-16px; + height: $font-16px; content: ""; top: 5px; left: 14px; diff --git a/res/css/views/dialogs/_AddressPickerDialog.scss b/res/css/views/dialogs/_AddressPickerDialog.scss index 39a9260ba3..136e497994 100644 --- a/res/css/views/dialogs/_AddressPickerDialog.scss +++ b/res/css/views/dialogs/_AddressPickerDialog.scss @@ -28,7 +28,7 @@ limitations under the License. .mx_AddressPickerDialog_input, .mx_AddressPickerDialog_input:focus { height: 26px; - font-size: 14px; + font-size: $font-14px; font-family: $font-family; padding-left: 12px; padding-right: 12px; @@ -50,7 +50,7 @@ limitations under the License. .mx_AddressPickerDialog_inputContainer { border-radius: 3px; border: solid 1px $input-border-color; - line-height: 36px; + line-height: $font-36px; padding-left: 4px; padding-right: 4px; padding-top: 1px; diff --git a/res/css/views/dialogs/_ConfirmUserActionDialog.scss b/res/css/views/dialogs/_ConfirmUserActionDialog.scss index b859d6bf4d..823f4d1e28 100644 --- a/res/css/views/dialogs/_ConfirmUserActionDialog.scss +++ b/res/css/views/dialogs/_ConfirmUserActionDialog.scss @@ -26,22 +26,22 @@ limitations under the License. } .mx_ConfirmUserActionDialog_name { - font-size: 18px; + font-size: $font-18px; } .mx_ConfirmUserActionDialog_userId { - font-size: 13px; + font-size: $font-13px; } .mx_ConfirmUserActionDialog_reasonField { font-family: $font-family; - font-size: 14px; + font-size: $font-14px; color: $primary-fg-color; background-color: $primary-bg-color; border-radius: 3px; border: solid 1px $input-border-color; - line-height: 36px; + line-height: $font-36px; padding-left: 16px; padding-right: 16px; padding-top: 1px; diff --git a/res/css/views/dialogs/_CreateGroupDialog.scss b/res/css/views/dialogs/_CreateGroupDialog.scss index 128eacc3ce..f7bfc61a98 100644 --- a/res/css/views/dialogs/_CreateGroupDialog.scss +++ b/res/css/views/dialogs/_CreateGroupDialog.scss @@ -25,7 +25,7 @@ limitations under the License. } .mx_CreateGroupDialog_input { - font-size: 15px; + font-size: $font-15px; border-radius: 3px; border: 1px solid $input-border-color; padding: 9px; @@ -44,7 +44,7 @@ limitations under the License. .mx_CreateGroupDialog_prefix, .mx_CreateGroupDialog_suffix { padding: 0px 5px; - line-height: 37px; + line-height: $font-37px; background-color: $input-darker-bg-color; border: 1px solid $input-border-color; text-align: center; diff --git a/res/css/views/dialogs/_CreateRoomDialog.scss b/res/css/views/dialogs/_CreateRoomDialog.scss index 7416ec2ef4..2678f7b4ad 100644 --- a/res/css/views/dialogs/_CreateRoomDialog.scss +++ b/res/css/views/dialogs/_CreateRoomDialog.scss @@ -15,6 +15,8 @@ limitations under the License. */ .mx_CreateRoomDialog_details { + margin-top: 15px; + .mx_CreateRoomDialog_details_summary { outline: none; list-style: none; @@ -49,7 +51,7 @@ limitations under the License. } .mx_CreateRoomDialog_input { - font-size: 15px; + font-size: $font-15px; border-radius: 3px; border: 1px solid $input-border-color; padding: 9px; @@ -71,11 +73,19 @@ limitations under the License. } .mx_CreateRoomDialog { - &.mx_Dialog_fixedWidth { width: 450px; } + .mx_Dialog_content { + margin-bottom: 40px; + } + + p, + .mx_Field_input label { + color: $muted-fg-color; + } + .mx_SettingsFlag { display: flex; } @@ -90,5 +100,18 @@ limitations under the License. flex: 0 0 auto; margin-left: 30px; } + + .mx_CreateRoomDialog_topic { + margin-bottom: 36px; + } + + .mx_Dialog_content > .mx_SettingsFlag { + margin-top: 24px; + } + + p { + margin: 0 85px 0 0; + font-size: $font-12px; + } } diff --git a/res/css/views/dialogs/_DevtoolsDialog.scss b/res/css/views/dialogs/_DevtoolsDialog.scss index 500c46b5fd..35cb6bc7ab 100644 --- a/res/css/views/dialogs/_DevtoolsDialog.scss +++ b/res/css/views/dialogs/_DevtoolsDialog.scss @@ -68,11 +68,11 @@ limitations under the License. width: 240px; color: $input-fg-color; font-family: $font-family; - font-size: 16px; + font-size: $font-16px; } .mx_DevTools_textarea { - font-size: 12px; + font-size: $font-12px; max-width: 684px; min-height: 250px; padding: 10px; diff --git a/res/css/views/dialogs/_GroupAddressPicker.scss b/res/css/views/dialogs/_GroupAddressPicker.scss index 20a7cc1047..5fa18931f0 100644 --- a/res/css/views/dialogs/_GroupAddressPicker.scss +++ b/res/css/views/dialogs/_GroupAddressPicker.scss @@ -18,8 +18,3 @@ limitations under the License. margin-top: 10px; display: flex; } - -.mx_GroupAddressPicker_checkboxContainer input[type="checkbox"] { - /* Stop flex from shrinking the checkbox */ - width: 20px; -} diff --git a/res/css/views/dialogs/_InviteDialog.scss b/res/css/views/dialogs/_InviteDialog.scss index 5e0893b8fd..a77d0bfbba 100644 --- a/res/css/views/dialogs/_InviteDialog.scss +++ b/res/css/views/dialogs/_InviteDialog.scss @@ -40,8 +40,8 @@ limitations under the License. textarea, textarea:focus { height: 34px; - line-height: 34px; - font-size: 14px; + line-height: $font-34px; + font-size: $font-14px; padding-left: 12px; margin: 0 !important; border: 0 !important; @@ -65,7 +65,7 @@ limitations under the License. min-width: 48px; margin-left: 10px; height: 25px; - line-height: 25px; + line-height: $font-25px; } .mx_InviteDialog_buttonAndSpinner { @@ -84,7 +84,7 @@ limitations under the License. padding-bottom: 10px; h3 { - font-size: 12px; + font-size: $font-12px; color: $muted-fg-color; font-weight: bold; text-transform: uppercase; @@ -143,23 +143,23 @@ limitations under the License. .mx_InviteDialog_roomTile_name { font-weight: 600; - font-size: 14px; + font-size: $font-14px; color: $primary-fg-color; margin-left: 7px; } .mx_InviteDialog_roomTile_userId { - font-size: 12px; + font-size: $font-12px; color: $muted-fg-color; margin-left: 7px; } .mx_InviteDialog_roomTile_time { text-align: right; - font-size: 12px; + font-size: $font-12px; color: $muted-fg-color; float: right; - line-height: 36px; // Height of the avatar to keep the time vertically aligned + line-height: $font-36px; // Height of the avatar to keep the time vertically aligned } .mx_InviteDialog_roomTile_highlight { @@ -176,7 +176,7 @@ limitations under the License. border-radius: 12px; display: inline-block; height: 24px; - line-height: 24px; + line-height: $font-24px; padding-left: 8px; padding-right: 8px; color: #ffffff; // this is fine without a var because it's for both themes diff --git a/res/css/views/dialogs/_MessageEditHistoryDialog.scss b/res/css/views/dialogs/_MessageEditHistoryDialog.scss index 0066faccae..e9d777effd 100644 --- a/res/css/views/dialogs/_MessageEditHistoryDialog.scss +++ b/res/css/views/dialogs/_MessageEditHistoryDialog.scss @@ -35,7 +35,7 @@ limitations under the License. .mx_MessageEditHistoryDialog_edits { list-style-type: none; - font-size: 14px; + font-size: $font-14px; padding: 0; color: $primary-fg-color; @@ -60,7 +60,7 @@ limitations under the License. } .mx_MessageActionBar .mx_AccessibleButton { - font-size: 10px; + font-size: $font-10px; padding: 0 8px; } } diff --git a/res/css/views/dialogs/_NewSessionReviewDialog.scss b/res/css/views/dialogs/_NewSessionReviewDialog.scss index 7e35fe941e..b35c570c80 100644 --- a/res/css/views/dialogs/_NewSessionReviewDialog.scss +++ b/res/css/views/dialogs/_NewSessionReviewDialog.scss @@ -32,6 +32,6 @@ limitations under the License. } .mx_NewSessionReviewDialog_deviceID { - font-size: 12px; + font-size: $font-12px; color: $notice-secondary-color; } diff --git a/res/css/views/dialogs/_RebrandDialog.scss b/res/css/views/dialogs/_RebrandDialog.scss new file mode 100644 index 0000000000..6c916e0f1d --- /dev/null +++ b/res/css/views/dialogs/_RebrandDialog.scss @@ -0,0 +1,63 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_RebrandDialog { + text-align: center; + + a:link, + a:hover, + a:visited { + @mixin mx_Dialog_link; + } + + .mx_Dialog_buttons { + margin-top: 43px; + text-align: center; + } +} + +.mx_RebrandDialog_body { + width: 550px; + margin-left: auto; + margin-right: auto; +} + +.mx_RebrandDialog_logoContainer { + margin-top: 35px; + margin-bottom: 20px; + display: flex; + align-items: center; + justify-content: center; +} + +.mx_RebrandDialog_logo { + margin-left: 28px; + margin-right: 28px; + width: 64px; + height: 64px; +} + +.mx_RebrandDialog_chevron::after { + content: ''; + display: inline-block; + width: 24px; + height: 24px; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background-color: $muted-fg-color; + mask-image: url('$(res)/img/feather-customised/chevron-right.svg'); +} diff --git a/res/css/views/dialogs/_RoomSettingsDialog.scss b/res/css/views/dialogs/_RoomSettingsDialog.scss index 2a4e62f9aa..d4199a1e66 100644 --- a/res/css/views/dialogs/_RoomSettingsDialog.scss +++ b/res/css/views/dialogs/_RoomSettingsDialog.scss @@ -18,15 +18,19 @@ limitations under the License. // ========================================================== .mx_RoomSettingsDialog_settingsIcon::before { - mask-image: url('$(res)/img/feather-customised/settings.svg'); + mask-image: url('$(res)/img/element-icons/settings.svg'); } .mx_RoomSettingsDialog_securityIcon::before { - mask-image: url('$(res)/img/feather-customised/lock.svg'); + mask-image: url('$(res)/img/element-icons/security.svg'); } .mx_RoomSettingsDialog_rolesIcon::before { - mask-image: url('$(res)/img/feather-customised/users-sm.svg'); + mask-image: url('$(res)/img/element-icons/room/settings/roles.svg'); +} + +.mx_RoomSettingsDialog_notificationsIcon::before { + mask-image: url('$(res)/img/element-icons/notifications.svg'); } .mx_RoomSettingsDialog_bridgesIcon::before { @@ -35,7 +39,7 @@ limitations under the License. } .mx_RoomSettingsDialog_warningIcon::before { - mask-image: url('$(res)/img/feather-customised/warning-triangle.svg'); + mask-image: url('$(res)/img/element-icons/room/settings/advanced.svg'); } .mx_RoomSettingsDialog .mx_Dialog_title { diff --git a/res/css/views/dialogs/_SetEmailDialog.scss b/res/css/views/dialogs/_SetEmailDialog.scss index 9d09a208df..37bee7a9ff 100644 --- a/res/css/views/dialogs/_SetEmailDialog.scss +++ b/res/css/views/dialogs/_SetEmailDialog.scss @@ -20,7 +20,7 @@ limitations under the License. padding: 9px; color: $input-fg-color; background-color: $primary-bg-color; - font-size: 15px; + font-size: $font-15px; width: 100%; max-width: 280px; margin-bottom: 10px; diff --git a/res/css/views/dialogs/_SetMxIdDialog.scss b/res/css/views/dialogs/_SetMxIdDialog.scss index f7d8a3d001..1df34f3408 100644 --- a/res/css/views/dialogs/_SetMxIdDialog.scss +++ b/res/css/views/dialogs/_SetMxIdDialog.scss @@ -29,7 +29,7 @@ limitations under the License. padding: 9px; color: $primary-fg-color; background-color: $primary-bg-color; - font-size: 15px; + font-size: $font-15px; width: 100%; max-width: 280px; } diff --git a/res/css/views/dialogs/_SetPasswordDialog.scss b/res/css/views/dialogs/_SetPasswordDialog.scss index 325ff6c6ed..1f99353298 100644 --- a/res/css/views/dialogs/_SetPasswordDialog.scss +++ b/res/css/views/dialogs/_SetPasswordDialog.scss @@ -20,7 +20,7 @@ limitations under the License. padding: 9px; color: $primary-fg-color; background-color: $primary-bg-color; - font-size: 15px; + font-size: $font-15px; max-width: 280px; margin-bottom: 10px; } diff --git a/res/css/views/dialogs/_ShareDialog.scss b/res/css/views/dialogs/_ShareDialog.scss index 9a2f67dea3..d2fe98e8f9 100644 --- a/res/css/views/dialogs/_ShareDialog.scss +++ b/res/css/views/dialogs/_ShareDialog.scss @@ -55,6 +55,7 @@ limitations under the License. margin-left: 5px; width: 20px; height: 20px; + background-repeat: no-repeat; } .mx_ShareDialog_split { @@ -64,9 +65,6 @@ limitations under the License. .mx_ShareDialog_qrcode_container { float: left; - background-color: #ffffff; - padding: 5px; // makes qr code more readable in dark theme - border-radius: 5px; height: 256px; width: 256px; margin-right: 64px; diff --git a/res/css/views/dialogs/_TermsDialog.scss b/res/css/views/dialogs/_TermsDialog.scss index beb507e778..939a31dee6 100644 --- a/res/css/views/dialogs/_TermsDialog.scss +++ b/res/css/views/dialogs/_TermsDialog.scss @@ -31,7 +31,7 @@ limitations under the License. } .mx_TermsDialog_termsTable { - font-size: 12px; + font-size: $font-12px; width: 100%; } diff --git a/res/css/views/dialogs/_UnknownDeviceDialog.scss b/res/css/views/dialogs/_UnknownDeviceDialog.scss deleted file mode 100644 index 2b0f8dceca..0000000000 --- a/res/css/views/dialogs/_UnknownDeviceDialog.scss +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_UnknownDeviceDialog { - height: 100%; - display: flex; - flex-direction: column; -} - -.mx_UnknownDeviceDialog ul { - list-style: none; - padding: 0; -} -// userid -.mx_UnknownDeviceDialog p { - font-weight: bold; - font-size: 16px; -} - -.mx_UnknownDeviceDialog .mx_DeviceVerifyButtons { - flex-direction: row !important; -} - -.mx_UnknownDeviceDialog .mx_Dialog_content { - margin-bottom: 24px; - overflow-y: scroll; -} - -.mx_UnknownDeviceDialog_deviceList > li { - padding: 4px; -} - -.mx_UnknownDeviceDialog_deviceList > li > * { - padding-bottom: 0; -} diff --git a/res/css/views/dialogs/_UserSettingsDialog.scss b/res/css/views/dialogs/_UserSettingsDialog.scss index 4d831d7858..bd472710ea 100644 --- a/res/css/views/dialogs/_UserSettingsDialog.scss +++ b/res/css/views/dialogs/_UserSettingsDialog.scss @@ -18,37 +18,41 @@ limitations under the License. // ========================================================== .mx_UserSettingsDialog_settingsIcon::before { - mask-image: url('$(res)/img/feather-customised/settings.svg'); + mask-image: url('$(res)/img/element-icons/settings.svg'); +} + +.mx_UserSettingsDialog_appearanceIcon::before { + mask-image: url('$(res)/img/element-icons/settings/appearance.svg'); } .mx_UserSettingsDialog_voiceIcon::before { - mask-image: url('$(res)/img/feather-customised/phone.svg'); + mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); } .mx_UserSettingsDialog_bellIcon::before { - mask-image: url('$(res)/img/feather-customised/notifications.svg'); + mask-image: url('$(res)/img/element-icons/notifications.svg'); } .mx_UserSettingsDialog_preferencesIcon::before { - mask-image: url('$(res)/img/feather-customised/sliders.svg'); + mask-image: url('$(res)/img/element-icons/settings/preference.svg'); } .mx_UserSettingsDialog_securityIcon::before { - mask-image: url('$(res)/img/feather-customised/lock.svg'); + mask-image: url('$(res)/img/element-icons/security.svg'); } .mx_UserSettingsDialog_helpIcon::before { - mask-image: url('$(res)/img/feather-customised/help-circle.svg'); + mask-image: url('$(res)/img/element-icons/settings/help.svg'); } .mx_UserSettingsDialog_labsIcon::before { - mask-image: url('$(res)/img/feather-customised/flag.svg'); + mask-image: url('$(res)/img/element-icons/settings/lab-flags.svg'); } .mx_UserSettingsDialog_mjolnirIcon::before { - mask-image: url('$(res)/img/feather-customised/face.svg'); + mask-image: url('$(res)/img/element-icons/room/composer/emoji.svg'); } .mx_UserSettingsDialog_flairIcon::before { - mask-image: url('$(res)/img/feather-customised/flair.svg'); + mask-image: url('$(res)/img/element-icons/settings/flair.svg'); } diff --git a/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss b/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss index b9babd05f5..9be98e25b2 100644 --- a/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss +++ b/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss @@ -35,17 +35,6 @@ limitations under the License. align-items: flex-start; } -.mx_CreateKeyBackupDialog_passPhraseHelp { - flex: 1; - height: 85px; - margin-left: 20px; - font-size: 80%; -} - -.mx_CreateKeyBackupDialog_passPhraseHelp progress { - width: 100%; -} - .mx_CreateKeyBackupDialog_passPhraseInput { flex: none; width: 250px; diff --git a/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss b/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss index 9cba8e0da9..5689d84bc5 100644 --- a/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss +++ b/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss @@ -32,3 +32,9 @@ limitations under the License. padding: 10px; } +.mx_RestoreKeyBackupDialog_content > div { + display: flex; + flex-direction: column; + justify-content: space-between; + min-height: 110px; /* Empirically measured */ +} diff --git a/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss index db11e91bdb..63d0ca555d 100644 --- a/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss +++ b/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss @@ -15,20 +15,79 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_AccessSecretStorageDialog_titleWithIcon::before { + content: ''; + display: inline-block; + width: 24px; + height: 24px; + margin-right: 8px; + position: relative; + top: 5px; + background-color: $primary-fg-color; +} + +.mx_AccessSecretStorageDialog_secureBackupTitle::before { + mask-image: url('$(res)/img/feather-customised/secure-backup.svg'); +} + +.mx_AccessSecretStorageDialog_securePhraseTitle::before { + mask-image: url('$(res)/img/feather-customised/secure-phrase.svg'); +} + .mx_AccessSecretStorageDialog_keyStatus { height: 30px; } -.mx_AccessSecretStorageDialog_primaryContainer { - /* FIXME: plinth colour in new theme(s). background-color: $accent-color; */ - padding: 20px; -} - -.mx_AccessSecretStorageDialog_passPhraseInput, -.mx_AccessSecretStorageDialog_recoveryKeyInput { +.mx_AccessSecretStorageDialog_passPhraseInput { width: 300px; border: 1px solid $accent-color; border-radius: 5px; padding: 10px; } +.mx_AccessSecretStorageDialog_recoveryKeyEntry { + display: flex; + align-items: center; +} + +.mx_AccessSecretStorageDialog_recoveryKeyEntry_textInput { + flex-grow: 1; +} + +.mx_AccessSecretStorageDialog_recoveryKeyEntry_entryControlSeparatorText { + margin: 16px; +} + +.mx_AccessSecretStorageDialog_recoveryKeyFeedback { + &::before { + content: ""; + display: inline-block; + vertical-align: bottom; + width: 20px; + height: 20px; + mask-repeat: no-repeat; + mask-position: center; + mask-size: 20px; + margin-right: 5px; + } +} + +.mx_AccessSecretStorageDialog_recoveryKeyFeedback_valid { + color: $input-valid-border-color; + &::before { + mask-image: url('$(res)/img/feather-customised/check.svg'); + background-color: $input-valid-border-color; + } +} + +.mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid { + color: $input-invalid-border-color; + &::before { + mask-image: url('$(res)/img/feather-customised/x.svg'); + background-color: $input-invalid-border-color; + } +} + +.mx_AccessSecretStorageDialog_recoveryKeyEntry_fileInput { + display: none; +} diff --git a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss index a9ebd54b31..d30803b1f0 100644 --- a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss +++ b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss @@ -48,6 +48,29 @@ limitations under the License. margin-bottom: 1em; } +.mx_CreateSecretStorageDialog_titleWithIcon::before { + content: ''; + display: inline-block; + width: 24px; + height: 24px; + margin-right: 8px; + position: relative; + top: 5px; + background-color: $primary-fg-color; +} + +.mx_CreateSecretStorageDialog_secureBackupTitle::before { + mask-image: url('$(res)/img/feather-customised/secure-backup.svg'); +} + +.mx_CreateSecretStorageDialog_securePhraseTitle::before { + mask-image: url('$(res)/img/feather-customised/secure-phrase.svg'); +} + +.mx_CreateSecretStorageDialog_centeredTitle, .mx_CreateSecretStorageDialog_centeredBody { + text-align: center; +} + .mx_CreateSecretStorageDialog_primaryContainer { /* FIXME: plinth colour in new theme(s). background-color: $accent-color; */ padding-top: 20px; @@ -59,6 +82,36 @@ limitations under the License. display: block; } +.mx_CreateSecretStorageDialog_primaryContainer .mx_RadioButton { + margin-bottom: 16px; + padding: 11px; +} + +.mx_CreateSecretStorageDialog_optionTitle { + color: $dialog-title-fg-color; + font-weight: 600; + font-size: $font-18px; + padding-bottom: 10px; +} + +.mx_CreateSecretStorageDialog_optionIcon { + display: inline-block; + width: 24px; + height: 24px; + margin-right: 8px; + position: relative; + top: 5px; + background-color: $primary-fg-color; +} + +.mx_CreateSecretStorageDialog_optionIcon_securePhrase { + mask-image: url('$(res)/img/feather-customised/secure-phrase.svg'); +} + +.mx_CreateSecretStorageDialog_optionIcon_secureBackup { + mask-image: url('$(res)/img/feather-customised/secure-backup.svg'); +} + .mx_CreateSecretStorageDialog_passPhraseContainer { display: flex; align-items: flex-start; @@ -68,49 +121,47 @@ limitations under the License. margin-top: 0px; } -.mx_CreateSecretStorageDialog_passPhraseHelp { - flex: 1; - height: 64px; - margin-left: 20px; - font-size: 80%; -} - -.mx_CreateSecretStorageDialog_passPhraseHelp progress { - width: 100%; -} - .mx_CreateSecretStorageDialog_passPhraseMatch { width: 200px; margin-left: 20px; } -.mx_CreateSecretStorageDialog_recoveryKeyHeader { - margin-bottom: 1em; -} - .mx_CreateSecretStorageDialog_recoveryKeyContainer { - display: flex; + width: 380px; + margin-left: auto; + margin-right: auto; } .mx_CreateSecretStorageDialog_recoveryKey { - width: 262px; + font-weight: bold; + text-align: center; padding: 20px; color: $info-plinth-fg-color; background-color: $info-plinth-bg-color; - margin-right: 12px; + border-radius: 6px; + word-spacing: 1em; + margin-bottom: 20px; } .mx_CreateSecretStorageDialog_recoveryKeyButtons { - flex: 1; display: flex; + justify-content: space-between; align-items: center; } .mx_CreateSecretStorageDialog_recoveryKeyButtons .mx_AccessibleButton { - margin-right: 10px; -} - -.mx_CreateSecretStorageDialog_recoveryKeyButtons button { - flex: 1; + width: 160px; + padding-left: 0px; + padding-right: 0px; white-space: nowrap; } + +.mx_CreateSecretStorageDialog_continueSpinner { + margin-top: 33px; + text-align: right; +} + +.mx_CreateSecretStorageDialog_continueSpinner img { + width: 20px; + height: 20px; +} diff --git a/res/css/views/directory/_NetworkDropdown.scss b/res/css/views/directory/_NetworkDropdown.scss index 106392f880..bd5c67c7ed 100644 --- a/res/css/views/directory/_NetworkDropdown.scss +++ b/res/css/views/directory/_NetworkDropdown.scss @@ -35,6 +35,8 @@ limitations under the License. border-radius: 4px; border: 1px solid $dialog-close-fg-color; background-color: $primary-bg-color; + max-height: calc(100vh - 20px); // allow 10px padding on both top and bottom + overflow-y: auto; } .mx_NetworkDropdown_menu_network { @@ -47,19 +49,20 @@ limitations under the License. .mx_NetworkDropdown_server_title { padding: 0 10px; - font-size: 15px; + font-size: $font-15px; font-weight: 600; - line-height: 20px; + line-height: $font-20px; margin-bottom: 4px; + position: relative; // remove server button .mx_AccessibleButton { position: absolute; display: inline; - right: 12px; + right: 10px; height: 16px; width: 16px; - margin-top: 4px; + margin-top: 2px; &::after { content: ""; @@ -77,16 +80,16 @@ limitations under the License. .mx_NetworkDropdown_server_subtitle { padding: 0 10px; - font-size: 10px; - line-height: 14px; + font-size: $font-10px; + line-height: $font-14px; margin-top: -4px; margin-bottom: 4px; color: $muted-fg-color; } .mx_NetworkDropdown_server_network { - font-size: 12px; - line-height: 16px; + font-size: $font-12px; + line-height: $font-16px; padding: 4px 10px; cursor: pointer; position: relative; @@ -154,7 +157,7 @@ limitations under the License. .mx_NetworkDropdown_handle_server { color: $muted-fg-color; - font-size: 12px; + font-size: $font-12px; } } diff --git a/res/css/views/elements/_AccessibleButton.scss b/res/css/views/elements/_AccessibleButton.scss index b87071745d..96269cea43 100644 --- a/res/css/views/elements/_AccessibleButton.scss +++ b/res/css/views/elements/_AccessibleButton.scss @@ -27,7 +27,7 @@ limitations under the License. text-align: center; border-radius: 4px; display: inline-block; - font-size: 14px; + font-size: $font-14px; } .mx_AccessibleButton_kind_primary { @@ -36,12 +36,20 @@ limitations under the License. font-weight: 600; } +.mx_AccessibleButton_kind_primary_outline { + color: $button-primary-bg-color; + background-color: $button-secondary-bg-color; + border: 1px solid $button-primary-bg-color; + font-weight: 600; +} + .mx_AccessibleButton_kind_secondary { color: $accent-color; font-weight: 600; } -.mx_AccessibleButton_kind_primary.mx_AccessibleButton_disabled { +.mx_AccessibleButton_kind_primary.mx_AccessibleButton_disabled, +.mx_AccessibleButton_kind_primary_outline.mx_AccessibleButton_disabled { opacity: 0.4; } @@ -60,7 +68,14 @@ limitations under the License. background-color: $button-danger-bg-color; } -.mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled { +.mx_AccessibleButton_kind_danger_outline { + color: $button-danger-bg-color; + background-color: $button-secondary-bg-color; + border: 1px solid $button-danger-bg-color; +} + +.mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled, +.mx_AccessibleButton_kind_danger_outline.mx_AccessibleButton_disabled { color: $button-danger-disabled-fg-color; background-color: $button-danger-disabled-bg-color; } diff --git a/res/css/views/elements/_AddressSelector.scss b/res/css/views/elements/_AddressSelector.scss index dd78fcc0f0..087504390c 100644 --- a/res/css/views/elements/_AddressSelector.scss +++ b/res/css/views/elements/_AddressSelector.scss @@ -23,6 +23,7 @@ limitations under the License. border-radius: 3px; border: solid 1px $accent-color; cursor: pointer; + z-index: 1; } .mx_AddressSelector.mx_AddressSelector_empty { diff --git a/res/css/views/elements/_AddressTile.scss b/res/css/views/elements/_AddressTile.scss index 0ecfb17c83..c42f52f8f4 100644 --- a/res/css/views/elements/_AddressTile.scss +++ b/res/css/views/elements/_AddressTile.scss @@ -19,9 +19,9 @@ limitations under the License. border-radius: 3px; background-color: rgba(74, 73, 74, 0.1); border: solid 1px $input-border-color; - line-height: 26px; + line-height: $font-26px; color: $primary-fg-color; - font-size: 14px; + font-size: $font-14px; font-weight: normal; margin-right: 4px; } diff --git a/res/css/views/elements/_DirectorySearchBox.scss b/res/css/views/elements/_DirectorySearchBox.scss index 75ef3fbabd..e4b1ac5574 100644 --- a/res/css/views/elements/_DirectorySearchBox.scss +++ b/res/css/views/elements/_DirectorySearchBox.scss @@ -32,7 +32,7 @@ limitations under the License. background-repeat: no-repeat; text-indent: 18px; font-weight: 600; - font-size: 12px; + font-size: $font-12px; user-select: none; cursor: pointer; } diff --git a/res/css/views/elements/_Dropdown.scss b/res/css/views/elements/_Dropdown.scss index 102ac56bf9..2a2508c17c 100644 --- a/res/css/views/elements/_Dropdown.scss +++ b/res/css/views/elements/_Dropdown.scss @@ -29,10 +29,14 @@ limitations under the License. position: relative; border-radius: 3px; border: 1px solid $strong-input-border-color; - font-size: 12px; + font-size: $font-12px; user-select: none; } +.mx_Dropdown_input.mx_AccessibleButton_disabled { + cursor: not-allowed; +} + .mx_Dropdown_input:focus { border-color: $input-focused-border-color; } @@ -53,7 +57,7 @@ limitations under the License. .mx_Dropdown_option { height: 35px; - line-height: 35px; + line-height: $font-35px; padding-left: 8px; padding-right: 8px; } @@ -63,6 +67,8 @@ limitations under the License. text-overflow: ellipsis; white-space: nowrap; flex: 1; + display: inline-flex; + align-items: center; } .mx_Dropdown_option div { @@ -71,12 +77,18 @@ limitations under the License. white-space: nowrap; } -.mx_Dropdown_option img { +.mx_Dropdown_option img, +.mx_Dropdown_option .mx_Dropdown_option_emoji { margin: 5px; width: 16px; vertical-align: middle; } +.mx_Dropdown_option_emoji { + font-size: $font-16px; + line-height: $font-16px; +} + input.mx_Dropdown_option, input.mx_Dropdown_option:focus { font-weight: normal; diff --git a/res/css/views/elements/_EditableItemList.scss b/res/css/views/elements/_EditableItemList.scss index ef60f006cc..f089fa3dc2 100644 --- a/res/css/views/elements/_EditableItemList.scss +++ b/res/css/views/elements/_EditableItemList.scss @@ -53,6 +53,9 @@ limitations under the License. .mx_EditableItem_item { flex: auto 1 0; order: 1; + width: calc(100% - 14px); // leave space for the remove button + overflow-x: hidden; + text-overflow: ellipsis; } .mx_EditableItemList_label { diff --git a/res/css/views/elements/_EventListSummary.scss b/res/css/views/elements/_EventListSummary.scss index 99a5c06a5f..f3e9f77aa3 100644 --- a/res/css/views/elements/_EventListSummary.scss +++ b/res/css/views/elements/_EventListSummary.scss @@ -19,7 +19,7 @@ limitations under the License. } .mx_TextualEvent.mx_EventListSummary_summary { - font-size: 14px; + font-size: $font-14px; display: inline-flex; } @@ -27,7 +27,7 @@ limitations under the License. display: inline-block; margin-right: 8px; padding-top: 8px; - line-height: 12px; + line-height: $font-12px; } .mx_EventListSummary_avatars .mx_BaseAvatar { @@ -46,19 +46,19 @@ limitations under the License. .mx_EventListSummary_line { border-bottom: 1px solid $primary-hairline-color; margin-left: 63px; - line-height: 30px; + line-height: $font-30px; } .mx_MatrixChat_useCompactLayout { .mx_EventListSummary { - font-size: 13px; + font-size: $font-13px; .mx_EventTile_line { - line-height: 20px; + line-height: $font-20px; } } .mx_EventListSummary_line { - line-height: 22px; + line-height: $font-22px; } .mx_EventListSummary_toggle { @@ -66,6 +66,6 @@ limitations under the License. } .mx_TextualEvent.mx_EventListSummary_summary { - font-size: 13px; + font-size: $font-13px; } } diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss index b260d4b097..f67da6477b 100644 --- a/res/css/views/elements/_Field.scss +++ b/res/css/views/elements/_Field.scss @@ -40,7 +40,7 @@ limitations under the License. .mx_Field textarea { font-weight: normal; font-family: $font-family; - font-size: 14px; + font-size: $font-14px; border: none; // Even without a border here, we still need this avoid overlapping the rounded // corners on the field above. @@ -102,7 +102,7 @@ limitations under the License. background-color 0.25s ease-out 0.1s; color: $primary-fg-color; background-color: transparent; - font-size: 14px; + font-size: $font-14px; position: absolute; left: 0px; top: 0px; @@ -126,7 +126,7 @@ limitations under the License. color 0.25s ease-out 0s, top 0.25s ease-out 0s, background-color 0.25s ease-out 0s; - font-size: 10px; + font-size: $font-10px; top: -13px; padding: 0 2px; background-color: $field-focused-label-bg-color; @@ -191,5 +191,5 @@ limitations under the License. } .mx_Field .mx_CountryDropdown { - width: 67px; + width: $font-78px; } diff --git a/res/css/views/elements/_FormButton.scss b/res/css/views/elements/_FormButton.scss index 1483fe2091..7ec01f17e6 100644 --- a/res/css/views/elements/_FormButton.scss +++ b/res/css/views/elements/_FormButton.scss @@ -15,9 +15,9 @@ limitations under the License. */ .mx_FormButton { - line-height: 16px; + line-height: $font-16px; padding: 5px 15px; - font-size: 12px; + font-size: $font-12px; height: min-content; &:not(:last-child) { diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index 67b0d6d7df..0a4ed2a194 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -102,13 +102,13 @@ limitations under the License. } .mx_ImageView_name { - font-size: 18px; + font-size: $font-18px; margin-bottom: 6px; word-wrap: break-word; } .mx_ImageView_metadata { - font-size: 15px; + font-size: $font-15px; opacity: 0.5; } @@ -118,13 +118,13 @@ limitations under the License. margin-bottom: 6px; border-radius: 5px; background-color: $lightbox-bg-color; - font-size: 14px; + font-size: $font-14px; padding: 9px; border: 1px solid $lightbox-border-color; } .mx_ImageView_size { - font-size: 11px; + font-size: $font-11px; } .mx_ImageView_link { @@ -133,7 +133,7 @@ limitations under the License. } .mx_ImageView_button { - font-size: 15px; + font-size: $font-15px; opacity: 0.5; margin-top: 18px; cursor: pointer; diff --git a/res/css/views/elements/_InlineSpinner.scss b/res/css/views/elements/_InlineSpinner.scss index 612b6209c6..6b91e45923 100644 --- a/res/css/views/elements/_InlineSpinner.scss +++ b/res/css/views/elements/_InlineSpinner.scss @@ -18,7 +18,7 @@ limitations under the License. display: inline; } -.mx_InlineSpinner img { +.mx_InlineSpinner_spin img { margin: 0px 6px; vertical-align: -3px; } diff --git a/res/css/views/elements/_InteractiveTooltip.scss b/res/css/views/elements/_InteractiveTooltip.scss deleted file mode 100644 index 17a76436e8..0000000000 --- a/res/css/views/elements/_InteractiveTooltip.scss +++ /dev/null @@ -1,91 +0,0 @@ -/* -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_InteractiveTooltip_wrapper { - position: fixed; - z-index: 5000; -} - -.mx_InteractiveTooltip { - border-radius: 3px; - background-color: $interactive-tooltip-bg-color; - color: $interactive-tooltip-fg-color; - position: absolute; - font-size: 10px; - font-weight: 600; - padding: 6px; - z-index: 5001; -} - -.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_top { - top: 10px; // 8px chevron + 2px spacing -} - -.mx_InteractiveTooltip_chevron_top { - position: absolute; - left: calc(50% - 8px); - top: -8px; - width: 0; - height: 0; - border-left: 8px solid transparent; - border-bottom: 8px solid $interactive-tooltip-bg-color; - border-right: 8px solid transparent; -} - -// Adapted from https://codyhouse.co/blog/post/css-rounded-triangles-with-clip-path -// by Sebastiano Guerriero (@guerriero_se) -@supports (clip-path: polygon(0% 0%, 100% 100%, 0% 100%)) { - .mx_InteractiveTooltip_chevron_top { - height: 16px; - width: 16px; - background-color: inherit; - border: none; - clip-path: polygon(0% 0%, 100% 100%, 0% 100%); - transform: rotate(135deg); - border-radius: 0 0 0 3px; - top: calc(-8px / 1.414); // sqrt(2) because of rotation - } -} - -.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_bottom { - bottom: 10px; // 8px chevron + 2px spacing -} - -.mx_InteractiveTooltip_chevron_bottom { - position: absolute; - left: calc(50% - 8px); - bottom: -8px; - width: 0; - height: 0; - border-left: 8px solid transparent; - border-top: 8px solid $interactive-tooltip-bg-color; - border-right: 8px solid transparent; -} - -// Adapted from https://codyhouse.co/blog/post/css-rounded-triangles-with-clip-path -// by Sebastiano Guerriero (@guerriero_se) -@supports (clip-path: polygon(0% 0%, 100% 100%, 0% 100%)) { - .mx_InteractiveTooltip_chevron_bottom { - height: 16px; - width: 16px; - background-color: inherit; - border: none; - clip-path: polygon(0% 0%, 100% 100%, 0% 100%); - transform: rotate(-45deg); - border-radius: 0 0 0 3px; - bottom: calc(-8px / 1.414); // sqrt(2) because of rotation - } -} diff --git a/res/css/views/elements/_ProgressBar.scss b/res/css/views/elements/_ProgressBar.scss index a3fee232d0..e49d85af04 100644 --- a/res/css/views/elements/_ProgressBar.scss +++ b/res/css/views/elements/_ProgressBar.scss @@ -1,5 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,12 +14,26 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_ProgressBar { - height: 5px; - border: 1px solid $progressbar-color; -} +progress.mx_ProgressBar { + height: 4px; + width: 60px; + border-radius: 10px; + overflow: hidden; + appearance: none; + border: 0; -.mx_ProgressBar_fill { - height: 100%; - background-color: $progressbar-color; + @mixin ProgressBarBorderRadius "10px"; + @mixin ProgressBarColour $accent-color; + ::-webkit-progress-value { + transition: width 1s; + } + ::-moz-progress-bar { + transition: padding-bottom 1s; + padding-bottom: var(--value); + transform-origin: 0 0; + transform: rotate(-90deg) translateX(-15px); + padding-left: 15px; + + height: 0; + } } diff --git a/res/css/views/messages/_ReactionsRowButtonTooltip.scss b/res/css/views/elements/_QRCode.scss similarity index 82% rename from res/css/views/messages/_ReactionsRowButtonTooltip.scss rename to res/css/views/elements/_QRCode.scss index cf4219fcec..96d9114b54 100644 --- a/res/css/views/messages/_ReactionsRowButtonTooltip.scss +++ b/res/css/views/elements/_QRCode.scss @@ -1,5 +1,5 @@ /* -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_ReactionsRowButtonTooltip_reactedWith { - opacity: 0.7; +.mx_QRCode { + img { + border-radius: 8px; + } } diff --git a/res/css/views/elements/_RichText.scss b/res/css/views/elements/_RichText.scss index 5066ee10f3..d60282695c 100644 --- a/res/css/views/elements/_RichText.scss +++ b/res/css/views/elements/_RichText.scss @@ -6,16 +6,33 @@ .mx_RoomPill, .mx_GroupPill, .mx_AtRoomPill { - border-radius: 16px; - display: inline-block; - height: 20px; - line-height: 20px; - padding-left: 5px; + display: inline-flex; + align-items: center; + vertical-align: middle; + border-radius: $font-16px; + line-height: $font-15px; + padding-left: 0; } a.mx_Pill { - word-break: break-all; - display: inline; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + max-width: calc(100% - 1ch); +} + +.mx_Pill { + padding: $font-1px; + padding-right: 0.4em; + vertical-align: text-top; + line-height: $font-17px; +} + +/* More specific to override `.markdown-body a` color */ +.mx_EventTile_content .markdown-body a.mx_GroupPill, +.mx_GroupPill { + color: $accent-fg-color; + background-color: $rte-group-pill-color; } /* More specific to override `.markdown-body a` text-decoration */ @@ -28,7 +45,6 @@ a.mx_Pill { .mx_UserPill { color: $primary-fg-color; background-color: $other-user-pill-bg-color; - padding-right: 5px; } .mx_UserPill_selected { @@ -42,7 +58,6 @@ a.mx_Pill { .mx_MessageComposer_input .mx_AtRoomPill { color: $accent-fg-color; background-color: $mention-user-pill-bg-color; - padding-right: 5px; } /* More specific to override `.markdown-body a` color */ @@ -52,15 +67,6 @@ a.mx_Pill { .mx_GroupPill { color: $accent-fg-color; background-color: $rte-room-pill-color; - padding-right: 5px; -} - -/* More specific to override `.markdown-body a` color */ -.mx_EventTile_content .markdown-body a.mx_GroupPill, -.mx_GroupPill { - color: $accent-fg-color; - background-color: $rte-group-pill-color; - padding-right: 5px; } .mx_EventTile_body .mx_UserPill, @@ -74,8 +80,10 @@ a.mx_Pill { .mx_GroupPill .mx_BaseAvatar, .mx_AtRoomPill .mx_BaseAvatar { position: relative; - left: -3px; - top: 2px; + display: inline-flex; + align-items: center; + border-radius: 10rem; + margin-right: 0.24rem; } .mx_Markdown_BOLD { diff --git a/res/css/views/elements/_Slider.scss b/res/css/views/elements/_Slider.scss new file mode 100644 index 0000000000..58ba2813b4 --- /dev/null +++ b/res/css/views/elements/_Slider.scss @@ -0,0 +1,99 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_Slider { + position: relative; + margin: 0px; + flex-grow: 1; +} + +.mx_Slider_dotContainer { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.mx_Slider_bar { + display: flex; + box-sizing: border-box; + position: absolute; + height: 1em; + width: 100%; + padding: 0 0.5em; // half the width of a dot. + align-items: center; +} + +.mx_Slider_bar > hr { + width: 100%; + height: 0.4em; + background-color: $slider-background-color; + border: 0; +} + +.mx_Slider_selection { + display: flex; + align-items: center; + width: calc(100% - 1em); // 2 * half the width of a dot + height: 1em; + position: absolute; + pointer-events: none; +} + +.mx_Slider_selectionDot { + position: absolute; + width: 1.1em; + height: 1.1em; + background-color: $slider-selection-color; + border-radius: 50%; + box-shadow: 0 0 6px lightgrey; + z-index: 10; +} + +.mx_Slider_selection > hr { + margin: 0; + border: 0.2em solid $slider-selection-color; +} + +.mx_Slider_dot { + height: 1em; + width: 1em; + border-radius: 50%; + background-color: $slider-background-color; + z-index: 0; +} + +.mx_Slider_dotActive { + background-color: $slider-selection-color; +} + +.mx_Slider_dotValue { + display: flex; + flex-direction: column; + align-items: center; + color: $slider-background-color; +} + +// The following is a hack to center the labels without adding +// any width to the slider's dots. +.mx_Slider_labelContainer { + width: 1em; +} + +.mx_Slider_label { + position: relative; + width: fit-content; + left: -50%; +} diff --git a/res/css/views/elements/_StyledCheckbox.scss b/res/css/views/elements/_StyledCheckbox.scss new file mode 100644 index 0000000000..60f1bf0277 --- /dev/null +++ b/res/css/views/elements/_StyledCheckbox.scss @@ -0,0 +1,84 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + + +.mx_Checkbox { + $size: $font-16px; + $border-size: $font-1-5px; + $border-radius: $font-4px; + + display: flex; + align-items: flex-start; + + input[type=checkbox] { + appearance: none; + margin: 0; + padding: 0; + + & + label { + display: flex; + align-items: center; + + flex-grow: 1; + } + + & + label > .mx_Checkbox_background { + display: inline-flex; + position: relative; + + flex-shrink: 0; + + height: $size; + width: $size; + size: 0.5rem; + + border: $border-size solid rgba($muted-fg-color, 0.5); + box-sizing: border-box; + border-radius: $border-radius; + + img { + display: none; + + height: 100%; + width: 100%; + filter: invert(100%); + } + } + + &:checked + label > .mx_Checkbox_background { + background: $accent-color; + border-color: $accent-color; + + img { + display: block; + } + } + + & + label > *:not(.mx_Checkbox_background) { + margin-left: 10px; + } + + &:disabled + label { + opacity: 0.5; + cursor: not-allowed; + } + + &:checked:disabled + label > .mx_Checkbox_background { + background-color: $accent-color; + border-color: $accent-color; + } + } +} diff --git a/res/css/views/elements/_StyledRadioButton.scss b/res/css/views/elements/_StyledRadioButton.scss new file mode 100644 index 0000000000..ffa1337ebb --- /dev/null +++ b/res/css/views/elements/_StyledRadioButton.scss @@ -0,0 +1,117 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/** +* This component expects the parent to specify a positive padding and +* width +*/ + +.mx_RadioButton { + $radio-circle-color: $muted-fg-color; + $active-radio-circle-color: $accent-color; + position: relative; + + display: flex; + align-items: baseline; + flex-grow: 1; + + > .mx_RadioButton_content { + flex-grow: 1; + + display: flex; + flex-direction: column; + + margin-left: 8px; + margin-right: 8px; + } + + .mx_RadioButton_spacer { + flex-shrink: 0; + flex-grow: 0; + + height: $font-16px; + width: $font-16px; + } + + > input[type=radio] { + // Remove the OS's representation + margin: 0; + padding: 0; + appearance: none; + + + div { + flex-shrink: 0; + flex-grow: 0; + + display: flex; + align-items: center; + justify-content: center; + + box-sizing: border-box; + height: $font-16px; + width: $font-16px; + + border: $font-1-5px solid $radio-circle-color; + border-radius: $font-16px; + + > div { + box-sizing: border-box; + + height: $font-8px; + width: $font-8px; + + border-radius: $font-8px; + } + } + + &:checked { + & + div { + border-color: $active-radio-circle-color; + + & > div { + background: $active-radio-circle-color; + } + } + } + + &:disabled { + & + div, + & + div + span { + opacity: 0.5; + cursor: not-allowed; + } + + & + div { + border-color: $radio-circle-color; + } + } + + &:checked:disabled { + & + div > div { + background-color: $radio-circle-color; + } + } + } +} + +.mx_RadioButton_outlined { + border: 1px solid $input-darker-bg-color; + border-radius: 8px; +} + +.mx_RadioButton_checked { + border-color: $accent-color; +} diff --git a/res/css/views/elements/_ToggleSwitch.scss b/res/css/views/elements/_ToggleSwitch.scss index 1f4445b88c..62669889ee 100644 --- a/res/css/views/elements/_ToggleSwitch.scss +++ b/res/css/views/elements/_ToggleSwitch.scss @@ -16,11 +16,13 @@ limitations under the License. .mx_ToggleSwitch { transition: background-color 0.20s ease-out 0.1s; - width: 48px; - height: 24px; - border-radius: 14px; + + width: $font-44px; + height: $font-20px; + border-radius: 1.5rem; + padding: 2px; + background-color: $togglesw-off-color; - position: relative; opacity: 0.5; } @@ -31,23 +33,18 @@ limitations under the License. .mx_ToggleSwitch.mx_ToggleSwitch_on { background-color: $togglesw-on-color; + + > .mx_ToggleSwitch_ball { + left: calc(100% - $font-20px); + } } .mx_ToggleSwitch_ball { - transition: left 0.15s ease-out 0.1s; - margin: 2px; - width: 20px; - height: 20px; - border-radius: 20px; + position: relative; + width: $font-20px; + height: $font-20px; + border-radius: $font-20px; background-color: $togglesw-ball-color; - position: absolute; - top: 0; -} - -.mx_ToggleSwitch_on > .mx_ToggleSwitch_ball { - left: 23px; // 48px switch - 20px ball - 5px padding = 23px -} - -.mx_ToggleSwitch:not(.mx_ToggleSwitch_on) > .mx_ToggleSwitch_ball { - left: 2px; + transition: left 0.15s ease-out 0.1s; + left: 0; } diff --git a/res/css/views/elements/_Tooltip.scss b/res/css/views/elements/_Tooltip.scss index cc4eb409df..d90c818f94 100644 --- a/res/css/views/elements/_Tooltip.scss +++ b/res/css/views/elements/_Tooltip.scss @@ -51,21 +51,27 @@ limitations under the License. .mx_Tooltip { display: none; position: fixed; - border: 1px solid $menu-border-color; - border-radius: 4px; + border-radius: 8px; box-shadow: 4px 4px 12px 0 $menu-box-shadow-color; - background-color: $menu-bg-color; - z-index: 4000; // Higher than dialogs so tooltips can be used in dialogs + z-index: 6000; // Higher than context menu so tooltips can be used everywhere padding: 10px; pointer-events: none; - line-height: 14px; - font-size: 12px; - font-weight: 600; - color: $primary-fg-color; + line-height: $font-14px; + font-size: $font-12px; + font-weight: 500; max-width: 200px; word-break: break-word; margin-right: 50px; + background-color: $inverted-bg-color; + color: $accent-fg-color; + border: 0; + text-align: center; + + .mx_Tooltip_chevron { + display: none; + } + &.mx_Tooltip_visible { animation: mx_fadein 0.2s forwards; } @@ -75,18 +81,23 @@ limitations under the License. } } -.mx_Tooltip_timeline { - box-shadow: none; - background-color: $tooltip-timeline-bg-color; - color: $tooltip-timeline-fg-color; - text-align: center; - border: none; - border-radius: 3px; - font-size: 14px; - line-height: 1.2; - padding: 6px 8px; +// These tooltips use an older style with a chevron +.mx_Field_tooltip { + background-color: $menu-bg-color; + color: $primary-fg-color; + border: 1px solid $menu-border-color; + text-align: unset; - .mx_Tooltip_chevron::after { - border-right-color: $tooltip-timeline-bg-color; + .mx_Tooltip_chevron { + display: unset; } } + +.mx_Tooltip_title { + font-weight: 600; +} + +.mx_Tooltip_sub { + opacity: 0.7; + margin-top: 4px; +} diff --git a/res/css/views/elements/_TooltipButton.scss b/res/css/views/elements/_TooltipButton.scss index 6ea36c800e..0c85dac818 100644 --- a/res/css/views/elements/_TooltipButton.scss +++ b/res/css/views/elements/_TooltipButton.scss @@ -28,7 +28,7 @@ limitations under the License. transition: opacity 0.2s ease-in; opacity: 0.6; - line-height: 11px; + line-height: $font-11px; text-align: center; cursor: pointer; diff --git a/res/css/views/emojipicker/_EmojiPicker.scss b/res/css/views/emojipicker/_EmojiPicker.scss index 5d9b3f2687..400e40e233 100644 --- a/res/css/views/emojipicker/_EmojiPicker.scss +++ b/res/css/views/emojipicker/_EmojiPicker.scss @@ -163,7 +163,7 @@ limitations under the License. .mx_EmojiPicker_item { display: inline-block; - font-size: 20px; + font-size: $font-20px; padding: 5px; width: 100%; height: 100%; @@ -183,21 +183,21 @@ limitations under the License. } .mx_EmojiPicker_category_label, .mx_EmojiPicker_preview_name { - font-size: 16px; + font-size: $font-16px; font-weight: 600; margin: 0; } .mx_EmojiPicker_footer { border-top: 1px solid $message-action-bar-border-color; - height: 72px; + min-height: 72px; display: flex; align-items: center; } .mx_EmojiPicker_preview_emoji { - font-size: 32px; + font-size: $font-32px; padding: 8px 16px; } @@ -212,7 +212,7 @@ limitations under the License. .mx_EmojiPicker_shortcode { color: $light-fg-color; - font-size: 14px; + font-size: $font-14px; &::before, &::after { content: ":"; diff --git a/res/css/views/globals/_MatrixToolbar.scss b/res/css/views/globals/_MatrixToolbar.scss deleted file mode 100644 index 5fdf572f99..0000000000 --- a/res/css/views/globals/_MatrixToolbar.scss +++ /dev/null @@ -1,73 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_MatrixToolbar { - background-color: $accent-color; - color: $accent-fg-color; - - display: flex; - align-items: center; -} - -.mx_MatrixToolbar_warning { - margin-left: 16px; - margin-right: 8px; - margin-top: -2px; -} - -.mx_MatrixToolbar_info { - padding-left: 16px; - padding-right: 8px; - background-color: $info-bg-color; -} - -.mx_MatrixToolbar_error { - padding-left: 16px; - padding-right: 8px; - background-color: $warning-bg-color; -} - -.mx_MatrixToolbar_content { - flex: 1; -} - -.mx_MatrixToolbar_link { - color: $accent-fg-color !important; - text-decoration: underline !important; - cursor: pointer; -} - -.mx_MatrixToolbar_clickable { - cursor: pointer; -} - -.mx_MatrixToolbar_close { - cursor: pointer; -} - -.mx_MatrixToolbar_close img { - display: block; - float: right; - margin-right: 10px; -} - -.mx_MatrixToolbar_action { - margin-right: 16px; -} - -.mx_MatrixToolbar_changelog { - white-space: pre; -} diff --git a/res/css/views/messages/_DateSeparator.scss b/res/css/views/messages/_DateSeparator.scss index 935ee1aba3..867f58d860 100644 --- a/res/css/views/messages/_DateSeparator.scss +++ b/res/css/views/messages/_DateSeparator.scss @@ -19,7 +19,7 @@ limitations under the License. margin: 4px 0; display: flex; align-items: center; - font-size: 14px; + font-size: $font-14px; color: $roomtopic-color; } diff --git a/res/css/views/rooms/_UserOnlineDot.scss b/res/css/views/messages/_MVideoBody.scss similarity index 73% rename from res/css/views/rooms/_UserOnlineDot.scss rename to res/css/views/messages/_MVideoBody.scss index 339e5cc48a..3b05c53f34 100644 --- a/res/css/views/rooms/_UserOnlineDot.scss +++ b/res/css/views/messages/_MVideoBody.scss @@ -1,5 +1,5 @@ /* -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,10 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_UserOnlineDot { - border-radius: 50%; - background-color: $accent-color; - height: 5px; - width: 5px; - display: inline-block; +span.mx_MVideoBody { + video.mx_MVideoBody { + max-width: 100%; + height: auto; + } } diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index c032051c36..e3ccd99611 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -21,7 +21,7 @@ limitations under the License. cursor: pointer; display: flex; height: 24px; - line-height: 24px; + line-height: $font-24px; border-radius: 4px; background: $message-action-bar-bg-color; top: -18px; @@ -91,17 +91,17 @@ limitations under the License. } .mx_MessageActionBar_reactButton::after { - mask-image: url('$(res)/img/react.svg'); + mask-image: url('$(res)/img/element-icons/room/message-bar/emoji.svg'); } .mx_MessageActionBar_replyButton::after { - mask-image: url('$(res)/img/reply.svg'); + mask-image: url('$(res)/img/element-icons/room/message-bar/reply.svg'); } .mx_MessageActionBar_editButton::after { - mask-image: url('$(res)/img/edit.svg'); + mask-image: url('$(res)/img/element-icons/room/message-bar/edit.svg'); } .mx_MessageActionBar_optionsButton::after { - mask-image: url('$(res)/img/icon_context.svg'); + mask-image: url('$(res)/img/element-icons/context-menu.svg'); } diff --git a/res/css/views/messages/_MessageTimestamp.scss b/res/css/views/messages/_MessageTimestamp.scss index e5c228aa68..f8d91cc083 100644 --- a/res/css/views/messages/_MessageTimestamp.scss +++ b/res/css/views/messages/_MessageTimestamp.scss @@ -16,5 +16,5 @@ limitations under the License. .mx_MessageTimestamp { color: $event-timestamp-color; - font-size: 10px; + font-size: $font-10px; } diff --git a/res/css/views/messages/_ReactionsRow.scss b/res/css/views/messages/_ReactionsRow.scss index 57c02ed3e5..2f5695e1fb 100644 --- a/res/css/views/messages/_ReactionsRow.scss +++ b/res/css/views/messages/_ReactionsRow.scss @@ -21,7 +21,7 @@ limitations under the License. .mx_ReactionsRow_showAll { text-decoration: none; - font-size: 10px; + font-size: $font-10px; font-weight: 600; margin-left: 6px; vertical-align: top; diff --git a/res/css/views/messages/_ReactionsRowButton.scss b/res/css/views/messages/_ReactionsRowButton.scss index e54201d963..7158ffc027 100644 --- a/res/css/views/messages/_ReactionsRowButton.scss +++ b/res/css/views/messages/_ReactionsRowButton.scss @@ -16,8 +16,7 @@ limitations under the License. .mx_ReactionsRowButton { display: inline-flex; - height: 20px; - line-height: 21px; + line-height: $font-21px; margin-right: 6px; padding: 0 6px; border: 1px solid $reaction-row-button-border-color; @@ -34,12 +33,12 @@ limitations under the License. background-color: $reaction-row-button-selected-bg-color; border-color: $reaction-row-button-selected-border-color; } -} -.mx_ReactionsRowButton_content { - max-width: 100px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - padding-right: 4px; + .mx_ReactionsRowButton_content { + max-width: 100px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + padding-right: 4px; + } } diff --git a/res/css/views/messages/_RedactedBody.scss b/res/css/views/messages/_RedactedBody.scss new file mode 100644 index 0000000000..e4ab0c0835 --- /dev/null +++ b/res/css/views/messages/_RedactedBody.scss @@ -0,0 +1,36 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_RedactedBody { + white-space: pre-wrap; + color: $muted-fg-color; + vertical-align: middle; + + padding-left: 20px; + position: relative; + + &::before { + height: 14px; + width: 14px; + background-color: $muted-fg-color; + mask-image: url('$(res)/img/feather-customised/trash.custom.svg'); + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + content: ''; + position: absolute; + top: 2px; + left: 0; + } +} diff --git a/res/css/views/messages/_ViewSourceEvent.scss b/res/css/views/messages/_ViewSourceEvent.scss index a15924e759..076932ee97 100644 --- a/res/css/views/messages/_ViewSourceEvent.scss +++ b/res/css/views/messages/_ViewSourceEvent.scss @@ -17,7 +17,7 @@ limitations under the License. .mx_EventTile_content.mx_ViewSourceEvent { display: flex; opacity: 0.6; - font-size: 12px; + font-size: $font-12px; pre, code { flex: 1; diff --git a/res/css/views/messages/_common_CryptoEvent.scss b/res/css/views/messages/_common_CryptoEvent.scss index 98e1e97e39..09c78ae5b4 100644 --- a/res/css/views/messages/_common_CryptoEvent.scss +++ b/res/css/views/messages/_common_CryptoEvent.scss @@ -15,28 +15,45 @@ limitations under the License. */ .mx_cryptoEvent { - display: grid; grid-template-columns: 24px minmax(0, 1fr) min-content; + &.mx_cryptoEvent_icon::before, &.mx_cryptoEvent_icon::after { grid-column: 1; grid-row: 1 / 3; width: 16px; height: 16px; content: ""; - background-image: url("$(res)/img/e2e/normal.svg"); - background-repeat: no-repeat; - background-size: 100%; + top: 0; + bottom: 0; + left: 0; + right: 0; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + mask-image: url('$(res)/img/e2e/normal.svg'); + background-color: $composer-e2e-icon-color; margin-top: 4px; } + // white infill for the transparency + &.mx_cryptoEvent_icon::before { + background-color: #ffffff; + mask-image: url('$(res)/img/e2e/normal.svg'); + mask-repeat: no-repeat; + mask-position: center; + mask-size: 90%; + } + &.mx_cryptoEvent_icon_verified::after { - background-image: url("$(res)/img/e2e/verified.svg"); + mask-image: url("$(res)/img/e2e/verified.svg"); + background-color: $accent-color; } &.mx_cryptoEvent_icon_warning::after { - background-image: url("$(res)/img/e2e/warning.svg"); + mask-image: url("$(res)/img/e2e/warning.svg"); + background-color: $notice-primary-color; } .mx_cryptoEvent_title, .mx_cryptoEvent_subtitle, .mx_cryptoEvent_state { @@ -45,7 +62,7 @@ limitations under the License. .mx_cryptoEvent_title { font-weight: 600; - font-size: 15px; + font-size: $font-15px; grid-column: 2; grid-row: 1; } @@ -56,7 +73,7 @@ limitations under the License. } .mx_cryptoEvent_state, .mx_cryptoEvent_subtitle { - font-size: 12px; + font-size: $font-12px; } .mx_cryptoEvent_state, .mx_cryptoEvent_buttons { diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss index 0e4b1bda9e..6f86d1ad18 100644 --- a/res/css/views/right_panel/_UserInfo.scss +++ b/res/css/views/right_panel/_UserInfo.scss @@ -20,7 +20,7 @@ limitations under the License. flex-direction: column; flex: 1; overflow-y: auto; - font-size: 12px; + font-size: $font-12px; .mx_UserInfo_cancel { cursor: pointer; @@ -43,7 +43,7 @@ limitations under the License. } h2 { - font-size: 18px; + font-size: $font-18px; font-weight: 600; margin: 18px 0 0 0; } @@ -53,7 +53,7 @@ limitations under the License. } .mx_UserInfo_separator { - border-bottom: 1px solid lightgray; + border-bottom: 1px solid rgba($primary-fg-color, .1); } .mx_UserInfo_memberDetailsContainer { @@ -98,8 +98,8 @@ limitations under the License. position: absolute; top: 0; left: 0; - width: 100%; - height: 100%; + width: 100% !important; + height: 100% !important; } .mx_UserInfo_avatar .mx_BaseAvatar_initial { @@ -109,7 +109,7 @@ limitations under the License. justify-content: center; // override the calculated sizes so that the letter isn't HUGE - font-size: 56px !important; + font-size: 6rem !important; width: 100% !important; transition: font-size 0.5s; } @@ -121,8 +121,8 @@ limitations under the License. h3 { text-transform: uppercase; color: $notice-secondary-color; - font-weight: bold; - font-size: 12px; + font-weight: 600; + font-size: $font-12px; margin: 4px 0; } @@ -134,24 +134,28 @@ limitations under the License. text-align: center; h2 { - font-size: 18px; - line-height: 25px; + display: flex; + font-size: $font-18px; + line-height: $font-25px; flex: 1; justify-content: center; - align-items: center; - // limit to 2 lines, show an ellipsis if it overflows - // this looks webkit specific but is supported by Firefox 68+ - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; + span { + // limit to 2 lines, show an ellipsis if it overflows + // this looks webkit specific but is supported by Firefox 68+ + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; - overflow: hidden; - word-break: break-all; - text-overflow: ellipsis; + overflow: hidden; + word-break: break-all; + text-overflow: ellipsis; + } .mx_E2EIcon { - margin: 5px; + margin-top: 3px; // visual vertical centering to the top line of text + margin-right: 4px; // margin from displyname + min-width: 18px; // convince flexbox to not collapse it } } @@ -197,7 +201,7 @@ limitations under the License. .mx_UserInfo_field { cursor: pointer; color: $accent-color; - line-height: 16px; + line-height: $font-16px; margin: 8px 0; &.mx_UserInfo_destructive { @@ -206,7 +210,7 @@ limitations under the License. } .mx_UserInfo_statusMessage { - font-size: 11px; + font-size: $font-11px; opacity: 0.5; overflow: hidden; white-space: nowrap; @@ -266,12 +270,31 @@ limitations under the License. } } + .mx_AccessibleButton.mx_AccessibleButton_hasKind { + padding: 8px 18px; + + &.mx_AccessibleButton_kind_primary { + color: $accent-color; + background-color: $accent-bg-color; + } + + &.mx_AccessibleButton_kind_danger { + color: $notice-primary-color; + background-color: $notice-primary-bg-color; + } + } + + .mx_VerificationShowSas .mx_AccessibleButton, .mx_UserInfo_wideButton { display: block; - margin: 16px 0; + margin: 16px 0 8px; } - button.mx_UserInfo_wideButton { - width: 100%; // FIXME get rid of this once we get rid of DialogButtons here + + + .mx_VerificationShowSas { + .mx_AccessibleButton + .mx_AccessibleButton { + margin: 8px 0; // space between buttons + } } } diff --git a/res/css/views/right_panel/_VerificationPanel.scss b/res/css/views/right_panel/_VerificationPanel.scss index 459622b277..a8466a1626 100644 --- a/res/css/views/right_panel/_VerificationPanel.scss +++ b/res/css/views/right_panel/_VerificationPanel.scss @@ -14,6 +14,16 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_VerificationPanel_verified_section, +.mx_VerificationPanel_reciprocate_section { + // center the big shield icon + .mx_E2EIcon { + // Override general user info margin + margin: 20px auto !important; + } +} + + .mx_UserInfo { .mx_EncryptionPanel_cancel { mask: url('$(res)/img/feather-customised/cancel.svg'); @@ -30,11 +40,6 @@ limitations under the License. right: 14px; } - .mx_VerificationPanel_verified_section .mx_E2EIcon { - // Override general user info margin - margin: 0 auto !important; - } - .mx_VerificationPanel_qrCode { padding: 4px 4px 0 4px; background: white; @@ -51,6 +56,16 @@ limitations under the License. max-width: 240px; } } + + .mx_VerificationPanel_reciprocate_section { + .mx_FormButton { + width: 100%; + box-sizing: border-box; + padding: 10px; + display: block; + margin: 10px 0; + } + } } // Special case styling for EncryptionPanel in a Modal dialog @@ -60,6 +75,7 @@ limitations under the License. margin-top: 10px; margin-bottom: 10px; align-items: stretch; + justify-content: center; > .mx_VerificationPanel_QRPhase_betweenText { width: 50px; @@ -75,10 +91,12 @@ limitations under the License. border-radius: 10px; flex: 1; display: flex; - padding: 10px; + padding: 20px; align-items: center; flex-direction: column; position: relative; + max-width: 310px; + justify-content: space-between; canvas, .mx_VerificationPanel_QRPhase_noQR { width: 220px !important; @@ -91,31 +109,36 @@ limitations under the License. } > p { + margin-top: 0; font-weight: 700; } .mx_VerificationPanel_QRPhase_helpText { - font-size: 14px; - margin-top: 71px; + font-size: $font-14px; + margin: 30px 0; text-align: center; } - - .mx_AccessibleButton { - position: absolute; - bottom: 30px; - } } } // EncryptionPanel when verification is done .mx_VerificationPanel_verified_section { - // center the big shield icon - .mx_E2EIcon { - margin: 0 auto; - } // right align the "Got it" button .mx_AccessibleButton { float: right; } } + + .mx_VerificationPanel_reciprocate_section { + .mx_AccessibleButton { + margin-left: 10px; + padding: 7px 40px; + } + + .mx_VerificationPanel_reciprocateButtons { + display: flex; + flex-direction: row; + justify-content: flex-end; + } + } } diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index a3fe573ad0..6be417f631 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -46,7 +46,7 @@ $AppsDrawerBodyHeight: 273px; padding: 0; margin: 5px auto 5px auto; color: $accent-color; - font-size: 12px; + font-size: $font-12px; } .mx_AddWidget_button_full_width { @@ -59,7 +59,7 @@ $AppsDrawerBodyHeight: 273px; padding: 9px; color: $primary-hairline-color; background-color: $primary-bg-color; - font-size: 15px; + font-size: $font-15px; } .mx_AppTile { @@ -81,7 +81,7 @@ $AppsDrawerBodyHeight: 273px; margin: 0; padding: 0; border: 5px solid $widget-menu-bar-bg-color; - border-radius: 4px; + border-radius: 8px; } .mx_AppTile_mini { @@ -98,11 +98,12 @@ $AppsDrawerBodyHeight: 273px; .mx_AppTile_mini .mx_AppTile_persistedWrapper { height: 114px; + min-width: 300px; } .mx_AppTileMenuBar { margin: 0; - font-size: 12px; + font-size: $font-12px; background-color: $widget-menu-bar-bg-color; display: flex; flex-direction: row; @@ -272,7 +273,7 @@ form.mx_Custom_Widget_Form div { flex-direction: column; justify-content: center; align-items: center; - font-size: 16px; + font-size: $font-16px; } .mx_AppPermissionWarning_row { @@ -280,7 +281,7 @@ form.mx_Custom_Widget_Form div { } .mx_AppPermissionWarning_smallText { - font-size: 12px; + font-size: $font-12px; } .mx_AppPermissionWarning_bolder { diff --git a/res/css/views/rooms/_Autocomplete.scss b/res/css/views/rooms/_Autocomplete.scss index e5316f5a46..f8e0a382b1 100644 --- a/res/css/views/rooms/_Autocomplete.scss +++ b/res/css/views/rooms/_Autocomplete.scss @@ -6,9 +6,10 @@ border: 1px solid $primary-hairline-color; background: $primary-bg-color; border-bottom: none; - border-radius: 4px 4px 0 0; + border-radius: 8px 8px 0 0; max-height: 50vh; overflow: auto; + box-shadow: 0px -16px 32px $composer-shadow-color; } .mx_Autocomplete_ProviderSection { @@ -31,9 +32,10 @@ } .mx_Autocomplete_Completion_pill { - border-radius: 17px; - height: 34px; - padding: 0px 5px; + box-sizing: border-box; + border-radius: 2rem; + height: $font-34px; + padding: 0.4rem; display: flex; user-select: none; cursor: pointer; @@ -42,7 +44,7 @@ } .mx_Autocomplete_Completion_pill > * { - margin: 0 3px; + margin-right: 0.3rem; } /* styling for common completion elements */ diff --git a/res/css/views/rooms/_BasicMessageComposer.scss b/res/css/views/rooms/_BasicMessageComposer.scss index ce519b1ea7..e126e523a6 100644 --- a/res/css/views/rooms/_BasicMessageComposer.scss +++ b/res/css/views/rooms/_BasicMessageComposer.scss @@ -44,27 +44,26 @@ limitations under the License. outline: none; overflow-x: hidden; - span.mx_UserPill, span.mx_RoomPill { - padding-left: 21px; - position: relative; + &.mx_BasicMessageComposer_input_shouldShowPillAvatar { + span.mx_UserPill, span.mx_RoomPill { + position: relative; - // avatar psuedo element - &::before { - position: absolute; - left: 2px; - top: 2px; - content: var(--avatar-letter); - width: 16px; - height: 16px; - background: var(--avatar-background), $avatar-bg-color; - color: $avatar-initial-color; - background-repeat: no-repeat; - background-size: 16px; - border-radius: 8px; - text-align: center; - font-weight: normal; - line-height: 16px; - font-size: 10.4px; + // avatar psuedo element + &::before { + content: var(--avatar-letter); + width: $font-16px; + height: $font-16px; + margin-right: 0.24rem; + background: var(--avatar-background), $avatar-bg-color; + color: $avatar-initial-color; + background-repeat: no-repeat; + background-size: $font-16px; + border-radius: $font-16px; + text-align: center; + font-weight: normal; + line-height: $font-16px; + font-size: $font-10-4px; + } } } } diff --git a/res/css/views/rooms/_E2EIcon.scss b/res/css/views/rooms/_E2EIcon.scss index 584ea17433..a3473dedec 100644 --- a/res/css/views/rooms/_E2EIcon.scss +++ b/res/css/views/rooms/_E2EIcon.scss @@ -22,28 +22,58 @@ limitations under the License. display: block; } -.mx_E2EIcon_warning::after, -.mx_E2EIcon_normal::after, -.mx_E2EIcon_verified::after { - content: ""; - display: block; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - background-repeat: no-repeat; - background-size: contain; +.mx_E2EIcon_warning, +.mx_E2EIcon_normal, +.mx_E2EIcon_verified { + &::before, &::after { + content: ""; + display: block; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + } +} + +// white infill for the transparency +.mx_E2EIcon::before { + background-color: #ffffff; + mask-image: url('$(res)/img/e2e/normal.svg'); + mask-repeat: no-repeat; + mask-position: center; + mask-size: 90%; +} + +// transparent-looking border surrounding the shield for when overlain over avatars +.mx_E2EIcon_bordered { + mask-image: url('$(res)/img/e2e/normal.svg'); + background-color: $header-panel-bg-color; + + // shrink the actual badge + &::after { + mask-size: 75%; + } + // shrink the infill of the badge + &::before { + mask-size: 65%; + } } .mx_E2EIcon_warning::after { - background-image: url('$(res)/img/e2e/warning.svg'); + mask-image: url('$(res)/img/e2e/warning.svg'); + background-color: $notice-primary-color; } .mx_E2EIcon_normal::after { - background-image: url('$(res)/img/e2e/normal.svg'); + mask-image: url('$(res)/img/e2e/normal.svg'); + background-color: $composer-e2e-icon-color; } .mx_E2EIcon_verified::after { - background-image: url('$(res)/img/e2e/verified.svg'); + mask-image: url('$(res)/img/e2e/verified.svg'); + background-color: $accent-color; } diff --git a/res/css/views/rooms/_EntityTile.scss b/res/css/views/rooms/_EntityTile.scss index a2867de3a7..27a4e67089 100644 --- a/res/css/views/rooms/_EntityTile.scss +++ b/res/css/views/rooms/_EntityTile.scss @@ -26,8 +26,6 @@ limitations under the License. position: absolute; bottom: 2px; right: 7px; - height: 15px; - width: 15px; } } @@ -69,8 +67,6 @@ limitations under the License. padding-right: 12px; padding-top: 4px; padding-bottom: 4px; - width: 36px; - height: 36px; position: relative; } @@ -78,7 +74,7 @@ limitations under the License. .mx_GroupRoomTile_name { flex: 1 1 0; overflow: hidden; - font-size: 14px; + font-size: $font-14px; text-overflow: ellipsis; white-space: nowrap; } @@ -116,7 +112,7 @@ limitations under the License. } .mx_EntityTile_subtext { - font-size: 11px; + font-size: $font-11px; opacity: 0.5; overflow: hidden; white-space: nowrap; @@ -125,7 +121,7 @@ limitations under the License. .mx_EntityTile_power { padding-inline-start: 6px; - font-size: 10px; + font-size: $font-10px; color: $notice-secondary-color; max-width: 6em; overflow: hidden; diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 2fa9994155..2a2191b799 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -15,11 +15,13 @@ See the License for the specific language governing permissions and limitations under the License. */ +$left-gutter: 64px; + .mx_EventTile { max-width: 100%; clear: both; padding-top: 18px; - font-size: 14px; + font-size: $font-14px; position: relative; } @@ -37,7 +39,6 @@ limitations under the License. } .mx_EventTile_avatar { - position: absolute; top: 14px; left: 8px; cursor: pointer; @@ -45,8 +46,8 @@ limitations under the License. } .mx_EventTile.mx_EventTile_info .mx_EventTile_avatar { - top: 8px; - left: 65px; + top: $font-8px; + left: $left-gutter; } .mx_EventTile_continuation { @@ -64,19 +65,17 @@ limitations under the License. .mx_EventTile .mx_SenderProfile { color: $primary-fg-color; - font-size: 14px; + font-size: $font-14px; display: inline-block; /* anti-zalgo, with overflow hidden */ overflow: hidden; cursor: pointer; - padding-left: 65px; /* left gutter */ padding-bottom: 0px; padding-top: 0px; margin: 0px; - line-height: 17px; /* the next three lines, along with overflow hidden, truncate long display names */ white-space: nowrap; text-overflow: ellipsis; - max-width: calc(100% - 65px); + max-width: calc(100% - $left-gutter); } .mx_EventTile .mx_SenderProfile .mx_Flair { @@ -104,24 +103,24 @@ limitations under the License. visibility: hidden; white-space: nowrap; left: 0px; - width: 46px; /* 8 + 30 (avatar) + 8 */ text-align: center; - position: absolute; user-select: none; } +.mx_EventTile_continuation .mx_EventTile_line { + clear: both; +} + .mx_EventTile_line, .mx_EventTile_reply { position: relative; - padding-left: 65px; /* left gutter */ - padding-top: 4px; - padding-bottom: 2px; + padding-left: $left-gutter; border-radius: 4px; - min-height: 24px; - line-height: 22px; } -.mx_RoomView_timeline_rr_enabled { - .mx_EventTile_line, .mx_EventTile_reply { +.mx_RoomView_timeline_rr_enabled, +// on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter +.mx_EventListSummary { + .mx_EventTile_line { /* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */ margin-right: 110px; } @@ -146,10 +145,6 @@ limitations under the License. margin-right: 10px; } -.mx_EventTile_info .mx_EventTile_line { - padding-left: 83px; -} - /* HACK to override line-height which is already marked important elsewhere */ .mx_EventTile_bigEmoji.mx_EventTile_bigEmoji { font-size: 48px !important; @@ -166,10 +161,15 @@ limitations under the License. } // Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies) +// The first set is to handle the 'group layout' (default) and the second for the IRC layout .mx_EventTile_last > div > a > .mx_MessageTimestamp, .mx_EventTile:hover > div > a > .mx_MessageTimestamp, .mx_EventTile.mx_EventTile_actionBarFocused > div > a > .mx_MessageTimestamp, -.mx_EventTile.focus-visible:focus-within > div > a > .mx_MessageTimestamp { +.mx_EventTile.focus-visible:focus-within > div > a > .mx_MessageTimestamp, +.mx_IRCLayout .mx_EventTile_last > a > .mx_MessageTimestamp, +.mx_IRCLayout .mx_EventTile:hover > a > .mx_MessageTimestamp, +.mx_IRCLayout .mx_EventTile.mx_EventTile_actionBarFocused > a > .mx_MessageTimestamp, +.mx_IRCLayout .mx_EventTile.focus-visible:focus-within > a > .mx_MessageTimestamp { visibility: visible; } @@ -184,7 +184,7 @@ limitations under the License. * TODO: ultimately we probably want some transition on here. */ .mx_EventTile_selected > .mx_EventTile_line { - border-left: $accent-color 5px solid; + border-left: $accent-color 4px solid; padding-left: 60px; background-color: $event-selected-color; } @@ -239,34 +239,6 @@ limitations under the License. color: $event-notsent-color; } -.mx_EventTile_redacted .mx_EventTile_line .mx_UnknownBody, -.mx_EventTile_redacted .mx_EventTile_reply .mx_UnknownBody { - --lozenge-color: $event-redacted-fg-color; - --lozenge-border-color: $event-redacted-border-color; - display: block; - height: 22px; - width: 250px; - border-radius: 11px; - background: - repeating-linear-gradient( - -45deg, - var(--lozenge-color), - var(--lozenge-color) 3px, - transparent 3px, - transparent 6px - ); - box-shadow: 0px 0px 3px var(--lozenge-border-color) inset; -} - -.mx_EventTile_sending.mx_EventTile_redacted .mx_UnknownBody { - opacity: 0.4; -} - -div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { - --lozenge-color: $event-notsent-color; - --lozenge-border-color: $event-notsent-color; -} - .mx_EventTile_contextual { opacity: 0.4; } @@ -308,11 +280,13 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { .mx_EventTile_readAvatars .mx_BaseAvatar { position: absolute; display: inline-block; + height: $font-14px; + width: $font-14px; } .mx_EventTile_readAvatarRemainder { color: $event-timestamp-color; - font-size: 11px; + font-size: $font-11px; position: absolute; } @@ -341,7 +315,7 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { .mx_EventTile_spoiler_reason { color: $event-timestamp-color; - font-size: 11px; + font-size: $font-11px; } .mx_EventTile_spoiler_content { @@ -356,29 +330,67 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { .mx_EventTile_e2eIcon { position: absolute; top: 6px; - left: 46px; - width: 15px; - height: 15px; + left: 44px; + width: 14px; + height: 14px; display: block; bottom: 0; right: 0; opacity: 0.2; background-repeat: no-repeat; background-size: contain; + + &::before, &::after { + content: ""; + display: block; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + } + + &::before { + background-color: #ffffff; + mask-image: url('$(res)/img/e2e/normal.svg'); + mask-repeat: no-repeat; + mask-position: center; + mask-size: 90%; + } } .mx_EventTile_e2eIcon_undecryptable, .mx_EventTile_e2eIcon_unverified { - background-image: url('$(res)/img/e2e/warning.svg'); + &::after { + mask-image: url('$(res)/img/e2e/warning.svg'); + background-color: $notice-primary-color; + } opacity: 1; } .mx_EventTile_e2eIcon_unknown { - background-image: url('$(res)/img/e2e/warning.svg'); + &::after { + mask-image: url('$(res)/img/e2e/warning.svg'); + background-color: $notice-primary-color; + } opacity: 1; } .mx_EventTile_e2eIcon_unencrypted { - background-image: url('$(res)/img/e2e/warning.svg'); + &::after { + mask-image: url('$(res)/img/e2e/warning.svg'); + background-color: $notice-primary-color; + } + opacity: 1; +} + +.mx_EventTile_e2eIcon_unauthenticated { + &::after { + mask-image: url('$(res)/img/e2e/normal.svg'); + background-color: $composer-e2e-icon-color; + } opacity: 1; } @@ -393,7 +405,7 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { } .mx_EventTile_keyRequestInfo { - font-size: 12px; + font-size: $font-12px; } .mx_EventTile_keyRequestInfo_text { @@ -420,10 +432,6 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { margin-bottom: 0px; } -.mx_EventTile_12hr .mx_EventTile_e2eIcon { - padding-left: 5px; -} - .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line, .mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line, .mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line { @@ -431,15 +439,15 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { } .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line { - border-left: $e2e-verified-color 5px solid; + border-left: $e2e-verified-color 4px solid; } .mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line { - border-left: $e2e-unverified-color 5px solid; + border-left: $e2e-unverified-color 4px solid; } .mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line { - border-left: $e2e-unknown-color 5px solid; + border-left: $e2e-unknown-color 4px solid; } .mx_EventTile:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line, @@ -471,7 +479,7 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { .mx_EventTile_content .mx_EventTile_edited { user-select: none; - font-size: 12px; + font-size: $font-12px; color: $roomtopic-color; display: inline-block; margin-left: 9px; @@ -489,7 +497,7 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { white-space: normal !important; line-height: inherit !important; color: inherit; // inherit the colour from the dark or light theme by default (but not for code blocks) - font-size: 14px; + font-size: $font-14px; pre, code { font-family: $monospace-font-family !important; @@ -528,7 +536,7 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { display: inline-block; visibility: hidden; cursor: pointer; - top: 6px; + top: 8px; right: 6px; width: 19px; height: 19px; @@ -560,7 +568,6 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { .mx_EventTile_content .markdown-body a { color: $accent-color-alt; - text-decoration: underline; } .mx_EventTile_content .markdown-body .hljs { @@ -581,80 +588,33 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { /* end of overrides */ -.mx_MatrixChat_useCompactLayout { - .mx_EventTile { - padding-top: 4px; +.mx_EventTile_tileError { + color: red; + text-align: center; + + // Remove some of the default tile padding so that the error is centered + margin-right: 0; + .mx_EventTile_line { + padding-left: 0; + margin-right: 0; } - .mx_EventTile.mx_EventTile_info { - // same as the padding for non-compact .mx_EventTile.mx_EventTile_info - padding-top: 0px; - font-size: 13px; - .mx_EventTile_line, .mx_EventTile_reply { - line-height: 20px; - } - .mx_EventTile_avatar { - top: 4px; - } + .mx_EventTile_line span { + padding: 4px 8px; } - .mx_EventTile .mx_SenderProfile { - font-size: 13px; - } - - .mx_EventTile.mx_EventTile_emote { - // add a bit more space for emotes so that avatars don't collide - padding-top: 8px; - .mx_EventTile_avatar { - top: 2px; - } - .mx_EventTile_line, .mx_EventTile_reply { - padding-top: 0px; - padding-bottom: 1px; - } - } - - .mx_EventTile.mx_EventTile_emote.mx_EventTile_continuation { - padding-top: 0; - .mx_EventTile_line, .mx_EventTile_reply { - padding-top: 0px; - padding-bottom: 0px; - } - } - - .mx_EventTile_line, .mx_EventTile_reply { - padding-top: 0px; - padding-bottom: 0px; - } - - .mx_EventTile_avatar { - top: 2px; - } - - .mx_EventTile_e2eIcon { - top: 3px; - } - - .mx_EventTile_readAvatars { - top: 27px; - } - - .mx_EventTile_continuation .mx_EventTile_readAvatars, - .mx_EventTile_emote .mx_EventTile_readAvatars { - top: 5px; - } - - .mx_EventTile_info .mx_EventTile_readAvatars { - top: 4px; - } - - .mx_RoomView_MessageList h2 { - margin-top: 6px; - } - - .mx_EventTile_content .markdown-body { - p, ul, ol, dl, blockquote, pre, table { - margin-bottom: 4px; // 1/4 of the non-compact margin-bottom - } + a { + margin-left: 1em; + } +} + +@media only screen and (max-width: 480px) { + .mx_EventTile_line, .mx_EventTile_reply { + padding-left: 0; + margin-right: 0; + } + .mx_EventTile_content { + margin-top: 10px; + margin-right: 0; } } diff --git a/res/css/views/rooms/_GroupLayout.scss b/res/css/views/rooms/_GroupLayout.scss new file mode 100644 index 0000000000..2b447be44a --- /dev/null +++ b/res/css/views/rooms/_GroupLayout.scss @@ -0,0 +1,130 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +$left-gutter: 64px; + +.mx_GroupLayout { + .mx_EventTile { + > .mx_SenderProfile { + line-height: $font-17px; + padding-left: $left-gutter; + } + + > .mx_EventTile_line { + padding-left: $left-gutter; + } + + > .mx_EventTile_avatar { + position: absolute; + } + + .mx_MessageTimestamp { + position: absolute; + width: 46px; /* 8 + 30 (avatar) + 8 */ + } + + .mx_EventTile_line, .mx_EventTile_reply { + padding-top: 3px; + padding-bottom: 3px; + line-height: $font-22px; + } + } + + .mx_EventTile_info .mx_EventTile_line { + padding-left: calc($left-gutter + 18px); + } +} + +/* Compact layout overrides */ + +.mx_MatrixChat_useCompactLayout { + .mx_EventTile { + padding-top: 4px; + + .mx_EventTile_line, .mx_EventTile_reply { + padding-top: 0; + padding-bottom: 0; + } + + &.mx_EventTile_info { + // same as the padding for non-compact .mx_EventTile.mx_EventTile_info + padding-top: 0px; + font-size: $font-13px; + .mx_EventTile_line, .mx_EventTile_reply { + line-height: $font-20px; + } + .mx_EventTile_avatar { + top: 4px; + } + } + + .mx_SenderProfile { + font-size: $font-13px; + } + + &.mx_EventTile_emote { + // add a bit more space for emotes so that avatars don't collide + padding-top: 8px; + .mx_EventTile_avatar { + top: 2px; + } + .mx_EventTile_line, .mx_EventTile_reply { + padding-top: 0px; + padding-bottom: 1px; + } + } + + &.mx_EventTile_emote.mx_EventTile_continuation { + padding-top: 0; + .mx_EventTile_line, .mx_EventTile_reply { + padding-top: 0px; + padding-bottom: 0px; + } + } + + .mx_EventTile_avatar { + top: 2px; + } + + .mx_EventTile_e2eIcon { + top: 3px; + } + + .mx_EventTile_readAvatars { + top: 27px; + } + + &.mx_EventTile_continuation .mx_EventTile_readAvatars, + &.mx_EventTile_emote .mx_EventTile_readAvatars { + top: 5px; + } + + &.mx_EventTile_info .mx_EventTile_readAvatars { + top: 4px; + } + + .mx_EventTile_content .markdown-body { + p, ul, ol, dl, blockquote, pre, table { + margin-bottom: 4px; // 1/4 of the non-compact margin-bottom + } + } + } + + .mx_RoomView_MessageList h2 { + margin-top: 6px; + } +} diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss new file mode 100644 index 0000000000..ed60c220e7 --- /dev/null +++ b/res/css/views/rooms/_IRCLayout.scss @@ -0,0 +1,225 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +$icon-width: 14px; +$timestamp-width: 45px; +$right-padding: 5px; +$irc-line-height: $font-18px; + +.mx_IRCLayout { + --name-width: 70px; + + line-height: $irc-line-height !important; + + .mx_EventTile { + + // timestamps are links which shouldn't be underlined + > a { + text-decoration: none; + } + + display: flex; + flex-direction: row; + align-items: flex-start; + padding-top: 0; + + > * { + margin-right: $right-padding; + } + + > .mx_EventTile_msgOption { + order: 5; + flex-shrink: 0; + + .mx_EventTile_readAvatars { + top: 0.2rem; // ($irc-line-height - avatar height) / 2 + } + } + + > .mx_SenderProfile { + order: 2; + flex-shrink: 0; + width: var(--name-width); + text-overflow: ellipsis; + text-align: right; + display: flex; + align-items: center; + overflow: visible; + justify-content: flex-end; + } + + .mx_EventTile_line, .mx_EventTile_reply { + padding: 0; + display: flex; + flex-direction: column; + order: 3; + flex-grow: 1; + flex-shrink: 1; + min-width: 0; + } + + > .mx_EventTile_avatar { + order: 1; + position: relative; + top: 0; + left: 0; + flex-shrink: 0; + height: $irc-line-height; + display: flex; + align-items: center; + + // Need to use important to override the js provided height and width values. + > .mx_BaseAvatar, > .mx_BaseAvatar > * { + height: $font-14px !important; + width: $font-14px !important; + font-size: $font-10px !important; + line-height: $font-15px !important; + } + } + + .mx_MessageTimestamp { + font-size: $font-10px; + width: $timestamp-width; + text-align: right; + } + + > .mx_EventTile_e2eIcon { + position: absolute; + right: unset; + left: unset; + top: 0; + + padding: 0; + + flex-shrink: 0; + flex-grow: 0; + + height: $font-18px; + + background-position: center; + } + + .mx_EventTile_line { + .mx_EventTile_e2eIcon, + .mx_TextualEvent, + .mx_MTextBody, + .mx_ReplyThread_wrapper_empty { + display: inline-block; + } + } + + .mx_EventTile_reply { + order: 4; + } + + .mx_EditMessageComposer_buttons { + position: relative; + } + } + + .mx_EventTile_emote { + > .mx_EventTile_avatar { + margin-left: calc(var(--name-width) + $icon-width + $right-padding); + } + } + + blockquote { + margin: 0; + } + + .mx_EventListSummary { + > .mx_EventTile_line { + padding-left: calc(var(--name-width) + $icon-width + $timestamp-width + 3 * $right-padding); // 15 px of padding + } + + .mx_EventListSummary_avatars { + padding: 0; + margin: 0 9px 0 0; + } + } + + .mx_EventTile.mx_EventTile_info { + .mx_EventTile_avatar { + left: calc(var(--name-width) + 10px + $icon-width); + top: 0; + } + + .mx_EventTile_line { + left: calc(var(--name-width) + 10px + $icon-width); + } + + .mx_TextualEvent { + line-height: $irc-line-height; + } + } + + // Suppress highlight thing from the normal Layout. + .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line, + .mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line, + .mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line { + padding-left: 0; + border-left: 0; + } + + .mx_SenderProfile_hover { + background-color: $primary-bg-color; + overflow: hidden; + + > span { + display: flex; + + > .mx_SenderProfile_name, + > .mx_SenderProfile_aux { + overflow: hidden; + text-overflow: ellipsis; + min-width: var(--name-width); + } + } + } + + .mx_SenderProfile:hover { + justify-content: flex-start; + } + + .mx_SenderProfile_hover:hover { + overflow: visible; + width: max(auto, 100%); + z-index: 10; + } + + .mx_ReplyThread { + margin: 0; + .mx_SenderProfile { + width: unset; + max-width: var(--name-width); + } + } + + .mx_ProfileResizer { + position: absolute; + height: 100%; + width: 15px; + left: calc(80px + var(--name-width)); + cursor: col-resize; + z-index: 100; + } + + // Need to use important to override the js provided height and width values. + .mx_Flair > img { + height: $font-14px !important; + width: $font-14px !important; + } +} diff --git a/res/css/views/rooms/_InviteOnlyIcon.scss b/res/css/views/rooms/_InviteOnlyIcon.scss deleted file mode 100644 index b71fd6348d..0000000000 --- a/res/css/views/rooms/_InviteOnlyIcon.scss +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -@define-mixin mx_InviteOnlyIcon { - width: 12px; - height: 12px; - position: relative; - display: block !important; -} - -@define-mixin mx_InviteOnlyIcon_padlock { - background-color: $roomtile-name-color; - mask-image: url("$(res)/img/feather-customised/lock-solid.svg"); - mask-position: center; - mask-repeat: no-repeat; - mask-size: contain; - content: ""; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; -} - -.mx_InviteOnlyIcon_large { - @mixin mx_InviteOnlyIcon; - margin: 0 4px; - - &::before { - @mixin mx_InviteOnlyIcon_padlock; - width: 12px; - height: 12px; - } -} - -.mx_InviteOnlyIcon_small { - @mixin mx_InviteOnlyIcon; - left: -2px; - - &::before { - @mixin mx_InviteOnlyIcon_padlock; - width: 10px; - height: 10px; - } -} diff --git a/res/css/views/rooms/_JumpToBottomButton.scss b/res/css/views/rooms/_JumpToBottomButton.scss index 7f458092fb..cb3803fe5b 100644 --- a/res/css/views/rooms/_JumpToBottomButton.scss +++ b/res/css/views/rooms/_JumpToBottomButton.scss @@ -34,13 +34,18 @@ limitations under the License. top: -12px; border-radius: 16px; font-weight: bold; - font-size: 12px; - line-height: 14px; + font-size: $font-12px; + line-height: $font-14px; text-align: center; // to be able to get it centered // with text-align in parent display: inline-block; padding: 0 4px; + color: $accent-fg-color; + background-color: $muted-fg-color; +} + +.mx_JumpToBottomButton_highlight .mx_JumpToBottomButton_badge { color: $secondary-accent-color; background-color: $warning-color; } @@ -51,7 +56,7 @@ limitations under the License. border-radius: 19px; box-sizing: border-box; background: $primary-bg-color; - border: 1.3px solid $roomtile-name-color; + border: 1.3px solid $muted-fg-color; cursor: pointer; } @@ -65,5 +70,5 @@ limitations under the License. mask: url('$(res)/img/icon-jump-to-bottom.svg'); mask-repeat: no-repeat; mask-position: 9px 14px; - background: $roomtile-name-color; + background: $muted-fg-color; } diff --git a/res/css/views/rooms/_MemberDeviceInfo.scss b/res/css/views/rooms/_MemberDeviceInfo.scss deleted file mode 100644 index 15b4832dc5..0000000000 --- a/res/css/views/rooms/_MemberDeviceInfo.scss +++ /dev/null @@ -1,95 +0,0 @@ -/* -Copyright 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_MemberDeviceInfo { - display: flex; - padding-bottom: 10px; - align-items: flex-start; -} - -.mx_MemberDeviceInfo_icon { - margin-top: 4px; - width: 12px; - height: 12px; - mask-repeat: no-repeat; - mask-size: 100%; -} -.mx_MemberDeviceInfo_icon_blacklisted { - mask-image: url('$(res)/img/e2e/blacklisted.svg'); - background-color: $warning-color; -} -.mx_MemberDeviceInfo_icon_verified { - mask-image: url('$(res)/img/e2e/verified.svg'); - background-color: $accent-color; -} -.mx_MemberDeviceInfo_icon_unverified { - mask-image: url('$(res)/img/e2e/warning.svg'); - background-color: $warning-color; -} - -.mx_MemberDeviceInfo > .mx_DeviceVerifyButtons { - display: flex; - flex-direction: column; - flex: 0 1 auto; - align-items: stretch; -} - -.mx_MemberDeviceInfo_textButton { - @mixin mx_DialogButton_small; - margin: 2px; - flex: 1; -} - -.mx_MemberDeviceInfo_textButton:hover { - @mixin mx_DialogButton_hover; -} - -.mx_MemberDeviceInfo_deviceId { - word-break: break-word; - font-size: 13px; -} - -.mx_MemberDeviceInfo_deviceInfo { - margin: 0 5px 5px 8px; - flex: 1; -} - -/* "Unblacklist" is too long for a regular button: make it wider and - reduce the padding. */ -.mx_EncryptedEventDialog .mx_MemberDeviceInfo_blacklist, -.mx_EncryptedEventDialog .mx_MemberDeviceInfo_unblacklist { - padding-left: 1em; - padding-right: 1em; -} - -.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_verified, -.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_unverified, -.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_blacklisted { - float: right; - padding-left: 1em; -} - -.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_verified { - color: $e2e-verified-color; -} - -.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_unverified { - color: $e2e-unverified-color; -} - -.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_blacklisted { - color: $e2e-warning-color; -} diff --git a/res/css/views/rooms/_MemberInfo.scss b/res/css/views/rooms/_MemberInfo.scss index e3f746e9d3..fb082843f1 100644 --- a/res/css/views/rooms/_MemberInfo.scss +++ b/res/css/views/rooms/_MemberInfo.scss @@ -48,7 +48,7 @@ limitations under the License. } .mx_MemberInfo h2 { - font-size: 18px; + font-size: $font-18px; font-weight: 600; margin: 16px 0 16px 15px; } @@ -94,12 +94,12 @@ limitations under the License. text-transform: uppercase; color: $input-darker-fg-color; font-weight: bold; - font-size: 12px; + font-size: $font-12px; margin: 4px 0; } .mx_MemberInfo_profileField { - font-size: 15px; + font-size: $font-15px; position: relative; } @@ -109,10 +109,10 @@ limitations under the License. .mx_MemberInfo_field { cursor: pointer; - font-size: 15px; + font-size: $font-15px; color: $primary-fg-color; margin-left: 8px; - line-height: 23px; + line-height: $font-23px; } .mx_MemberInfo_createRoom { @@ -128,7 +128,7 @@ limitations under the License. } .mx_MemberInfo label { - font-size: 13px; + font-size: $font-13px; } .mx_MemberInfo label .mx_MemberInfo_label_text { @@ -144,7 +144,7 @@ limitations under the License. } .mx_MemberInfo_statusMessage { - font-size: 11px; + font-size: $font-11px; opacity: 0.5; overflow: hidden; white-space: nowrap; diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss index 6e4465583c..90667d41b4 100644 --- a/res/css/views/rooms/_MemberList.scss +++ b/res/css/views/rooms/_MemberList.scss @@ -26,11 +26,15 @@ limitations under the License. flex: 1 0 auto; } + .mx_SearchBox { + margin-bottom: 5px; + } + h2 { text-transform: uppercase; color: $h3-color; font-weight: 600; - font-size: 13px; + font-size: $font-13px; padding-left: 3px; padding-right: 12px; margin-top: 8px; @@ -59,32 +63,27 @@ limitations under the License. .mx_GroupMemberList_query, .mx_GroupRoomList_query { flex: 1 1 0; + + // stricter rule to override the one in _common.scss + &[type="text"] { + font-size: $font-12px; + } } - - .mx_MemberList_wrapper { padding: 10px; } - -.mx_MemberList_invite, -.mx_RightPanel_invite { +.mx_MemberList_invite { flex: 0 0 auto; position: relative; background-color: $button-bg-color; border-radius: 4px; - padding: 8px; - margin: 9px; + margin: 5px 9px 9px; display: flex; justify-content: center; color: $button-fg-color; font-weight: 600; - - .mx_RightPanel_icon { - padding-right: 5px; - padding-top: 2px; - } } .mx_MemberList_invite.mx_AccessibleButton_disabled { @@ -93,8 +92,17 @@ limitations under the License. } .mx_MemberList_invite span { - background-image: url('$(res)/img/feather-customised/user-add.svg'); + background-image: url('$(res)/img/element-icons/room/invite.svg'); background-repeat: no-repeat; background-position: center left; - padding-left: 25px; + background-size: 20px; + padding: 8px 0 8px 25px; +} + +.mx_MemberList_inviteCommunity span { + background-image: url('$(res)/img/icon-invite-people.svg'); +} + +.mx_MemberList_addRoomToCommunity span { + background-image: url('$(res)/img/icons-room-add.svg'); } diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index a05b4c0c0e..ec95403262 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -20,7 +20,7 @@ limitations under the License. margin: auto; border-top: 1px solid $primary-hairline-color; position: relative; - padding-left: 84px; + padding-left: 82px; } .mx_MessageComposer_replaced_wrapper { @@ -60,7 +60,7 @@ limitations under the License. .mx_MessageComposer .mx_MessageComposer_avatar { position: absolute; - left: 27px; + left: 26px; } .mx_MessageComposer .mx_MessageComposer_avatar .mx_BaseAvatar { @@ -76,8 +76,8 @@ limitations under the License. left: 60px; margin-right: 0; // Counteract the E2EIcon class margin-left: 3px; // Counteract the E2EIcon class - width: 15px; - height: 15px; + width: 12px; + height: 12px; } .mx_MessageComposer_noperm_error { @@ -105,7 +105,7 @@ limitations under the License. min-height: 60px; justify-content: flex-start; align-items: flex-start; - font-size: 14px; + font-size: $font-14px; margin-right: 6px; } @@ -161,7 +161,7 @@ limitations under the License. box-shadow: none; color: $primary-fg-color; background-color: $primary-bg-color; - font-size: 14px; + font-size: $font-14px; max-height: 120px; overflow: auto; /* needed for FF */ @@ -196,26 +196,35 @@ limitations under the License. mask-size: contain; mask-position: center; } + + &.mx_MessageComposer_hangup::before { + background-color: $warning-color; + } } + .mx_MessageComposer_upload::before { - mask-image: url('$(res)/img/feather-customised/paperclip.svg'); + mask-image: url('$(res)/img/element-icons/room/composer/attach.svg'); } .mx_MessageComposer_hangup::before { - mask-image: url('$(res)/img/hangup.svg'); + mask-image: url('$(res)/img/element-icons/call/hangup.svg'); } .mx_MessageComposer_voicecall::before { - mask-image: url('$(res)/img/feather-customised/phone.svg'); + mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); } .mx_MessageComposer_videocall::before { - mask-image: url('$(res)/img/feather-customised/video.svg'); + mask-image: url('$(res)/img/element-icons/call/video-call.svg'); +} + +.mx_MessageComposer_emoji::before { + mask-image: url('$(res)/img/element-icons/room/composer/emoji.svg'); } .mx_MessageComposer_stickers::before { - mask-image: url('$(res)/img/feather-customised/face.svg'); + mask-image: url('$(res)/img/element-icons/room/composer/sticker.svg'); } .mx_MessageComposer_formatting { @@ -242,7 +251,7 @@ limitations under the License. flex-direction: row; align-items: center; - font-size: 10px; + font-size: $font-10px; color: $greyed-fg-color; } diff --git a/res/css/views/rooms/_MessageComposerFormatBar.scss b/res/css/views/rooms/_MessageComposerFormatBar.scss index 1b5a21bed0..d97c49630a 100644 --- a/res/css/views/rooms/_MessageComposerFormatBar.scss +++ b/res/css/views/rooms/_MessageComposerFormatBar.scss @@ -75,35 +75,35 @@ limitations under the License. } .mx_MessageComposerFormatBar_buttonIconBold::after { - mask-image: url('$(res)/img/format/bold.svg'); + mask-image: url('$(res)/img/element-icons/room/format-bar/bold.svg'); } .mx_MessageComposerFormatBar_buttonIconItalic::after { - mask-image: url('$(res)/img/format/italics.svg'); + mask-image: url('$(res)/img/element-icons/room/format-bar/italic.svg'); } .mx_MessageComposerFormatBar_buttonIconStrikethrough::after { - mask-image: url('$(res)/img/format/strikethrough.svg'); + mask-image: url('$(res)/img/element-icons/room/format-bar/strikethrough.svg'); } .mx_MessageComposerFormatBar_buttonIconQuote::after { - mask-image: url('$(res)/img/format/quote.svg'); + mask-image: url('$(res)/img/element-icons/room/format-bar/quote.svg'); } .mx_MessageComposerFormatBar_buttonIconCode::after { - mask-image: url('$(res)/img/format/code.svg'); + mask-image: url('$(res)/img/element-icons/room/format-bar/code.svg'); } } .mx_MessageComposerFormatBar_buttonTooltip { white-space: nowrap; - font-size: 13px; + font-size: $font-13px; font-weight: 600; min-width: 54px; text-align: center; .mx_MessageComposerFormatBar_tooltipShortcut { - font-size: 9px; + font-size: $font-9px; opacity: 0.7; } } diff --git a/res/css/views/rooms/_NotificationBadge.scss b/res/css/views/rooms/_NotificationBadge.scss new file mode 100644 index 0000000000..64b2623238 --- /dev/null +++ b/res/css/views/rooms/_NotificationBadge.scss @@ -0,0 +1,72 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_NotificationBadge { + &:not(.mx_NotificationBadge_visible) { + display: none; + } + + // Badges are structured a bit weirdly to work around issues with non-monospace + // font styles. The badge pill is actually a background div and the count floats + // within that. For example: + // + // ( 99+ ) <-- Rounded pill is a _bg class. + // ^- The count is an element floating within that. + + &.mx_NotificationBadge_visible { + background-color: $roomtile-default-badge-bg-color; + + // Create a flexbox to order the count a bit easier + display: flex; + align-items: center; + justify-content: center; + + &.mx_NotificationBadge_highlighted { + // TODO: Use a more specific variable + background-color: $warning-color; + } + + // These are the 3 background types + + &.mx_NotificationBadge_dot { + background-color: $primary-fg-color; // increased visibility + + width: 6px; + height: 6px; + border-radius: 6px; + } + + &.mx_NotificationBadge_2char { + width: $font-16px; + height: $font-16px; + border-radius: $font-16px; + } + + &.mx_NotificationBadge_3char { + width: $font-26px; + height: $font-16px; + border-radius: $font-16px; + } + + // The following is the floating badge + + .mx_NotificationBadge_count { + font-size: $font-10px; + line-height: $font-14px; + color: #fff; // TODO: Variable + } + } +} diff --git a/res/css/views/rooms/_PresenceLabel.scss b/res/css/views/rooms/_PresenceLabel.scss index 26ed1aa6a3..5be83c77d7 100644 --- a/res/css/views/rooms/_PresenceLabel.scss +++ b/res/css/views/rooms/_PresenceLabel.scss @@ -15,6 +15,6 @@ limitations under the License. */ .mx_PresenceLabel { - font-size: 11px; + font-size: $font-11px; opacity: 0.5; } diff --git a/res/css/views/rooms/_ReplyPreview.scss b/res/css/views/rooms/_ReplyPreview.scss index 4dc4cb2c40..9feb337042 100644 --- a/res/css/views/rooms/_ReplyPreview.scss +++ b/res/css/views/rooms/_ReplyPreview.scss @@ -22,9 +22,10 @@ limitations under the License. border: 1px solid $primary-hairline-color; background: $primary-bg-color; border-bottom: none; - border-radius: 4px 4px 0 0; + border-radius: 8px 8px 0 0; max-height: 50vh; overflow: auto; + box-shadow: 0px -16px 32px $composer-shadow-color; } .mx_ReplyPreview_section { diff --git a/res/css/views/rooms/_RoomBreadcrumbs.scss b/res/css/views/rooms/_RoomBreadcrumbs.scss index 3858d836e6..6512797401 100644 --- a/res/css/views/rooms/_RoomBreadcrumbs.scss +++ b/res/css/views/rooms/_RoomBreadcrumbs.scss @@ -1,5 +1,5 @@ /* -Copyright 2019 New Vector Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,98 +15,42 @@ limitations under the License. */ .mx_RoomBreadcrumbs { - position: relative; - height: 42px; - padding: 8px; - padding-bottom: 0; + width: 100%; + + // Create a flexbox for the crumbs display: flex; flex-direction: row; - - // repeating circles as empty placeholders - background: - radial-gradient( - circle at center, - $breadcrumb-placeholder-bg-color, - $breadcrumb-placeholder-bg-color 15px, - transparent 16px - ); - background-size: 36px; - background-position: 6px -1px; - background-repeat: repeat-x; - - - // Autohide the scrollbar - overflow-x: hidden; - &:hover { - overflow-x: visible; - } - - .mx_AutoHideScrollbar { - display: flex; - flex-direction: row; - height: 100%; - } + align-items: flex-start; .mx_RoomBreadcrumbs_crumb { - margin-left: 4px; - height: 32px; - display: inline-block; - transition: transform 0.3s, width 0.3s; - position: relative; - - .mx_RoomTile_badge { - position: absolute; - top: -3px; - right: -4px; - } - - .mx_RoomBreadcrumbs_dmIndicator { - position: absolute; - bottom: 0; - right: -4px; - } - } - - .mx_RoomBreadcrumbs_animate { - margin-left: 0; + margin-right: 8px; width: 32px; - transform: scale(1); } - .mx_RoomBreadcrumbs_preAnimate { - width: 0; - transform: scale(0); + // These classes come from the CSSTransition component. There's many more classes we + // could care about, but this is all we worried about for now. The animation works by + // first triggering the enter state with the newest breadcrumb off screen (-40px) then + // sliding it into view. + &.mx_RoomBreadcrumbs-enter { + margin-left: -40px; // 32px for the avatar, 8px for the margin + } + &.mx_RoomBreadcrumbs-enter-active { + margin-left: 0; + + // Timing function is as-requested by design. + // NOTE: The transition time MUST match the value passed to CSSTransition! + transition: margin-left 640ms cubic-bezier(0.66, 0.02, 0.36, 1); } - .mx_RoomBreadcrumbs_left { - opacity: 0.5; - } - - // Note: we have to manually control the gradient and stuff, but the IndicatorScrollbar - // will deal with left/right positioning for us. Normally we'd use position:sticky on - // a few key elements, however that doesn't work in horizontal scrolling scenarios. - - .mx_IndicatorScrollbar_leftOverflowIndicator, - .mx_IndicatorScrollbar_rightOverflowIndicator { - display: none; - } - - .mx_IndicatorScrollbar_leftOverflowIndicator { - background: linear-gradient(to left, $panel-gradient); - } - - .mx_IndicatorScrollbar_rightOverflowIndicator { - background: linear-gradient(to right, $panel-gradient); - } - - &.mx_IndicatorScrollbar_leftOverflow .mx_IndicatorScrollbar_leftOverflowIndicator, - &.mx_IndicatorScrollbar_rightOverflow .mx_IndicatorScrollbar_rightOverflowIndicator { - position: absolute; - top: 0; - bottom: 0; - width: 15px; - display: block; - pointer-events: none; - z-index: 100; + .mx_RoomBreadcrumbs_placeholder { + font-weight: 600; + font-size: $font-14px; + line-height: 32px; // specifically to match the height this is not scaled + height: 32px; } } + +.mx_RoomBreadcrumbs_Tooltip { + margin-left: -42px; + margin-top: -42px; +} diff --git a/res/css/views/rooms/_RoomDropTarget.scss b/res/css/views/rooms/_RoomDropTarget.scss deleted file mode 100644 index 1076a0563a..0000000000 --- a/res/css/views/rooms/_RoomDropTarget.scss +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_RoomDropTarget_container { - background-color: $secondary-accent-color; - padding-left: 18px; - padding-right: 18px; - padding-top: 8px; - padding-bottom: 7px; -} - -.collapsed .mx_RoomDropTarget_container { - padding-right: 10px; - padding-left: 10px; -} - -.mx_RoomDropTarget { - font-size: 13px; - padding-top: 5px; - padding-bottom: 5px; - border: 1px dashed $accent-color; - color: $primary-fg-color; - background-color: $droptarget-bg-color; - border-radius: 4px; -} - - -.mx_RoomDropTarget_label { - position: relative; - margin-top: 3px; - line-height: 21px; - z-index: 1; - text-align: center; -} - -.collapsed .mx_RoomDropTarget_avatar { - float: none; -} - -.collapsed .mx_RoomDropTarget_label { - display: none; -} diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index 47b8131ef0..ba46100ea6 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -15,26 +15,34 @@ limitations under the License. */ .mx_RoomHeader { - flex: 0 0 52px; + flex: 0 0 50px; border-bottom: 1px solid $primary-hairline-color; + background-color: $roomheader-bg-color; - .mx_E2EIcon { - margin: 0; - position: absolute; - bottom: -2px; - right: -6px; - height: 15px; - width: 15px; + .mx_RoomHeader_e2eIcon { + height: 12px; + width: 12px; + + .mx_E2EIcon { + margin: 0; + position: absolute; + height: 12px; + width: 12px; + } } } .mx_RoomHeader_wrapper { margin: auto; - height: 52px; + height: 50px; display: flex; align-items: center; min-width: 0; - padding: 0 10px 0 19px; + padding: 0 10px 0 18px; + + .mx_InviteOnlyIcon_large { + margin: 0; + } } .mx_RoomHeader_spinner { @@ -67,7 +75,6 @@ limitations under the License. .mx_RoomHeader_buttons { display: flex; background-color: $primary-bg-color; - padding-right: 5px; } .mx_RoomHeader_info { @@ -77,9 +84,9 @@ limitations under the License. } .mx_RoomHeader_simpleHeader { - line-height: 52px; + line-height: $font-52px; color: $roomheader-color; - font-size: 18px; + font-size: $font-18px; font-weight: 600; overflow: hidden; margin-left: 63px; @@ -102,7 +109,7 @@ limitations under the License. overflow: hidden; color: $roomheader-color; font-weight: 600; - font-size: 18px; + font-size: $font-18px; margin: 0 7px; border-bottom: 1px solid transparent; display: flex; @@ -161,7 +168,7 @@ limitations under the License. flex: 1; color: $roomtopic-color; font-weight: 400; - font-size: 13px; + font-size: $font-13px; margin: 0 7px; margin-top: 4px; // to align baseline of topic with room name overflow: hidden; @@ -173,9 +180,7 @@ limitations under the License. .mx_RoomHeader_avatar { flex: 0; - width: 28px; - height: 28px; - margin: 0 7px; + margin: 0 6px 0 7px; position: relative; } @@ -203,41 +208,53 @@ limitations under the License. .mx_RoomHeader_button { position: relative; - margin-left: 10px; + margin-left: 1px; + margin-right: 1px; cursor: pointer; - height: 20px; - width: 20px; + height: 32px; + width: 32px; + border-radius: 100%; &::before { content: ''; position: absolute; - height: 20px; - width: 20px; + top: 4px; // center with parent of 32px + left: 4px; // center with parent of 32px + height: 24px; + width: 24px; background-color: $roomheader-button-color; mask-repeat: no-repeat; mask-size: contain; } + + &:hover { + background: rgba($accent-color, 0.1); + + &::before { + background-color: $accent-color; + } + } } .mx_RoomHeader_settingsButton::before { - mask-image: url('$(res)/img/feather-customised/settings.svg'); + mask-image: url('$(res)/img/element-icons/settings.svg'); } .mx_RoomHeader_forgetButton::before { - mask-image: url('$(res)/img/leave.svg'); + mask-image: url('$(res)/img/element-icons/leave.svg'); width: 26px; } .mx_RoomHeader_searchButton::before { - mask-image: url('$(res)/img/feather-customised/search.svg'); + mask-image: url('$(res)/img/element-icons/room/search-inset.svg'); } .mx_RoomHeader_shareButton::before { - mask-image: url('$(res)/img/feather-customised/share.svg'); + mask-image: url('$(res)/img/element-icons/room/share.svg'); } .mx_RoomHeader_manageIntegsButton::before { - mask-image: url('$(res)/img/feather-customised/grid.svg'); + mask-image: url('$(res)/img/element-icons/room/integrations.svg'); } .mx_RoomHeader_showPanel { @@ -253,7 +270,7 @@ limitations under the License. } .mx_RoomHeader_pinnedButton::before { - mask-image: url('$(res)/img/icons-pin.svg'); + mask-image: url('$(res)/img/element-icons/room/pin.svg'); } .mx_RoomHeader_pinsIndicator { @@ -269,3 +286,12 @@ limitations under the License. .mx_RoomHeader_pinsIndicatorUnread { background-color: $pinned-unread-color; } + +@media only screen and (max-width: 480px) { + .mx_RoomHeader_wrapper { + padding: 0; + } + .mx_RoomHeader { + overflow: hidden; + } +} diff --git a/res/css/views/rooms/_RoomList.scss b/res/css/views/rooms/_RoomList.scss index 5ed22f997d..690ed0646e 100644 --- a/res/css/views/rooms/_RoomList.scss +++ b/res/css/views/rooms/_RoomList.scss @@ -1,6 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017 Vector Creations Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,51 +15,11 @@ limitations under the License. */ .mx_RoomList { - /* take up remaining space below TopLeftMenu */ - flex: 1; - min-height: 0; - overflow-y: hidden; -} + width: calc(100% - 16px); // 16px of artificial right-side margin (8px is overflowed from the sublists) -.mx_RoomList .mx_ResizeHandle { - // needed so the z-index takes effect - position: relative; -} - -/* hide resize handles next to collapsed / empty sublists */ -.mx_RoomList .mx_RoomSubList:not(.mx_RoomSubList_nonEmpty) + .mx_ResizeHandle { - display: none; -} - -.mx_RoomList_expandButton { - margin-left: 8px; - cursor: pointer; - padding-left: 12px; - padding-right: 12px; -} - -.mx_RoomList_emptySubListTip_container { - padding-left: 18px; - padding-right: 18px; - padding-top: 8px; - padding-bottom: 7px; -} - -.mx_RoomList_emptySubListTip { - font-size: 13px; - padding: 5px; - border: 1px dashed $accent-color; - color: $primary-fg-color; - background-color: $droptarget-bg-color; - border-radius: 4px; - line-height: 16px; -} - -.mx_RoomList_emptySubListTip .mx_RoleButton { - vertical-align: -2px; -} - -.mx_RoomList_headerButtons { - position: absolute; - right: 60px; + // Create a column-based flexbox for the sublists. That's pretty much all we have to + // worry about in this stylesheet. + display: flex; + flex-direction: column; + flex-wrap: nowrap; // let the column overflow } diff --git a/res/css/views/rooms/_RoomPreviewBar.scss b/res/css/views/rooms/_RoomPreviewBar.scss index 981cf06c69..8708f13ada 100644 --- a/res/css/views/rooms/_RoomPreviewBar.scss +++ b/res/css/views/rooms/_RoomPreviewBar.scss @@ -23,7 +23,7 @@ limitations under the License. -webkit-align-items: center; h3 { - font-size: 18px; + font-size: $font-18px; font-weight: 600; &.mx_RoomPreviewBar_spinnerTitle { @@ -48,8 +48,8 @@ limitations under the License. } .mx_RoomPreviewBar_footer { - font-size: 12px; - line-height: 20px; + font-size: $font-12px; + line-height: $font-20px; .mx_Spinner { vertical-align: middle; diff --git a/res/css/views/rooms/_RoomRecoveryReminder.scss b/res/css/views/rooms/_RoomRecoveryReminder.scss index 85d42ca4b4..09b28ae235 100644 --- a/res/css/views/rooms/_RoomRecoveryReminder.scss +++ b/res/css/views/rooms/_RoomRecoveryReminder.scss @@ -33,11 +33,6 @@ limitations under the License. margin-bottom: 1em; } -.mx_RoomRecoveryReminder_button { - @mixin mx_DialogButton; - margin: 0 10px; -} - .mx_RoomRecoveryReminder_secondary { font-size: 90%; margin-top: 1em; diff --git a/res/css/views/rooms/_RoomSublist.scss b/res/css/views/rooms/_RoomSublist.scss new file mode 100644 index 0000000000..3ec4d114af --- /dev/null +++ b/res/css/views/rooms/_RoomSublist.scss @@ -0,0 +1,396 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_RoomSublist { + // The sublist is a column of rows, essentially + display: flex; + flex-direction: column; + + margin-left: 8px; + margin-bottom: 4px; + width: 100%; + + flex-shrink: 0; // to convince safari's layout engine the flexbox is fine + + .mx_RoomSublist_headerContainer { + // Create a flexbox to make alignment easy + display: flex; + align-items: center; + + // *************************** + // Sticky Headers Start + + // Ideally we'd be able to use `position: sticky; top: 0; bottom: 0;` on the + // headerContainer, however due to our layout concerns we actually have to + // calculate it manually so we can sticky things in the right places. We also + // target the headerText instead of the container to reduce jumps when scrolling, + // and to help hide the badges/other buttons that could appear on hover. This + // all works by ensuring the header text has a fixed height when sticky so the + // fixed height of the container can maintain the scroll position. + + // The combined height must be set in the LeftPanel component for sticky headers + // to work correctly. + padding-bottom: 8px; + height: 24px; + color: $roomlist-header-color; + + .mx_RoomSublist_stickable { + flex: 1; + max-width: 100%; + + // Create a flexbox to make ordering easy + display: flex; + align-items: center; + + // We use a generic sticky class for 2 reasons: to reduce style duplication and + // to identify when a header is sticky. If we didn't have a consistent sticky class, + // we'd have to do the "is sticky" checks again on click, as clicking the header + // when sticky scrolls instead of collapses the list. + &.mx_RoomSublist_headerContainer_sticky { + position: fixed; + height: 32px; // to match the header container + // width set by JS + width: calc(100% - 22px); + } + + &.mx_RoomSublist_headerContainer_stickyBottom { + bottom: 0; + } + + // We don't have a top style because the top is dependent on the room list header's + // height, and is therefore calculated in JS. + // The class, mx_RoomSublist_headerContainer_stickyTop, is applied though. + } + + // Sticky Headers End + // *************************** + + .mx_RoomSublist_badgeContainer { + // Create another flexbox row because it's super easy to position the badge this way. + display: flex; + align-items: center; + justify-content: center; + + // Apply the width and margin to the badge so the container doesn't occupy dead space + .mx_NotificationBadge { + // Do not set a width so the badges get properly sized + margin-left: 8px; // same as menu+aux buttons + } + } + + &:not(.mx_RoomSublist_headerContainer_withAux) { + .mx_NotificationBadge { + margin-right: 4px; // just to push it over a bit, aligning it with the other elements + } + } + + .mx_RoomSublist_auxButton, + .mx_RoomSublist_menuButton { + margin-left: 8px; // should be the same as the notification badge + position: relative; + width: 24px; + height: 24px; + border-radius: 32px; + + &::before { + content: ''; + width: 16px; + height: 16px; + position: absolute; + top: 4px; + left: 4px; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background: $muted-fg-color; + } + } + + // Hide the menu button by default + .mx_RoomSublist_menuButton { + visibility: hidden; + width: 0; + margin: 0; + } + + .mx_RoomSublist_auxButton::before { + mask-image: url('$(res)/img/feather-customised/plus.svg'); + } + + .mx_RoomSublist_menuButton::before { + mask-image: url('$(res)/img/element-icons/context-menu.svg'); + } + + .mx_RoomSublist_headerText { + flex: 1; + max-width: calc(100% - 16px); // 16px is the badge width + line-height: $font-16px; + font-size: $font-13px; + font-weight: 600; + + // Ellipsize any text overflow + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + + .mx_RoomSublist_collapseBtn { + display: inline-block; + position: relative; + width: 12px; + height: 12px; + margin-right: 8px; + + &::before { + content: ''; + width: 12px; + height: 12px; + position: absolute; + top: 1px; + left: 1px; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background-color: $primary-fg-color; + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + } + + &.mx_RoomSublist_collapseBtn_collapsed::before { + mask-image: url('$(res)/img/feather-customised/chevron-right.svg'); + } + } + } + } + + // In the general case, we leave height of headers alone even if sticky, so + // that the sublists below them do not jump. However, that leaves a gap + // when scrolled to the top above the first sublist (whose header can only + // ever stick to top), so we force height to 0 for only that first header. + // See also https://github.com/vector-im/riot-web/issues/14429. + &:first-child .mx_RoomSublist_headerContainer { + height: 0; + padding-bottom: 4px; + } + + .mx_RoomSublist_resizeBox { + position: relative; + + // Create another flexbox column for the tiles + display: flex; + flex-direction: column; + overflow: hidden; + + .mx_RoomSublist_tiles { + flex: 1 0 0; + overflow: hidden; + // need this to be flex otherwise the overflow hidden from above + // sometimes vertically centers the clipped list ... no idea why it would do this + // as the box model should be top aligned. Happens in both FF and Chromium + display: flex; + flex-direction: column; + + mask-image: linear-gradient(0deg, transparent, black 4px); + } + + .mx_RoomSublist_resizerHandles_showNButton { + flex: 0 0 32px; + } + + .mx_RoomSublist_resizerHandles { + flex: 0 0 4px; + } + + // Class name comes from the ResizableBox component + // The hover state needs to use the whole sublist, not just the resizable box, + // so that selector is below and one level higher. + .mx_RoomSublist_resizerHandle { + cursor: ns-resize; + border-radius: 3px; + + // Override styles from library + width: unset !important; + height: 4px !important; // Update RESIZE_HANDLE_HEIGHT if this changes + + // This is positioned directly below the 'show more' button. + position: absolute; + bottom: 0 !important; // override from library + + // Together, these make the bar 64px wide + // These are also overridden from the library + left: calc(50% - 32px) !important; + right: calc(50% - 32px) !important; + } + + &:hover, &.mx_RoomSublist_hasMenuOpen { + .mx_RoomSublist_resizerHandle { + opacity: 0.8; + background-color: $primary-fg-color; + } + } + } + + .mx_RoomSublist_showNButton { + cursor: pointer; + font-size: $font-13px; + line-height: $font-18px; + color: $roomtile-preview-color; + + // Update the render() function for RoomSublist if these change + // Update the ListLayout class for minVisibleTiles if these change. + height: 24px; + padding-bottom: 4px; + + // We create a flexbox to cheat at alignment + display: flex; + align-items: center; + + .mx_RoomSublist_showNButtonChevron { + position: relative; + width: 16px; + height: 16px; + margin-left: 12px; + margin-right: 18px; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background: $roomtile-preview-color; + } + + .mx_RoomSublist_showMoreButtonChevron { + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + } + + .mx_RoomSublist_showLessButtonChevron { + mask-image: url('$(res)/img/feather-customised/chevron-up.svg'); + } + } + + &.mx_RoomSublist_hasMenuOpen, + &:not(.mx_RoomSublist_minimized) > .mx_RoomSublist_headerContainer:focus-within, + &:not(.mx_RoomSublist_minimized) > .mx_RoomSublist_headerContainer:hover { + .mx_RoomSublist_menuButton { + visibility: visible; + width: 24px; + margin-left: 8px; + } + } + + &.mx_RoomSublist_minimized { + .mx_RoomSublist_headerContainer { + height: auto; + flex-direction: column; + position: relative; + + .mx_RoomSublist_badgeContainer { + order: 0; + align-self: flex-end; + margin-right: 0; + } + + .mx_RoomSublist_stickable { + order: 1; + max-width: 100%; + } + + .mx_RoomSublist_auxButton { + order: 2; + visibility: visible; + width: 32px !important; // !important to override hover styles + height: 32px !important; // !important to override hover styles + margin-left: 0 !important; // !important to override hover styles + background-color: $roomlist-button-bg-color; + margin-top: 8px; + + &::before { + top: 8px; + left: 8px; + } + } + } + + .mx_RoomSublist_resizeBox { + align-items: center; + } + + .mx_RoomSublist_showNButton { + flex-direction: column; + + .mx_RoomSublist_showNButtonChevron { + margin-right: 12px; // to center + } + } + + .mx_RoomSublist_menuButton { + height: 16px; + } + + &.mx_RoomSublist_hasMenuOpen, + & > .mx_RoomSublist_headerContainer:hover { + .mx_RoomSublist_menuButton { + visibility: visible; + position: absolute; + bottom: 48px; // align to middle of name, 40px for aux button (with padding) and 8px for alignment + right: 0; + width: 16px; + height: 16px; + border-radius: 0; + z-index: 1; // occlude the list name + + // This is the same color as the left panel background because it needs + // to occlude the sublist title + background-color: $roomlist-bg-color; + + &::before { + top: 0; + left: 0; + } + } + + &.mx_RoomSublist_headerContainer:not(.mx_RoomSublist_headerContainer_withAux) { + .mx_RoomSublist_menuButton { + bottom: 8px; // align to the middle of name, 40px less than the `bottom` above. + } + } + } + } +} + +.mx_RoomSublist_contextMenu { + padding: 20px 16px; + width: 250px; + + hr { + margin-top: 16px; + margin-bottom: 16px; + margin-right: 16px; // additional 16px + border: 1px solid $roomsublist-divider-color; + opacity: 0.1; + } + + .mx_RoomSublist_contextMenu_title { + font-size: $font-15px; + line-height: $font-20px; + font-weight: 600; + margin-bottom: 4px; + } + + .mx_RadioButton, .mx_Checkbox { + margin-top: 8px; + } +} + +.mx_RoomSublist_addRoomTooltip { + margin-top: -3px; +} diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index 31d887cbbb..9afbd44b20 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -1,6 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,202 +14,226 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Note: the room tile expects to be in a flexbox column container .mx_RoomTile { - display: flex; - flex-direction: row; - align-items: center; - cursor: pointer; - height: 34px; - margin: 0; - padding: 0 8px 0 10px; - position: relative; -} - -.mx_RoomTile:focus { - filter: none !important; - background-color: $roomtile-focused-bg-color; -} - -.mx_RoomTile_menuButton { - display: none; - flex: 0 0 16px; - height: 16px; - background-image: url('$(res)/img/icon_context.svg'); - background-repeat: no-repeat; - background-position: center; -} - -.mx_RoomTile_tooltip { - display: inline-block; - position: relative; - top: -54px; - left: -12px; -} - -.mx_RoomTile_nameContainer { - display: flex; - align-items: center; - flex: 1; - vertical-align: middle; - min-width: 0; -} - -.mx_RoomTile_labelContainer { - display: flex; - flex-direction: column; - flex: 1; - min-width: 0; -} - -.mx_RoomTile_subtext { - display: inline-block; - font-size: 11px; - padding: 0 0 0 7px; - margin: 0; - overflow: hidden; - white-space: nowrap; - text-overflow: clip; - position: relative; - bottom: 4px; -} - -.mx_RoomTile_avatar_container { - position: relative; -} - -.mx_RoomTile_avatar { - flex: 0; + margin-bottom: 4px; padding: 4px; - width: 24px; - vertical-align: middle; -} -.mx_RoomTile_hasSubtext .mx_RoomTile_avatar { - padding-top: 0; - vertical-align: super; -} + // The tile is also a flexbox row itself + display: flex; -.mx_RoomTile_dm { - display: block; - position: absolute; - bottom: 0; - right: -5px; - z-index: 2; -} + &.mx_RoomTile_selected, + &:hover, + &:focus-within, + &.mx_RoomTile_hasMenuOpen { + background-color: $roomtile-selected-bg-color; + border-radius: 8px; + } -// Note we match .mx_E2EIcon to make sure this matches more tightly than just -// .mx_E2EIcon on its own -.mx_RoomTile_e2eIcon.mx_E2EIcon { - height: 14px; - width: 14px; - display: block; - position: absolute; - bottom: -2px; - right: -5px; - z-index: 1; - margin: 0; -} + .mx_DecoratedRoomAvatar, .mx_RoomTile_avatarContainer { + margin-right: 8px; + } -.mx_RoomTile_name { - font-size: 14px; - padding: 0 4px; - color: $roomtile-name-color; - white-space: nowrap; - overflow-x: hidden; - text-overflow: ellipsis; -} + .mx_RoomTile_nameContainer { + flex-grow: 1; + min-width: 0; // allow flex to shrink it + margin-right: 8px; // spacing to buttons/badges -.mx_RoomTile_badge { - flex: 0 1 content; - border-radius: 0.8em; - padding: 0 0.4em; - color: $roomtile-badge-fg-color; - font-weight: 600; - font-size: 12px; -} - -.collapsed { - .mx_RoomTile { - margin: 0 6px; - padding: 0 2px; - position: relative; + // Create a new column layout flexbox for the name parts + display: flex; + flex-direction: column; justify-content: center; + + .mx_RoomTile_name, + .mx_RoomTile_messagePreview { + margin: 0 2px; + width: 100%; + + // Ellipsize any text overflow + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + .mx_RoomTile_name { + font-size: $font-14px; + line-height: $font-18px; + } + + .mx_RoomTile_name.mx_RoomTile_nameHasUnreadEvents { + font-weight: 600; + } + + .mx_RoomTile_messagePreview { + font-size: $font-13px; + line-height: $font-18px; + color: $roomtile-preview-color; + } + + .mx_RoomTile_nameWithPreview { + margin-top: -4px; // shift the name up a bit more + } } - .mx_RoomTile_name { + .mx_RoomTile_notificationsButton { + margin-left: 4px; // spacing between buttons + } + + .mx_RoomTile_badgeContainer { + height: 16px; + // don't set width so that it takes no space when there is no badge to show + margin: auto 0; // vertically align + + // Create a flexbox to make aligning dot badges easier + display: flex; + align-items: center; + + .mx_NotificationBadge { + margin-right: 2px; // centering + } + + .mx_NotificationBadge_dot { + // make the smaller dot occupy the same width for centering + margin-left: 5px; + margin-right: 7px; + } + } + + // The context menu buttons are hidden by default + .mx_RoomTile_menuButton, + .mx_RoomTile_notificationsButton { + width: 20px; + min-width: 20px; // yay flex + height: 20px; + margin-top: auto; + margin-bottom: auto; + position: relative; display: none; + + &::before { + top: 2px; + left: 2px; + content: ''; + width: 16px; + height: 16px; + position: absolute; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background: $primary-fg-color; + } } - .mx_RoomTile_badge { - position: absolute; - right: 6px; - top: 0px; - border-radius: 16px; - z-index: 3; - border: 0.18em solid $secondary-accent-color; - } - - .mx_RoomTile_menuButton { - display: none; //no design for this for now - } -} - -// toggle menuButton and badge on menu displayed -.mx_RoomTile_menuDisplayed, -// or on keyboard focus of room tile -.mx_LeftPanel_container:not(.collapsed) .mx_RoomTile:focus-within, -// or on pointer hover -.mx_LeftPanel_container:not(.collapsed) .mx_RoomTile:hover { - .mx_RoomTile_menuButton { + // If the room has an overriden notification setting then we always show the notifications menu button + .mx_RoomTile_notificationsButton.mx_RoomTile_notificationsButton_show { display: block; } -} -.mx_RoomTile_unreadNotify .mx_RoomTile_badge, -.mx_RoomTile_badge.mx_RoomTile_badgeUnread { - background-color: $roomtile-name-color; -} + .mx_RoomTile_menuButton::before { + mask-image: url('$(res)/img/element-icons/context-menu.svg'); + } -.mx_RoomTile_highlight .mx_RoomTile_badge, -.mx_RoomTile_badge.mx_RoomTile_badgeRed { - color: $accent-fg-color; - background-color: $warning-color; -} + &:not(.mx_RoomTile_minimized) { + &:hover, + &:focus-within, + &.mx_RoomTile_hasMenuOpen { + // Hide the badge container on hover because it'll be a menu button + .mx_RoomTile_badgeContainer { + width: 0; + height: 0; + display: none; + } -.mx_RoomTile_unread, .mx_RoomTile_highlight { - .mx_RoomTile_name { - font-weight: 600; - color: $roomtile-selected-color; + .mx_RoomTile_notificationsButton, + .mx_RoomTile_menuButton { + display: block; + } + } + } + + &.mx_RoomTile_minimized { + flex-direction: column; + align-items: center; + position: relative; + + .mx_DecoratedRoomAvatar, .mx_RoomTile_avatarContainer { + margin-right: 0; + } } } -.mx_RoomTile_selected { - border-radius: 4px; - background-color: $roomtile-selected-bg-color; +// We use these both in context menus and the room tiles +.mx_RoomTile_iconBell::before { + mask-image: url('$(res)/img/element-icons/notifications.svg'); +} +.mx_RoomTile_iconBellDot::before { + mask-image: url('$(res)/img/element-icons/roomlist/notifications-default.svg'); +} +.mx_RoomTile_iconBellCrossed::before { + mask-image: url('$(res)/img/element-icons/roomlist/notifications-off.svg'); +} +.mx_RoomTile_iconBellMentions::before { + mask-image: url('$(res)/img/element-icons/roomlist/notifications-dm.svg'); +} +.mx_RoomTile_iconCheck::before { + mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg'); } -.mx_DNDRoomTile { - transform: none; - transition: transform 0.2s; -} +.mx_RoomTile_contextMenu { + .mx_RoomTile_contextMenu_redRow { + .mx_AccessibleButton { + color: $warning-color !important; // !important to override styles from context menu + } -.mx_DNDRoomTile_dragging { - transform: scale(1.05, 1.05); -} + .mx_IconizedContextMenu_icon::before { + background-color: $warning-color; + } + } -.mx_RoomTile_arrow { - position: absolute; - right: 0px; -} + .mx_RoomTile_contextMenu_activeRow { + &.mx_AccessibleButton, .mx_AccessibleButton { + color: $accent-color !important; // !important to override styles from context menu + } -.mx_RoomTile.mx_RoomTile_transparent { - background-color: transparent; -} + .mx_IconizedContextMenu_icon::before { + background-color: $accent-color; + } + } -.mx_RoomTile.mx_RoomTile_transparent:focus { - background-color: $roomtile-transparent-focused-color; -} + .mx_IconizedContextMenu_icon { + position: relative; + width: 16px; + height: 16px; -.mx_GroupInviteTile .mx_RoomTile_name { - flex: 1; + &::before { + content: ''; + width: 16px; + height: 16px; + position: absolute; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background: $primary-fg-color; + } + } + + .mx_RoomTile_iconStar::before { + mask-image: url('$(res)/img/element-icons/roomlist/favorite.svg'); + } + + .mx_RoomTile_iconFavorite::before { + mask-image: url('$(res)/img/feather-customised/favourites.svg'); + } + + .mx_RoomTile_iconArrowDown::before { + mask-image: url('$(res)/img/element-icons/roomlist/low-priority.svg'); + } + + .mx_RoomTile_iconSettings::before { + mask-image: url('$(res)/img/element-icons/settings.svg'); + } + + .mx_RoomTile_iconSignOut::before { + mask-image: url('$(res)/img/element-icons/leave.svg'); + } } diff --git a/res/css/views/rooms/_RoomTileIcon.scss b/res/css/views/rooms/_RoomTileIcon.scss new file mode 100644 index 0000000000..2f3afdd446 --- /dev/null +++ b/res/css/views/rooms/_RoomTileIcon.scss @@ -0,0 +1,69 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_RoomTileIcon { + width: 12px; + height: 12px; + border-radius: 12px; + background-color: $roomlist-bg-color; // to match the room list itself +} + +.mx_RoomTileIcon_globe::before { + content: ''; + width: 8px; + height: 8px; + top: 2px; + left: 2px; + position: absolute; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background: $primary-fg-color; + mask-image: url('$(res)/img/globe.svg'); +} + +.mx_RoomTileIcon_offline::before { + content: ''; + width: 8px; + height: 8px; + top: 2px; + left: 2px; + position: absolute; + border-radius: 8px; + background-color: $presence-offline; +} + +.mx_RoomTileIcon_online::before { + content: ''; + width: 8px; + height: 8px; + top: 2px; + left: 2px; + position: absolute; + border-radius: 8px; + background-color: $presence-online; +} + +.mx_RoomTileIcon_away::before { + content: ''; + width: 8px; + height: 8px; + top: 2px; + left: 2px; + position: absolute; + border-radius: 8px; + background-color: $presence-away; +} diff --git a/res/css/views/rooms/_SearchBar.scss b/res/css/views/rooms/_SearchBar.scss index b6748e5ad2..fecc8d78d8 100644 --- a/res/css/views/rooms/_SearchBar.scss +++ b/res/css/views/rooms/_SearchBar.scss @@ -22,7 +22,7 @@ limitations under the License. .mx_SearchBar_input { // border: 1px solid $input-border-color; - // font-size: 15px; + // font-size: $font-15px; flex: 1 1 0; margin-left: 22px; } @@ -45,7 +45,7 @@ limitations under the License. border: 0; margin: 0 0 0 22px; padding: 5px; - font-size: 15px; + font-size: $font-15px; cursor: pointer; color: $primary-fg-color; border-bottom: 2px solid $accent-color; diff --git a/res/css/views/rooms/_SendMessageComposer.scss b/res/css/views/rooms/_SendMessageComposer.scss index d20f7107b3..0b646666e7 100644 --- a/res/css/views/rooms/_SendMessageComposer.scss +++ b/res/css/views/rooms/_SendMessageComposer.scss @@ -18,7 +18,7 @@ limitations under the License. flex: 1; display: flex; flex-direction: column; - font-size: 14px; + font-size: $font-14px; justify-content: center; margin-right: 6px; // don't grow wider than available space diff --git a/res/css/views/rooms/_Stickers.scss b/res/css/views/rooms/_Stickers.scss index d33ecc0bb6..4bd45631cc 100644 --- a/res/css/views/rooms/_Stickers.scss +++ b/res/css/views/rooms/_Stickers.scss @@ -31,7 +31,7 @@ .mx_Stickers_addLink { display: inline; cursor: pointer; - text-decoration: underline; + color: $button-link-fg-color; } .mx_Stickers_hideStickers { diff --git a/res/css/views/rooms/_TopUnreadMessagesBar.scss b/res/css/views/rooms/_TopUnreadMessagesBar.scss index 28eddf1fa2..c0545660c2 100644 --- a/res/css/views/rooms/_TopUnreadMessagesBar.scss +++ b/res/css/views/rooms/_TopUnreadMessagesBar.scss @@ -42,7 +42,7 @@ limitations under the License. border-radius: 19px; box-sizing: border-box; background: $primary-bg-color; - border: 1.3px solid $roomtile-name-color; + border: 1.3px solid $muted-fg-color; cursor: pointer; } @@ -54,7 +54,7 @@ limitations under the License. mask-image: url('$(res)/img/icon-jump-to-first-unread.svg'); mask-repeat: no-repeat; mask-position: 9px 13px; - background: $roomtile-name-color; + background: $muted-fg-color; } .mx_TopUnreadMessagesBar_markAsRead { @@ -62,7 +62,7 @@ limitations under the License. width: 18px; height: 18px; background: $primary-bg-color; - border: 1.3px solid $roomtile-name-color; + border: 1.3px solid $muted-fg-color; border-radius: 10px; margin: 5px auto; } @@ -76,5 +76,5 @@ limitations under the License. mask-repeat: no-repeat; mask-size: 10px; mask-position: 4px 4px; - background: $roomtile-name-color; + background: $muted-fg-color; } diff --git a/res/css/views/rooms/_WhoIsTypingTile.scss b/res/css/views/rooms/_WhoIsTypingTile.scss index 579ea7e73e..1c0dabbeb5 100644 --- a/res/css/views/rooms/_WhoIsTypingTile.scss +++ b/res/css/views/rooms/_WhoIsTypingTile.scss @@ -49,7 +49,7 @@ limitations under the License. border-radius: 40px; width: 24px; height: 24px; - line-height: 24px; + line-height: $font-24px; font-size: 0.8em; vertical-align: top; text-align: center; @@ -57,9 +57,9 @@ limitations under the License. .mx_WhoIsTypingTile_label { flex: 1; - font-size: 14px; + font-size: $font-14px; font-weight: 600; - color: $eventtile-meta-color; + color: $roomtopic-color; } .mx_WhoIsTypingTile_label > span { diff --git a/res/css/views/settings/_AvatarSetting.scss b/res/css/views/settings/_AvatarSetting.scss index 35dba90f85..eddcf9f55a 100644 --- a/res/css/views/settings/_AvatarSetting.scss +++ b/res/css/views/settings/_AvatarSetting.scss @@ -15,40 +15,18 @@ limitations under the License. */ .mx_AvatarSetting_avatar { - width: 88px; - height: 88px; + width: $font-88px; + height: $font-88px; margin-left: 13px; position: relative; & > * { - width: 88px; + width: $font-88px; box-sizing: border-box; } .mx_AccessibleButton.mx_AccessibleButton_kind_primary { margin-top: 8px; - - div { - position: relative; - height: 12px; - width: 12px; - display: inline; - padding-right: 6px; // 0.5 * 12px - left: -6px; // 0.5 * 12px - top: 3px; - } - - div::before { - content: ''; - position: absolute; - height: 12px; - width: 12px; - - background-color: $button-primary-fg-color; - mask-repeat: no-repeat; - mask-size: contain; - mask-image: url('$(res)/img/feather-customised/upload.svg'); - } } .mx_AccessibleButton.mx_AccessibleButton_kind_link_sm { @@ -63,7 +41,7 @@ limitations under the License. & > img, .mx_AvatarSetting_avatarPlaceholder { display: block; - height: 88px; + height: $font-88px; border-radius: 4px; } diff --git a/res/css/views/settings/_UpdateCheckButton.scss b/res/css/views/settings/_UpdateCheckButton.scss new file mode 100644 index 0000000000..f35a023ac1 --- /dev/null +++ b/res/css/views/settings/_UpdateCheckButton.scss @@ -0,0 +1,23 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_UpdateCheckButton_summary { + margin-left: 16px; + + .mx_AccessibleButton_kind_link { + padding: 0; + } +} diff --git a/res/css/views/settings/tabs/_SettingsTab.scss b/res/css/views/settings/tabs/_SettingsTab.scss index 01a1d94956..e3a61e6825 100644 --- a/res/css/views/settings/tabs/_SettingsTab.scss +++ b/res/css/views/settings/tabs/_SettingsTab.scss @@ -19,7 +19,7 @@ limitations under the License. } .mx_SettingsTab_heading { - font-size: 20px; + font-size: $font-20px; font-weight: 600; color: $primary-fg-color; } @@ -29,7 +29,7 @@ limitations under the License. } .mx_SettingsTab_subheading { - font-size: 16px; + font-size: $font-16px; display: block; font-family: $font-family; font-weight: 600; @@ -40,7 +40,7 @@ limitations under the License. .mx_SettingsTab_subsectionText { color: $settings-subsection-fg-color; - font-size: 14px; + font-size: $font-14px; display: block; margin: 10px 100px 10px 0; // Align with the rest of the view } @@ -61,9 +61,9 @@ limitations under the License. .mx_SettingsTab_section .mx_SettingsFlag .mx_SettingsFlag_label { vertical-align: middle; display: inline-block; - font-size: 14px; + font-size: $font-14px; color: $primary-fg-color; - max-width: calc(100% - 48px); // Force word wrap instead of colliding with the switch + max-width: calc(100% - $font-48px); // Force word wrap instead of colliding with the switch box-sizing: border-box; padding-right: 10px; } diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss new file mode 100644 index 0000000000..94983a60bf --- /dev/null +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss @@ -0,0 +1,230 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_AppearanceUserSettingsTab_fontSlider, +.mx_AppearanceUserSettingsTab_fontSlider_preview, +.mx_AppearanceUserSettingsTab_Layout { + @mixin mx_Settings_fullWidthField; +} + +.mx_AppearanceUserSettingsTab .mx_Field { + width: 256px; +} + +.mx_AppearanceUserSettingsTab_fontScaling { + color: $primary-fg-color; +} + +.mx_AppearanceUserSettingsTab_fontSlider { + display: flex; + flex-direction: row; + align-items: center; + padding: 15px; + background: rgba($appearance-tab-border-color, 0.2); + border-radius: 10px; + font-size: 10px; + margin-top: 24px; + margin-bottom: 24px; +} + +.mx_AppearanceUserSettingsTab_fontSlider_preview { + border: 1px solid $appearance-tab-border-color; + border-radius: 10px; + padding: 0 16px 9px 16px; + pointer-events: none; + + .mx_EventTile_msgOption { + display: none; + } + + &.mx_IRCLayout { + padding-top: 9px; + } +} + +.mx_AppearanceUserSettingsTab_fontSlider_smallText { + font-size: 15px; + padding-right: 20px; + padding-left: 5px; + font-weight: 500; +} + +.mx_AppearanceUserSettingsTab_fontSlider_largeText { + font-size: 18px; + padding-left: 20px; + padding-right: 5px; + font-weight: 500; +} + +.mx_AppearanceUserSettingsTab { + > .mx_SettingsTab_SubHeading { + margin-bottom: 32px; + } +} + +.mx_AppearanceUserSettingsTab_themeSection { + $radio-bg-color: $input-darker-bg-color; + color: $primary-fg-color; + + > .mx_ThemeSelectors { + display: flex; + flex-direction: row; + flex-wrap: wrap; + + margin-top: 4px; + margin-bottom: 30px; + + > .mx_RadioButton { + padding: $font-16px; + box-sizing: border-box; + border-radius: 10px; + width: 180px; + + background: $radio-bg-color; + opacity: 0.4; + + flex-shrink: 1; + flex-grow: 0; + + margin-right: 15px; + margin-top: 10px; + + font-weight: 600; + color: $muted-fg-color; + + > span { + justify-content: center; + } + } + + > .mx_RadioButton_enabled { + opacity: 1; + + // These colors need to be hardcoded because they don't change with the theme + &.mx_ThemeSelector_light { + background-color: #f3f8fd; + color: #2e2f32; + } + + &.mx_ThemeSelector_dark { + // 5% lightened version of 181b21 + background-color: #25282e; + color: #f3f8fd; + + > input > div { + border-color: $input-darker-bg-color; + > div { + border-color: $input-darker-bg-color; + } + } + } + + &.mx_ThemeSelector_black { + background-color: #000000; + color: #f3f8fd; + + > input > div { + border-color: $input-darker-bg-color; + > div { + border-color: $input-darker-bg-color; + } + } + } + } + } +} + +.mx_SettingsTab_customFontSizeField { + margin-left: calc($font-16px + 10px); +} + +.mx_AppearanceUserSettingsTab_Layout_RadioButtons { + display: flex; + flex-direction: row; + + color: $primary-fg-color; + + .mx_AppearanceUserSettingsTab_spacer { + width: 24px; + } + + > .mx_AppearanceUserSettingsTab_Layout_RadioButton { + flex-grow: 0; + flex-shrink: 1; + display: flex; + flex-direction: column; + + width: 300px; + + border: 1px solid $appearance-tab-border-color; + border-radius: 10px; + + .mx_EventTile_msgOption, + .mx_MessageActionBar { + display: none; + } + + .mx_AppearanceUserSettingsTab_Layout_RadioButton_preview { + flex-grow: 1; + display: flex; + align-items: center; + padding: 10px; + pointer-events: none; + } + + .mx_RadioButton { + flex-grow: 0; + padding: 10px; + } + + .mx_EventTile_content { + margin-right: 0; + } + + &.mx_AppearanceUserSettingsTab_Layout_RadioButton_selected { + border-color: $accent-color; + } + } + + .mx_RadioButton { + border-top: 1px solid $appearance-tab-border-color; + + > input + div { + border-color: rgba($muted-fg-color, 0.2); + } + } + + .mx_RadioButton_checked { + background-color: rgba($accent-color, 0.08); + } +} + +.mx_AppearanceUserSettingsTab_Advanced { + color: $primary-fg-color; + + > * { + margin-bottom: 16px; + } + + .mx_AppearanceUserSettingsTab_AdvancedToggle { + color: $accent-color; + cursor: pointer; + } + + .mx_AppearanceUserSettingsTab_systemFont { + margin-left: calc($font-16px + 10px); + } +} diff --git a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss index 62d230e752..6c9b89cf5a 100644 --- a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss @@ -14,8 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_GeneralUserSettingsTab_changePassword .mx_Field, -.mx_GeneralUserSettingsTab_themeSection .mx_Field { +.mx_GeneralUserSettingsTab_changePassword .mx_Field { @mixin mx_Settings_fullWidthField; } @@ -23,6 +22,12 @@ limitations under the License. margin-top: 0; } +.mx_GeneralUserSettingsTab_accountSection .mx_Spinner, +.mx_GeneralUserSettingsTab_discovery .mx_Spinner { + // Move the spinner to the left side of the container (default center) + justify-content: left; +} + .mx_GeneralUserSettingsTab_accountSection .mx_EmailAddresses, .mx_GeneralUserSettingsTab_accountSection .mx_PhoneNumbers, .mx_GeneralUserSettingsTab_discovery .mx_ExistingEmailAddress, diff --git a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss index b5a6693006..d6466a03f9 100644 --- a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss @@ -55,3 +55,33 @@ limitations under the License. .mx_SecurityUserSettingsTab_ignoredUser .mx_AccessibleButton { margin-right: 10px; } + +.mx_SecurityUserSettingsTab { + .mx_SettingsTab_section { + .mx_AccessibleButton_kind_link { + padding: 0; + font-size: inherit; + } + } + + .mx_SecurityUserSettingsTab_warning { + color: $notice-primary-color; + position: relative; + padding-left: 40px; + margin-top: 30px; + + &::before { + mask-repeat: no-repeat; + mask-position: 0 center; + mask-size: $font-24px; + position: absolute; + width: $font-24px; + height: $font-24px; + content: ""; + top: 0; + left: 0; + background-color: $notice-primary-color; + mask-image: url('$(res)/img/feather-customised/alert-triangle.svg'); + } + } +} diff --git a/res/css/views/terms/_InlineTermsAgreement.scss b/res/css/views/terms/_InlineTermsAgreement.scss index e00dcf31d1..1d0e3ea8c5 100644 --- a/res/css/views/terms/_InlineTermsAgreement.scss +++ b/res/css/views/terms/_InlineTermsAgreement.scss @@ -16,7 +16,7 @@ limitations under the License. .mx_InlineTermsAgreement_cbContainer { margin-bottom: 10px; - font-size: 14px; + font-size: $font-14px; a { color: $accent-color; diff --git a/res/css/views/verification/_VerificationShowSas.scss b/res/css/views/verification/_VerificationShowSas.scss index 5038d40b73..af003112f7 100644 --- a/res/css/views/verification/_VerificationShowSas.scss +++ b/res/css/views/verification/_VerificationShowSas.scss @@ -48,16 +48,34 @@ limitations under the License. } .mx_VerificationShowSas_emojiSas_emoji { - font-size: 32px; + font-size: $font-32px; } .mx_VerificationShowSas_emojiSas_label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - font-size: 12px; + font-size: $font-12px; } .mx_VerificationShowSas_emojiSas_break { flex-basis: 100%; } + +.mx_VerificationShowSas { + .mx_Dialog_buttons { + // this is more specific than the DialogButtons css so gets preference + button.mx_VerificationShowSas_matchButton { + color: $accent-color; + background-color: $accent-bg-color; + border: none; + } + + // this is more specific than the DialogButtons css so gets preference + button.mx_VerificationShowSas_noMatchButton { + color: $notice-primary-color; + background-color: $notice-primary-bg-color; + border: none; + } + } +} diff --git a/res/css/views/voip/_CallContainer.scss b/res/css/views/voip/_CallContainer.scss new file mode 100644 index 0000000000..8d1b68dd99 --- /dev/null +++ b/res/css/views/voip/_CallContainer.scss @@ -0,0 +1,89 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_CallContainer { + position: absolute; + right: 20px; + bottom: 72px; + border-radius: 8px; + overflow: hidden; + z-index: 100; + box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08); + + cursor: pointer; + + .mx_CallPreview { + .mx_VideoView { + width: 350px; + } + + .mx_VideoView_localVideoFeed { + border-radius: 8px; + overflow: hidden; + } + } + + .mx_IncomingCallBox { + min-width: 250px; + background-color: $primary-bg-color; + padding: 8px; + + .mx_IncomingCallBox_CallerInfo { + display: flex; + direction: row; + + img { + margin: 8px; + } + + > div { + display: flex; + flex-direction: column; + + justify-content: center; + } + + h1, p { + margin: 0px; + padding: 0px; + font-size: $font-14px; + line-height: $font-16px; + } + + h1 { + font-weight: bold; + } + } + + .mx_IncomingCallBox_buttons { + padding: 8px; + display: flex; + flex-direction: row; + + > .mx_IncomingCallBox_spacer { + width: 8px; + } + + > * { + flex-shrink: 0; + flex-grow: 1; + margin-right: 0; + font-size: $font-15px; + line-height: $font-24px; + } + } + } +} diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index b01fbf8c66..f6f3d40308 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,8 +19,76 @@ limitations under the License. background-color: $accent-color; color: $accent-fg-color; cursor: pointer; - text-align: center; padding: 6px; font-weight: bold; - font-size: 13px; + + border-radius: 8px; + min-width: 200px; + + display: flex; + align-items: center; + + img { + margin: 4px; + margin-right: 10px; + } + + > div { + display: flex; + flex-direction: column; + // Hacky vertical align + padding-top: 3px; + } + + > div > p, + > div > h1 { + padding: 0; + margin: 0; + font-size: $font-13px; + line-height: $font-15px; + } + + > div > p { + font-weight: bold; + } + + > * { + flex-grow: 0; + flex-shrink: 0; + } +} + +.mx_CallView_hangup { + position: absolute; + + right: 8px; + bottom: 10px; + + height: 35px; + width: 35px; + + border-radius: 35px; + + background-color: $notice-primary-color; + + z-index: 101; + + cursor: pointer; + + &::before { + content: ''; + position: absolute; + + height: 20px; + width: 20px; + + top: 6.5px; + left: 7.5px; + + mask: url('$(res)/img/hangup.svg'); + mask-size: contain; + background-size: contain; + + background-color: $primary-fg-color; + } } diff --git a/res/css/views/voip/_IncomingCallbox.scss b/res/css/views/voip/_IncomingCallbox.scss deleted file mode 100644 index 64eac25d01..0000000000 --- a/res/css/views/voip/_IncomingCallbox.scss +++ /dev/null @@ -1,69 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_IncomingCallBox { - text-align: center; - border: 1px solid #a4a4a4; - border-radius: 8px; - background-color: $primary-bg-color; - position: fixed; - z-index: 1000; - padding: 6px; - margin-top: -3px; - margin-left: -20px; - width: 200px; -} - -.mx_IncomingCallBox_chevron { - padding: 12px; - position: absolute; - left: -21px; - top: 0px; -} - -.mx_IncomingCallBox_title { - padding: 6px; - font-weight: bold; -} - -.mx_IncomingCallBox_buttons { - display: flex; -} - -.mx_IncomingCallBox_buttons_cell { - vertical-align: middle; - padding: 6px; - flex: 1; -} - -.mx_IncomingCallBox_buttons_decline, -.mx_IncomingCallBox_buttons_accept { - vertical-align: middle; - width: 80px; - height: 36px; - line-height: 36px; - border-radius: 36px; - color: $accent-fg-color; - margin: auto; -} - -.mx_IncomingCallBox_buttons_decline { - background-color: $voip-decline-color; -} - -.mx_IncomingCallBox_buttons_accept { - background-color: $voip-accept-color; -} diff --git a/res/fonts/Inter/Inter-Bold.woff b/res/fonts/Inter/Inter-Bold.woff new file mode 100644 index 0000000000..61e1c25e64 Binary files /dev/null and b/res/fonts/Inter/Inter-Bold.woff differ diff --git a/res/fonts/Inter/Inter-Bold.woff2 b/res/fonts/Inter/Inter-Bold.woff2 new file mode 100644 index 0000000000..6c401bb09b Binary files /dev/null and b/res/fonts/Inter/Inter-Bold.woff2 differ diff --git a/res/fonts/Inter/Inter-BoldItalic.woff b/res/fonts/Inter/Inter-BoldItalic.woff new file mode 100644 index 0000000000..2de403edd1 Binary files /dev/null and b/res/fonts/Inter/Inter-BoldItalic.woff differ diff --git a/res/fonts/Inter/Inter-BoldItalic.woff2 b/res/fonts/Inter/Inter-BoldItalic.woff2 new file mode 100644 index 0000000000..80efd4848d Binary files /dev/null and b/res/fonts/Inter/Inter-BoldItalic.woff2 differ diff --git a/res/fonts/Inter/Inter-Italic.woff b/res/fonts/Inter/Inter-Italic.woff new file mode 100644 index 0000000000..e7da6663fe Binary files /dev/null and b/res/fonts/Inter/Inter-Italic.woff differ diff --git a/res/fonts/Inter/Inter-Italic.woff2 b/res/fonts/Inter/Inter-Italic.woff2 new file mode 100644 index 0000000000..8559dfde38 Binary files /dev/null and b/res/fonts/Inter/Inter-Italic.woff2 differ diff --git a/res/fonts/Inter/Inter-Medium.woff b/res/fonts/Inter/Inter-Medium.woff new file mode 100644 index 0000000000..8c36a6345e Binary files /dev/null and b/res/fonts/Inter/Inter-Medium.woff differ diff --git a/res/fonts/Inter/Inter-Medium.woff2 b/res/fonts/Inter/Inter-Medium.woff2 new file mode 100644 index 0000000000..3b31d3350a Binary files /dev/null and b/res/fonts/Inter/Inter-Medium.woff2 differ diff --git a/res/fonts/Inter/Inter-MediumItalic.woff b/res/fonts/Inter/Inter-MediumItalic.woff new file mode 100644 index 0000000000..fb79e91ff4 Binary files /dev/null and b/res/fonts/Inter/Inter-MediumItalic.woff differ diff --git a/res/fonts/Inter/Inter-MediumItalic.woff2 b/res/fonts/Inter/Inter-MediumItalic.woff2 new file mode 100644 index 0000000000..d32c111f9c Binary files /dev/null and b/res/fonts/Inter/Inter-MediumItalic.woff2 differ diff --git a/res/fonts/Inter/Inter-Regular.woff b/res/fonts/Inter/Inter-Regular.woff new file mode 100644 index 0000000000..7d587c40bf Binary files /dev/null and b/res/fonts/Inter/Inter-Regular.woff differ diff --git a/res/fonts/Inter/Inter-Regular.woff2 b/res/fonts/Inter/Inter-Regular.woff2 new file mode 100644 index 0000000000..d5ffd2a1f1 Binary files /dev/null and b/res/fonts/Inter/Inter-Regular.woff2 differ diff --git a/res/fonts/Inter/Inter-SemiBold.woff b/res/fonts/Inter/Inter-SemiBold.woff new file mode 100644 index 0000000000..99df06cbee Binary files /dev/null and b/res/fonts/Inter/Inter-SemiBold.woff differ diff --git a/res/fonts/Inter/Inter-SemiBold.woff2 b/res/fonts/Inter/Inter-SemiBold.woff2 new file mode 100644 index 0000000000..df746af999 Binary files /dev/null and b/res/fonts/Inter/Inter-SemiBold.woff2 differ diff --git a/res/fonts/Inter/Inter-SemiBoldItalic.woff b/res/fonts/Inter/Inter-SemiBoldItalic.woff new file mode 100644 index 0000000000..91e192b9f1 Binary files /dev/null and b/res/fonts/Inter/Inter-SemiBoldItalic.woff differ diff --git a/res/fonts/Inter/Inter-SemiBoldItalic.woff2 b/res/fonts/Inter/Inter-SemiBoldItalic.woff2 new file mode 100644 index 0000000000..ff8774ccb4 Binary files /dev/null and b/res/fonts/Inter/Inter-SemiBoldItalic.woff2 differ diff --git a/res/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2 b/res/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2 index 593d7c8f5c..a52e5a3800 100644 Binary files a/res/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2 and b/res/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2 differ diff --git a/res/fonts/Twemoji_Mozilla/TwemojiMozilla-sbix.woff2 b/res/fonts/Twemoji_Mozilla/TwemojiMozilla-sbix.woff2 index 277324851f..660a93193d 100644 Binary files a/res/fonts/Twemoji_Mozilla/TwemojiMozilla-sbix.woff2 and b/res/fonts/Twemoji_Mozilla/TwemojiMozilla-sbix.woff2 differ diff --git a/res/img/03b381.png b/res/img/03b381.png deleted file mode 100644 index cf28fc7e59..0000000000 Binary files a/res/img/03b381.png and /dev/null differ diff --git a/res/img/368bd6.png b/res/img/368bd6.png deleted file mode 100644 index a2700bd0ae..0000000000 Binary files a/res/img/368bd6.png and /dev/null differ diff --git a/res/img/ac3ba8.png b/res/img/ac3ba8.png deleted file mode 100644 index 031471d85a..0000000000 Binary files a/res/img/ac3ba8.png and /dev/null differ diff --git a/res/img/e2e/normal.svg b/res/img/e2e/normal.svg index 5b848bc27f..23ca78e44d 100644 --- a/res/img/e2e/normal.svg +++ b/res/img/e2e/normal.svg @@ -1,3 +1,3 @@ - - + + diff --git a/res/img/e2e/verified.svg b/res/img/e2e/verified.svg index 464b443dcf..ac4827baed 100644 --- a/res/img/e2e/verified.svg +++ b/res/img/e2e/verified.svg @@ -1,4 +1,3 @@ - - - + + diff --git a/res/img/e2e/warning.svg b/res/img/e2e/warning.svg index 209ae0f71f..d42922892a 100644 --- a/res/img/e2e/warning.svg +++ b/res/img/e2e/warning.svg @@ -1,5 +1,3 @@ - - - - + + diff --git a/res/img/element-icons/call/fullscreen.svg b/res/img/element-icons/call/fullscreen.svg new file mode 100644 index 0000000000..d2a4c2aa8c --- /dev/null +++ b/res/img/element-icons/call/fullscreen.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/img/element-icons/call/hangup.svg b/res/img/element-icons/call/hangup.svg new file mode 100644 index 0000000000..1a1b82a1d7 --- /dev/null +++ b/res/img/element-icons/call/hangup.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/call/video-call.svg b/res/img/element-icons/call/video-call.svg new file mode 100644 index 0000000000..0c1cd2d419 --- /dev/null +++ b/res/img/element-icons/call/video-call.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/element-icons/call/video-muted.svg b/res/img/element-icons/call/video-muted.svg new file mode 100644 index 0000000000..d2aea71d11 --- /dev/null +++ b/res/img/element-icons/call/video-muted.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/call/voice-call.svg b/res/img/element-icons/call/voice-call.svg new file mode 100644 index 0000000000..d32b703523 --- /dev/null +++ b/res/img/element-icons/call/voice-call.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/call/voice-muted.svg b/res/img/element-icons/call/voice-muted.svg new file mode 100644 index 0000000000..32abafb04a --- /dev/null +++ b/res/img/element-icons/call/voice-muted.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/element-icons/call/voice-unmuted.svg b/res/img/element-icons/call/voice-unmuted.svg new file mode 100644 index 0000000000..e664080217 --- /dev/null +++ b/res/img/element-icons/call/voice-unmuted.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/community-members.svg b/res/img/element-icons/community-members.svg new file mode 100644 index 0000000000..553ba3b1af --- /dev/null +++ b/res/img/element-icons/community-members.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/res/img/element-icons/community-rooms.svg b/res/img/element-icons/community-rooms.svg new file mode 100644 index 0000000000..570b45a488 --- /dev/null +++ b/res/img/element-icons/community-rooms.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/context-menu.svg b/res/img/element-icons/context-menu.svg new file mode 100644 index 0000000000..76a28d50d0 --- /dev/null +++ b/res/img/element-icons/context-menu.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/hide.svg b/res/img/element-icons/hide.svg new file mode 100644 index 0000000000..8ea50a028f --- /dev/null +++ b/res/img/element-icons/hide.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/leave.svg b/res/img/element-icons/leave.svg new file mode 100644 index 0000000000..773e27d4ce --- /dev/null +++ b/res/img/element-icons/leave.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/img/element-icons/notifications.svg b/res/img/element-icons/notifications.svg new file mode 100644 index 0000000000..7002782129 --- /dev/null +++ b/res/img/element-icons/notifications.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/room/composer/attach.svg b/res/img/element-icons/room/composer/attach.svg new file mode 100644 index 0000000000..0cac44d29f --- /dev/null +++ b/res/img/element-icons/room/composer/attach.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/room/composer/emoji.svg b/res/img/element-icons/room/composer/emoji.svg new file mode 100644 index 0000000000..9613d9edd9 --- /dev/null +++ b/res/img/element-icons/room/composer/emoji.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/img/element-icons/room/composer/send.svg b/res/img/element-icons/room/composer/send.svg new file mode 100644 index 0000000000..b255a9b23b --- /dev/null +++ b/res/img/element-icons/room/composer/send.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/room/composer/sticker.svg b/res/img/element-icons/room/composer/sticker.svg new file mode 100644 index 0000000000..3d8f445926 --- /dev/null +++ b/res/img/element-icons/room/composer/sticker.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/img/element-icons/room/files.svg b/res/img/element-icons/room/files.svg new file mode 100644 index 0000000000..6648ab00a5 --- /dev/null +++ b/res/img/element-icons/room/files.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/room/format-bar/bold.svg b/res/img/element-icons/room/format-bar/bold.svg new file mode 100644 index 0000000000..e21210c525 --- /dev/null +++ b/res/img/element-icons/room/format-bar/bold.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/room/format-bar/code.svg b/res/img/element-icons/room/format-bar/code.svg new file mode 100644 index 0000000000..38f94457e8 --- /dev/null +++ b/res/img/element-icons/room/format-bar/code.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/room/format-bar/italic.svg b/res/img/element-icons/room/format-bar/italic.svg new file mode 100644 index 0000000000..270c4f5f15 --- /dev/null +++ b/res/img/element-icons/room/format-bar/italic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/room/format-bar/quote.svg b/res/img/element-icons/room/format-bar/quote.svg new file mode 100644 index 0000000000..3d3d444487 --- /dev/null +++ b/res/img/element-icons/room/format-bar/quote.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/img/element-icons/room/format-bar/strikethrough.svg b/res/img/element-icons/room/format-bar/strikethrough.svg new file mode 100644 index 0000000000..775e0cf8ec --- /dev/null +++ b/res/img/element-icons/room/format-bar/strikethrough.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/element-icons/room/in-call.svg b/res/img/element-icons/room/in-call.svg new file mode 100644 index 0000000000..0e574faa84 --- /dev/null +++ b/res/img/element-icons/room/in-call.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/img/element-icons/room/integrations.svg b/res/img/element-icons/room/integrations.svg new file mode 100644 index 0000000000..3a39506411 --- /dev/null +++ b/res/img/element-icons/room/integrations.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/room/invite.svg b/res/img/element-icons/room/invite.svg new file mode 100644 index 0000000000..f713e57d73 --- /dev/null +++ b/res/img/element-icons/room/invite.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/room/members.svg b/res/img/element-icons/room/members.svg new file mode 100644 index 0000000000..03aba81ad4 --- /dev/null +++ b/res/img/element-icons/room/members.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/img/element-icons/room/message-bar/edit.svg b/res/img/element-icons/room/message-bar/edit.svg new file mode 100644 index 0000000000..d4a7e8eaaf --- /dev/null +++ b/res/img/element-icons/room/message-bar/edit.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/element-icons/room/message-bar/emoji.svg b/res/img/element-icons/room/message-bar/emoji.svg new file mode 100644 index 0000000000..697f656b8a --- /dev/null +++ b/res/img/element-icons/room/message-bar/emoji.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/room/message-bar/reply.svg b/res/img/element-icons/room/message-bar/reply.svg new file mode 100644 index 0000000000..9900d4d19d --- /dev/null +++ b/res/img/element-icons/room/message-bar/reply.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/element-icons/room/pin.svg b/res/img/element-icons/room/pin.svg new file mode 100644 index 0000000000..16941b329b --- /dev/null +++ b/res/img/element-icons/room/pin.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/img/element-icons/room/search-inset.svg b/res/img/element-icons/room/search-inset.svg new file mode 100644 index 0000000000..699cdd1d00 --- /dev/null +++ b/res/img/element-icons/room/search-inset.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/room/settings/advanced.svg b/res/img/element-icons/room/settings/advanced.svg new file mode 100644 index 0000000000..734ae543ea --- /dev/null +++ b/res/img/element-icons/room/settings/advanced.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/room/settings/roles.svg b/res/img/element-icons/room/settings/roles.svg new file mode 100644 index 0000000000..24bccf78f4 --- /dev/null +++ b/res/img/element-icons/room/settings/roles.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/room/share.svg b/res/img/element-icons/room/share.svg new file mode 100644 index 0000000000..dac35ae5a7 --- /dev/null +++ b/res/img/element-icons/room/share.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/roomlist/archived.svg b/res/img/element-icons/roomlist/archived.svg new file mode 100644 index 0000000000..4d30195082 --- /dev/null +++ b/res/img/element-icons/roomlist/archived.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/roomlist/checkmark.svg b/res/img/element-icons/roomlist/checkmark.svg new file mode 100644 index 0000000000..3be39fc9b2 --- /dev/null +++ b/res/img/element-icons/roomlist/checkmark.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/roomlist/clear-input.svg b/res/img/element-icons/roomlist/clear-input.svg new file mode 100644 index 0000000000..29fc097600 --- /dev/null +++ b/res/img/element-icons/roomlist/clear-input.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/roomlist/dark-light-mode.svg b/res/img/element-icons/roomlist/dark-light-mode.svg new file mode 100644 index 0000000000..a6a6464b5c --- /dev/null +++ b/res/img/element-icons/roomlist/dark-light-mode.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/roomlist/direct-chat.svg b/res/img/element-icons/roomlist/direct-chat.svg new file mode 100644 index 0000000000..4b92dd9521 --- /dev/null +++ b/res/img/element-icons/roomlist/direct-chat.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/img/element-icons/roomlist/e2ee-default.svg b/res/img/element-icons/roomlist/e2ee-default.svg new file mode 100644 index 0000000000..76525f48b2 --- /dev/null +++ b/res/img/element-icons/roomlist/e2ee-default.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/roomlist/e2ee-error.svg b/res/img/element-icons/roomlist/e2ee-error.svg new file mode 100644 index 0000000000..7f1a761dde --- /dev/null +++ b/res/img/element-icons/roomlist/e2ee-error.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/roomlist/e2ee-none.svg b/res/img/element-icons/roomlist/e2ee-none.svg new file mode 100644 index 0000000000..cfafe6d09d --- /dev/null +++ b/res/img/element-icons/roomlist/e2ee-none.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/roomlist/e2ee-trusted.svg b/res/img/element-icons/roomlist/e2ee-trusted.svg new file mode 100644 index 0000000000..577d6a31e1 --- /dev/null +++ b/res/img/element-icons/roomlist/e2ee-trusted.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/element-icons/roomlist/explore-rooms.svg b/res/img/element-icons/roomlist/explore-rooms.svg new file mode 100644 index 0000000000..3786ce1153 --- /dev/null +++ b/res/img/element-icons/roomlist/explore-rooms.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/element-icons/roomlist/favorite.svg b/res/img/element-icons/roomlist/favorite.svg new file mode 100644 index 0000000000..0c33999ea3 --- /dev/null +++ b/res/img/element-icons/roomlist/favorite.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/roomlist/feedback.svg b/res/img/element-icons/roomlist/feedback.svg new file mode 100644 index 0000000000..c15edd709a --- /dev/null +++ b/res/img/element-icons/roomlist/feedback.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/img/element-icons/roomlist/home.svg b/res/img/element-icons/roomlist/home.svg new file mode 100644 index 0000000000..9598ccf184 --- /dev/null +++ b/res/img/element-icons/roomlist/home.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/roomlist/low-priority.svg b/res/img/element-icons/roomlist/low-priority.svg new file mode 100644 index 0000000000..832501527b --- /dev/null +++ b/res/img/element-icons/roomlist/low-priority.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/roomlist/notifications-default.svg b/res/img/element-icons/roomlist/notifications-default.svg new file mode 100644 index 0000000000..59743f5d67 --- /dev/null +++ b/res/img/element-icons/roomlist/notifications-default.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/roomlist/notifications-dm.svg b/res/img/element-icons/roomlist/notifications-dm.svg new file mode 100644 index 0000000000..e0bd435240 --- /dev/null +++ b/res/img/element-icons/roomlist/notifications-dm.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/roomlist/notifications-off.svg b/res/img/element-icons/roomlist/notifications-off.svg new file mode 100644 index 0000000000..c848471f63 --- /dev/null +++ b/res/img/element-icons/roomlist/notifications-off.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/img/element-icons/roomlist/search.svg b/res/img/element-icons/roomlist/search.svg new file mode 100644 index 0000000000..3786ce1153 --- /dev/null +++ b/res/img/element-icons/roomlist/search.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/element-icons/security.svg b/res/img/element-icons/security.svg new file mode 100644 index 0000000000..3fe62b7af9 --- /dev/null +++ b/res/img/element-icons/security.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/settings.svg b/res/img/element-icons/settings.svg new file mode 100644 index 0000000000..05d640df27 --- /dev/null +++ b/res/img/element-icons/settings.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/settings/appearance.svg b/res/img/element-icons/settings/appearance.svg new file mode 100644 index 0000000000..6f91759354 --- /dev/null +++ b/res/img/element-icons/settings/appearance.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/settings/flair.svg b/res/img/element-icons/settings/flair.svg new file mode 100644 index 0000000000..e1ae44f386 --- /dev/null +++ b/res/img/element-icons/settings/flair.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/settings/help.svg b/res/img/element-icons/settings/help.svg new file mode 100644 index 0000000000..2ac4f675ec --- /dev/null +++ b/res/img/element-icons/settings/help.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/settings/lab-flags.svg b/res/img/element-icons/settings/lab-flags.svg new file mode 100644 index 0000000000..b96aa17d26 --- /dev/null +++ b/res/img/element-icons/settings/lab-flags.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/element-icons/settings/preference.svg b/res/img/element-icons/settings/preference.svg new file mode 100644 index 0000000000..d466662117 --- /dev/null +++ b/res/img/element-icons/settings/preference.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/view-community.svg b/res/img/element-icons/view-community.svg new file mode 100644 index 0000000000..ee33aa525e --- /dev/null +++ b/res/img/element-icons/view-community.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/res/img/element-logo.svg b/res/img/element-logo.svg new file mode 100644 index 0000000000..2cd11ed193 --- /dev/null +++ b/res/img/element-logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/img/ems-logo.svg b/res/img/ems-logo.svg new file mode 100644 index 0000000000..5ad29173cb --- /dev/null +++ b/res/img/ems-logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/res/img/feather-customised/alert-triangle.svg b/res/img/feather-customised/alert-triangle.svg new file mode 100644 index 0000000000..ceb664790f --- /dev/null +++ b/res/img/feather-customised/alert-triangle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/feather-customised/archive.svg b/res/img/feather-customised/archive.svg new file mode 100644 index 0000000000..428882c87b --- /dev/null +++ b/res/img/feather-customised/archive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/img/feather-customised/arrow-down.svg b/res/img/feather-customised/arrow-down.svg new file mode 100644 index 0000000000..4f84f627bd --- /dev/null +++ b/res/img/feather-customised/arrow-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/img/feather-customised/bell-crossed.svg b/res/img/feather-customised/bell-crossed.svg new file mode 100644 index 0000000000..3ca24662b9 --- /dev/null +++ b/res/img/feather-customised/bell-crossed.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/feather-customised/bell-mentions.custom.svg b/res/img/feather-customised/bell-mentions.custom.svg new file mode 100644 index 0000000000..fcc02f337f --- /dev/null +++ b/res/img/feather-customised/bell-mentions.custom.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/feather-customised/bell-notification.custom.svg b/res/img/feather-customised/bell-notification.custom.svg new file mode 100644 index 0000000000..7bfd551f97 --- /dev/null +++ b/res/img/feather-customised/bell-notification.custom.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/feather-customised/bell.svg b/res/img/feather-customised/bell.svg new file mode 100644 index 0000000000..b6bc5ec502 --- /dev/null +++ b/res/img/feather-customised/bell.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/feather-customised/brush.svg b/res/img/feather-customised/brush.svg new file mode 100644 index 0000000000..d7f2738629 --- /dev/null +++ b/res/img/feather-customised/brush.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/feather-customised/chevron-right.svg b/res/img/feather-customised/chevron-right.svg new file mode 100644 index 0000000000..258de414a1 --- /dev/null +++ b/res/img/feather-customised/chevron-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/img/feather-customised/chevron-up.svg b/res/img/feather-customised/chevron-up.svg new file mode 100644 index 0000000000..4eb5ecc33e --- /dev/null +++ b/res/img/feather-customised/chevron-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/img/feather-customised/compass.svg b/res/img/feather-customised/compass.svg new file mode 100644 index 0000000000..3296260803 --- /dev/null +++ b/res/img/feather-customised/compass.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/img/feather-customised/emoji3.custom.svg b/res/img/feather-customised/emoji3.custom.svg new file mode 100644 index 0000000000..d91ba1c132 --- /dev/null +++ b/res/img/feather-customised/emoji3.custom.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/img/feather-customised/explore.svg b/res/img/feather-customised/explore.svg new file mode 100644 index 0000000000..45be889bb7 --- /dev/null +++ b/res/img/feather-customised/explore.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/res/img/feather-customised/favourites.svg b/res/img/feather-customised/favourites.svg new file mode 100644 index 0000000000..80f08f6e55 --- /dev/null +++ b/res/img/feather-customised/favourites.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/feather-customised/group.svg b/res/img/feather-customised/group.svg new file mode 100644 index 0000000000..7051860e62 --- /dev/null +++ b/res/img/feather-customised/group.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/img/feather-customised/message-circle.svg b/res/img/feather-customised/message-circle.svg new file mode 100644 index 0000000000..acc6d2fb0f --- /dev/null +++ b/res/img/feather-customised/message-circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/feather-customised/monitor.svg b/res/img/feather-customised/monitor.svg new file mode 100644 index 0000000000..231811d5a6 --- /dev/null +++ b/res/img/feather-customised/monitor.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/feather-customised/more-horizontal.svg b/res/img/feather-customised/more-horizontal.svg new file mode 100644 index 0000000000..dc6a85564e --- /dev/null +++ b/res/img/feather-customised/more-horizontal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/img/feather-customised/secure-backup.svg b/res/img/feather-customised/secure-backup.svg new file mode 100644 index 0000000000..c06f93c1fe --- /dev/null +++ b/res/img/feather-customised/secure-backup.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/img/feather-customised/secure-phrase.svg b/res/img/feather-customised/secure-phrase.svg new file mode 100644 index 0000000000..eb13d3f048 --- /dev/null +++ b/res/img/feather-customised/secure-phrase.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/img/feather-customised/smartphone.svg b/res/img/feather-customised/smartphone.svg new file mode 100644 index 0000000000..fde78c82e2 --- /dev/null +++ b/res/img/feather-customised/smartphone.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/feather-customised/star.svg b/res/img/feather-customised/star.svg new file mode 100644 index 0000000000..bcdc31aa47 --- /dev/null +++ b/res/img/feather-customised/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/img/feather-customised/sticker.custom.svg b/res/img/feather-customised/sticker.custom.svg new file mode 100644 index 0000000000..691e3b3925 --- /dev/null +++ b/res/img/feather-customised/sticker.custom.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/feather-customised/sun.svg b/res/img/feather-customised/sun.svg new file mode 100644 index 0000000000..7f51b94d1c --- /dev/null +++ b/res/img/feather-customised/sun.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/img/feather-customised/trash.custom.svg b/res/img/feather-customised/trash.custom.svg new file mode 100644 index 0000000000..dc1985ddb2 --- /dev/null +++ b/res/img/feather-customised/trash.custom.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/img/file.png b/res/img/file.png deleted file mode 100644 index 5904ea8284..0000000000 Binary files a/res/img/file.png and /dev/null differ diff --git a/res/img/files.png b/res/img/files.png deleted file mode 100644 index 83932267f8..0000000000 Binary files a/res/img/files.png and /dev/null differ diff --git a/res/img/files.svg b/res/img/files.svg deleted file mode 100644 index 20aba851ea..0000000000 --- a/res/img/files.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - icons_browse_files - Created with bin/sketchtool. - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/img/flags/AD.png b/res/img/flags/AD.png deleted file mode 100644 index d5d59645fe..0000000000 Binary files a/res/img/flags/AD.png and /dev/null differ diff --git a/res/img/flags/AE.png b/res/img/flags/AE.png deleted file mode 100644 index 05c7418aa4..0000000000 Binary files a/res/img/flags/AE.png and /dev/null differ diff --git a/res/img/flags/AF.png b/res/img/flags/AF.png deleted file mode 100644 index bc7cef0916..0000000000 Binary files a/res/img/flags/AF.png and /dev/null differ diff --git a/res/img/flags/AG.png b/res/img/flags/AG.png deleted file mode 100644 index d48facad47..0000000000 Binary files a/res/img/flags/AG.png and /dev/null differ diff --git a/res/img/flags/AI.png b/res/img/flags/AI.png deleted file mode 100644 index 8fd27cd39e..0000000000 Binary files a/res/img/flags/AI.png and /dev/null differ diff --git a/res/img/flags/AL.png b/res/img/flags/AL.png deleted file mode 100644 index 883835ffb3..0000000000 Binary files a/res/img/flags/AL.png and /dev/null differ diff --git a/res/img/flags/AM.png b/res/img/flags/AM.png deleted file mode 100644 index b1bb36b987..0000000000 Binary files a/res/img/flags/AM.png and /dev/null differ diff --git a/res/img/flags/AO.png b/res/img/flags/AO.png deleted file mode 100644 index ae68b12c44..0000000000 Binary files a/res/img/flags/AO.png and /dev/null differ diff --git a/res/img/flags/AQ.png b/res/img/flags/AQ.png deleted file mode 100644 index 146e9c0a04..0000000000 Binary files a/res/img/flags/AQ.png and /dev/null differ diff --git a/res/img/flags/AR.png b/res/img/flags/AR.png deleted file mode 100644 index 8142adfc83..0000000000 Binary files a/res/img/flags/AR.png and /dev/null differ diff --git a/res/img/flags/AS.png b/res/img/flags/AS.png deleted file mode 100644 index cc5bf30daf..0000000000 Binary files a/res/img/flags/AS.png and /dev/null differ diff --git a/res/img/flags/AT.png b/res/img/flags/AT.png deleted file mode 100644 index e32414bd6a..0000000000 Binary files a/res/img/flags/AT.png and /dev/null differ diff --git a/res/img/flags/AU.png b/res/img/flags/AU.png deleted file mode 100644 index 8d1e143791..0000000000 Binary files a/res/img/flags/AU.png and /dev/null differ diff --git a/res/img/flags/AW.png b/res/img/flags/AW.png deleted file mode 100644 index 6ec178847e..0000000000 Binary files a/res/img/flags/AW.png and /dev/null differ diff --git a/res/img/flags/AX.png b/res/img/flags/AX.png deleted file mode 100644 index ba269c0453..0000000000 Binary files a/res/img/flags/AX.png and /dev/null differ diff --git a/res/img/flags/AZ.png b/res/img/flags/AZ.png deleted file mode 100644 index 2bf3c746e7..0000000000 Binary files a/res/img/flags/AZ.png and /dev/null differ diff --git a/res/img/flags/BA.png b/res/img/flags/BA.png deleted file mode 100644 index 3e3ec3fc76..0000000000 Binary files a/res/img/flags/BA.png and /dev/null differ diff --git a/res/img/flags/BB.png b/res/img/flags/BB.png deleted file mode 100644 index 694050ca46..0000000000 Binary files a/res/img/flags/BB.png and /dev/null differ diff --git a/res/img/flags/BD.png b/res/img/flags/BD.png deleted file mode 100644 index 6de2cde85b..0000000000 Binary files a/res/img/flags/BD.png and /dev/null differ diff --git a/res/img/flags/BE.png b/res/img/flags/BE.png deleted file mode 100644 index 742ba9231f..0000000000 Binary files a/res/img/flags/BE.png and /dev/null differ diff --git a/res/img/flags/BF.png b/res/img/flags/BF.png deleted file mode 100644 index 17f9f67d26..0000000000 Binary files a/res/img/flags/BF.png and /dev/null differ diff --git a/res/img/flags/BG.png b/res/img/flags/BG.png deleted file mode 100644 index b01d3ff57b..0000000000 Binary files a/res/img/flags/BG.png and /dev/null differ diff --git a/res/img/flags/BH.png b/res/img/flags/BH.png deleted file mode 100644 index d0f82e8285..0000000000 Binary files a/res/img/flags/BH.png and /dev/null differ diff --git a/res/img/flags/BI.png b/res/img/flags/BI.png deleted file mode 100644 index 21865ac720..0000000000 Binary files a/res/img/flags/BI.png and /dev/null differ diff --git a/res/img/flags/BJ.png b/res/img/flags/BJ.png deleted file mode 100644 index a7c6091434..0000000000 Binary files a/res/img/flags/BJ.png and /dev/null differ diff --git a/res/img/flags/BL.png b/res/img/flags/BL.png deleted file mode 100644 index 6d50a0f544..0000000000 Binary files a/res/img/flags/BL.png and /dev/null differ diff --git a/res/img/flags/BM.png b/res/img/flags/BM.png deleted file mode 100644 index 310a25ea23..0000000000 Binary files a/res/img/flags/BM.png and /dev/null differ diff --git a/res/img/flags/BN.png b/res/img/flags/BN.png deleted file mode 100644 index bc4da8d9a6..0000000000 Binary files a/res/img/flags/BN.png and /dev/null differ diff --git a/res/img/flags/BO.png b/res/img/flags/BO.png deleted file mode 100644 index 144b8d32db..0000000000 Binary files a/res/img/flags/BO.png and /dev/null differ diff --git a/res/img/flags/BQ.png b/res/img/flags/BQ.png deleted file mode 100644 index 0897943760..0000000000 Binary files a/res/img/flags/BQ.png and /dev/null differ diff --git a/res/img/flags/BR.png b/res/img/flags/BR.png deleted file mode 100644 index 0278492592..0000000000 Binary files a/res/img/flags/BR.png and /dev/null differ diff --git a/res/img/flags/BS.png b/res/img/flags/BS.png deleted file mode 100644 index 2b05a8fc7c..0000000000 Binary files a/res/img/flags/BS.png and /dev/null differ diff --git a/res/img/flags/BT.png b/res/img/flags/BT.png deleted file mode 100644 index 1f031df071..0000000000 Binary files a/res/img/flags/BT.png and /dev/null differ diff --git a/res/img/flags/BV.png b/res/img/flags/BV.png deleted file mode 100644 index aafb0f1776..0000000000 Binary files a/res/img/flags/BV.png and /dev/null differ diff --git a/res/img/flags/BW.png b/res/img/flags/BW.png deleted file mode 100644 index 3084016718..0000000000 Binary files a/res/img/flags/BW.png and /dev/null differ diff --git a/res/img/flags/BY.png b/res/img/flags/BY.png deleted file mode 100644 index ce9de9c9c7..0000000000 Binary files a/res/img/flags/BY.png and /dev/null differ diff --git a/res/img/flags/BZ.png b/res/img/flags/BZ.png deleted file mode 100644 index 33620c3f31..0000000000 Binary files a/res/img/flags/BZ.png and /dev/null differ diff --git a/res/img/flags/CA.png b/res/img/flags/CA.png deleted file mode 100644 index 4bbf8b1169..0000000000 Binary files a/res/img/flags/CA.png and /dev/null differ diff --git a/res/img/flags/CC.png b/res/img/flags/CC.png deleted file mode 100644 index fd40fc8a78..0000000000 Binary files a/res/img/flags/CC.png and /dev/null differ diff --git a/res/img/flags/CD.png b/res/img/flags/CD.png deleted file mode 100644 index 230aacd454..0000000000 Binary files a/res/img/flags/CD.png and /dev/null differ diff --git a/res/img/flags/CF.png b/res/img/flags/CF.png deleted file mode 100644 index c58ed4f7b2..0000000000 Binary files a/res/img/flags/CF.png and /dev/null differ diff --git a/res/img/flags/CG.png b/res/img/flags/CG.png deleted file mode 100644 index 6c2441e3e0..0000000000 Binary files a/res/img/flags/CG.png and /dev/null differ diff --git a/res/img/flags/CH.png b/res/img/flags/CH.png deleted file mode 100644 index 9fd87167df..0000000000 Binary files a/res/img/flags/CH.png and /dev/null differ diff --git a/res/img/flags/CI.png b/res/img/flags/CI.png deleted file mode 100644 index 9741b9b11f..0000000000 Binary files a/res/img/flags/CI.png and /dev/null differ diff --git a/res/img/flags/CK.png b/res/img/flags/CK.png deleted file mode 100644 index 6cca35967c..0000000000 Binary files a/res/img/flags/CK.png and /dev/null differ diff --git a/res/img/flags/CL.png b/res/img/flags/CL.png deleted file mode 100644 index 13b993d15d..0000000000 Binary files a/res/img/flags/CL.png and /dev/null differ diff --git a/res/img/flags/CM.png b/res/img/flags/CM.png deleted file mode 100644 index bca5730fb5..0000000000 Binary files a/res/img/flags/CM.png and /dev/null differ diff --git a/res/img/flags/CN.png b/res/img/flags/CN.png deleted file mode 100644 index e086855c73..0000000000 Binary files a/res/img/flags/CN.png and /dev/null differ diff --git a/res/img/flags/CO.png b/res/img/flags/CO.png deleted file mode 100644 index 65c0aba447..0000000000 Binary files a/res/img/flags/CO.png and /dev/null differ diff --git a/res/img/flags/CR.png b/res/img/flags/CR.png deleted file mode 100644 index b351c67a53..0000000000 Binary files a/res/img/flags/CR.png and /dev/null differ diff --git a/res/img/flags/CU.png b/res/img/flags/CU.png deleted file mode 100644 index e7a25c60b3..0000000000 Binary files a/res/img/flags/CU.png and /dev/null differ diff --git a/res/img/flags/CV.png b/res/img/flags/CV.png deleted file mode 100644 index f249bbaa46..0000000000 Binary files a/res/img/flags/CV.png and /dev/null differ diff --git a/res/img/flags/CW.png b/res/img/flags/CW.png deleted file mode 100644 index e02cacd3dd..0000000000 Binary files a/res/img/flags/CW.png and /dev/null differ diff --git a/res/img/flags/CX.png b/res/img/flags/CX.png deleted file mode 100644 index 3ea21422f0..0000000000 Binary files a/res/img/flags/CX.png and /dev/null differ diff --git a/res/img/flags/CY.png b/res/img/flags/CY.png deleted file mode 100644 index 3182f48bd2..0000000000 Binary files a/res/img/flags/CY.png and /dev/null differ diff --git a/res/img/flags/CZ.png b/res/img/flags/CZ.png deleted file mode 100644 index 5462334638..0000000000 Binary files a/res/img/flags/CZ.png and /dev/null differ diff --git a/res/img/flags/DE.png b/res/img/flags/DE.png deleted file mode 100644 index 93e269166b..0000000000 Binary files a/res/img/flags/DE.png and /dev/null differ diff --git a/res/img/flags/DJ.png b/res/img/flags/DJ.png deleted file mode 100644 index 243bb7390d..0000000000 Binary files a/res/img/flags/DJ.png and /dev/null differ diff --git a/res/img/flags/DK.png b/res/img/flags/DK.png deleted file mode 100644 index fc74cc396c..0000000000 Binary files a/res/img/flags/DK.png and /dev/null differ diff --git a/res/img/flags/DM.png b/res/img/flags/DM.png deleted file mode 100644 index c3a0e9d102..0000000000 Binary files a/res/img/flags/DM.png and /dev/null differ diff --git a/res/img/flags/DO.png b/res/img/flags/DO.png deleted file mode 100644 index 5c4a004fef..0000000000 Binary files a/res/img/flags/DO.png and /dev/null differ diff --git a/res/img/flags/DZ.png b/res/img/flags/DZ.png deleted file mode 100644 index 1589d0cc40..0000000000 Binary files a/res/img/flags/DZ.png and /dev/null differ diff --git a/res/img/flags/EC.png b/res/img/flags/EC.png deleted file mode 100644 index 4c53dead1c..0000000000 Binary files a/res/img/flags/EC.png and /dev/null differ diff --git a/res/img/flags/EE.png b/res/img/flags/EE.png deleted file mode 100644 index 3668de7919..0000000000 Binary files a/res/img/flags/EE.png and /dev/null differ diff --git a/res/img/flags/EG.png b/res/img/flags/EG.png deleted file mode 100644 index 66ec709df7..0000000000 Binary files a/res/img/flags/EG.png and /dev/null differ diff --git a/res/img/flags/EH.png b/res/img/flags/EH.png deleted file mode 100644 index 148be93c08..0000000000 Binary files a/res/img/flags/EH.png and /dev/null differ diff --git a/res/img/flags/ER.png b/res/img/flags/ER.png deleted file mode 100644 index 7cb8441514..0000000000 Binary files a/res/img/flags/ER.png and /dev/null differ diff --git a/res/img/flags/ES.png b/res/img/flags/ES.png deleted file mode 100644 index aae73b6fcb..0000000000 Binary files a/res/img/flags/ES.png and /dev/null differ diff --git a/res/img/flags/ET.png b/res/img/flags/ET.png deleted file mode 100644 index 7b420f02f4..0000000000 Binary files a/res/img/flags/ET.png and /dev/null differ diff --git a/res/img/flags/FI.png b/res/img/flags/FI.png deleted file mode 100644 index 42f64bf360..0000000000 Binary files a/res/img/flags/FI.png and /dev/null differ diff --git a/res/img/flags/FJ.png b/res/img/flags/FJ.png deleted file mode 100644 index cecc683c9c..0000000000 Binary files a/res/img/flags/FJ.png and /dev/null differ diff --git a/res/img/flags/FK.png b/res/img/flags/FK.png deleted file mode 100644 index 6074fea09c..0000000000 Binary files a/res/img/flags/FK.png and /dev/null differ diff --git a/res/img/flags/FM.png b/res/img/flags/FM.png deleted file mode 100644 index 45fdb66426..0000000000 Binary files a/res/img/flags/FM.png and /dev/null differ diff --git a/res/img/flags/FO.png b/res/img/flags/FO.png deleted file mode 100644 index d8fd75c638..0000000000 Binary files a/res/img/flags/FO.png and /dev/null differ diff --git a/res/img/flags/FR.png b/res/img/flags/FR.png deleted file mode 100644 index 6d50a0f544..0000000000 Binary files a/res/img/flags/FR.png and /dev/null differ diff --git a/res/img/flags/GA.png b/res/img/flags/GA.png deleted file mode 100644 index 3808a61f1d..0000000000 Binary files a/res/img/flags/GA.png and /dev/null differ diff --git a/res/img/flags/GB.png b/res/img/flags/GB.png deleted file mode 100644 index 589be70063..0000000000 Binary files a/res/img/flags/GB.png and /dev/null differ diff --git a/res/img/flags/GD.png b/res/img/flags/GD.png deleted file mode 100644 index babe1e4cc6..0000000000 Binary files a/res/img/flags/GD.png and /dev/null differ diff --git a/res/img/flags/GE.png b/res/img/flags/GE.png deleted file mode 100644 index d34cddeca9..0000000000 Binary files a/res/img/flags/GE.png and /dev/null differ diff --git a/res/img/flags/GF.png b/res/img/flags/GF.png deleted file mode 100644 index 98828a5906..0000000000 Binary files a/res/img/flags/GF.png and /dev/null differ diff --git a/res/img/flags/GG.png b/res/img/flags/GG.png deleted file mode 100644 index aec8969b28..0000000000 Binary files a/res/img/flags/GG.png and /dev/null differ diff --git a/res/img/flags/GH.png b/res/img/flags/GH.png deleted file mode 100644 index 70b1a623de..0000000000 Binary files a/res/img/flags/GH.png and /dev/null differ diff --git a/res/img/flags/GI.png b/res/img/flags/GI.png deleted file mode 100644 index 9aa58327e3..0000000000 Binary files a/res/img/flags/GI.png and /dev/null differ diff --git a/res/img/flags/GL.png b/res/img/flags/GL.png deleted file mode 100644 index cf1645c2b5..0000000000 Binary files a/res/img/flags/GL.png and /dev/null differ diff --git a/res/img/flags/GM.png b/res/img/flags/GM.png deleted file mode 100644 index ec374fb3c3..0000000000 Binary files a/res/img/flags/GM.png and /dev/null differ diff --git a/res/img/flags/GN.png b/res/img/flags/GN.png deleted file mode 100644 index 46874b4d98..0000000000 Binary files a/res/img/flags/GN.png and /dev/null differ diff --git a/res/img/flags/GP.png b/res/img/flags/GP.png deleted file mode 100644 index 81b7abdf0e..0000000000 Binary files a/res/img/flags/GP.png and /dev/null differ diff --git a/res/img/flags/GQ.png b/res/img/flags/GQ.png deleted file mode 100644 index 7fd1015e8b..0000000000 Binary files a/res/img/flags/GQ.png and /dev/null differ diff --git a/res/img/flags/GR.png b/res/img/flags/GR.png deleted file mode 100644 index 101de51eab..0000000000 Binary files a/res/img/flags/GR.png and /dev/null differ diff --git a/res/img/flags/GS.png b/res/img/flags/GS.png deleted file mode 100644 index 772c2cbe6d..0000000000 Binary files a/res/img/flags/GS.png and /dev/null differ diff --git a/res/img/flags/GT.png b/res/img/flags/GT.png deleted file mode 100644 index d5bd8c1e46..0000000000 Binary files a/res/img/flags/GT.png and /dev/null differ diff --git a/res/img/flags/GU.png b/res/img/flags/GU.png deleted file mode 100644 index 8923085d5a..0000000000 Binary files a/res/img/flags/GU.png and /dev/null differ diff --git a/res/img/flags/GW.png b/res/img/flags/GW.png deleted file mode 100644 index 20c268ce06..0000000000 Binary files a/res/img/flags/GW.png and /dev/null differ diff --git a/res/img/flags/GY.png b/res/img/flags/GY.png deleted file mode 100644 index 86f56635ef..0000000000 Binary files a/res/img/flags/GY.png and /dev/null differ diff --git a/res/img/flags/HK.png b/res/img/flags/HK.png deleted file mode 100644 index 907dc59624..0000000000 Binary files a/res/img/flags/HK.png and /dev/null differ diff --git a/res/img/flags/HM.png b/res/img/flags/HM.png deleted file mode 100644 index 8d1e143791..0000000000 Binary files a/res/img/flags/HM.png and /dev/null differ diff --git a/res/img/flags/HN.png b/res/img/flags/HN.png deleted file mode 100644 index 4cf8c3112c..0000000000 Binary files a/res/img/flags/HN.png and /dev/null differ diff --git a/res/img/flags/HR.png b/res/img/flags/HR.png deleted file mode 100644 index 413ceb1586..0000000000 Binary files a/res/img/flags/HR.png and /dev/null differ diff --git a/res/img/flags/HT.png b/res/img/flags/HT.png deleted file mode 100644 index 097abeb434..0000000000 Binary files a/res/img/flags/HT.png and /dev/null differ diff --git a/res/img/flags/HU.png b/res/img/flags/HU.png deleted file mode 100644 index 23499bf63c..0000000000 Binary files a/res/img/flags/HU.png and /dev/null differ diff --git a/res/img/flags/ID.png b/res/img/flags/ID.png deleted file mode 100644 index 80200657c6..0000000000 Binary files a/res/img/flags/ID.png and /dev/null differ diff --git a/res/img/flags/IE.png b/res/img/flags/IE.png deleted file mode 100644 index 63f2220118..0000000000 Binary files a/res/img/flags/IE.png and /dev/null differ diff --git a/res/img/flags/IL.png b/res/img/flags/IL.png deleted file mode 100644 index 0268826321..0000000000 Binary files a/res/img/flags/IL.png and /dev/null differ diff --git a/res/img/flags/IM.png b/res/img/flags/IM.png deleted file mode 100644 index c777acc490..0000000000 Binary files a/res/img/flags/IM.png and /dev/null differ diff --git a/res/img/flags/IN.png b/res/img/flags/IN.png deleted file mode 100644 index 85fa9bfe72..0000000000 Binary files a/res/img/flags/IN.png and /dev/null differ diff --git a/res/img/flags/IO.png b/res/img/flags/IO.png deleted file mode 100644 index 1675d8e7db..0000000000 Binary files a/res/img/flags/IO.png and /dev/null differ diff --git a/res/img/flags/IQ.png b/res/img/flags/IQ.png deleted file mode 100644 index f2c21f7260..0000000000 Binary files a/res/img/flags/IQ.png and /dev/null differ diff --git a/res/img/flags/IR.png b/res/img/flags/IR.png deleted file mode 100644 index 0b8e67506c..0000000000 Binary files a/res/img/flags/IR.png and /dev/null differ diff --git a/res/img/flags/IS.png b/res/img/flags/IS.png deleted file mode 100644 index 5ee3e63c5c..0000000000 Binary files a/res/img/flags/IS.png and /dev/null differ diff --git a/res/img/flags/IT.png b/res/img/flags/IT.png deleted file mode 100644 index 53b967be99..0000000000 Binary files a/res/img/flags/IT.png and /dev/null differ diff --git a/res/img/flags/JE.png b/res/img/flags/JE.png deleted file mode 100644 index a1437aba78..0000000000 Binary files a/res/img/flags/JE.png and /dev/null differ diff --git a/res/img/flags/JM.png b/res/img/flags/JM.png deleted file mode 100644 index 0d462fa3ae..0000000000 Binary files a/res/img/flags/JM.png and /dev/null differ diff --git a/res/img/flags/JO.png b/res/img/flags/JO.png deleted file mode 100644 index 8934db7eca..0000000000 Binary files a/res/img/flags/JO.png and /dev/null differ diff --git a/res/img/flags/JP.png b/res/img/flags/JP.png deleted file mode 100644 index 6f92d52365..0000000000 Binary files a/res/img/flags/JP.png and /dev/null differ diff --git a/res/img/flags/KE.png b/res/img/flags/KE.png deleted file mode 100644 index 866b3f15dc..0000000000 Binary files a/res/img/flags/KE.png and /dev/null differ diff --git a/res/img/flags/KG.png b/res/img/flags/KG.png deleted file mode 100644 index 56b433c756..0000000000 Binary files a/res/img/flags/KG.png and /dev/null differ diff --git a/res/img/flags/KH.png b/res/img/flags/KH.png deleted file mode 100644 index e1ddd5f84c..0000000000 Binary files a/res/img/flags/KH.png and /dev/null differ diff --git a/res/img/flags/KI.png b/res/img/flags/KI.png deleted file mode 100644 index 8b7c54bc0f..0000000000 Binary files a/res/img/flags/KI.png and /dev/null differ diff --git a/res/img/flags/KM.png b/res/img/flags/KM.png deleted file mode 100644 index 227a3b3396..0000000000 Binary files a/res/img/flags/KM.png and /dev/null differ diff --git a/res/img/flags/KN.png b/res/img/flags/KN.png deleted file mode 100644 index bc6189bed1..0000000000 Binary files a/res/img/flags/KN.png and /dev/null differ diff --git a/res/img/flags/KP.png b/res/img/flags/KP.png deleted file mode 100644 index c92248b910..0000000000 Binary files a/res/img/flags/KP.png and /dev/null differ diff --git a/res/img/flags/KR.png b/res/img/flags/KR.png deleted file mode 100644 index ab1cb94943..0000000000 Binary files a/res/img/flags/KR.png and /dev/null differ diff --git a/res/img/flags/KW.png b/res/img/flags/KW.png deleted file mode 100644 index 0b41c7a532..0000000000 Binary files a/res/img/flags/KW.png and /dev/null differ diff --git a/res/img/flags/KY.png b/res/img/flags/KY.png deleted file mode 100644 index 7af5290d31..0000000000 Binary files a/res/img/flags/KY.png and /dev/null differ diff --git a/res/img/flags/KZ.png b/res/img/flags/KZ.png deleted file mode 100644 index e10a1255a0..0000000000 Binary files a/res/img/flags/KZ.png and /dev/null differ diff --git a/res/img/flags/LA.png b/res/img/flags/LA.png deleted file mode 100644 index 6ad67d4255..0000000000 Binary files a/res/img/flags/LA.png and /dev/null differ diff --git a/res/img/flags/LB.png b/res/img/flags/LB.png deleted file mode 100644 index 865df57a42..0000000000 Binary files a/res/img/flags/LB.png and /dev/null differ diff --git a/res/img/flags/LC.png b/res/img/flags/LC.png deleted file mode 100644 index e83a2d08bc..0000000000 Binary files a/res/img/flags/LC.png and /dev/null differ diff --git a/res/img/flags/LI.png b/res/img/flags/LI.png deleted file mode 100644 index 57034d367c..0000000000 Binary files a/res/img/flags/LI.png and /dev/null differ diff --git a/res/img/flags/LK.png b/res/img/flags/LK.png deleted file mode 100644 index 6e7ad58254..0000000000 Binary files a/res/img/flags/LK.png and /dev/null differ diff --git a/res/img/flags/LR.png b/res/img/flags/LR.png deleted file mode 100644 index 46c3b84a92..0000000000 Binary files a/res/img/flags/LR.png and /dev/null differ diff --git a/res/img/flags/LS.png b/res/img/flags/LS.png deleted file mode 100644 index 79b505d490..0000000000 Binary files a/res/img/flags/LS.png and /dev/null differ diff --git a/res/img/flags/LT.png b/res/img/flags/LT.png deleted file mode 100644 index 7740cdc0a0..0000000000 Binary files a/res/img/flags/LT.png and /dev/null differ diff --git a/res/img/flags/LU.png b/res/img/flags/LU.png deleted file mode 100644 index 8f383e674e..0000000000 Binary files a/res/img/flags/LU.png and /dev/null differ diff --git a/res/img/flags/LV.png b/res/img/flags/LV.png deleted file mode 100644 index a0f36d89c4..0000000000 Binary files a/res/img/flags/LV.png and /dev/null differ diff --git a/res/img/flags/LY.png b/res/img/flags/LY.png deleted file mode 100644 index 2884c4c0a9..0000000000 Binary files a/res/img/flags/LY.png and /dev/null differ diff --git a/res/img/flags/MA.png b/res/img/flags/MA.png deleted file mode 100644 index 1f76cfc9bd..0000000000 Binary files a/res/img/flags/MA.png and /dev/null differ diff --git a/res/img/flags/MC.png b/res/img/flags/MC.png deleted file mode 100644 index 06fc2ad166..0000000000 Binary files a/res/img/flags/MC.png and /dev/null differ diff --git a/res/img/flags/MD.png b/res/img/flags/MD.png deleted file mode 100644 index 8e54c2b815..0000000000 Binary files a/res/img/flags/MD.png and /dev/null differ diff --git a/res/img/flags/ME.png b/res/img/flags/ME.png deleted file mode 100644 index 97424d4ec2..0000000000 Binary files a/res/img/flags/ME.png and /dev/null differ diff --git a/res/img/flags/MF.png b/res/img/flags/MF.png deleted file mode 100644 index 6d50a0f544..0000000000 Binary files a/res/img/flags/MF.png and /dev/null differ diff --git a/res/img/flags/MG.png b/res/img/flags/MG.png deleted file mode 100644 index 28bfccc9e8..0000000000 Binary files a/res/img/flags/MG.png and /dev/null differ diff --git a/res/img/flags/MH.png b/res/img/flags/MH.png deleted file mode 100644 index e482a65924..0000000000 Binary files a/res/img/flags/MH.png and /dev/null differ diff --git a/res/img/flags/MK.png b/res/img/flags/MK.png deleted file mode 100644 index 84e2e65e76..0000000000 Binary files a/res/img/flags/MK.png and /dev/null differ diff --git a/res/img/flags/ML.png b/res/img/flags/ML.png deleted file mode 100644 index 38fec34796..0000000000 Binary files a/res/img/flags/ML.png and /dev/null differ diff --git a/res/img/flags/MM.png b/res/img/flags/MM.png deleted file mode 100644 index 70a03c6b14..0000000000 Binary files a/res/img/flags/MM.png and /dev/null differ diff --git a/res/img/flags/MN.png b/res/img/flags/MN.png deleted file mode 100644 index 1e1bbe6089..0000000000 Binary files a/res/img/flags/MN.png and /dev/null differ diff --git a/res/img/flags/MO.png b/res/img/flags/MO.png deleted file mode 100644 index 3833d683e7..0000000000 Binary files a/res/img/flags/MO.png and /dev/null differ diff --git a/res/img/flags/MP.png b/res/img/flags/MP.png deleted file mode 100644 index 63119096b0..0000000000 Binary files a/res/img/flags/MP.png and /dev/null differ diff --git a/res/img/flags/MQ.png b/res/img/flags/MQ.png deleted file mode 100644 index 9cab441aec..0000000000 Binary files a/res/img/flags/MQ.png and /dev/null differ diff --git a/res/img/flags/MR.png b/res/img/flags/MR.png deleted file mode 100644 index c144de17f7..0000000000 Binary files a/res/img/flags/MR.png and /dev/null differ diff --git a/res/img/flags/MS.png b/res/img/flags/MS.png deleted file mode 100644 index 1221707042..0000000000 Binary files a/res/img/flags/MS.png and /dev/null differ diff --git a/res/img/flags/MT.png b/res/img/flags/MT.png deleted file mode 100644 index 7963aa618a..0000000000 Binary files a/res/img/flags/MT.png and /dev/null differ diff --git a/res/img/flags/MU.png b/res/img/flags/MU.png deleted file mode 100644 index d5d4d4008d..0000000000 Binary files a/res/img/flags/MU.png and /dev/null differ diff --git a/res/img/flags/MV.png b/res/img/flags/MV.png deleted file mode 100644 index 0f2ecb4389..0000000000 Binary files a/res/img/flags/MV.png and /dev/null differ diff --git a/res/img/flags/MW.png b/res/img/flags/MW.png deleted file mode 100644 index d0a5d24f55..0000000000 Binary files a/res/img/flags/MW.png and /dev/null differ diff --git a/res/img/flags/MX.png b/res/img/flags/MX.png deleted file mode 100644 index 096cb1111f..0000000000 Binary files a/res/img/flags/MX.png and /dev/null differ diff --git a/res/img/flags/MY.png b/res/img/flags/MY.png deleted file mode 100644 index 17f18ac519..0000000000 Binary files a/res/img/flags/MY.png and /dev/null differ diff --git a/res/img/flags/MZ.png b/res/img/flags/MZ.png deleted file mode 100644 index 66be6563c6..0000000000 Binary files a/res/img/flags/MZ.png and /dev/null differ diff --git a/res/img/flags/NA.png b/res/img/flags/NA.png deleted file mode 100644 index 7ecfd317c7..0000000000 Binary files a/res/img/flags/NA.png and /dev/null differ diff --git a/res/img/flags/NC.png b/res/img/flags/NC.png deleted file mode 100644 index 11126ade77..0000000000 Binary files a/res/img/flags/NC.png and /dev/null differ diff --git a/res/img/flags/NE.png b/res/img/flags/NE.png deleted file mode 100644 index d584fa8429..0000000000 Binary files a/res/img/flags/NE.png and /dev/null differ diff --git a/res/img/flags/NF.png b/res/img/flags/NF.png deleted file mode 100644 index c054042591..0000000000 Binary files a/res/img/flags/NF.png and /dev/null differ diff --git a/res/img/flags/NG.png b/res/img/flags/NG.png deleted file mode 100644 index 73aee15b3f..0000000000 Binary files a/res/img/flags/NG.png and /dev/null differ diff --git a/res/img/flags/NI.png b/res/img/flags/NI.png deleted file mode 100644 index fd044933e4..0000000000 Binary files a/res/img/flags/NI.png and /dev/null differ diff --git a/res/img/flags/NL.png b/res/img/flags/NL.png deleted file mode 100644 index 0897943760..0000000000 Binary files a/res/img/flags/NL.png and /dev/null differ diff --git a/res/img/flags/NO.png b/res/img/flags/NO.png deleted file mode 100644 index aafb0f1776..0000000000 Binary files a/res/img/flags/NO.png and /dev/null differ diff --git a/res/img/flags/NP.png b/res/img/flags/NP.png deleted file mode 100644 index 744458e17e..0000000000 Binary files a/res/img/flags/NP.png and /dev/null differ diff --git a/res/img/flags/NR.png b/res/img/flags/NR.png deleted file mode 100644 index 58c2afb228..0000000000 Binary files a/res/img/flags/NR.png and /dev/null differ diff --git a/res/img/flags/NU.png b/res/img/flags/NU.png deleted file mode 100644 index 007c99eca5..0000000000 Binary files a/res/img/flags/NU.png and /dev/null differ diff --git a/res/img/flags/NZ.png b/res/img/flags/NZ.png deleted file mode 100644 index 839368dd7b..0000000000 Binary files a/res/img/flags/NZ.png and /dev/null differ diff --git a/res/img/flags/OM.png b/res/img/flags/OM.png deleted file mode 100644 index 63a893367f..0000000000 Binary files a/res/img/flags/OM.png and /dev/null differ diff --git a/res/img/flags/PA.png b/res/img/flags/PA.png deleted file mode 100644 index 3515d95d37..0000000000 Binary files a/res/img/flags/PA.png and /dev/null differ diff --git a/res/img/flags/PE.png b/res/img/flags/PE.png deleted file mode 100644 index 58f70b8d18..0000000000 Binary files a/res/img/flags/PE.png and /dev/null differ diff --git a/res/img/flags/PF.png b/res/img/flags/PF.png deleted file mode 100644 index 2f33f2574f..0000000000 Binary files a/res/img/flags/PF.png and /dev/null differ diff --git a/res/img/flags/PG.png b/res/img/flags/PG.png deleted file mode 100644 index c796f587c6..0000000000 Binary files a/res/img/flags/PG.png and /dev/null differ diff --git a/res/img/flags/PH.png b/res/img/flags/PH.png deleted file mode 100644 index 0d98de0386..0000000000 Binary files a/res/img/flags/PH.png and /dev/null differ diff --git a/res/img/flags/PK.png b/res/img/flags/PK.png deleted file mode 100644 index 87f4e2f492..0000000000 Binary files a/res/img/flags/PK.png and /dev/null differ diff --git a/res/img/flags/PL.png b/res/img/flags/PL.png deleted file mode 100644 index 273869dfc6..0000000000 Binary files a/res/img/flags/PL.png and /dev/null differ diff --git a/res/img/flags/PM.png b/res/img/flags/PM.png deleted file mode 100644 index b74c396d92..0000000000 Binary files a/res/img/flags/PM.png and /dev/null differ diff --git a/res/img/flags/PN.png b/res/img/flags/PN.png deleted file mode 100644 index e34c62d598..0000000000 Binary files a/res/img/flags/PN.png and /dev/null differ diff --git a/res/img/flags/PR.png b/res/img/flags/PR.png deleted file mode 100644 index 8efdb91252..0000000000 Binary files a/res/img/flags/PR.png and /dev/null differ diff --git a/res/img/flags/PS.png b/res/img/flags/PS.png deleted file mode 100644 index 7a0cceec00..0000000000 Binary files a/res/img/flags/PS.png and /dev/null differ diff --git a/res/img/flags/PT.png b/res/img/flags/PT.png deleted file mode 100644 index 49e290827c..0000000000 Binary files a/res/img/flags/PT.png and /dev/null differ diff --git a/res/img/flags/PW.png b/res/img/flags/PW.png deleted file mode 100644 index 6cb2e1e70d..0000000000 Binary files a/res/img/flags/PW.png and /dev/null differ diff --git a/res/img/flags/PY.png b/res/img/flags/PY.png deleted file mode 100644 index a61c42c423..0000000000 Binary files a/res/img/flags/PY.png and /dev/null differ diff --git a/res/img/flags/QA.png b/res/img/flags/QA.png deleted file mode 100644 index bb091cc88c..0000000000 Binary files a/res/img/flags/QA.png and /dev/null differ diff --git a/res/img/flags/RE.png b/res/img/flags/RE.png deleted file mode 100644 index 6d50a0f544..0000000000 Binary files a/res/img/flags/RE.png and /dev/null differ diff --git a/res/img/flags/RO.png b/res/img/flags/RO.png deleted file mode 100644 index 4495d29eb0..0000000000 Binary files a/res/img/flags/RO.png and /dev/null differ diff --git a/res/img/flags/RS.png b/res/img/flags/RS.png deleted file mode 100644 index ebb0f28a7b..0000000000 Binary files a/res/img/flags/RS.png and /dev/null differ diff --git a/res/img/flags/RU.png b/res/img/flags/RU.png deleted file mode 100644 index 64532ffa58..0000000000 Binary files a/res/img/flags/RU.png and /dev/null differ diff --git a/res/img/flags/RW.png b/res/img/flags/RW.png deleted file mode 100644 index 64b3cfff04..0000000000 Binary files a/res/img/flags/RW.png and /dev/null differ diff --git a/res/img/flags/SA.png b/res/img/flags/SA.png deleted file mode 100644 index 250de6f6f5..0000000000 Binary files a/res/img/flags/SA.png and /dev/null differ diff --git a/res/img/flags/SB.png b/res/img/flags/SB.png deleted file mode 100644 index 5833c130eb..0000000000 Binary files a/res/img/flags/SB.png and /dev/null differ diff --git a/res/img/flags/SC.png b/res/img/flags/SC.png deleted file mode 100644 index ce5248f434..0000000000 Binary files a/res/img/flags/SC.png and /dev/null differ diff --git a/res/img/flags/SD.png b/res/img/flags/SD.png deleted file mode 100644 index d8711a83d6..0000000000 Binary files a/res/img/flags/SD.png and /dev/null differ diff --git a/res/img/flags/SE.png b/res/img/flags/SE.png deleted file mode 100644 index 81880931f3..0000000000 Binary files a/res/img/flags/SE.png and /dev/null differ diff --git a/res/img/flags/SG.png b/res/img/flags/SG.png deleted file mode 100644 index 6f00e57923..0000000000 Binary files a/res/img/flags/SG.png and /dev/null differ diff --git a/res/img/flags/SH.png b/res/img/flags/SH.png deleted file mode 100644 index 055dde68bc..0000000000 Binary files a/res/img/flags/SH.png and /dev/null differ diff --git a/res/img/flags/SI.png b/res/img/flags/SI.png deleted file mode 100644 index 9635983406..0000000000 Binary files a/res/img/flags/SI.png and /dev/null differ diff --git a/res/img/flags/SJ.png b/res/img/flags/SJ.png deleted file mode 100644 index aafb0f1776..0000000000 Binary files a/res/img/flags/SJ.png and /dev/null differ diff --git a/res/img/flags/SK.png b/res/img/flags/SK.png deleted file mode 100644 index 84c7021f0a..0000000000 Binary files a/res/img/flags/SK.png and /dev/null differ diff --git a/res/img/flags/SL.png b/res/img/flags/SL.png deleted file mode 100644 index c5ed199141..0000000000 Binary files a/res/img/flags/SL.png and /dev/null differ diff --git a/res/img/flags/SM.png b/res/img/flags/SM.png deleted file mode 100644 index 1af1ca284f..0000000000 Binary files a/res/img/flags/SM.png and /dev/null differ diff --git a/res/img/flags/SN.png b/res/img/flags/SN.png deleted file mode 100644 index d0b1843561..0000000000 Binary files a/res/img/flags/SN.png and /dev/null differ diff --git a/res/img/flags/SO.png b/res/img/flags/SO.png deleted file mode 100644 index 64e2970b9d..0000000000 Binary files a/res/img/flags/SO.png and /dev/null differ diff --git a/res/img/flags/SR.png b/res/img/flags/SR.png deleted file mode 100644 index b072dda835..0000000000 Binary files a/res/img/flags/SR.png and /dev/null differ diff --git a/res/img/flags/SS.png b/res/img/flags/SS.png deleted file mode 100644 index 83933d4521..0000000000 Binary files a/res/img/flags/SS.png and /dev/null differ diff --git a/res/img/flags/ST.png b/res/img/flags/ST.png deleted file mode 100644 index c102721a86..0000000000 Binary files a/res/img/flags/ST.png and /dev/null differ diff --git a/res/img/flags/SV.png b/res/img/flags/SV.png deleted file mode 100644 index 80de92e556..0000000000 Binary files a/res/img/flags/SV.png and /dev/null differ diff --git a/res/img/flags/SX.png b/res/img/flags/SX.png deleted file mode 100644 index dd52215c5d..0000000000 Binary files a/res/img/flags/SX.png and /dev/null differ diff --git a/res/img/flags/SY.png b/res/img/flags/SY.png deleted file mode 100644 index 78f45b7c0b..0000000000 Binary files a/res/img/flags/SY.png and /dev/null differ diff --git a/res/img/flags/SZ.png b/res/img/flags/SZ.png deleted file mode 100644 index 2182f4ff93..0000000000 Binary files a/res/img/flags/SZ.png and /dev/null differ diff --git a/res/img/flags/TC.png b/res/img/flags/TC.png deleted file mode 100644 index 3e3e19d4b3..0000000000 Binary files a/res/img/flags/TC.png and /dev/null differ diff --git a/res/img/flags/TD.png b/res/img/flags/TD.png deleted file mode 100644 index 753bec22b0..0000000000 Binary files a/res/img/flags/TD.png and /dev/null differ diff --git a/res/img/flags/TF.png b/res/img/flags/TF.png deleted file mode 100644 index 6d50a0f544..0000000000 Binary files a/res/img/flags/TF.png and /dev/null differ diff --git a/res/img/flags/TG.png b/res/img/flags/TG.png deleted file mode 100644 index 8501ada655..0000000000 Binary files a/res/img/flags/TG.png and /dev/null differ diff --git a/res/img/flags/TH.png b/res/img/flags/TH.png deleted file mode 100644 index 0c884c329e..0000000000 Binary files a/res/img/flags/TH.png and /dev/null differ diff --git a/res/img/flags/TJ.png b/res/img/flags/TJ.png deleted file mode 100644 index 3c9026fa0f..0000000000 Binary files a/res/img/flags/TJ.png and /dev/null differ diff --git a/res/img/flags/TK.png b/res/img/flags/TK.png deleted file mode 100644 index fd605749ea..0000000000 Binary files a/res/img/flags/TK.png and /dev/null differ diff --git a/res/img/flags/TL.png b/res/img/flags/TL.png deleted file mode 100644 index b4c834b1d6..0000000000 Binary files a/res/img/flags/TL.png and /dev/null differ diff --git a/res/img/flags/TM.png b/res/img/flags/TM.png deleted file mode 100644 index d18cb939a9..0000000000 Binary files a/res/img/flags/TM.png and /dev/null differ diff --git a/res/img/flags/TN.png b/res/img/flags/TN.png deleted file mode 100644 index 21c4b98be7..0000000000 Binary files a/res/img/flags/TN.png and /dev/null differ diff --git a/res/img/flags/TO.png b/res/img/flags/TO.png deleted file mode 100644 index c828206e35..0000000000 Binary files a/res/img/flags/TO.png and /dev/null differ diff --git a/res/img/flags/TR.png b/res/img/flags/TR.png deleted file mode 100644 index f2a5bd22c8..0000000000 Binary files a/res/img/flags/TR.png and /dev/null differ diff --git a/res/img/flags/TT.png b/res/img/flags/TT.png deleted file mode 100644 index 66d698334b..0000000000 Binary files a/res/img/flags/TT.png and /dev/null differ diff --git a/res/img/flags/TV.png b/res/img/flags/TV.png deleted file mode 100644 index 7a127f51ae..0000000000 Binary files a/res/img/flags/TV.png and /dev/null differ diff --git a/res/img/flags/TW.png b/res/img/flags/TW.png deleted file mode 100644 index 2353ba1b0a..0000000000 Binary files a/res/img/flags/TW.png and /dev/null differ diff --git a/res/img/flags/TZ.png b/res/img/flags/TZ.png deleted file mode 100644 index 7949f65d8a..0000000000 Binary files a/res/img/flags/TZ.png and /dev/null differ diff --git a/res/img/flags/UA.png b/res/img/flags/UA.png deleted file mode 100644 index 687e305294..0000000000 Binary files a/res/img/flags/UA.png and /dev/null differ diff --git a/res/img/flags/UG.png b/res/img/flags/UG.png deleted file mode 100644 index 0a21ad15c3..0000000000 Binary files a/res/img/flags/UG.png and /dev/null differ diff --git a/res/img/flags/US.png b/res/img/flags/US.png deleted file mode 100644 index c3a245b767..0000000000 Binary files a/res/img/flags/US.png and /dev/null differ diff --git a/res/img/flags/UY.png b/res/img/flags/UY.png deleted file mode 100644 index 21a347c6fc..0000000000 Binary files a/res/img/flags/UY.png and /dev/null differ diff --git a/res/img/flags/UZ.png b/res/img/flags/UZ.png deleted file mode 100644 index 643b6ae0cf..0000000000 Binary files a/res/img/flags/UZ.png and /dev/null differ diff --git a/res/img/flags/VA.png b/res/img/flags/VA.png deleted file mode 100644 index 63a13c0e81..0000000000 Binary files a/res/img/flags/VA.png and /dev/null differ diff --git a/res/img/flags/VC.png b/res/img/flags/VC.png deleted file mode 100644 index da991a9344..0000000000 Binary files a/res/img/flags/VC.png and /dev/null differ diff --git a/res/img/flags/VE.png b/res/img/flags/VE.png deleted file mode 100644 index e75e17c9f0..0000000000 Binary files a/res/img/flags/VE.png and /dev/null differ diff --git a/res/img/flags/VG.png b/res/img/flags/VG.png deleted file mode 100644 index 46f93cad1e..0000000000 Binary files a/res/img/flags/VG.png and /dev/null differ diff --git a/res/img/flags/VI.png b/res/img/flags/VI.png deleted file mode 100644 index 8c849a733e..0000000000 Binary files a/res/img/flags/VI.png and /dev/null differ diff --git a/res/img/flags/VN.png b/res/img/flags/VN.png deleted file mode 100644 index 6ea2122f9d..0000000000 Binary files a/res/img/flags/VN.png and /dev/null differ diff --git a/res/img/flags/VU.png b/res/img/flags/VU.png deleted file mode 100644 index bad3ba4d46..0000000000 Binary files a/res/img/flags/VU.png and /dev/null differ diff --git a/res/img/flags/WF.png b/res/img/flags/WF.png deleted file mode 100644 index d94359dcc4..0000000000 Binary files a/res/img/flags/WF.png and /dev/null differ diff --git a/res/img/flags/WS.png b/res/img/flags/WS.png deleted file mode 100644 index f8b80e5ba9..0000000000 Binary files a/res/img/flags/WS.png and /dev/null differ diff --git a/res/img/flags/YE.png b/res/img/flags/YE.png deleted file mode 100644 index 8b9bbd8942..0000000000 Binary files a/res/img/flags/YE.png and /dev/null differ diff --git a/res/img/flags/YT.png b/res/img/flags/YT.png deleted file mode 100644 index 328879361e..0000000000 Binary files a/res/img/flags/YT.png and /dev/null differ diff --git a/res/img/flags/ZA.png b/res/img/flags/ZA.png deleted file mode 100644 index 7f0a52d3b2..0000000000 Binary files a/res/img/flags/ZA.png and /dev/null differ diff --git a/res/img/flags/ZM.png b/res/img/flags/ZM.png deleted file mode 100644 index 87adc3afaa..0000000000 Binary files a/res/img/flags/ZM.png and /dev/null differ diff --git a/res/img/flags/ZW.png b/res/img/flags/ZW.png deleted file mode 100644 index 742c9f7e71..0000000000 Binary files a/res/img/flags/ZW.png and /dev/null differ diff --git a/res/img/globe.svg b/res/img/globe.svg new file mode 100644 index 0000000000..cc22bc6e66 --- /dev/null +++ b/res/img/globe.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/img/icon_person.svg b/res/img/icon_person.svg deleted file mode 100644 index 4be70df0db..0000000000 --- a/res/img/icon_person.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - 815EF7DE-169A-4322-AE2A-B65CBE91DCED - Created with sketchtool. - - - - - - - - - - - - - - - - - - diff --git a/res/img/modular-bw-logo.svg b/res/img/modular-bw-logo.svg deleted file mode 100644 index 924a587805..0000000000 --- a/res/img/modular-bw-logo.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/res/img/riot-logo.svg b/res/img/riot-logo.svg new file mode 100644 index 0000000000..ac1e547234 --- /dev/null +++ b/res/img/riot-logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/img/spinner.svg b/res/img/spinner.svg new file mode 100644 index 0000000000..08965e982e --- /dev/null +++ b/res/img/spinner.svg @@ -0,0 +1,141 @@ + + + start + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/themes/dark-custom/css/dark-custom.scss b/res/themes/dark-custom/css/dark-custom.scss index aff647ce26..a5fed6a320 100644 --- a/res/themes/dark-custom/css/dark-custom.scss +++ b/res/themes/dark-custom/css/dark-custom.scss @@ -1,6 +1,7 @@ -@import "../../light/css/_paths.scss"; -@import "../../light/css/_fonts.scss"; -@import "../../light/css/_light.scss"; -@import "../../dark/css/_dark.scss"; +@import "../../../../res/css/_font-sizes.scss"; +@import "../../legacy-light/css/_paths.scss"; +@import "../../legacy-light/css/_fonts.scss"; +@import "../../legacy-light/css/_legacy-light.scss"; +@import "../../legacy-dark/css/_legacy-dark.scss"; @import "../../light-custom/css/_custom.scss"; @import "../../../../res/css/_components.scss"; diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index bfa2272283..15155ba854 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -1,14 +1,14 @@ // unified palette // try to use these colors when possible -$bg-color: #181b21; -$base-color: #15171b; -$base-text-color: #edf3ff; -$header-panel-bg-color: #22262e; +$bg-color: #15191E; +$base-color: $bg-color; +$base-text-color: #ffffff; +$header-panel-bg-color: #20252B; $header-panel-border-color: #000000; -$header-panel-text-primary-color: #a1b2d1; +$header-panel-text-primary-color: #B9BEC6; $header-panel-text-secondary-color: #c8c8cd; -$text-primary-color: #edf3ff; -$text-secondary-color: #a1b2d1; +$text-primary-color: #ffffff; +$text-secondary-color: #B9BEC6; $search-bg-color: #181b21; $search-placeholder-color: #61708b; $room-highlight-color: #343a46; @@ -35,7 +35,8 @@ $info-plinth-fg-color: #888; $preview-bar-bg-color: $header-panel-bg-color; -$tagpanel-bg-color: $base-color; +$tagpanel-bg-color: rgba(38, 39, 43, 0.82); +$inverted-bg-color: $base-color; // used by AddressSelector $selected-color: $room-highlight-color; @@ -44,10 +45,10 @@ $selected-color: $room-highlight-color; $event-selected-color: $header-panel-bg-color; // used for the hairline dividers in RoomView -$primary-hairline-color: $header-panel-border-color; +$primary-hairline-color: transparent; // used for the border of input text fields -$input-border-color: #e7e7e7; +$input-border-color: rgba(231, 231, 231, 0.2); $input-darker-bg-color: $search-bg-color; $input-darker-fg-color: $search-placeholder-color; $input-lighter-bg-color: #f2f5f8; @@ -91,7 +92,8 @@ $settings-subsection-fg-color: $text-secondary-color; $topleftmenu-color: $text-primary-color; $roomheader-color: $text-primary-color; -$roomheader-addroom-bg-color: #3c4556; // $search-placeholder-color at 0.5 opacity +$roomheader-bg-color: $bg-color; +$roomheader-addroom-bg-color: rgba(92, 100, 112, 0.3); $roomheader-addroom-fg-color: $text-primary-color; $tagpanel-button-color: $header-panel-text-primary-color; $roomheader-button-color: $header-panel-text-primary-color; @@ -102,16 +104,26 @@ $roomtopic-color: $text-secondary-color; $eventtile-meta-color: $roomtopic-color; $header-divider-color: $header-panel-text-primary-color; +$composer-e2e-icon-color: $header-panel-text-primary-color; -$roomtile-name-color: $header-panel-text-primary-color; -$roomtile-selected-color: $text-primary-color; -$roomtile-notified-color: $text-primary-color; -$roomtile-selected-bg-color: $room-highlight-color; -$roomtile-focused-bg-color: $room-highlight-color; +// ******************** -$roomtile-transparent-focused-color: rgba(0, 0, 0, 0.1); +$theme-button-bg-color: #e3e8f0; -$panel-divider-color: $header-panel-border-color; +$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons +$roomlist-bg-color: rgba(33, 38, 44, 0.90); +$roomlist-header-color: #8E99A4; +$roomsublist-divider-color: $primary-fg-color; + +$roomtile-preview-color: #A9B2BC; +$roomtile-default-badge-bg-color: #61708b; +$roomtile-selected-bg-color: rgba(141, 151, 165, 0.2); + +// ******************** + +$notice-secondary-color: $roomlist-header-color; + +$panel-divider-color: transparent; $widget-menu-bar-bg-color: $header-panel-bg-color; @@ -147,6 +159,9 @@ $button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color $button-link-fg-color: $accent-color; $button-link-bg-color: transparent; +// Toggle switch +$togglesw-off-color: $room-highlight-color; + $visual-bell-bg-color: #800; $room-warning-bg-color: $header-panel-bg-color; @@ -177,6 +192,15 @@ $breadcrumb-placeholder-bg-color: #272c35; $user-tile-hover-bg-color: $header-panel-bg-color; +// Appearance tab colors +$appearance-tab-border-color: $room-highlight-color; + +// blur amounts for left left panel (only for element theme, used in _mods.scss) +$roomlist-background-blur-amount: 60px; +$tagpanel-background-blur-amount: 30px; + +$composer-shadow-color: rgba(0, 0, 0, 0.28); + // ***** Mixins! ***** @define-mixin mx_DialogButton { @@ -185,7 +209,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color; border: 0px; border-radius: 4px; font-family: $font-family; - font-size: 14px; + font-size: $font-14px; color: $button-fg-color; background-color: $button-bg-color; width: auto; diff --git a/res/themes/dark/css/dark.scss b/res/themes/dark/css/dark.scss index e7ae7c8cf8..6d9dc7352c 100644 --- a/res/themes/dark/css/dark.scss +++ b/res/themes/dark/css/dark.scss @@ -1,5 +1,11 @@ +@import "../../../../res/css/_font-sizes.scss"; @import "../../light/css/_paths.scss"; @import "../../light/css/_fonts.scss"; @import "../../light/css/_light.scss"; +// important this goes before _mods, +// as $tagpanel-background-blur-amount and +// $roomlist-background-blur-amount +// are overridden in _dark.scss @import "_dark.scss"; +@import "../../light/css/_mods.scss"; @import "../../../../res/css/_components.scss"; diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss new file mode 100644 index 0000000000..7ecfcf13d9 --- /dev/null +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -0,0 +1,273 @@ +// unified palette +// try to use these colors when possible +$bg-color: #181b21; +$base-color: #15171b; +$base-text-color: #edf3ff; +$header-panel-bg-color: #22262e; +$header-panel-border-color: #000000; +$header-panel-text-primary-color: #a1b2d1; +$header-panel-text-secondary-color: #c8c8cd; +$text-primary-color: #edf3ff; +$text-secondary-color: #a1b2d1; +$search-bg-color: #181b21; +$search-placeholder-color: #61708b; +$room-highlight-color: #343a46; + +// typical text (dark-on-white in light skin) +$primary-fg-color: $text-primary-color; +$primary-bg-color: $bg-color; +$muted-fg-color: $header-panel-text-primary-color; + +// used for dialog box text +$light-fg-color: $header-panel-text-secondary-color; + +// used for focusing form controls +$focus-bg-color: $room-highlight-color; + +$mention-user-pill-bg-color: $warning-color; +$other-user-pill-bg-color: $room-highlight-color; +$rte-room-pill-color: $room-highlight-color; +$rte-group-pill-color: $room-highlight-color; + +// informational plinth +$info-plinth-bg-color: $header-panel-bg-color; +$info-plinth-fg-color: #888; + +$preview-bar-bg-color: $header-panel-bg-color; + +$tagpanel-bg-color: $base-color; +$inverted-bg-color: $tagpanel-bg-color; + +// used by AddressSelector +$selected-color: $room-highlight-color; + +// selected for hoverover & selected event tiles +$event-selected-color: $header-panel-bg-color; + +// used for the hairline dividers in RoomView +$primary-hairline-color: $header-panel-border-color; + +// used for the border of input text fields +$input-border-color: #e7e7e7; +$input-darker-bg-color: $search-bg-color; +$input-darker-fg-color: $search-placeholder-color; +$input-lighter-bg-color: #f2f5f8; +$input-lighter-fg-color: $input-darker-fg-color; +$input-focused-border-color: #238cf5; +$input-valid-border-color: $accent-color; +$input-invalid-border-color: $warning-color; + +$field-focused-label-bg-color: $bg-color; + +// scrollbars +$scrollbar-thumb-color: rgba(255, 255, 255, 0.2); +$scrollbar-track-color: transparent; + +// context menus +$menu-border-color: $header-panel-border-color; +$menu-bg-color: $header-panel-bg-color; +$menu-box-shadow-color: $bg-color; +$menu-selected-color: $room-highlight-color; + +$avatar-initial-color: #ffffff; +$avatar-bg-color: $bg-color; + +$h3-color: $primary-fg-color; + +$dialog-title-fg-color: $base-text-color; +$dialog-backdrop-color: #000; +$dialog-shadow-color: rgba(0, 0, 0, 0.48); +$dialog-close-fg-color: #9fa9ba; + +$dialog-background-bg-color: $header-panel-bg-color; +$lightbox-background-bg-color: #000; + +$settings-grey-fg-color: #a2a2a2; +$settings-profile-placeholder-bg-color: #e7e7e7; +$settings-profile-overlay-bg-color: #000; +$settings-profile-overlay-placeholder-bg-color: transparent; +$settings-profile-overlay-fg-color: #fff; +$settings-profile-overlay-placeholder-fg-color: #454545; +$settings-subsection-fg-color: $text-secondary-color; + +$topleftmenu-color: $text-primary-color; +$roomheader-color: $text-primary-color; +$roomheader-addroom-bg-color: #3c4556; // $search-placeholder-color at 0.5 opacity +$roomheader-addroom-fg-color: $text-primary-color; +$tagpanel-button-color: $header-panel-text-primary-color; +$roomheader-button-color: $header-panel-text-primary-color; +$groupheader-button-color: $header-panel-text-primary-color; +$rightpanel-button-color: $header-panel-text-primary-color; +$composer-button-color: $header-panel-text-primary-color; +$roomtopic-color: $text-secondary-color; +$eventtile-meta-color: $roomtopic-color; + +$header-divider-color: $header-panel-text-primary-color; +$composer-e2e-icon-color: $header-panel-text-primary-color; + +// ******************** + +$theme-button-bg-color: #e3e8f0; + +$roomlist-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons +$roomlist-bg-color: $header-panel-bg-color; + +$roomsublist-divider-color: $primary-fg-color; + +$roomtile-preview-color: #9e9e9e; +$roomtile-default-badge-bg-color: #61708b; +$roomtile-selected-bg-color: #1A1D23; + +// ******************** + +$panel-divider-color: $header-panel-border-color; + +$widget-menu-bar-bg-color: $header-panel-bg-color; + +// event tile lifecycle +$event-sending-color: $text-secondary-color; + +// event redaction +$event-redacted-fg-color: #606060; +$event-redacted-border-color: #000000; + +$event-highlight-fg-color: $warning-color; +$event-highlight-bg-color: #25271F; + +// event timestamp +$event-timestamp-color: $text-secondary-color; + +// Tabbed views +$tab-label-fg-color: $text-primary-color; +$tab-label-active-fg-color: $text-primary-color; +$tab-label-bg-color: transparent; +$tab-label-active-bg-color: $accent-color; +$tab-label-icon-bg-color: $text-primary-color; +$tab-label-active-icon-bg-color: $text-primary-color; + +// Buttons +$button-primary-fg-color: #ffffff; +$button-primary-bg-color: $accent-color; +$button-secondary-bg-color: transparent; +$button-danger-fg-color: #ffffff; +$button-danger-bg-color: $notice-primary-color; +$button-danger-disabled-fg-color: #ffffff; +$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color +$button-link-fg-color: $accent-color; +$button-link-bg-color: transparent; + +// Toggle switch +$togglesw-off-color: $room-highlight-color; + +$visual-bell-bg-color: #800; + +$room-warning-bg-color: $header-panel-bg-color; + +$dark-panel-bg-color: $header-panel-bg-color; +$panel-gradient: rgba(34, 38, 46, 0), rgba(34, 38, 46, 1); + +$message-action-bar-bg-color: $header-panel-bg-color; +$message-action-bar-fg-color: $header-panel-text-primary-color; +$message-action-bar-border-color: #616b7f; +$message-action-bar-hover-border-color: $header-panel-text-primary-color; + +$reaction-row-button-bg-color: $header-panel-bg-color; +$reaction-row-button-border-color: #616b7f; +$reaction-row-button-hover-border-color: $header-panel-text-primary-color; +$reaction-row-button-selected-bg-color: #1f6954; +$reaction-row-button-selected-border-color: $accent-color; + +$kbd-border-color: #000000; + +$tooltip-timeline-bg-color: $tagpanel-bg-color; +$tooltip-timeline-fg-color: #ffffff; + +$interactive-tooltip-bg-color: $base-color; +$interactive-tooltip-fg-color: #ffffff; + +$breadcrumb-placeholder-bg-color: #272c35; + +$user-tile-hover-bg-color: $header-panel-bg-color; + +// Appearance tab colors +$appearance-tab-border-color: $room-highlight-color; + +$composer-shadow-color: tranparent; + +// ***** Mixins! ***** + +@define-mixin mx_DialogButton { + /* align images in buttons (eg spinners) */ + vertical-align: middle; + border: 0px; + border-radius: 4px; + font-family: $font-family; + font-size: $font-14px; + color: $button-fg-color; + background-color: $button-bg-color; + width: auto; + padding: 7px; + padding-left: 1.5em; + padding-right: 1.5em; + cursor: pointer; + display: inline-block; + outline: none; +} + +@define-mixin mx_DialogButton_danger { + background-color: $accent-color; +} + +@define-mixin mx_DialogButton_secondary { + // flip colours for the secondary ones + font-weight: 600; + border: 1px solid $accent-color ! important; + color: $accent-color; + background-color: $button-secondary-bg-color; +} + +@define-mixin mx_Dialog_link { + color: $accent-color; + text-decoration: none; +} + +// Nasty hacks to apply a filter to arbitrary monochrome artwork to make it +// better match the theme. Typically applied to dark grey 'off' buttons or +// light grey 'on' buttons. +.mx_filterFlipColor { + filter: invert(1); +} + +// markdown overrides: +.mx_EventTile_content .markdown-body pre:hover { + border-color: #808080 !important; // inverted due to rules below +} +.mx_EventTile_content .markdown-body { + pre, code { + filter: invert(1); + } + + pre code { + filter: none; + } + + table { + tr { + background-color: #000000; + } + + tr:nth-child(2n) { + background-color: #080808; + } + } +} + +// diff highlight colors +// intentionally swapped to avoid inversion +.hljs-addition { + background: #fdd; +} + +.hljs-deletion { + background: #dfd; +} diff --git a/res/themes/legacy-dark/css/legacy-dark.scss b/res/themes/legacy-dark/css/legacy-dark.scss new file mode 100644 index 0000000000..2a4d432d26 --- /dev/null +++ b/res/themes/legacy-dark/css/legacy-dark.scss @@ -0,0 +1,6 @@ +@import "../../../../res/css/_font-sizes.scss"; +@import "../../legacy-light/css/_paths.scss"; +@import "../../legacy-light/css/_fonts.scss"; +@import "../../legacy-light/css/_legacy-light.scss"; +@import "_legacy-dark.scss"; +@import "../../../../res/css/_components.scss"; diff --git a/res/themes/legacy-light/css/_fonts.scss b/res/themes/legacy-light/css/_fonts.scss new file mode 100644 index 0000000000..1bc9b5a4a3 --- /dev/null +++ b/res/themes/legacy-light/css/_fonts.scss @@ -0,0 +1,84 @@ +/* + * Nunito. + * Includes extended Latin and Vietnamese character sets + * Current URLs are taken from + * https://github.com/alexeiva/NunitoFont/releases/tag/v3.500 + * ...in order to include cyrillic. + * + * Previously, they were + * https://fonts.googleapis.com/css?family=Nunito:400,400i,600,600i,700,700i&subset=latin-ext,vietnamese + * + * We explicitly do not include Nunito's italic variants, as they are not italic enough + * and it's better to rely on the browser's built-in obliquing behaviour. + */ + +/* the 'src' links are relative to the bundle.css, which is in a subdirectory. + */ +@font-face { + font-family: 'Nunito'; + font-style: normal; + font-weight: 400; + src: url('$(res)/fonts/Nunito/Nunito-Regular.ttf') format('truetype'); +} +@font-face { + font-family: 'Nunito'; + font-style: normal; + font-weight: 600; + src: url('$(res)/fonts/Nunito/Nunito-SemiBold.ttf') format('truetype'); +} +@font-face { + font-family: 'Nunito'; + font-style: normal; + font-weight: 700; + src: url('$(res)/fonts/Nunito/Nunito-Bold.ttf') format('truetype'); +} + +/* latin-ext */ +@font-face { + font-family: 'Inconsolata'; + font-style: normal; + font-weight: 400; + src: local('Inconsolata Regular'), local('Inconsolata-Regular'), url('$(res)/fonts/Inconsolata/QldKNThLqRwH-OJ1UHjlKGlX5qhExfHwNJU.woff2') format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Inconsolata'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: local('Inconsolata Regular'), local('Inconsolata-Regular'), url('$(res)/fonts/Inconsolata/QldKNThLqRwH-OJ1UHjlKGlZ5qhExfHw.woff2') format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Inconsolata'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: local('Inconsolata Bold'), local('Inconsolata-Bold'), url('$(res)/fonts/Inconsolata/QldXNThLqRwH-OJ1UHjlKGHiw71n5_zaDpwm80E.woff2') format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Inconsolata'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: local('Inconsolata Bold'), local('Inconsolata-Bold'), url('$(res)/fonts/Inconsolata/QldXNThLqRwH-OJ1UHjlKGHiw71p5_zaDpwm.woff2') format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* a COLR/CPAL version of Twemoji used for consistent cross-browser emoji + * taken from https://github.com/mozilla/twemoji-colr + * using the fix from https://github.com/mozilla/twemoji-colr/issues/50 to + * work on macOS + */ +/* +// except we now load it dynamically via FontManager to handle browsers +// which can't render COLR/CPAL still +@font-face { + font-family: "Twemoji Mozilla"; + src: url('$(res)/fonts/Twemoji_Mozilla/TwemojiMozilla.woff2') format('woff2'); +} +*/ \ No newline at end of file diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss new file mode 100644 index 0000000000..3465aa307e --- /dev/null +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -0,0 +1,372 @@ +// XXX: check this? +/* Nunito lacks combining diacritics, so these will fall through + to the next font. Helevetica's diacritics however do not combine + nicely (on OSX, at least) and result in a huge horizontal mess. + Arial empirically gets it right, hence prioritising Arial here. */ +/* We fall through to Twemoji for emoji rather than falling through + to native Emoji fonts (if any) to ensure cross-browser consistency */ +/* Noto Color Emoji contains digits, in fixed-width, therefore causing + digits in flowed text to stand out. + TODO: Consider putting all emoji fonts to the end rather than the front. */ +$font-family: Nunito, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Arial, Helvetica, Sans-Serif, 'Noto Color Emoji'; + +$monospace-font-family: Inconsolata, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Courier, monospace, 'Noto Color Emoji'; + +// unified palette +// try to use these colors when possible +$accent-color: #03b381; +$accent-bg-color: rgba(3, 179, 129, 0.16); +$notice-primary-color: #ff4b55; +$notice-primary-bg-color: rgba(255, 75, 85, 0.16); +$notice-secondary-color: #61708b; +$header-panel-bg-color: #f3f8fd; + +// typical text (dark-on-white in light skin) +$primary-fg-color: #2e2f32; +$primary-bg-color: #ffffff; +$muted-fg-color: #61708b; // Commonly used in headings and relevant alt text + +// used for dialog box text +$light-fg-color: #747474; + +// used for focusing form controls +$focus-bg-color: #dddddd; + +// button UI (white-on-green in light skin) +$accent-fg-color: #ffffff; +$accent-color-50pct: rgba(3, 179, 129, 0.5); //#03b381 in rgb +$accent-color-darker: #92caad; +$accent-color-alt: #238cf5; + +$selection-fg-color: $primary-bg-color; + +$focus-brightness: 105%; + +// warning colours +$warning-color: $notice-primary-color; // red +$orange-warning-color: #ff8d13; // used for true warnings +// background colour for warnings +$warning-bg-color: #df2a8b; +$info-bg-color: #2a9edf; +$mention-user-pill-bg-color: $warning-color; +$other-user-pill-bg-color: rgba(0, 0, 0, 0.1); + +// pinned events indicator +$pinned-unread-color: $notice-primary-color; +$pinned-color: $notice-secondary-color; + +// informational plinth +$info-plinth-bg-color: #f7f7f7; +$info-plinth-fg-color: #888; + +$preview-bar-bg-color: #f7f7f7; + +// left-panel style muted accent color +$secondary-accent-color: #f2f5f8; +$tertiary-accent-color: #d3efe1; + +$tagpanel-bg-color: #27303a; +$inverted-bg-color: $tagpanel-bg-color; + +// used by RoomDirectory permissions +$plinth-bg-color: $secondary-accent-color; + +// used by RoomDropTarget +$droptarget-bg-color: rgba(255, 255, 255, 0.5); + +// used by AddressSelector +$selected-color: $secondary-accent-color; + +// selected for hoverover & selected event tiles +$event-selected-color: $header-panel-bg-color; + +// used for the hairline dividers in RoomView +$primary-hairline-color: #e5e5e5; + +// used for the border of input text fields +$input-border-color: #e7e7e7; +$input-darker-bg-color: #e3e8f0; +$input-darker-fg-color: #9fa9ba; +$input-lighter-bg-color: #f2f5f8; +$input-lighter-fg-color: $input-darker-fg-color; +$input-focused-border-color: #238cf5; +$input-valid-border-color: $accent-color; +$input-invalid-border-color: $warning-color; + +$field-focused-label-bg-color: #ffffff; + +$button-bg-color: $accent-color; +$button-fg-color: white; + +// apart from login forms, which have stronger border +$strong-input-border-color: #c7c7c7; + +// used for UserSettings EditableText +$input-underline-color: rgba(151, 151, 151, 0.5); +$input-fg-color: rgba(74, 74, 74, 0.9); +// scrollbars +$scrollbar-thumb-color: rgba(0, 0, 0, 0.2); +$scrollbar-track-color: transparent; +// context menus +$menu-border-color: #e7e7e7; +$menu-bg-color: #fff; +$menu-box-shadow-color: rgba(118, 131, 156, 0.6); +$menu-selected-color: #f5f8fa; + +$avatar-initial-color: #ffffff; +$avatar-bg-color: #ffffff; + +$h3-color: #3d3b39; + +$dialog-title-fg-color: #45474a; +$dialog-backdrop-color: rgba(46, 48, 51, 0.38); +$dialog-shadow-color: rgba(0, 0, 0, 0.48); +$dialog-close-fg-color: #c1c1c1; + +$dialog-background-bg-color: #e9e9e9; +$lightbox-background-bg-color: #000; + +$imagebody-giflabel: rgba(0, 0, 0, 0.7); +$imagebody-giflabel-border: rgba(0, 0, 0, 0.2); +$imagebody-giflabel-color: rgba(255, 255, 255, 1); + +$greyed-fg-color: #888; + +$neutral-badge-color: #dbdbdb; + +$preview-widget-bar-color: #ddd; +$preview-widget-fg-color: $greyed-fg-color; + +$blockquote-bar-color: #ddd; +$blockquote-fg-color: #777; + +$settings-grey-fg-color: #a2a2a2; +$settings-profile-placeholder-bg-color: #e7e7e7; +$settings-profile-overlay-bg-color: #000; +$settings-profile-overlay-placeholder-bg-color: transparent; +$settings-profile-overlay-fg-color: #fff; +$settings-profile-overlay-placeholder-fg-color: #2e2f32; +$settings-subsection-fg-color: #61708b; + +$voip-decline-color: #f48080; +$voip-accept-color: #80f480; + +$rte-bg-color: #e9e9e9; +$rte-code-bg-color: rgba(0, 0, 0, 0.04); +$rte-room-pill-color: #aaa; +$rte-group-pill-color: #aaa; + +$topleftmenu-color: #212121; +$roomheader-color: #45474a; +$roomheader-bg-color: $primary-bg-color; +$roomheader-addroom-bg-color: #91a1c0; +$roomheader-addroom-fg-color: $accent-fg-color; +$tagpanel-button-color: #91a1c0; +$roomheader-button-color: #91a1c0; +$groupheader-button-color: #91a1c0; +$rightpanel-button-color: #91a1c0; +$composer-button-color: #91a1c0; +$roomtopic-color: #9e9e9e; +$eventtile-meta-color: $roomtopic-color; + +$composer-e2e-icon-color: #91a1c0; +$header-divider-color: #91a1c0; + +// ******************** + +$theme-button-bg-color: #e3e8f0; + +$roomlist-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons +$roomlist-bg-color: $header-panel-bg-color; +$roomlist-header-color: $primary-fg-color; +$roomsublist-divider-color: $primary-fg-color; + +$roomtile-preview-color: #9e9e9e; +$roomtile-default-badge-bg-color: #61708b; +$roomtile-selected-bg-color: #fff; + +$presence-online: $accent-color; +$presence-away: #d9b072; +$presence-offline: #e3e8f0; + +// ******************** + +$username-variant1-color: #368bd6; +$username-variant2-color: #ac3ba8; +$username-variant3-color: #03b381; +$username-variant4-color: #e64f7a; +$username-variant5-color: #ff812d; +$username-variant6-color: #2dc2c5; +$username-variant7-color: #5c56f5; +$username-variant8-color: #74d12c; + +$panel-divider-color: #dee1f3; + +// ******************** + +$widget-menu-bar-bg-color: $secondary-accent-color; + +// ******************** + +// both $event-highlight-bg-color and $room-warning-bg-color share this value, +// so to not make their order dependent on who depends on who, have a shared value +// defined before both +$yellow-background: #fff8e3; + +// event tile lifecycle +$event-encrypting-color: #abddbc; +$event-sending-color: #ddd; +$event-notsent-color: #f44; + +$event-highlight-fg-color: $warning-color; +$event-highlight-bg-color: $yellow-background; + +// event redaction +$event-redacted-fg-color: #e2e2e2; +$event-redacted-border-color: #cccccc; + +// event timestamp +$event-timestamp-color: #acacac; + +$copy-button-url: "$(res)/img/icon_copy_message.svg"; + +// e2e +$e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color +$e2e-unknown-color: #e8bf37; +$e2e-unverified-color: #e8bf37; +$e2e-warning-color: #ba6363; + +/*** ImageView ***/ +$lightbox-bg-color: #454545; +$lightbox-fg-color: #ffffff; +$lightbox-border-color: #ffffff; + +// Tabbed views +$tab-label-fg-color: #45474a; +$tab-label-active-fg-color: #ffffff; +$tab-label-bg-color: transparent; +$tab-label-active-bg-color: $accent-color; +$tab-label-icon-bg-color: #454545; +$tab-label-active-icon-bg-color: $tab-label-active-fg-color; + +// Buttons +$button-primary-fg-color: #ffffff; +$button-primary-bg-color: $accent-color; +$button-secondary-bg-color: $accent-fg-color; +$button-danger-fg-color: #ffffff; +$button-danger-bg-color: $notice-primary-color; +$button-danger-disabled-fg-color: #ffffff; +$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color +$button-link-fg-color: $accent-color; +$button-link-bg-color: transparent; + +$visual-bell-bg-color: #faa; + +// Toggle switch +$togglesw-off-color: #c1c9d6; +$togglesw-on-color: $accent-color; +$togglesw-ball-color: #fff; + +// Slider +$slider-selection-color: $accent-color; +$slider-background-color: #c1c9d6; + +$progressbar-color: #000; + +$room-warning-bg-color: $yellow-background; + +$memberstatus-placeholder-color: $muted-fg-color; + +$authpage-bg-color: #2e3649; +$authpage-modal-bg-color: rgba(255, 255, 255, 0.59); +$authpage-body-bg-color: #ffffff; +$authpage-focus-bg-color: #dddddd; +$authpage-lang-color: #4e5054; +$authpage-primary-color: #232f32; +$authpage-secondary-color: #61708b; + +$dark-panel-bg-color: $secondary-accent-color; +$panel-gradient: rgba(242, 245, 248, 0), rgba(242, 245, 248, 1); + +$message-action-bar-bg-color: $primary-bg-color; +$message-action-bar-fg-color: $primary-fg-color; +$message-action-bar-border-color: #e9edf1; +$message-action-bar-hover-border-color: $focus-bg-color; + +$reaction-row-button-bg-color: $header-panel-bg-color; +$reaction-row-button-border-color: #e9edf1; +$reaction-row-button-hover-border-color: $focus-bg-color; +$reaction-row-button-selected-bg-color: #e9fff9; +$reaction-row-button-selected-border-color: $accent-color; + +$kbd-border-color: $reaction-row-button-border-color; + +$tooltip-timeline-bg-color: $tagpanel-bg-color; +$tooltip-timeline-fg-color: #ffffff; + +$interactive-tooltip-bg-color: #27303a; +$interactive-tooltip-fg-color: #ffffff; + +$breadcrumb-placeholder-bg-color: #e8eef5; + +$user-tile-hover-bg-color: $header-panel-bg-color; + +// FontSlider colors +$appearance-tab-border-color: $input-darker-bg-color; + +$composer-shadow-color: tranparent; + +// ***** Mixins! ***** + +@define-mixin mx_DialogButton { + /* align images in buttons (eg spinners) */ + vertical-align: middle; + border: 0px; + border-radius: 4px; + font-family: $font-family; + font-size: $font-14px; + color: $button-fg-color; + background-color: $button-bg-color; + width: auto; + padding: 7px; + padding-left: 1.5em; + padding-right: 1.5em; + cursor: pointer; + display: inline-block; + outline: none; +} + +@define-mixin mx_DialogButton_hover { +} + +@define-mixin mx_DialogButton_danger { + background-color: $accent-color; +} + +@define-mixin mx_DialogButton_small { + @mixin mx_DialogButton; + font-size: $font-15px; + padding: 0px 1.5em 0px 1.5em; +} + +@define-mixin mx_DialogButton_secondary { + // flip colours for the secondary ones + font-weight: 600; + border: 1px solid $accent-color ! important; + color: $accent-color; + background-color: $button-secondary-bg-color; +} + +@define-mixin mx_Dialog_link { + color: $accent-color; + text-decoration: none; +} + +// diff highlight colors +.hljs-addition { + background: #dfd; +} + +.hljs-deletion { + background: #fdd; +} diff --git a/res/themes/legacy-light/css/_paths.scss b/res/themes/legacy-light/css/_paths.scss new file mode 100644 index 0000000000..0744347826 --- /dev/null +++ b/res/themes/legacy-light/css/_paths.scss @@ -0,0 +1,3 @@ +// Path from root SCSS file (such as `light.scss`) to `res` dir in the source tree +// This value is overridden by external themes in `riot-web`. +$res: ../../..; diff --git a/res/themes/legacy-light/css/legacy-light.scss b/res/themes/legacy-light/css/legacy-light.scss new file mode 100644 index 0000000000..e39a1765f3 --- /dev/null +++ b/res/themes/legacy-light/css/legacy-light.scss @@ -0,0 +1,5 @@ +@import "../../../../res/css/_font-sizes.scss"; +@import "_paths.scss"; +@import "_fonts.scss"; +@import "_legacy-light.scss"; +@import "../../../../res/css/_components.scss"; diff --git a/res/themes/light-custom/css/_custom.scss b/res/themes/light-custom/css/_custom.scss index e4a08277f9..b830e86e02 100644 --- a/res/themes/light-custom/css/_custom.scss +++ b/res/themes/light-custom/css/_custom.scss @@ -14,15 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ +$font-family: var(--font-family, $font-family); +$monospace-font-family: var(--font-family-monospace, $monospace-font-family); // // --accent-color $accent-color: var(--accent-color); +$accent-bg-color: var(--accent-color-15pct); $button-bg-color: var(--accent-color); $button-link-fg-color: var(--accent-color); $button-primary-bg-color: var(--accent-color); $input-valid-border-color: var(--accent-color); $reaction-row-button-selected-border-color: var(--accent-color); -$roomsublist-chevron-color: var(--accent-color); $tab-label-active-bg-color: var(--accent-color); $togglesw-on-color: var(--accent-color); $username-variant3-color: var(--accent-color); @@ -37,10 +39,10 @@ $menu-bg-color: var(--timeline-background-color); $avatar-bg-color: var(--timeline-background-color); $message-action-bar-bg-color: var(--timeline-background-color); $primary-bg-color: var(--timeline-background-color); -$roomtile-focused-bg-color: var(--timeline-background-color); $togglesw-ball-color: var(--timeline-background-color); $droptarget-bg-color: var(--timeline-background-color-50pct); //still needs alpha at .5 $authpage-modal-bg-color: var(--timeline-background-color-50pct); //still needs alpha at .59 +$roomheader-bg-color: var(--timeline-background-color); // // --roomlist-highlights-color $roomtile-selected-bg-color: var(--roomlist-highlights-color); @@ -50,9 +52,9 @@ $interactive-tooltip-bg-color: var(--sidebar-color); $tagpanel-bg-color: var(--sidebar-color); $tooltip-timeline-bg-color: var(--sidebar-color); $dialog-backdrop-color: var(--sidebar-color-50pct); +$roomlist-button-bg-color: var(--sidebar-color-15pct); // // --roomlist-background-color -$event-selected-color: var(--roomlist-background-color); $header-panel-bg-color: var(--roomlist-background-color); $reaction-row-button-bg-color: var(--roomlist-background-color); $panel-gradient: var(--roomlist-background-color-0pct), var(--roomlist-background-color); @@ -60,11 +62,10 @@ $panel-gradient: var(--roomlist-background-color-0pct), var(--roomlist-backgroun $dark-panel-bg-color: var(--roomlist-background-color); $input-lighter-bg-color: var(--roomlist-background-color); $plinth-bg-color: var(--roomlist-background-color); -$roomsublist-background: var(--roomlist-background-color); $secondary-accent-color: var(--roomlist-background-color); $selected-color: var(--roomlist-background-color); $widget-menu-bar-bg-color: var(--roomlist-background-color); -$roomtile-badge-fg-color: var(--roomlist-background-color); +$roomlist-bg-color: var(--roomlist-background-color); // // --timeline-text-color $message-action-bar-fg-color: var(--timeline-text-color); @@ -81,19 +82,17 @@ $tab-label-fg-color: var(--timeline-text-color); // was #4e5054 $authpage-lang-color: var(--timeline-text-color); $roomheader-color: var(--timeline-text-color); -// -// --roomlist-text-color -$roomtile-notified-color: var(--roomlist-text-color); -$roomtile-selected-color: var(--roomlist-text-color); -// // --roomlist-text-secondary-color -$roomsublist-label-fg-color: var(--roomlist-text-secondary-color); -$roomtile-name-color: var(--roomlist-text-secondary-color); +$roomtile-preview-color: var(--roomlist-text-secondary-color); +$roomlist-header-color: var(--roomlist-text-secondary-color); +$roomtile-default-badge-bg-color: var(--roomlist-text-secondary-color); + // // --roomlist-separator-color $input-darker-bg-color: var(--roomlist-separator-color); $panel-divider-color: var(--roomlist-separator-color);// originally #dee1f3, but close enough $primary-hairline-color: var(--roomlist-separator-color);// originally #e5e5e5, but close enough +$roomsublist-divider-color: var(--roomlist-separator-color); // // --timeline-text-secondary-color $authpage-secondary-color: var(--timeline-text-secondary-color); @@ -124,3 +123,20 @@ $notice-primary-color: var(--warning-color); $pinned-unread-color: var(--warning-color); $warning-color: var(--warning-color); $button-danger-disabled-bg-color: var(--warning-color-50pct); // still needs alpha at 0.5 +// +// --username colors +$username-variant1-color: var(--username-colors_1, $username-variant1-color); +$username-variant2-color: var(--username-colors_2, $username-variant2-color); +$username-variant3-color: var(--username-colors_3, $username-variant3-color); +$username-variant4-color: var(--username-colors_4, $username-variant4-color); +$username-variant5-color: var(--username-colors_5, $username-variant5-color); +$username-variant6-color: var(--username-colors_6, $username-variant6-color); +$username-variant7-color: var(--username-colors_7, $username-variant7-color); +$username-variant8-color: var(--username-colors_8, $username-variant8-color); +// +// --timeline-highlights-color +$event-selected-color: var(--timeline-highlights-color); +$event-highlight-bg-color: var(--timeline-highlights-color); +// +// redirect some variables away from their hardcoded values in the light theme +$settings-grey-fg-color: $primary-fg-color; diff --git a/res/themes/light-custom/css/light-custom.scss b/res/themes/light-custom/css/light-custom.scss index 278ca5f0b1..6e9d0ff736 100644 --- a/res/themes/light-custom/css/light-custom.scss +++ b/res/themes/light-custom/css/light-custom.scss @@ -1,5 +1,6 @@ -@import "../../light/css/_paths.scss"; -@import "../../light/css/_fonts.scss"; -@import "../../light/css/_light.scss"; +@import "../../../../res/css/_font-sizes.scss"; +@import "../../legacy-light/css/_paths.scss"; +@import "../../legacy-light/css/_fonts.scss"; +@import "../../legacy-light/css/_legacy-light.scss"; @import "_custom.scss"; @import "../../../../res/css/_components.scss"; diff --git a/res/themes/light/css/_fonts.scss b/res/themes/light/css/_fonts.scss index 1bc9b5a4a3..ba64830f15 100644 --- a/res/themes/light/css/_fonts.scss +++ b/res/themes/light/css/_fonts.scss @@ -1,36 +1,88 @@ -/* - * Nunito. - * Includes extended Latin and Vietnamese character sets - * Current URLs are taken from - * https://github.com/alexeiva/NunitoFont/releases/tag/v3.500 - * ...in order to include cyrillic. - * - * Previously, they were - * https://fonts.googleapis.com/css?family=Nunito:400,400i,600,600i,700,700i&subset=latin-ext,vietnamese - * - * We explicitly do not include Nunito's italic variants, as they are not italic enough - * and it's better to rely on the browser's built-in obliquing behaviour. - */ - /* the 'src' links are relative to the bundle.css, which is in a subdirectory. */ + +/* Inter unexpectedly contains various codepoints which collide with emoji, even + when variation-16 is applied to request the emoji variant. From eyeballing + the emoji picker, these are: 20e3, 23cf, 24c2, 25a0-25c1, 2665, 2764, 2b06, 2b1c. + Therefore we define a unicode-range to load which excludes the glyphs + (to avoid having to maintain a fork of Inter). */ + +$inter-unicode-range: U+0000-20e2,U+20e4-23ce,U+23d0-24c1,U+24c3-259f,U+25c2-2664,U+2666-2763,U+2765-2b05,U+2b07-2b1b,U+2b1d-10FFFF; + @font-face { - font-family: 'Nunito'; - font-style: normal; - font-weight: 400; - src: url('$(res)/fonts/Nunito/Nunito-Regular.ttf') format('truetype'); + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + font-display: swap; + unicode-range: $inter-unicode-range; + src: url("$(res)/fonts/Inter/Inter-Regular.woff2?v=3.13") format("woff2"), + url("$(res)/fonts/Inter/Inter-Regular.woff?v=3.13") format("woff"); } @font-face { - font-family: 'Nunito'; - font-style: normal; - font-weight: 600; - src: url('$(res)/fonts/Nunito/Nunito-SemiBold.ttf') format('truetype'); + font-family: 'Inter'; + font-style: italic; + font-weight: 400; + font-display: swap; + unicode-range: $inter-unicode-range; + src: url("$(res)/fonts/Inter/Inter-Italic.woff2?v=3.13") format("woff2"), + url("$(res)/fonts/Inter/Inter-Italic.woff?v=3.13") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 500; + font-display: swap; + unicode-range: $inter-unicode-range; + src: url("$(res)/fonts/Inter/Inter-Medium.woff2?v=3.13") format("woff2"), + url("$(res)/fonts/Inter/Inter-Medium.woff?v=3.13") format("woff"); } @font-face { - font-family: 'Nunito'; - font-style: normal; - font-weight: 700; - src: url('$(res)/fonts/Nunito/Nunito-Bold.ttf') format('truetype'); + font-family: 'Inter'; + font-style: italic; + font-weight: 500; + font-display: swap; + unicode-range: $inter-unicode-range; + src: url("$(res)/fonts/Inter/Inter-MediumItalic.woff2?v=3.13") format("woff2"), + url("$(res)/fonts/Inter/Inter-MediumItalic.woff?v=3.13") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 600; + font-display: swap; + unicode-range: $inter-unicode-range; + src: url("$(res)/fonts/Inter/Inter-SemiBold.woff2?v=3.13") format("woff2"), + url("$(res)/fonts/Inter/Inter-SemiBold.woff?v=3.13") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 600; + font-display: swap; + unicode-range: $inter-unicode-range; + src: url("$(res)/fonts/Inter/Inter-SemiBoldItalic.woff2?v=3.13") format("woff2"), + url("$(res)/fonts/Inter/Inter-SemiBoldItalic.woff?v=3.13") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 700; + font-display: swap; + unicode-range: $inter-unicode-range; + src: url("$(res)/fonts/Inter/Inter-Bold.woff2?v=3.13") format("woff2"), + url("$(res)/fonts/Inter/Inter-Bold.woff?v=3.13") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 700; + font-display: swap; + unicode-range: $inter-unicode-range; + src: url("$(res)/fonts/Inter/Inter-BoldItalic.woff2?v=3.13") format("woff2"), + url("$(res)/fonts/Inter/Inter-BoldItalic.woff?v=3.13") format("woff"); } /* latin-ext */ @@ -68,17 +120,3 @@ src: local('Inconsolata Bold'), local('Inconsolata-Bold'), url('$(res)/fonts/Inconsolata/QldXNThLqRwH-OJ1UHjlKGHiw71p5_zaDpwm.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } - -/* a COLR/CPAL version of Twemoji used for consistent cross-browser emoji - * taken from https://github.com/mozilla/twemoji-colr - * using the fix from https://github.com/mozilla/twemoji-colr/issues/50 to - * work on macOS - */ -/* -// except we now load it dynamically via FontManager to handle browsers -// which can't render COLR/CPAL still -@font-face { - font-family: "Twemoji Mozilla"; - src: url('$(res)/fonts/Twemoji_Mozilla/TwemojiMozilla.woff2') format('woff2'); -} -*/ \ No newline at end of file diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 9bdd712e07..e317683963 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -8,21 +8,22 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: Nunito, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Arial, Helvetica, Sans-Serif, 'Noto Color Emoji'; +$font-family: Inter, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Arial, Helvetica, Sans-Serif, 'Noto Color Emoji'; $monospace-font-family: Inconsolata, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Courier, monospace, 'Noto Color Emoji'; // unified palette // try to use these colors when possible -$accent-color: #03b381; +$accent-color: #0DBD8B; $accent-bg-color: rgba(3, 179, 129, 0.16); $notice-primary-color: #ff4b55; $notice-primary-bg-color: rgba(255, 75, 85, 0.16); -$notice-secondary-color: #61708b; +$primary-fg-color: #2e2f32; +$roomlist-header-color: $primary-fg-color; +$notice-secondary-color: $roomlist-header-color; $header-panel-bg-color: #f3f8fd; // typical text (dark-on-white in light skin) -$primary-fg-color: #2e2f32; $primary-bg-color: #ffffff; $muted-fg-color: #61708b; // Commonly used in headings and relevant alt text @@ -34,7 +35,7 @@ $focus-bg-color: #dddddd; // button UI (white-on-green in light skin) $accent-fg-color: #ffffff; -$accent-color-50pct: rgba(3, 179, 129, 0.5); //#03b381 in rgb +$accent-color-50pct: rgba($accent-color, 0.5); $accent-color-darker: #92caad; $accent-color-alt: #238CF5; @@ -65,7 +66,7 @@ $preview-bar-bg-color: #f7f7f7; $secondary-accent-color: #f2f5f8; $tertiary-accent-color: #d3efe1; -$tagpanel-bg-color: #27303a; +$tagpanel-bg-color: rgba(232, 232, 232, 0.77); // used by RoomDirectory permissions $plinth-bg-color: $secondary-accent-color; @@ -80,7 +81,7 @@ $selected-color: $secondary-accent-color; $event-selected-color: $header-panel-bg-color; // used for the hairline dividers in RoomView -$primary-hairline-color: #e5e5e5; +$primary-hairline-color: transparent; // used for the border of input text fields $input-border-color: #e7e7e7; @@ -157,8 +158,9 @@ $rte-group-pill-color: #aaa; $topleftmenu-color: #212121; $roomheader-color: #45474a; -$roomheader-addroom-bg-color: #91A1C0; -$roomheader-addroom-fg-color: $accent-fg-color; +$roomheader-bg-color: $primary-bg-color; +$roomheader-addroom-bg-color: rgba(92, 100, 112, 0.2); +$roomheader-addroom-fg-color: #5c6470; $tagpanel-button-color: #91A1C0; $roomheader-button-color: #91A1C0; $groupheader-button-color: #91A1C0; @@ -167,35 +169,37 @@ $composer-button-color: #91A1C0; $roomtopic-color: #9e9e9e; $eventtile-meta-color: $roomtopic-color; -$composer-e2e-icon-color: #c9ced6; +$composer-e2e-icon-color: #91A1C0; $header-divider-color: #91A1C0; // ******************** -$roomtile-name-color: #61708b; -$roomtile-badge-fg-color: $accent-fg-color; -$roomtile-selected-color: #212121; -$roomtile-notified-color: #212121; -$roomtile-selected-bg-color: #fff; -$roomtile-focused-bg-color: #fff; +$theme-button-bg-color: #e3e8f0; + +$roomlist-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons +$roomlist-bg-color: rgba(245, 245, 245, 0.90); +$roomsublist-divider-color: $primary-fg-color; + +$roomtile-preview-color: #737D8C; +$roomtile-default-badge-bg-color: #61708b; +$roomtile-selected-bg-color: #FFF; + +$presence-online: $accent-color; +$presence-away: #d9b072; +$presence-offline: #E3E8F0; + +// ******************** $username-variant1-color: #368bd6; $username-variant2-color: #ac3ba8; -$username-variant3-color: #03b381; +$username-variant3-color: #0DBD8B; $username-variant4-color: #e64f7a; $username-variant5-color: #ff812d; $username-variant6-color: #2dc2c5; $username-variant7-color: #5c56f5; $username-variant8-color: #74d12c; -$roomtile-transparent-focused-color: rgba(0, 0, 0, 0.1); - -$roomsublist-background: $secondary-accent-color; -$roomsublist-label-fg-color: $roomtile-name-color; -$roomsublist-label-bg-color: $tertiary-accent-color; -$roomsublist-chevron-color: $accent-color; - -$panel-divider-color: #dee1f3; +$panel-divider-color: transparent; // ******************** @@ -262,14 +266,18 @@ $togglesw-off-color: #c1c9d6; $togglesw-on-color: $accent-color; $togglesw-ball-color: #fff; +// Slider +$slider-selection-color: $accent-color; +$slider-background-color: #c1c9d6; + $progressbar-color: #000; $room-warning-bg-color: $yellow-background; -$memberstatus-placeholder-color: $roomtile-name-color; +$memberstatus-placeholder-color: $muted-fg-color; $authpage-bg-color: #2e3649; -$authpage-modal-bg-color: rgba(255, 255, 255, 0.59); +$authpage-modal-bg-color: rgba(245, 245, 245, 0.90); $authpage-body-bg-color: #ffffff; $authpage-focus-bg-color: #dddddd; $authpage-lang-color: #4e5054; @@ -292,7 +300,8 @@ $reaction-row-button-selected-border-color: $accent-color; $kbd-border-color: $reaction-row-button-border-color; -$tooltip-timeline-bg-color: $tagpanel-bg-color; +$inverted-bg-color: #27303a; +$tooltip-timeline-bg-color: $inverted-bg-color; $tooltip-timeline-fg-color: #ffffff; $interactive-tooltip-bg-color: #27303a; @@ -302,6 +311,15 @@ $breadcrumb-placeholder-bg-color: #e8eef5; $user-tile-hover-bg-color: $header-panel-bg-color; +// FontSlider colors +$appearance-tab-border-color: $input-darker-bg-color; + +// blur amounts for left left panel (only for element theme, used in _mods.scss) +$roomlist-background-blur-amount: 40px; +$tagpanel-background-blur-amount: 20px; + +$composer-shadow-color: rgba(0, 0, 0, 0.04); + // ***** Mixins! ***** @define-mixin mx_DialogButton { @@ -310,7 +328,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color; border: 0px; border-radius: 4px; font-family: $font-family; - font-size: 14px; + font-size: $font-14px; color: $button-fg-color; background-color: $button-bg-color; width: auto; @@ -331,7 +349,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color; @define-mixin mx_DialogButton_small { @mixin mx_DialogButton; - font-size: 15px; + font-size: $font-15px; padding: 0px 1.5em 0px 1.5em; } diff --git a/res/themes/light/css/_mods.scss b/res/themes/light/css/_mods.scss new file mode 100644 index 0000000000..9a59acba8e --- /dev/null +++ b/res/themes/light/css/_mods.scss @@ -0,0 +1,32 @@ +// sidebar blurred avatar background +// +// if backdrop-filter is supported, +// set the user avatar (if any) as a background so +// it can be blurred by the tag panel and room list + +@supports (backdrop-filter: none) { + .mx_LeftPanel { + background-image: var(--avatar-url); + background-repeat: no-repeat; + background-size: cover; + background-position: left top; + } + + .mx_TagPanel { + backdrop-filter: blur($tagpanel-background-blur-amount); + } + + .mx_LeftPanel .mx_LeftPanel_roomListContainer { + backdrop-filter: blur($roomlist-background-blur-amount); + } +} + +.mx_RoomSublist_showNButton { + background-color: transparent !important; +} + +a:hover, +a:link, +a:visited { + text-decoration: none; +} diff --git a/res/themes/light/css/light.scss b/res/themes/light/css/light.scss index 6acb2d9d94..f31ce5c139 100644 --- a/res/themes/light/css/light.scss +++ b/res/themes/light/css/light.scss @@ -1,4 +1,6 @@ +@import "../../../../res/css/_font-sizes.scss"; @import "_paths.scss"; @import "_fonts.scss"; @import "_light.scss"; +@import "_mods.scss"; @import "../../../../res/css/_components.scss"; diff --git a/scripts/ci/end-to-end-tests.sh b/scripts/ci/end-to-end-tests.sh index 2f907dffa2..1233677db4 100755 --- a/scripts/ci/end-to-end-tests.sh +++ b/scripts/ci/end-to-end-tests.sh @@ -13,7 +13,6 @@ handle_error() { trap 'handle_error' ERR - echo "--- Building Riot" scripts/ci/layered-riot-web.sh cd ../riot-web diff --git a/scripts/gen-i18n.js b/scripts/gen-i18n.js index a4d53aea2f..a1823cdf50 100755 --- a/scripts/gen-i18n.js +++ b/scripts/gen-i18n.js @@ -237,7 +237,7 @@ const walkOpts = { const fullPath = path.join(root, fileStats.name); let trs; - if (fileStats.name.endsWith('.js') || fileStats.name.endsWith('.tsx')) { + if (fileStats.name.endsWith('.js') || fileStats.name.endsWith('.ts') || fileStats.name.endsWith('.tsx')) { trs = getTranslationsJs(fullPath); } else if (fileStats.name.endsWith('.html')) { trs = getTranslationsOther(fullPath); diff --git a/src/@types/common.ts b/src/@types/common.ts new file mode 100644 index 0000000000..a24d47ac9e --- /dev/null +++ b/src/@types/common.ts @@ -0,0 +1,20 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Based on https://stackoverflow.com/a/53229857/3532235 +export type Without = {[P in Exclude] ? : never}; +export type XOR = (T | U) extends object ? (Without & U) | (Without & T) : T | U; +export type Writeable = { -readonly [P in keyof T]: T[P] }; diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts new file mode 100644 index 0000000000..080cdacafd --- /dev/null +++ b/src/@types/global.d.ts @@ -0,0 +1,78 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import * as ModernizrStatic from "modernizr"; +import ContentMessages from "../ContentMessages"; +import { IMatrixClientPeg } from "../MatrixClientPeg"; +import ToastStore from "../stores/ToastStore"; +import DeviceListener from "../DeviceListener"; +import RebrandListener from "../RebrandListener"; +import { RoomListStoreClass } from "../stores/room-list/RoomListStore"; +import { PlatformPeg } from "../PlatformPeg"; +import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore"; +import {IntegrationManagers} from "../integrations/IntegrationManagers"; +import {ModalManager} from "../Modal"; + +declare global { + interface Window { + Modernizr: ModernizrStatic; + mxMatrixClientPeg: IMatrixClientPeg; + Olm: { + init: () => Promise; + }; + + mxContentMessages: ContentMessages; + mxToastStore: ToastStore; + mxDeviceListener: DeviceListener; + mxRebrandListener: RebrandListener; + mxRoomListStore: RoomListStoreClass; + mxRoomListLayoutStore: RoomListLayoutStore; + mxPlatformPeg: PlatformPeg; + mxIntegrationManagers: typeof IntegrationManagers; + singletonModalManager: ModalManager; + } + + // workaround for https://github.com/microsoft/TypeScript/issues/30933 + interface ObjectConstructor { + fromEntries?(xs: [string|number|symbol, any][]): object; + } + + interface Document { + // https://developer.mozilla.org/en-US/docs/Web/API/Document/hasStorageAccess + hasStorageAccess?: () => Promise; + } + + interface Navigator { + userLanguage?: string; + } + + interface StorageEstimate { + usageDetails?: {[key: string]: number}; + } + + export interface ISettledFulfilled { + status: "fulfilled"; + value: T; + } + export interface ISettledRejected { + status: "rejected"; + reason: any; + } + + interface PromiseConstructor { + allSettled(promises: Promise[]): Promise | ISettledRejected>>; + } +} diff --git a/src/@types/polyfill.ts b/src/@types/polyfill.ts new file mode 100644 index 0000000000..3ce05d9c2f --- /dev/null +++ b/src/@types/polyfill.ts @@ -0,0 +1,38 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// This is intended to fix re-resizer because of its unguarded `instanceof TouchEvent` checks. +export function polyfillTouchEvent() { + // Firefox doesn't have touch events without touch devices being present, so create a fake + // one we can rely on lying about. + if (!window.TouchEvent) { + // We have no intention of actually using this, so just lie. + window.TouchEvent = class TouchEvent extends UIEvent { + public get altKey(): boolean { return false; } + public get changedTouches(): any { return []; } + public get ctrlKey(): boolean { return false; } + public get metaKey(): boolean { return false; } + public get shiftKey(): boolean { return false; } + public get targetTouches(): any { return []; } + public get touches(): any { return []; } + public get rotation(): number { return 0.0; } + public get scale(): number { return 0.0; } + constructor(eventType: string, params?: any) { + super(eventType, params); + } + }; + } +} diff --git a/src/ActiveRoomObserver.js b/src/ActiveRoomObserver.js index d6fbb460b5..b7695d401d 100644 --- a/src/ActiveRoomObserver.js +++ b/src/ActiveRoomObserver.js @@ -27,7 +27,7 @@ import RoomViewStore from './stores/RoomViewStore'; */ class ActiveRoomObserver { constructor() { - this._listeners = {}; + this._listeners = {}; // key=roomId, value=function(isActive:boolean) this._activeRoomId = RoomViewStore.getRoomId(); // TODO: We could self-destruct when the last listener goes away, or at least @@ -35,6 +35,10 @@ class ActiveRoomObserver { this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate.bind(this)); } + get activeRoomId(): string { + return this._activeRoomId; + } + addListener(roomId, listener) { if (!this._listeners[roomId]) this._listeners[roomId] = []; this._listeners[roomId].push(listener); @@ -51,23 +55,23 @@ class ActiveRoomObserver { } } - _emit(roomId) { + _emit(roomId, isActive: boolean) { if (!this._listeners[roomId]) return; for (const l of this._listeners[roomId]) { - l.call(); + l.call(null, isActive); } } _onRoomViewStoreUpdate() { // emit for the old room ID - if (this._activeRoomId) this._emit(this._activeRoomId); + if (this._activeRoomId) this._emit(this._activeRoomId, false); // update our cache this._activeRoomId = RoomViewStore.getRoomId(); // and emit for the new one - if (this._activeRoomId) this._emit(this._activeRoomId); + if (this._activeRoomId) this._emit(this._activeRoomId, true); } } diff --git a/src/AddThreepid.js b/src/AddThreepid.js index 7a3250d0ca..f06f7c187d 100644 --- a/src/AddThreepid.js +++ b/src/AddThreepid.js @@ -21,6 +21,7 @@ import * as sdk from './index'; import Modal from './Modal'; import { _t } from './languageHandler'; import IdentityAuthClient from './IdentityAuthClient'; +import {SSOAuthEntry} from "./components/views/auth/InteractiveAuthEntryComponents"; function getIdServerDomain() { return MatrixClientPeg.get().idBaseUrl.split("://")[1]; @@ -188,11 +189,31 @@ export default class AddThreepid { // pop up an interactive auth dialog const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog"); + + const dialogAesthetics = { + [SSOAuthEntry.PHASE_PREAUTH]: { + title: _t("Use Single Sign On to continue"), + body: _t("Confirm adding this email address by using " + + "Single Sign On to prove your identity."), + continueText: _t("Single Sign On"), + continueKind: "primary", + }, + [SSOAuthEntry.PHASE_POSTAUTH]: { + title: _t("Confirm adding email"), + body: _t("Click the button below to confirm adding this email address."), + continueText: _t("Confirm"), + continueKind: "primary", + }, + }; const { finished } = Modal.createTrackedDialog('Add Email', '', InteractiveAuthDialog, { title: _t("Add Email Address"), matrixClient: MatrixClientPeg.get(), authData: e.data, makeRequest: this._makeAddThreepidOnlyRequest, + aestheticsForStagePhases: { + [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, + [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, + }, }); return finished; } @@ -285,11 +306,30 @@ export default class AddThreepid { // pop up an interactive auth dialog const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog"); + const dialogAesthetics = { + [SSOAuthEntry.PHASE_PREAUTH]: { + title: _t("Use Single Sign On to continue"), + body: _t("Confirm adding this phone number by using " + + "Single Sign On to prove your identity."), + continueText: _t("Single Sign On"), + continueKind: "primary", + }, + [SSOAuthEntry.PHASE_POSTAUTH]: { + title: _t("Confirm adding phone number"), + body: _t("Click the button below to confirm adding this phone number."), + continueText: _t("Confirm"), + continueKind: "primary", + }, + }; const { finished } = Modal.createTrackedDialog('Add MSISDN', '', InteractiveAuthDialog, { title: _t("Add Phone Number"), matrixClient: MatrixClientPeg.get(), authData: e.data, makeRequest: this._makeAddThreepidOnlyRequest, + aestheticsForStagePhases: { + [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, + [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, + }, }); return finished; } diff --git a/src/Analytics.js b/src/Analytics.js index c96cfdefee..9966d0845e 100644 --- a/src/Analytics.js +++ b/src/Analytics.js @@ -66,7 +66,10 @@ const customVariables = { }, 'App Version': { id: 2, - expl: _td('The version of Riot'), + expl: _td('The version of %(brand)s'), + getTextVariables: () => ({ + brand: SdkConfig.get().brand, + }), example: '15.0.0', }, 'User Type': { @@ -96,7 +99,10 @@ const customVariables = { }, 'Touch Input': { id: 8, - expl: _td("Whether you're using Riot on a device where touch is the primary input mechanism"), + expl: _td("Whether you're using %(brand)s on a device where touch is the primary input mechanism"), + getTextVariables: () => ({ + brand: SdkConfig.get().brand, + }), example: 'false', }, 'Breadcrumbs': { @@ -106,7 +112,10 @@ const customVariables = { }, 'Installed PWA': { id: 10, - expl: _td("Whether you're using Riot as an installed Progressive Web App"), + expl: _td("Whether you're using %(brand)s as an installed Progressive Web App"), + getTextVariables: () => ({ + brand: SdkConfig.get().brand, + }), example: 'false', }, }; @@ -123,8 +132,8 @@ const LAST_VISIT_TS_KEY = "mx_Riot_Analytics_lvts"; function getUid() { try { - let data = localStorage.getItem(UID_KEY); - if (!data) { + let data = localStorage && localStorage.getItem(UID_KEY); + if (!data && localStorage) { localStorage.setItem(UID_KEY, data = [...Array(16)].map(() => Math.random().toString(16)[2]).join('')); } return data; @@ -145,14 +154,16 @@ class Analytics { this.firstPage = true; this._heartbeatIntervalID = null; - this.creationTs = localStorage.getItem(CREATION_TS_KEY); - if (!this.creationTs) { + this.creationTs = localStorage && localStorage.getItem(CREATION_TS_KEY); + if (!this.creationTs && localStorage) { localStorage.setItem(CREATION_TS_KEY, this.creationTs = new Date().getTime()); } - this.lastVisitTs = localStorage.getItem(LAST_VISIT_TS_KEY); - this.visitCount = localStorage.getItem(VISIT_COUNT_KEY) || 0; - localStorage.setItem(VISIT_COUNT_KEY, parseInt(this.visitCount, 10) + 1); + this.lastVisitTs = localStorage && localStorage.getItem(LAST_VISIT_TS_KEY); + this.visitCount = localStorage && localStorage.getItem(VISIT_COUNT_KEY) || 0; + if (localStorage) { + localStorage.setItem(VISIT_COUNT_KEY, parseInt(this.visitCount, 10) + 1); + } } get disabled() { @@ -193,8 +204,11 @@ class Analytics { this._setVisitVariable('Chosen Language', getCurrentLanguage()); - if (window.location.hostname === 'riot.im') { + const hostname = window.location.hostname; + if (hostname === 'riot.im') { this._setVisitVariable('Instance', window.location.pathname); + } else if (hostname.endsWith('.element.io')) { + this._setVisitVariable('Instance', hostname.replace('.element.io', '')); } let installedPWA = "unknown"; @@ -354,12 +368,17 @@ class Analytics { Modal.createTrackedDialog('Analytics Details', '', ErrorDialog, { title: _t('Analytics'), description:
-
- { _t('The information being sent to us to help make Riot better includes:') } -
+
{_t('The information being sent to us to help make %(brand)s better includes:', { + brand: SdkConfig.get().brand, + })}
{ rows.map((row) => - + { row[1] !== undefined && } ) } { otherVariables.map((item, index) => diff --git a/src/AsyncWrapper.js b/src/AsyncWrapper.js index b7b81688e1..05054cf63a 100644 --- a/src/AsyncWrapper.js +++ b/src/AsyncWrapper.js @@ -38,7 +38,7 @@ export default createReactClass({ }; }, - componentWillMount: function() { + componentDidMount: function() { this._unmounted = false; // XXX: temporary logging to try to diagnose // https://github.com/vector-im/riot-web/issues/3148 diff --git a/src/Avatar.js b/src/Avatar.js index 217b196348..d76ea6f2c4 100644 --- a/src/Avatar.js +++ b/src/Avatar.js @@ -19,6 +19,7 @@ import {MatrixClientPeg} from './MatrixClientPeg'; import DMRoomMap from './utils/DMRoomMap'; import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo"; +// Not to be used for BaseAvatar urls as that has similar default avatar fallback already export function avatarUrlForMember(member, width, height, resizeMethod) { let url; if (member && member.getAvatarUrl) { @@ -53,13 +54,56 @@ export function avatarUrlForUser(user, width, height, resizeMethod) { return url; } +function isValidHexColor(color) { + return typeof color === "string" && + (color.length === 7 || color.lengh === 9) && + color.charAt(0) === "#" && + !color.substr(1).split("").some(c => isNaN(parseInt(c, 16))); +} + +function urlForColor(color) { + const size = 40; + const canvas = document.createElement("canvas"); + canvas.width = size; + canvas.height = size; + const ctx = canvas.getContext("2d"); + // bail out when using jsdom in unit tests + if (!ctx) { + return ""; + } + ctx.fillStyle = color; + ctx.fillRect(0, 0, size, size); + return canvas.toDataURL(); +} + +// XXX: Ideally we'd clear this cache when the theme changes +// but since this function is at global scope, it's a bit +// hard to install a listener here, even if there were a clear event to listen to +const colorToDataURLCache = new Map(); + export function defaultAvatarUrlForString(s) { - const images = ['03b381', '368bd6', 'ac3ba8']; + const defaultColors = ['#0DBD8B', '#368bd6', '#ac3ba8']; let total = 0; for (let i = 0; i < s.length; ++i) { total += s.charCodeAt(i); } - return require('../res/img/' + images[total % images.length] + '.png'); + const colorIndex = total % defaultColors.length; + // overwritten color value in custom themes + const cssVariable = `--avatar-background-colors_${colorIndex}`; + const cssValue = document.body.style.getPropertyValue(cssVariable); + const color = cssValue || defaultColors[colorIndex]; + let dataUrl = colorToDataURLCache.get(color); + if (!dataUrl) { + // validate color as this can come from account_data + // with custom theming + if (isValidHexColor(color)) { + dataUrl = urlForColor(color); + colorToDataURLCache.set(color, dataUrl); + } else { + dataUrl = ""; + } + } + return dataUrl; } /** diff --git a/src/BasePlatform.js b/src/BasePlatform.js deleted file mode 100644 index 5d809eb28f..0000000000 --- a/src/BasePlatform.js +++ /dev/null @@ -1,191 +0,0 @@ -// @flow - -/* -Copyright 2016 Aviral Dasgupta -Copyright 2016 OpenMarket Ltd -Copyright 2018 New Vector Ltd -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import {MatrixClient} from "matrix-js-sdk"; -import dis from './dispatcher'; -import BaseEventIndexManager from './indexing/BaseEventIndexManager'; - -/** - * Base class for classes that provide platform-specific functionality - * eg. Setting an application badge or displaying notifications - * - * Instances of this class are provided by the application. - */ -export default class BasePlatform { - constructor() { - this.notificationCount = 0; - this.errorDidOccur = false; - - dis.register(this._onAction.bind(this)); - } - - _onAction(payload: Object) { - switch (payload.action) { - case 'on_client_not_viable': - case 'on_logged_out': - this.setNotificationCount(0); - break; - } - } - - // Used primarily for Analytics - getHumanReadableName(): string { - return 'Base Platform'; - } - - setNotificationCount(count: number) { - this.notificationCount = count; - } - - setErrorStatus(errorDidOccur: boolean) { - this.errorDidOccur = errorDidOccur; - } - - /** - * Returns true if the platform supports displaying - * notifications, otherwise false. - * @returns {boolean} whether the platform supports displaying notifications - */ - supportsNotifications(): boolean { - return false; - } - - /** - * Returns true if the application currently has permission - * to display notifications. Otherwise false. - * @returns {boolean} whether the application has permission to display notifications - */ - maySendNotifications(): boolean { - return false; - } - - /** - * Requests permission to send notifications. Returns - * a promise that is resolved when the user has responded - * to the request. The promise has a single string argument - * that is 'granted' if the user allowed the request or - * 'denied' otherwise. - */ - requestNotificationPermission(): Promise { - } - - displayNotification(title: string, msg: string, avatarUrl: string, room: Object) { - } - - loudNotification(ev: Event, room: Object) { - } - - /** - * Returns a promise that resolves to a string representing - * the current version of the application. - */ - getAppVersion(): Promise { - throw new Error("getAppVersion not implemented!"); - } - - /* - * If it's not expected that capturing the screen will work - * with getUserMedia, return a string explaining why not. - * Otherwise, return null. - */ - screenCaptureErrorString(): string { - return "Not implemented"; - } - - /** - * Restarts the application, without neccessarily reloading - * any application code - */ - reload() { - throw new Error("reload not implemented!"); - } - - supportsAutoLaunch(): boolean { - return false; - } - - // XXX: Surely this should be a setting like any other? - async getAutoLaunchEnabled(): boolean { - return false; - } - - async setAutoLaunchEnabled(enabled: boolean): void { - throw new Error("Unimplemented"); - } - - supportsAutoHideMenuBar(): boolean { - return false; - } - - async getAutoHideMenuBarEnabled(): boolean { - return false; - } - - async setAutoHideMenuBarEnabled(enabled: boolean): void { - throw new Error("Unimplemented"); - } - - supportsMinimizeToTray(): boolean { - return false; - } - - async getMinimizeToTrayEnabled(): boolean { - return false; - } - - async setMinimizeToTrayEnabled(enabled: boolean): void { - throw new Error("Unimplemented"); - } - - /** - * Get our platform specific EventIndexManager. - * - * @return {BaseEventIndexManager} The EventIndex manager for our platform, - * can be null if the platform doesn't support event indexing. - */ - getEventIndexingManager(): BaseEventIndexManager | null { - return null; - } - - setLanguage(preferredLangs: string[]) {} - - getSSOCallbackUrl(hsUrl: string, isUrl: string): URL { - const url = new URL(window.location.href); - // XXX: at this point, the fragment will always be #/login, which is no - // use to anyone. Ideally, we would get the intended fragment from - // MatrixChat.screenAfterLogin so that you could follow #/room links etc - // through an SSO login. - url.hash = ""; - url.searchParams.set("homeserver", hsUrl); - url.searchParams.set("identityServer", isUrl); - return url; - } - - /** - * Begin Single Sign On flows. - * @param {MatrixClient} mxClient the matrix client using which we should start the flow - * @param {"sso"|"cas"} loginType the type of SSO it is, CAS/SSO. - */ - startSingleSignOn(mxClient: MatrixClient, loginType: "sso"|"cas") { - const callbackUrl = this.getSSOCallbackUrl(mxClient.getHomeserverUrl(), mxClient.getIdentityServerUrl()); - window.location.href = mxClient.getSsoLoginUrl(callbackUrl.toString(), loginType); // redirect to SSO - } -} diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts new file mode 100644 index 0000000000..acf72a986c --- /dev/null +++ b/src/BasePlatform.ts @@ -0,0 +1,284 @@ +/* +Copyright 2016 Aviral Dasgupta +Copyright 2016 OpenMarket Ltd +Copyright 2018 New Vector Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {MatrixClient} from "matrix-js-sdk/src/client"; +import dis from './dispatcher/dispatcher'; +import BaseEventIndexManager from './indexing/BaseEventIndexManager'; +import {ActionPayload} from "./dispatcher/payloads"; +import {CheckUpdatesPayload} from "./dispatcher/payloads/CheckUpdatesPayload"; +import {Action} from "./dispatcher/actions"; +import {hideToast as hideUpdateToast} from "./toasts/UpdateToast"; + +export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url"; +export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url"; + +export enum UpdateCheckStatus { + Checking = "CHECKING", + Error = "ERROR", + NotAvailable = "NOTAVAILABLE", + Downloading = "DOWNLOADING", + Ready = "READY", +} + +const UPDATE_DEFER_KEY = "mx_defer_update"; + +/** + * Base class for classes that provide platform-specific functionality + * eg. Setting an application badge or displaying notifications + * + * Instances of this class are provided by the application. + */ +export default abstract class BasePlatform { + protected notificationCount = 0; + protected errorDidOccur = false; + + constructor() { + dis.register(this.onAction); + this.startUpdateCheck = this.startUpdateCheck.bind(this); + } + + abstract async getConfig(): Promise<{}>; + + abstract getDefaultDeviceDisplayName(): string; + + protected onAction = (payload: ActionPayload) => { + switch (payload.action) { + case 'on_client_not_viable': + case 'on_logged_out': + this.setNotificationCount(0); + break; + } + }; + + // Used primarily for Analytics + abstract getHumanReadableName(): string; + + setNotificationCount(count: number) { + this.notificationCount = count; + } + + setErrorStatus(errorDidOccur: boolean) { + this.errorDidOccur = errorDidOccur; + } + + /** + * Whether we can call checkForUpdate on this platform build + */ + async canSelfUpdate(): Promise { + return false; + } + + startUpdateCheck() { + hideUpdateToast(); + localStorage.removeItem(UPDATE_DEFER_KEY); + dis.dispatch({ + action: Action.CheckUpdates, + status: UpdateCheckStatus.Checking, + }); + } + + /** + * Update the currently running app to the latest available version + * and replace this instance of the app with the new version. + */ + installUpdate() { + } + + /** + * Check if the version update has been deferred and that deferment is still in effect + * @param newVersion the version string to check + */ + protected shouldShowUpdate(newVersion: string): boolean { + try { + const [version, deferUntil] = JSON.parse(localStorage.getItem(UPDATE_DEFER_KEY)); + return newVersion !== version || Date.now() > deferUntil; + } catch (e) { + return true; + } + } + + /** + * Ignore the pending update and don't prompt about this version + * until the next morning (8am). + */ + deferUpdate(newVersion: string) { + const date = new Date(Date.now() + 24 * 60 * 60 * 1000); + date.setHours(8, 0, 0, 0); // set to next 8am + localStorage.setItem(UPDATE_DEFER_KEY, JSON.stringify([newVersion, date.getTime()])); + hideUpdateToast(); + } + + /** + * Returns true if the platform supports displaying + * notifications, otherwise false. + * @returns {boolean} whether the platform supports displaying notifications + */ + supportsNotifications(): boolean { + return false; + } + + /** + * Returns true if the application currently has permission + * to display notifications. Otherwise false. + * @returns {boolean} whether the application has permission to display notifications + */ + maySendNotifications(): boolean { + return false; + } + + /** + * Requests permission to send notifications. Returns + * a promise that is resolved when the user has responded + * to the request. The promise has a single string argument + * that is 'granted' if the user allowed the request or + * 'denied' otherwise. + */ + abstract requestNotificationPermission(): Promise; + + abstract displayNotification(title: string, msg: string, avatarUrl: string, room: Object); + + loudNotification(ev: Event, room: Object) { + } + + /** + * Returns a promise that resolves to a string representing the current version of the application. + */ + abstract getAppVersion(): Promise; + + /* + * If it's not expected that capturing the screen will work + * with getUserMedia, return a string explaining why not. + * Otherwise, return null. + */ + screenCaptureErrorString(): string { + return "Not implemented"; + } + + /** + * Restarts the application, without neccessarily reloading + * any application code + */ + abstract reload(); + + supportsAutoLaunch(): boolean { + return false; + } + + // XXX: Surely this should be a setting like any other? + async getAutoLaunchEnabled(): Promise { + return false; + } + + async setAutoLaunchEnabled(enabled: boolean): Promise { + throw new Error("Unimplemented"); + } + + supportsAutoHideMenuBar(): boolean { + return false; + } + + async getAutoHideMenuBarEnabled(): Promise { + return false; + } + + async setAutoHideMenuBarEnabled(enabled: boolean): Promise { + throw new Error("Unimplemented"); + } + + supportsMinimizeToTray(): boolean { + return false; + } + + async getMinimizeToTrayEnabled(): Promise { + return false; + } + + async setMinimizeToTrayEnabled(enabled: boolean): Promise { + throw new Error("Unimplemented"); + } + + /** + * Get our platform specific EventIndexManager. + * + * @return {BaseEventIndexManager} The EventIndex manager for our platform, + * can be null if the platform doesn't support event indexing. + */ + getEventIndexingManager(): BaseEventIndexManager | null { + return null; + } + + setLanguage(preferredLangs: string[]) {} + + protected getSSOCallbackUrl(fragmentAfterLogin: string): URL { + const url = new URL(window.location.href); + url.hash = fragmentAfterLogin || ""; + return url; + } + + /** + * Begin Single Sign On flows. + * @param {MatrixClient} mxClient the matrix client using which we should start the flow + * @param {"sso"|"cas"} loginType the type of SSO it is, CAS/SSO. + * @param {string} fragmentAfterLogin the hash to pass to the app during sso callback. + */ + startSingleSignOn(mxClient: MatrixClient, loginType: "sso" | "cas", fragmentAfterLogin: string) { + // persist hs url and is url for when the user is returned to the app with the login token + localStorage.setItem(SSO_HOMESERVER_URL_KEY, mxClient.getHomeserverUrl()); + if (mxClient.getIdentityServerUrl()) { + localStorage.setItem(SSO_ID_SERVER_URL_KEY, mxClient.getIdentityServerUrl()); + } + const callbackUrl = this.getSSOCallbackUrl(fragmentAfterLogin); + window.location.href = mxClient.getSsoLoginUrl(callbackUrl.toString(), loginType); // redirect to SSO + } + + onKeyDown(ev: KeyboardEvent): boolean { + return false; // no shortcuts implemented + } + + /** + * Get a previously stored pickle key. The pickle key is used for + * encrypting libolm objects. + * @param {string} userId the user ID for the user that the pickle key is for. + * @param {string} userId the device ID that the pickle key is for. + * @returns {string|null} the previously stored pickle key, or null if no + * pickle key has been stored. + */ + async getPickleKey(userId: string, deviceId: string): Promise { + return null; + } + + /** + * Create and store a pickle key for encrypting libolm objects. + * @param {string} userId the user ID for the user that the pickle key is for. + * @param {string} userId the device ID that the pickle key is for. + * @returns {string|null} the pickle key, or null if the platform does not + * support storing pickle keys. + */ + async createPickleKey(userId: string, deviceId: string): Promise { + return null; + } + + /** + * Delete a previously stored pickle key from storage. + * @param {string} userId the user ID for the user that the pickle key is for. + * @param {string} userId the device ID that the pickle key is for. + */ + async destroyPickleKey(userId: string, deviceId: string): Promise { + } +} diff --git a/src/CallHandler.js b/src/CallHandler.js index 362db939a3..4414bce457 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -59,13 +59,13 @@ import Modal from './Modal'; import * as sdk from './index'; import { _t } from './languageHandler'; import Matrix from 'matrix-js-sdk'; -import dis from './dispatcher'; -import SdkConfig from './SdkConfig'; -import { showUnknownDeviceDialogForCalls } from './cryptodevices'; +import dis from './dispatcher/dispatcher'; import WidgetUtils from './utils/WidgetUtils'; import WidgetEchoStore from './stores/WidgetEchoStore'; import SettingsStore, { SettingLevel } from './settings/SettingsStore'; import {generateHumanReadableId} from "./utils/NamingUtils"; +import {Jitsi} from "./widgets/Jitsi"; +import {WidgetType} from "./widgets/WidgetType"; global.mxCalls = { //room_id: MatrixCall @@ -118,62 +118,22 @@ function pause(audioId) { } } -function _reAttemptCall(call) { - if (call.direction === 'outbound') { - dis.dispatch({ - action: 'place_call', - room_id: call.roomId, - type: call.type, - }); - } else { - call.answer(); - } -} - function _setCallListeners(call) { call.on("error", function(err) { console.error("Call error:", err); - if (err.code === 'unknown_devices') { - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - - Modal.createTrackedDialog('Call Failed', '', QuestionDialog, { - title: _t('Call Failed'), - description: _t( - "There are unknown sessions in this room: "+ - "if you proceed without verifying them, it will be "+ - "possible for someone to eavesdrop on your call.", - ), - button: _t('Review Sessions'), - onFinished: function(confirmed) { - if (confirmed) { - const room = MatrixClientPeg.get().getRoom(call.roomId); - showUnknownDeviceDialogForCalls( - MatrixClientPeg.get(), - room, - () => { - _reAttemptCall(call); - }, - call.direction === 'outbound' ? _t("Call Anyway") : _t("Answer Anyway"), - call.direction === 'outbound' ? _t("Call") : _t("Answer"), - ); - } - }, - }); - } else { - if ( - MatrixClientPeg.get().getTurnServers().length === 0 && - SettingsStore.getValue("fallbackICEServerAllowed") === null - ) { - _showICEFallbackPrompt(); - return; - } - - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Call Failed', '', ErrorDialog, { - title: _t('Call Failed'), - description: err.message, - }); + if ( + MatrixClientPeg.get().getTurnServers().length === 0 && + SettingsStore.getValue("fallbackICEServerAllowed") === null + ) { + _showICEFallbackPrompt(); + return; } + + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + Modal.createTrackedDialog('Call Failed', '', ErrorDialog, { + title: _t('Call Failed'), + description: err.message, + }); }); call.on("hangup", function() { _setCallState(undefined, call.roomId, "ended"); @@ -401,9 +361,9 @@ async function _startCallApp(roomId, type) { }); const room = MatrixClientPeg.get().getRoom(roomId); - const currentRoomWidgets = WidgetUtils.getRoomWidgets(room); + const currentJitsiWidgets = WidgetUtils.getRoomWidgetsOfType(room, WidgetType.JITSI); - if (WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentRoomWidgets, 'jitsi')) { + if (WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentJitsiWidgets, WidgetType.JITSI)) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Call already in progress', '', ErrorDialog, { @@ -413,9 +373,6 @@ async function _startCallApp(roomId, type) { return; } - const currentJitsiWidgets = currentRoomWidgets.filter((ev) => { - return ev.getContent().type === 'jitsi'; - }); if (currentJitsiWidgets.length > 0) { console.warn( "Refusing to start conference call widget in " + roomId + @@ -430,8 +387,8 @@ async function _startCallApp(roomId, type) { return; } - const confId = `JitsiConference_${generateHumanReadableId()}`; - const jitsiDomain = SdkConfig.get()['jitsi']['preferredDomain']; + const confId = `JitsiConference${generateHumanReadableId()}`; + const jitsiDomain = Jitsi.getInstance().preferredDomain; let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl(); @@ -454,7 +411,7 @@ async function _startCallApp(roomId, type) { Date.now() ); - WidgetUtils.setRoomWidget(roomId, widgetId, 'jitsi', widgetUrl, 'Jitsi', widgetData).then(() => { + WidgetUtils.setRoomWidget(roomId, widgetId, WidgetType.JITSI, widgetUrl, 'Jitsi', widgetData).then(() => { console.log('Jitsi widget added'); }).catch((e) => { if (e.errcode === 'M_FORBIDDEN') { diff --git a/src/ContentMessages.js b/src/ContentMessages.tsx similarity index 73% rename from src/ContentMessages.js rename to src/ContentMessages.tsx index 34379c029b..6f55a75d0c 100644 --- a/src/ContentMessages.js +++ b/src/ContentMessages.tsx @@ -1,6 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2019 New Vector Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,20 +16,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; - +import React from "react"; import extend from './extend'; -import dis from './dispatcher'; +import dis from './dispatcher/dispatcher'; import {MatrixClientPeg} from './MatrixClientPeg'; +import {MatrixClient} from "matrix-js-sdk/src/client"; import * as sdk from './index'; import { _t } from './languageHandler'; import Modal from './Modal'; import RoomViewStore from './stores/RoomViewStore'; import encrypt from "browser-encrypt-attachment"; import extractPngChunks from "png-chunks-extract"; +import Spinner from "./components/views/elements/Spinner"; // Polyfill for Canvas.toBlob API using Canvas.toDataURL import "blueimp-canvas-to-blob"; +import { Action } from "./dispatcher/actions"; const MAX_WIDTH = 800; const MAX_HEIGHT = 600; @@ -39,6 +42,50 @@ const PHYS_HIDPI = [0x00, 0x00, 0x16, 0x25, 0x00, 0x00, 0x16, 0x25, 0x01]; export class UploadCanceledError extends Error {} +type ThumbnailableElement = HTMLImageElement | HTMLVideoElement; + +interface IUpload { + fileName: string; + roomId: string; + total: number; + loaded: number; + promise: Promise; + canceled?: boolean; +} + +interface IMediaConfig { + "m.upload.size"?: number; +} + +interface IContent { + body: string; + msgtype: string; + info: { + size: number; + mimetype?: string; + }; + file?: string; + url?: string; +} + +interface IThumbnail { + info: { + thumbnail_info: { + w: number; + h: number; + mimetype: string; + size: number; + }; + w: number; + h: number; + }; + thumbnail: Blob; +} + +interface IAbortablePromise extends Promise { + abort(): void; +} + /** * Create a thumbnail for a image DOM element. * The image will be smaller than MAX_WIDTH and MAX_HEIGHT. @@ -51,13 +98,13 @@ export class UploadCanceledError extends Error {} * about the original image and the thumbnail. * * @param {HTMLElement} element The element to thumbnail. - * @param {integer} inputWidth The width of the image in the input element. - * @param {integer} inputHeight the width of the image in the input element. + * @param {number} inputWidth The width of the image in the input element. + * @param {number} inputHeight the width of the image in the input element. * @param {String} mimeType The mimeType to save the blob as. * @return {Promise} A promise that resolves with an object with an info key * and a thumbnail key. */ -function createThumbnail(element, inputWidth, inputHeight, mimeType) { +function createThumbnail(element: ThumbnailableElement, inputWidth: number, inputHeight: number, mimeType: string): Promise { return new Promise((resolve) => { let targetWidth = inputWidth; let targetHeight = inputHeight; @@ -98,7 +145,7 @@ function createThumbnail(element, inputWidth, inputHeight, mimeType) { * @param {File} imageFile The file to load in an image element. * @return {Promise} A promise that resolves with the html image element. */ -async function loadImageElement(imageFile) { +async function loadImageElement(imageFile: File) { // Load the file into an html element const img = document.createElement("img"); const objectUrl = URL.createObjectURL(imageFile); @@ -128,8 +175,7 @@ async function loadImageElement(imageFile) { for (const chunk of chunks) { if (chunk.name === 'pHYs') { if (chunk.data.byteLength !== PHYS_HIDPI.length) return; - const hidpi = chunk.data.every((val, i) => val === PHYS_HIDPI[i]); - return hidpi; + return chunk.data.every((val, i) => val === PHYS_HIDPI[i]); } } return false; @@ -152,7 +198,7 @@ async function loadImageElement(imageFile) { */ function infoForImageFile(matrixClient, roomId, imageFile) { let thumbnailType = "image/png"; - if (imageFile.type == "image/jpeg") { + if (imageFile.type === "image/jpeg") { thumbnailType = "image/jpeg"; } @@ -175,15 +221,15 @@ function infoForImageFile(matrixClient, roomId, imageFile) { * @param {File} videoFile The file to load in an video element. * @return {Promise} A promise that resolves with the video image element. */ -function loadVideoElement(videoFile) { +function loadVideoElement(videoFile): Promise { return new Promise((resolve, reject) => { // Load the file into an html element const video = document.createElement("video"); const reader = new FileReader(); - reader.onload = function(e) { - video.src = e.target.result; + reader.onload = function(ev) { + video.src = ev.target.result as string; // Once ready, returns its size // Wait until we have enough data to thumbnail the first frame. @@ -231,11 +277,11 @@ function infoForVideoFile(matrixClient, roomId, videoFile) { * @return {Promise} A promise that resolves with an ArrayBuffer when the file * is read. */ -function readFileAsArrayBuffer(file) { +function readFileAsArrayBuffer(file: File | Blob): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = function(e) { - resolve(e.target.result); + resolve(e.target.result as ArrayBuffer); }; reader.onerror = function(e) { reject(e); @@ -257,11 +303,11 @@ function readFileAsArrayBuffer(file) { * If the file is unencrypted then the object will have a "url" key. * If the file is encrypted then the object will have a "file" key. */ -function uploadFile(matrixClient, roomId, file, progressHandler) { +function uploadFile(matrixClient: MatrixClient, roomId: string, file: File | Blob, progressHandler?: any) { + let canceled = false; if (matrixClient.isRoomEncrypted(roomId)) { // If the room is encrypted then encrypt the file before uploading it. // First read the file into memory. - let canceled = false; let uploadPromise; let encryptInfo; const prom = readFileAsArrayBuffer(file).then(function(data) { @@ -278,9 +324,9 @@ function uploadFile(matrixClient, roomId, file, progressHandler) { progressHandler: progressHandler, includeFilename: false, }); - return uploadPromise; }).then(function(url) { + if (canceled) throw new UploadCanceledError(); // If the attachment is encrypted then bundle the URL along // with the information needed to decrypt the attachment and // add it under a file key. @@ -290,7 +336,7 @@ function uploadFile(matrixClient, roomId, file, progressHandler) { } return {"file": encryptInfo}; }); - prom.abort = () => { + (prom as IAbortablePromise).abort = () => { canceled = true; if (uploadPromise) MatrixClientPeg.get().cancelUpload(uploadPromise); }; @@ -300,55 +346,23 @@ function uploadFile(matrixClient, roomId, file, progressHandler) { progressHandler: progressHandler, }); const promise1 = basePromise.then(function(url) { + if (canceled) throw new UploadCanceledError(); // If the attachment isn't encrypted then include the URL directly. return {"url": url}; }); - // XXX: copy over the abort method to the new promise - promise1.abort = basePromise.abort; + promise1.abort = () => { + canceled = true; + MatrixClientPeg.get().cancelUpload(basePromise); + }; return promise1; } } export default class ContentMessages { - constructor() { - this.inprogress = []; - this.nextId = 0; - this._mediaConfig = null; - } + private inprogress: IUpload[] = []; + private mediaConfig: IMediaConfig = null; - static sharedInstance() { - if (global.mx_ContentMessages === undefined) { - global.mx_ContentMessages = new ContentMessages(); - } - return global.mx_ContentMessages; - } - - _isFileSizeAcceptable(file) { - if (this._mediaConfig !== null && - this._mediaConfig["m.upload.size"] !== undefined && - file.size > this._mediaConfig["m.upload.size"]) { - return false; - } - return true; - } - - _ensureMediaConfigFetched() { - if (this._mediaConfig !== null) return; - - console.log("[Media Config] Fetching"); - return MatrixClientPeg.get().getMediaConfig().then((config) => { - console.log("[Media Config] Fetched config:", config); - return config; - }).catch(() => { - // Media repo can't or won't report limits, so provide an empty object (no limits). - console.log("[Media Config] Could not fetch config, so not limiting uploads."); - return {}; - }).then((config) => { - this._mediaConfig = config; - }); - } - - sendStickerContentToRoom(url, roomId, info, text, matrixClient) { + sendStickerContentToRoom(url: string, roomId: string, info: string, text: string, matrixClient: MatrixClient) { return MatrixClientPeg.get().sendStickerMessage(roomId, url, info, text).catch((e) => { console.warn(`Failed to send content with URL ${url} to room ${roomId}`, e); throw e; @@ -356,14 +370,14 @@ export default class ContentMessages { } getUploadLimit() { - if (this._mediaConfig !== null && this._mediaConfig["m.upload.size"] !== undefined) { - return this._mediaConfig["m.upload.size"]; + if (this.mediaConfig !== null && this.mediaConfig["m.upload.size"] !== undefined) { + return this.mediaConfig["m.upload.size"]; } else { return null; } } - async sendContentListToRoom(files, roomId, matrixClient) { + async sendContentListToRoom(files: File[], roomId: string, matrixClient: MatrixClient) { if (matrixClient.isGuest()) { dis.dispatch({action: 'require_registration'}); return; @@ -372,32 +386,32 @@ export default class ContentMessages { const isQuoting = Boolean(RoomViewStore.getQuotingEvent()); if (isQuoting) { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - const shouldUpload = await new Promise((resolve) => { - Modal.createTrackedDialog('Upload Reply Warning', '', QuestionDialog, { - title: _t('Replying With Files'), - description: ( -
{_t( - 'At this time it is not possible to reply with a file. ' + - 'Would you like to upload this file without replying?', - )}
- ), - hasCancelButton: true, - button: _t("Continue"), - onFinished: (shouldUpload) => { - resolve(shouldUpload); - }, - }); + const {finished} = Modal.createTrackedDialog<[boolean]>('Upload Reply Warning', '', QuestionDialog, { + title: _t('Replying With Files'), + description: ( +
{_t( + 'At this time it is not possible to reply with a file. ' + + 'Would you like to upload this file without replying?', + )}
+ ), + hasCancelButton: true, + button: _t("Continue"), }); + const [shouldUpload] = await finished; if (!shouldUpload) return; } - await this._ensureMediaConfigFetched(); + if (!this.mediaConfig) { // hot-path optimization to not flash a spinner if we don't need to + const modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner'); + await this.ensureMediaConfigFetched(); + modal.close(); + } const tooBigFiles = []; const okFiles = []; for (let i = 0; i < files.length; ++i) { - if (this._isFileSizeAcceptable(files[i])) { + if (this.isFileSizeAcceptable(files[i])) { okFiles.push(files[i]); } else { tooBigFiles.push(files[i]); @@ -406,17 +420,12 @@ export default class ContentMessages { if (tooBigFiles.length > 0) { const UploadFailureDialog = sdk.getComponent("dialogs.UploadFailureDialog"); - const uploadFailureDialogPromise = new Promise((resolve) => { - Modal.createTrackedDialog('Upload Failure', '', UploadFailureDialog, { - badFiles: tooBigFiles, - totalFiles: files.length, - contentMessages: this, - onFinished: (shouldContinue) => { - resolve(shouldContinue); - }, - }); + const {finished} = Modal.createTrackedDialog<[boolean]>('Upload Failure', '', UploadFailureDialog, { + badFiles: tooBigFiles, + totalFiles: files.length, + contentMessages: this, }); - const shouldContinue = await uploadFailureDialogPromise; + const [shouldContinue] = await finished; if (!shouldContinue) return; } @@ -428,31 +437,47 @@ export default class ContentMessages { for (let i = 0; i < okFiles.length; ++i) { const file = okFiles[i]; if (!uploadAll) { - const shouldContinue = await new Promise((resolve) => { - Modal.createTrackedDialog('Upload Files confirmation', '', UploadConfirmDialog, { - file, - currentIndex: i, - totalFiles: okFiles.length, - onFinished: (shouldContinue, shouldUploadAll) => { - if (shouldUploadAll) { - uploadAll = true; - } - resolve(shouldContinue); - }, - }); + const {finished} = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation', '', UploadConfirmDialog, { + file, + currentIndex: i, + totalFiles: okFiles.length, }); + const [shouldContinue, shouldUploadAll] = await finished; if (!shouldContinue) break; + if (shouldUploadAll) { + uploadAll = true; + } } - promBefore = this._sendContentToRoom(file, roomId, matrixClient, promBefore); + promBefore = this.sendContentToRoom(file, roomId, matrixClient, promBefore); } } - _sendContentToRoom(file, roomId, matrixClient, promBefore) { - const content = { + getCurrentUploads() { + return this.inprogress.filter(u => !u.canceled); + } + + cancelUpload(promise: Promise) { + let upload: IUpload; + for (let i = 0; i < this.inprogress.length; ++i) { + if (this.inprogress[i].promise === promise) { + upload = this.inprogress[i]; + break; + } + } + if (upload) { + upload.canceled = true; + MatrixClientPeg.get().cancelUpload(upload.promise); + dis.dispatch({action: 'upload_canceled', upload}); + } + } + + private sendContentToRoom(file: File, roomId: string, matrixClient: MatrixClient, promBefore: Promise) { + const content: IContent = { body: file.name || 'Attachment', info: { size: file.size, }, + msgtype: "", // set later }; // if we have a mime type for the file, add it to the message metadata @@ -461,25 +486,25 @@ export default class ContentMessages { } const prom = new Promise((resolve) => { - if (file.type.indexOf('image/') == 0) { + if (file.type.indexOf('image/') === 0) { content.msgtype = 'm.image'; - infoForImageFile(matrixClient, roomId, file).then((imageInfo)=>{ + infoForImageFile(matrixClient, roomId, file).then((imageInfo) => { extend(content.info, imageInfo); resolve(); - }, (error)=>{ - console.error(error); + }, (e) => { + console.error(e); content.msgtype = 'm.file'; resolve(); }); - } else if (file.type.indexOf('audio/') == 0) { + } else if (file.type.indexOf('audio/') === 0) { content.msgtype = 'm.audio'; resolve(); - } else if (file.type.indexOf('video/') == 0) { + } else if (file.type.indexOf('video/') === 0) { content.msgtype = 'm.video'; - infoForVideoFile(matrixClient, roomId, file).then((videoInfo)=>{ + infoForVideoFile(matrixClient, roomId, file).then((videoInfo) => { extend(content.info, videoInfo); resolve(); - }, (error)=>{ + }, (e) => { content.msgtype = 'm.file'; resolve(); }); @@ -489,19 +514,23 @@ export default class ContentMessages { } }); - const upload = { + // create temporary abort handler for before the actual upload gets passed off to js-sdk + (prom as IAbortablePromise).abort = () => { + upload.canceled = true; + }; + + const upload: IUpload = { fileName: file.name || 'Attachment', roomId: roomId, - total: 0, + total: file.size, loaded: 0, + promise: prom, }; this.inprogress.push(upload); dis.dispatch({action: 'upload_started'}); // Focus the composer view - dis.dispatch({action: 'focus_composer'}); - - let error; + dis.fire(Action.FocusComposer); function onProgress(ev) { upload.total = ev.total; @@ -509,7 +538,9 @@ export default class ContentMessages { dis.dispatch({action: 'upload_progress', upload: upload}); } + let error; return prom.then(function() { + if (upload.canceled) throw new UploadCanceledError(); // XXX: upload.promise must be the promise that // is returned by uploadFile as it has an abort() // method hacked onto it. @@ -520,16 +551,17 @@ export default class ContentMessages { content.file = result.file; content.url = result.url; }); - }).then((url) => { + }).then(() => { // Await previous message being sent into the room return promBefore; }).then(function() { + if (upload.canceled) throw new UploadCanceledError(); return matrixClient.sendMessage(roomId, content); }, function(err) { error = err; if (!upload.canceled) { let desc = _t("The file '%(fileName)s' failed to upload.", {fileName: upload.fileName}); - if (err.http_status == 413) { + if (err.http_status === 413) { desc = _t( "The file '%(fileName)s' exceeds this homeserver's size limit for uploads", {fileName: upload.fileName}, @@ -542,11 +574,9 @@ export default class ContentMessages { }); } }).finally(() => { - const inprogressKeys = Object.keys(this.inprogress); for (let i = 0; i < this.inprogress.length; ++i) { - const k = inprogressKeys[i]; - if (this.inprogress[k].promise === upload.promise) { - this.inprogress.splice(k, 1); + if (this.inprogress[i].promise === upload.promise) { + this.inprogress.splice(i, 1); break; } } @@ -555,7 +585,7 @@ export default class ContentMessages { // clear the media size limit so we fetch it again next time // we try to upload if (error && error.http_status === 413) { - this._mediaConfig = null; + this.mediaConfig = null; } dis.dispatch({action: 'upload_failed', upload, error}); } else { @@ -565,24 +595,35 @@ export default class ContentMessages { }); } - getCurrentUploads() { - return this.inprogress.filter(u => !u.canceled); + private isFileSizeAcceptable(file: File) { + if (this.mediaConfig !== null && + this.mediaConfig["m.upload.size"] !== undefined && + file.size > this.mediaConfig["m.upload.size"]) { + return false; + } + return true; } - cancelUpload(promise) { - const inprogressKeys = Object.keys(this.inprogress); - let upload; - for (let i = 0; i < this.inprogress.length; ++i) { - const k = inprogressKeys[i]; - if (this.inprogress[k].promise === promise) { - upload = this.inprogress[k]; - break; - } - } - if (upload) { - upload.canceled = true; - MatrixClientPeg.get().cancelUpload(upload.promise); - dis.dispatch({action: 'upload_canceled', upload}); + private ensureMediaConfigFetched() { + if (this.mediaConfig !== null) return; + + console.log("[Media Config] Fetching"); + return MatrixClientPeg.get().getMediaConfig().then((config) => { + console.log("[Media Config] Fetched config:", config); + return config; + }).catch(() => { + // Media repo can't or won't report limits, so provide an empty object (no limits). + console.log("[Media Config] Could not fetch config, so not limiting uploads."); + return {}; + }).then((config) => { + this.mediaConfig = config; + }); + } + + static sharedInstance() { + if (window.mxContentMessages === undefined) { + window.mxContentMessages = new ContentMessages(); } + return window.mxContentMessages; } } diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index da7c0673d2..a584a69d35 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -20,7 +20,6 @@ import {MatrixClientPeg} from './MatrixClientPeg'; import { deriveKey } from 'matrix-js-sdk/src/crypto/key_passphrase'; import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey'; import { _t } from './languageHandler'; -import SettingsStore from './settings/SettingsStore'; import {encodeBase64} from "matrix-js-sdk/src/crypto/olmlib"; // This stores the secret storage private keys in memory for the JS SDK. This is @@ -32,10 +31,7 @@ let secretStorageKeys = {}; let secretStorageBeingAccessed = false; function isCachingAllowed() { - return ( - secretStorageBeingAccessed || - SettingsStore.getValue("keepSecretStoragePassphraseForSession") - ); + return secretStorageBeingAccessed; } export class AccessCancelledError extends Error { @@ -44,25 +40,16 @@ export class AccessCancelledError extends Error { } } -async function confirmToDismiss(name) { - let description; - if (name === "m.cross_signing.user_signing") { - description = _t("If you cancel now, you won't complete verifying the other user."); - } else if (name === "m.cross_signing.self_signing") { - description = _t("If you cancel now, you won't complete verifying your other session."); - } else { - description = _t("If you cancel now, you won't complete your secret storage operation."); - } - +async function confirmToDismiss() { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const [sure] = await Modal.createDialog(QuestionDialog, { title: _t("Cancel entering passphrase?"), - description, - danger: true, - cancelButton: _t("Enter passphrase"), - button: _t("Cancel"), + description: _t("Are you sure you want to cancel entering passphrase?"), + danger: false, + button: _t("Go Back"), + cancelButton: _t("Cancel"), }).finished; - return sure; + return !sure; } async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { @@ -96,11 +83,8 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { { keyInfo: info, checkPrivateKey: async (input) => { - if (!info.pubkey) { - return true; - } const key = await inputToKey(input); - return MatrixClientPeg.get().checkSecretStoragePrivateKey(key, info.pubkey); + return await MatrixClientPeg.get().checkSecretStorageKey(key, info); }, }, /* className= */ null, @@ -109,7 +93,7 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { /* options= */ { onBeforeClose: async (reason) => { if (reason === "backgroundClick") { - return confirmToDismiss(ssssItemName); + return confirmToDismiss(); } return true; }, @@ -188,7 +172,7 @@ export async function promptForBackupPassphrase() { const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, { - showSummary: false, keyCallback: k => key = k, + showSummary: false, keyCallback: k => key = k, }, null, /* priority = */ false, /* static = */ true); const success = await finished; diff --git a/src/DeviceListener.js b/src/DeviceListener.js deleted file mode 100644 index f8555c7602..0000000000 --- a/src/DeviceListener.js +++ /dev/null @@ -1,203 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { MatrixClientPeg } from './MatrixClientPeg'; -import SettingsStore from './settings/SettingsStore'; -import * as sdk from './index'; -import { _t } from './languageHandler'; -import ToastStore from './stores/ToastStore'; - -function toastKey(deviceId) { - return 'unverified_session_' + deviceId; -} - -const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000; -const THIS_DEVICE_TOAST_KEY = 'setupencryption'; - -export default class DeviceListener { - static sharedInstance() { - if (!global.mx_DeviceListener) global.mx_DeviceListener = new DeviceListener(); - return global.mx_DeviceListener; - } - - constructor() { - // set of device IDs we're currently showing toasts for - this._activeNagToasts = new Set(); - // device IDs for which the user has dismissed the verify toast ('Later') - this._dismissed = new Set(); - // has the user dismissed any of the various nag toasts to setup encryption on this device? - this._dismissedThisDeviceToast = false; - - // cache of the key backup info - this._keyBackupInfo = null; - this._keyBackupFetchedAt = null; - } - - start() { - MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated); - MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged); - MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged); - MatrixClientPeg.get().on('accountData', this._onAccountData); - this._recheck(); - } - - stop() { - if (MatrixClientPeg.get()) { - MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated); - MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged); - MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged); - MatrixClientPeg.get().removeListener('accountData', this._onAccountData); - } - this._dismissed.clear(); - } - - dismissVerification(deviceId) { - this._dismissed.add(deviceId); - this._recheck(); - } - - dismissEncryptionSetup() { - this._dismissedThisDeviceToast = true; - this._recheck(); - } - - _onDevicesUpdated = (users) => { - if (!users.includes(MatrixClientPeg.get().getUserId())) return; - this._recheck(); - } - - _onDeviceVerificationChanged = (userId) => { - if (userId !== MatrixClientPeg.get().getUserId()) return; - this._recheck(); - } - - _onUserTrustStatusChanged = (userId, trustLevel) => { - if (userId !== MatrixClientPeg.get().getUserId()) return; - this._recheck(); - } - - _onAccountData = (ev) => { - // User may have migrated SSSS to symmetric, in which case we can dismiss that toast - if (ev.getType().startsWith('m.secret_storage.key.')) { - this._recheck(); - } - } - - // The server doesn't tell us when key backup is set up, so we poll - // & cache the result - async _getKeyBackupInfo() { - const now = (new Date()).getTime(); - if (!this._keyBackupInfo || this._keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) { - this._keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); - this._keyBackupFetchedAt = now; - } - return this._keyBackupInfo; - } - - async _recheck() { - const cli = MatrixClientPeg.get(); - - if ( - !SettingsStore.isFeatureEnabled("feature_cross_signing") || - !await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing") - ) return; - - if (!cli.isCryptoEnabled()) return; - - const crossSigningReady = await cli.isCrossSigningReady(); - - if (this._dismissedThisDeviceToast) { - ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY); - } else { - if (!crossSigningReady) { - // cross signing isn't enabled - nag to enable it - // There are 3 different toasts for: - if (cli.getStoredCrossSigningForUser(cli.getUserId())) { - // Cross-signing on account but this device doesn't trust the master key (verify this session) - ToastStore.sharedInstance().addOrReplaceToast({ - key: THIS_DEVICE_TOAST_KEY, - title: _t("Verify this session"), - icon: "verification_warning", - props: {kind: 'verify_this_session'}, - component: sdk.getComponent("toasts.SetupEncryptionToast"), - }); - } else { - const backupInfo = await this._getKeyBackupInfo(); - if (backupInfo) { - // No cross-signing on account but key backup available (upgrade encryption) - ToastStore.sharedInstance().addOrReplaceToast({ - key: THIS_DEVICE_TOAST_KEY, - title: _t("Encryption upgrade available"), - icon: "verification_warning", - props: {kind: 'upgrade_encryption'}, - component: sdk.getComponent("toasts.SetupEncryptionToast"), - }); - } else { - // No cross-signing or key backup on account (set up encryption) - ToastStore.sharedInstance().addOrReplaceToast({ - key: THIS_DEVICE_TOAST_KEY, - title: _t("Set up encryption"), - icon: "verification_warning", - props: {kind: 'set_up_encryption'}, - component: sdk.getComponent("toasts.SetupEncryptionToast"), - }); - } - } - return; - } else if (await cli.secretStorageKeyNeedsUpgrade()) { - ToastStore.sharedInstance().addOrReplaceToast({ - key: THIS_DEVICE_TOAST_KEY, - title: _t("Encryption upgrade available"), - icon: "verification_warning", - props: {kind: 'upgrade_ssss'}, - component: sdk.getComponent("toasts.SetupEncryptionToast"), - }); - } - } - - // as long as cross-signing isn't ready, - // you can't see or dismiss any device toasts - if (crossSigningReady) { - const newActiveToasts = new Set(); - - const devices = await cli.getStoredDevicesForUser(cli.getUserId()); - for (const device of devices) { - if (device.deviceId == cli.deviceId) continue; - - const deviceTrust = await cli.checkDeviceTrust(cli.getUserId(), device.deviceId); - if (deviceTrust.isCrossSigningVerified() || this._dismissed.has(device.deviceId)) { - ToastStore.sharedInstance().dismissToast(toastKey(device.deviceId)); - } else { - this._activeNagToasts.add(device.deviceId); - ToastStore.sharedInstance().addOrReplaceToast({ - key: toastKey(device.deviceId), - title: _t("Unverified login. Was this you?"), - icon: "verification_warning", - props: { device }, - component: sdk.getComponent("toasts.UnverifiedSessionToast"), - }); - newActiveToasts.add(device.deviceId); - } - } - - // clear any other outstanding toasts (eg. logged out devices) - for (const deviceId of this._activeNagToasts) { - if (!newActiveToasts.has(deviceId)) ToastStore.sharedInstance().dismissToast(toastKey(deviceId)); - } - this._activeNagToasts = newActiveToasts; - } - } -} diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts new file mode 100644 index 0000000000..a37521118f --- /dev/null +++ b/src/DeviceListener.ts @@ -0,0 +1,266 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {MatrixClientPeg} from './MatrixClientPeg'; +import { + hideToast as hideBulkUnverifiedSessionsToast, + showToast as showBulkUnverifiedSessionsToast, +} from "./toasts/BulkUnverifiedSessionsToast"; +import { + hideToast as hideSetupEncryptionToast, + Kind as SetupKind, + showToast as showSetupEncryptionToast, +} from "./toasts/SetupEncryptionToast"; +import { + hideToast as hideUnverifiedSessionsToast, + showToast as showUnverifiedSessionsToast, +} from "./toasts/UnverifiedSessionToast"; +import {privateShouldBeEncrypted} from "./createRoom"; + +const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000; + +export default class DeviceListener { + // device IDs for which the user has dismissed the verify toast ('Later') + private dismissed = new Set(); + // has the user dismissed any of the various nag toasts to setup encryption on this device? + private dismissedThisDeviceToast = false; + // cache of the key backup info + private keyBackupInfo: object = null; + private keyBackupFetchedAt: number = null; + // We keep a list of our own device IDs so we can batch ones that were already + // there the last time the app launched into a single toast, but display new + // ones in their own toasts. + private ourDeviceIdsAtStart: Set = null; + // The set of device IDs we're currently displaying toasts for + private displayingToastsForDeviceIds = new Set(); + + static sharedInstance() { + if (!window.mxDeviceListener) window.mxDeviceListener = new DeviceListener(); + return window.mxDeviceListener; + } + + start() { + MatrixClientPeg.get().on('crypto.willUpdateDevices', this._onWillUpdateDevices); + MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated); + MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged); + MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged); + MatrixClientPeg.get().on('crossSigning.keysChanged', this._onCrossSingingKeysChanged); + MatrixClientPeg.get().on('accountData', this._onAccountData); + MatrixClientPeg.get().on('sync', this._onSync); + this._recheck(); + } + + stop() { + if (MatrixClientPeg.get()) { + MatrixClientPeg.get().removeListener('crypto.willUpdateDevices', this._onWillUpdateDevices); + MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated); + MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged); + MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged); + MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this._onCrossSingingKeysChanged); + MatrixClientPeg.get().removeListener('accountData', this._onAccountData); + MatrixClientPeg.get().removeListener('sync', this._onSync); + } + this.dismissed.clear(); + this.dismissedThisDeviceToast = false; + this.keyBackupInfo = null; + this.keyBackupFetchedAt = null; + this.ourDeviceIdsAtStart = null; + this.displayingToastsForDeviceIds = new Set(); + } + + /** + * Dismiss notifications about our own unverified devices + * + * @param {String[]} deviceIds List of device IDs to dismiss notifications for + */ + async dismissUnverifiedSessions(deviceIds: Iterable) { + for (const d of deviceIds) { + this.dismissed.add(d); + } + + this._recheck(); + } + + dismissEncryptionSetup() { + this.dismissedThisDeviceToast = true; + this._recheck(); + } + + _ensureDeviceIdsAtStartPopulated() { + if (this.ourDeviceIdsAtStart === null) { + const cli = MatrixClientPeg.get(); + this.ourDeviceIdsAtStart = new Set( + cli.getStoredDevicesForUser(cli.getUserId()).map(d => d.deviceId), + ); + } + } + + _onWillUpdateDevices = async (users: string[], initialFetch?: boolean) => { + // If we didn't know about *any* devices before (ie. it's fresh login), + // then they are all pre-existing devices, so ignore this and set the + // devicesAtStart list to the devices that we see after the fetch. + if (initialFetch) return; + + const myUserId = MatrixClientPeg.get().getUserId(); + if (users.includes(myUserId)) this._ensureDeviceIdsAtStartPopulated(); + + // No need to do a recheck here: we just need to get a snapshot of our devices + // before we download any new ones. + }; + + _onDevicesUpdated = (users: string[]) => { + if (!users.includes(MatrixClientPeg.get().getUserId())) return; + this._recheck(); + }; + + _onDeviceVerificationChanged = (userId: string) => { + if (userId !== MatrixClientPeg.get().getUserId()) return; + this._recheck(); + }; + + _onUserTrustStatusChanged = (userId: string) => { + if (userId !== MatrixClientPeg.get().getUserId()) return; + this._recheck(); + }; + + _onCrossSingingKeysChanged = () => { + this._recheck(); + }; + + _onAccountData = (ev) => { + // User may have: + // * migrated SSSS to symmetric + // * uploaded keys to secret storage + // * completed secret storage creation + // which result in account data changes affecting checks below. + if ( + ev.getType().startsWith('m.secret_storage.') || + ev.getType().startsWith('m.cross_signing.') + ) { + this._recheck(); + } + }; + + _onSync = (state, prevState) => { + if (state === 'PREPARED' && prevState === null) this._recheck(); + }; + + // The server doesn't tell us when key backup is set up, so we poll + // & cache the result + async _getKeyBackupInfo() { + const now = (new Date()).getTime(); + if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) { + this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); + this.keyBackupFetchedAt = now; + } + return this.keyBackupInfo; + } + + private shouldShowSetupEncryptionToast() { + // In a default configuration, show the toasts. If the well-known config causes e2ee default to be false + // then do not show the toasts until user is in at least one encrypted room. + if (privateShouldBeEncrypted()) return true; + const cli = MatrixClientPeg.get(); + return cli && cli.getRooms().some(r => cli.isRoomEncrypted(r.roomId)); + } + + async _recheck() { + const cli = MatrixClientPeg.get(); + + if (!await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) return; + + if (!cli.isCryptoEnabled()) return; + // don't recheck until the initial sync is complete: lots of account data events will fire + // while the initial sync is processing and we don't need to recheck on each one of them + // (we add a listener on sync to do once check after the initial sync is done) + if (!cli.isInitialSyncComplete()) return; + + const crossSigningReady = await cli.isCrossSigningReady(); + + if (this.dismissedThisDeviceToast || crossSigningReady) { + hideSetupEncryptionToast(); + } else if (this.shouldShowSetupEncryptionToast()) { + // make sure our keys are finished downloading + await cli.downloadKeys([cli.getUserId()]); + // cross signing isn't enabled - nag to enable it + // There are 3 different toasts for: + if (cli.getStoredCrossSigningForUser(cli.getUserId())) { + // Cross-signing on account but this device doesn't trust the master key (verify this session) + showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION); + } else { + const backupInfo = await this._getKeyBackupInfo(); + if (backupInfo) { + // No cross-signing on account but key backup available (upgrade encryption) + showSetupEncryptionToast(SetupKind.UPGRADE_ENCRYPTION); + } else { + // No cross-signing or key backup on account (set up encryption) + showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION); + } + } + } + + // This needs to be done after awaiting on downloadKeys() above, so + // we make sure we get the devices after the fetch is done. + this._ensureDeviceIdsAtStartPopulated(); + + // Unverified devices that were there last time the app ran + // (technically could just be a boolean: we don't actually + // need to remember the device IDs, but for the sake of + // symmetry...). + const oldUnverifiedDeviceIds = new Set(); + // Unverified devices that have appeared since then + const newUnverifiedDeviceIds = new Set(); + + // as long as cross-signing isn't ready, + // you can't see or dismiss any device toasts + if (crossSigningReady) { + const devices = cli.getStoredDevicesForUser(cli.getUserId()); + for (const device of devices) { + if (device.deviceId === cli.deviceId) continue; + + const deviceTrust = await cli.checkDeviceTrust(cli.getUserId(), device.deviceId); + if (!deviceTrust.isCrossSigningVerified() && !this.dismissed.has(device.deviceId)) { + if (this.ourDeviceIdsAtStart.has(device.deviceId)) { + oldUnverifiedDeviceIds.add(device.deviceId); + } else { + newUnverifiedDeviceIds.add(device.deviceId); + } + } + } + } + + // Display or hide the batch toast for old unverified sessions + if (oldUnverifiedDeviceIds.size > 0) { + showBulkUnverifiedSessionsToast(oldUnverifiedDeviceIds); + } else { + hideBulkUnverifiedSessionsToast(); + } + + // Show toasts for new unverified devices if they aren't already there + for (const deviceId of newUnverifiedDeviceIds) { + showUnverifiedSessionsToast(deviceId); + } + + // ...and hide any we don't need any more + for (const deviceId of this.displayingToastsForDeviceIds) { + if (!newUnverifiedDeviceIds.has(deviceId)) { + hideUnverifiedSessionsToast(deviceId); + } + } + + this.displayingToastsForDeviceIds = newUnverifiedDeviceIds; + } +} diff --git a/src/FromWidgetPostMessageApi.js b/src/FromWidgetPostMessageApi.js index ea76c85643..1b4aa19ebf 100644 --- a/src/FromWidgetPostMessageApi.js +++ b/src/FromWidgetPostMessageApi.js @@ -17,15 +17,15 @@ limitations under the License. */ import URL from 'url'; -import dis from './dispatcher'; +import dis from './dispatcher/dispatcher'; import WidgetMessagingEndpoint from './WidgetMessagingEndpoint'; import ActiveWidgetStore from './stores/ActiveWidgetStore'; import {MatrixClientPeg} from "./MatrixClientPeg"; import RoomViewStore from "./stores/RoomViewStore"; import {IntegrationManagers} from "./integrations/IntegrationManagers"; import SettingsStore from "./settings/SettingsStore"; -import {Capability, KnownWidgetActions} from "./widgets/WidgetApi"; -import SdkConfig from "./SdkConfig"; +import {Capability} from "./widgets/WidgetApi"; +import {objectClone} from "./utils/objects"; const WIDGET_API_VERSION = '0.0.2'; // Current API version const SUPPORTED_WIDGET_API_VERSIONS = [ @@ -101,7 +101,7 @@ export default class FromWidgetPostMessageApi { console.warn('Add FromWidgetPostMessageApi - Endpoint already registered'); return; } else { - console.warn(`Adding fromWidget messaging endpoint for ${widgetId}`, endpoint); + console.log(`Adding fromWidget messaging endpoint for ${widgetId}`, endpoint); this.widgetMessagingEndpoints.push(endpoint); } } @@ -166,7 +166,7 @@ export default class FromWidgetPostMessageApi { const action = event.data.action; const widgetId = event.data.widgetId; if (action === 'content_loaded') { - console.warn('Widget reported content loaded for', widgetId); + console.log('Widget reported content loaded for', widgetId); dis.dispatch({ action: 'widget_content_loaded', widgetId: widgetId, @@ -220,13 +220,6 @@ export default class FromWidgetPostMessageApi { } } else if (action === 'get_openid') { // Handled by caller - } else if (action === KnownWidgetActions.GetRiotWebConfig) { - if (ActiveWidgetStore.widgetHasCapability(widgetId, Capability.GetRiotWebConfig)) { - this.sendResponse(event, { - api: INBOUND_API_NAME, - config: SdkConfig.get(), - }); - } } else { console.warn('Widget postMessage event unhandled'); this.sendError(event, {message: 'The postMessage was unhandled'}); @@ -255,7 +248,7 @@ export default class FromWidgetPostMessageApi { * @param {Object} res Response data */ sendResponse(event, res) { - const data = JSON.parse(JSON.stringify(event.data)); + const data = objectClone(event.data); data.response = res; event.source.postMessage(data, event.origin); } @@ -268,7 +261,7 @@ export default class FromWidgetPostMessageApi { */ sendError(event, msg, nestedError) { console.error('Action:' + event.data.action + ' failed with message: ' + msg); - const data = JSON.parse(JSON.stringify(event.data)); + const data = objectClone(event.data); data.response = { error: { message: msg, diff --git a/src/GroupAddressPicker.js b/src/GroupAddressPicker.js index 9131a89e5d..e7ae3217bb 100644 --- a/src/GroupAddressPicker.js +++ b/src/GroupAddressPicker.js @@ -22,6 +22,7 @@ import { _t } from './languageHandler'; import {MatrixClientPeg} from './MatrixClientPeg'; import GroupStore from './stores/GroupStore'; import {allSettled} from "./utils/promise"; +import StyledCheckbox from './components/views/elements/StyledCheckbox'; export function showGroupInviteDialog(groupId) { return new Promise((resolve, reject) => { @@ -61,19 +62,19 @@ export function showGroupAddRoomDialog(groupId) {
{ _t("Which rooms would you like to add to this community?") }
; - const checkboxContainer = ; + const checkboxContainer = + { _t("Show these rooms to non-members on the community page and room list?") } + ; const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog"); Modal.createTrackedDialog('Add Rooms to Group', '', AddressPickerDialog, { title: _t("Add rooms to the community"), description: description, extraNode: checkboxContainer, - placeholder: _t("Room name or alias"), + placeholder: _t("Room name or address"), button: _t("Add to community"), pickerType: 'room', validAddressTypes: ['mx-room-id'], diff --git a/src/HtmlUtils.js b/src/HtmlUtils.tsx similarity index 81% rename from src/HtmlUtils.js rename to src/HtmlUtils.tsx index a58ea25c8a..77a9579f2c 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.tsx @@ -17,10 +17,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; - -import ReplyThread from "./components/views/elements/ReplyThread"; - import React from 'react'; import sanitizeHtml from 'sanitize-html'; import * as linkify from 'linkifyjs'; @@ -28,12 +24,13 @@ import linkifyMatrix from './linkify-matrix'; import _linkifyElement from 'linkifyjs/element'; import _linkifyString from 'linkifyjs/string'; import classNames from 'classnames'; -import {MatrixClientPeg} from './MatrixClientPeg'; +import EMOJIBASE_REGEX from 'emojibase-regex'; import url from 'url'; -import EMOJIBASE_REGEX from 'emojibase-regex'; +import {MatrixClientPeg} from './MatrixClientPeg'; import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks"; import {SHORTCODE_TO_EMOJI, getEmojiFromUnicode} from "./emoji"; +import ReplyThread from "./components/views/elements/ReplyThread"; linkifyMatrix(linkify); @@ -64,7 +61,7 @@ const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet']; * need emojification. * unicodeToImage uses this function. */ -function mightContainEmoji(str) { +function mightContainEmoji(str: string) { return SURROGATE_PAIR_PATTERN.test(str) || SYMBOL_PATTERN.test(str); } @@ -74,7 +71,7 @@ function mightContainEmoji(str) { * @param {String} char The emoji character * @return {String} The shortcode (such as :thumbup:) */ -export function unicodeToShortcode(char) { +export function unicodeToShortcode(char: string) { const data = getEmojiFromUnicode(char); return (data && data.shortcodes ? `:${data.shortcodes[0]}:` : ''); } @@ -85,7 +82,7 @@ export function unicodeToShortcode(char) { * @param {String} shortcode The shortcode (such as :thumbup:) * @return {String} The emoji character; null if none exists */ -export function shortcodeToUnicode(shortcode) { +export function shortcodeToUnicode(shortcode: string) { shortcode = shortcode.slice(1, shortcode.length - 1); const data = SHORTCODE_TO_EMOJI.get(shortcode); return data ? data.unicode : null; @@ -100,7 +97,7 @@ export function processHtmlForSending(html: string): string { } let contentHTML = ""; - for (let i=0; i < contentDiv.children.length; i++) { + for (let i = 0; i < contentDiv.children.length; i++) { const element = contentDiv.children[i]; if (element.tagName.toLowerCase() === 'p') { contentHTML += element.innerHTML; @@ -122,12 +119,19 @@ export function processHtmlForSending(html: string): string { * Given an untrusted HTML string, return a React node with an sanitized version * of that HTML. */ -export function sanitizedHtmlNode(insaneHtml) { +export function sanitizedHtmlNode(insaneHtml: string) { const saneHtml = sanitizeHtml(insaneHtml, sanitizeHtmlParams); return
; } +export function sanitizedHtmlNodeInnerText(insaneHtml: string) { + const saneHtml = sanitizeHtml(insaneHtml, sanitizeHtmlParams); + const contentDiv = document.createElement("div"); + contentDiv.innerHTML = saneHtml; + return contentDiv.innerText; +} + /** * Tests if a URL from an untrusted source may be safely put into the DOM * The biggest threat here is javascript: URIs. @@ -136,7 +140,7 @@ export function sanitizedHtmlNode(insaneHtml) { * other places we need to sanitise URLs. * @return true if permitted, otherwise false */ -export function isUrlPermitted(inputUrl) { +export function isUrlPermitted(inputUrl: string) { try { const parsed = url.parse(inputUrl); if (!parsed.protocol) return false; @@ -147,9 +151,9 @@ export function isUrlPermitted(inputUrl) { } } -const transformTags = { // custom to matrix +const transformTags: sanitizeHtml.IOptions["transformTags"] = { // custom to matrix // add blank targets to all hyperlinks except vector URLs - 'a': function(tagName, attribs) { + 'a': function(tagName: string, attribs: sanitizeHtml.Attributes) { if (attribs.href) { attribs.target = '_blank'; // by default @@ -162,7 +166,7 @@ const transformTags = { // custom to matrix attribs.rel = 'noreferrer noopener'; // https://mathiasbynens.github.io/rel-noopener/ return { tagName, attribs }; }, - 'img': function(tagName, attribs) { + 'img': function(tagName: string, attribs: sanitizeHtml.Attributes) { // Strip out imgs that aren't `mxc` here instead of using allowedSchemesByTag // because transformTags is used _before_ we filter by allowedSchemesByTag and // we don't want to allow images with `https?` `src`s. @@ -176,17 +180,17 @@ const transformTags = { // custom to matrix ); return { tagName, attribs }; }, - 'code': function(tagName, attribs) { + 'code': function(tagName: string, attribs: sanitizeHtml.Attributes) { if (typeof attribs.class !== 'undefined') { // Filter out all classes other than ones starting with language- for syntax highlighting. const classes = attribs.class.split(/\s/).filter(function(cl) { - return cl.startsWith('language-'); + return cl.startsWith('language-') && !cl.startsWith('language-_'); }); attribs.class = classes.join(' '); } return { tagName, attribs }; }, - '*': function(tagName, attribs) { + '*': function(tagName: string, attribs: sanitizeHtml.Attributes) { // Delete any style previously assigned, style is an allowedTag for font and span // because attributes are stripped after transforming delete attribs.style; @@ -220,7 +224,7 @@ const transformTags = { // custom to matrix }, }; -const sanitizeHtmlParams = { +const sanitizeHtmlParams: sanitizeHtml.IOptions = { allowedTags: [ 'font', // custom to matrix for IRC-style font coloring 'del', // for markdown @@ -247,16 +251,16 @@ const sanitizeHtmlParams = { }; // this is the same as the above except with less rewriting -const composerSanitizeHtmlParams = Object.assign({}, sanitizeHtmlParams); -composerSanitizeHtmlParams.transformTags = { - 'code': transformTags['code'], - '*': transformTags['*'], +const composerSanitizeHtmlParams: sanitizeHtml.IOptions = { + ...sanitizeHtmlParams, + transformTags: { + 'code': transformTags['code'], + '*': transformTags['*'], + }, }; -class BaseHighlighter { - constructor(highlightClass, highlightLink) { - this.highlightClass = highlightClass; - this.highlightLink = highlightLink; +abstract class BaseHighlighter { + constructor(public highlightClass: string, public highlightLink: string) { } /** @@ -270,47 +274,49 @@ class BaseHighlighter { * returns a list of results (strings for HtmlHighligher, react nodes for * TextHighlighter). */ - applyHighlights(safeSnippet, safeHighlights) { + public applyHighlights(safeSnippet: string, safeHighlights: string[]): T[] { let lastOffset = 0; let offset; - let nodes = []; + let nodes: T[] = []; const safeHighlight = safeHighlights[0]; while ((offset = safeSnippet.toLowerCase().indexOf(safeHighlight.toLowerCase(), lastOffset)) >= 0) { // handle preamble if (offset > lastOffset) { - var subSnippet = safeSnippet.substring(lastOffset, offset); - nodes = nodes.concat(this._applySubHighlights(subSnippet, safeHighlights)); + const subSnippet = safeSnippet.substring(lastOffset, offset); + nodes = nodes.concat(this.applySubHighlights(subSnippet, safeHighlights)); } // do highlight. use the original string rather than safeHighlight // to preserve the original casing. const endOffset = offset + safeHighlight.length; - nodes.push(this._processSnippet(safeSnippet.substring(offset, endOffset), true)); + nodes.push(this.processSnippet(safeSnippet.substring(offset, endOffset), true)); lastOffset = endOffset; } // handle postamble if (lastOffset !== safeSnippet.length) { - subSnippet = safeSnippet.substring(lastOffset, undefined); - nodes = nodes.concat(this._applySubHighlights(subSnippet, safeHighlights)); + const subSnippet = safeSnippet.substring(lastOffset, undefined); + nodes = nodes.concat(this.applySubHighlights(subSnippet, safeHighlights)); } return nodes; } - _applySubHighlights(safeSnippet, safeHighlights) { + private applySubHighlights(safeSnippet: string, safeHighlights: string[]): T[] { if (safeHighlights[1]) { // recurse into this range to check for the next set of highlight matches return this.applyHighlights(safeSnippet, safeHighlights.slice(1)); } else { // no more highlights to be found, just return the unhighlighted string - return [this._processSnippet(safeSnippet, false)]; + return [this.processSnippet(safeSnippet, false)]; } } + + protected abstract processSnippet(snippet: string, highlight: boolean): T; } -class HtmlHighlighter extends BaseHighlighter { +class HtmlHighlighter extends BaseHighlighter { /* highlight the given snippet if required * * snippet: content of the span; must have been sanitised @@ -318,28 +324,23 @@ class HtmlHighlighter extends BaseHighlighter { * * returns an HTML string */ - _processSnippet(snippet, highlight) { + protected processSnippet(snippet: string, highlight: boolean): string { if (!highlight) { // nothing required here return snippet; } - let span = "" - + snippet + ""; + let span = `${snippet}`; if (this.highlightLink) { - span = "" - +span+""; + span = `${span}`; } return span; } } -class TextHighlighter extends BaseHighlighter { - constructor(highlightClass, highlightLink) { - super(highlightClass, highlightLink); - this._key = 0; - } +class TextHighlighter extends BaseHighlighter { + private key = 0; /* create a node to hold the given content * @@ -348,13 +349,12 @@ class TextHighlighter extends BaseHighlighter { * * returns a React node */ - _processSnippet(snippet, highlight) { - const key = this._key++; + protected processSnippet(snippet: string, highlight: boolean): React.ReactNode { + const key = this.key++; - let node = - - { snippet } - ; + let node = + { snippet } + ; if (highlight && this.highlightLink) { node = { node }; @@ -364,6 +364,20 @@ class TextHighlighter extends BaseHighlighter { } } +interface IContent { + format?: string; + formatted_body?: string; + body: string; +} + +interface IOpts { + highlightLink?: string; + disableBigEmoji?: boolean; + stripReplyFallback?: boolean; + returnString?: boolean; + forComposerQuote?: boolean; + ref?: React.Ref; +} /* turn a matrix event body into html * @@ -378,7 +392,7 @@ class TextHighlighter extends BaseHighlighter { * opts.forComposerQuote: optional param to lessen the url rewriting done by sanitization, for quoting into composer * opts.ref: React ref to attach to any React components returned (not compatible with opts.returnString) */ -export function bodyToHtml(content, highlights, opts={}) { +export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts = {}) { const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body; let bodyHasEmoji = false; @@ -387,9 +401,9 @@ export function bodyToHtml(content, highlights, opts={}) { sanitizeParams = composerSanitizeHtmlParams; } - let strippedBody; - let safeBody; - let isDisplayedWithHtml; + let strippedBody: string; + let safeBody: string; + let isDisplayedWithHtml: boolean; // XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying // to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which // are interrupted by HTML tags (not that we did before) - e.g. foobar won't get highlighted @@ -446,7 +460,8 @@ export function bodyToHtml(content, highlights, opts={}) { // their username. Permalinks (links in pills) can be any URL // now, so we just check for an HTTP-looking thing. ( - content.formatted_body == undefined || + strippedBody === safeBody || // replies have the html fallbacks, account for that here + content.formatted_body === undefined || (!content.formatted_body.includes("http:") && !content.formatted_body.includes("https:")) ); @@ -470,7 +485,7 @@ export function bodyToHtml(content, highlights, opts={}) { * @param {object} [options] Options for linkifyString. Default: linkifyMatrix.options * @returns {string} Linkified string */ -export function linkifyString(str, options = linkifyMatrix.options) { +export function linkifyString(str: string, options = linkifyMatrix.options) { return _linkifyString(str, options); } @@ -481,7 +496,7 @@ export function linkifyString(str, options = linkifyMatrix.options) { * @param {object} [options] Options for linkifyElement. Default: linkifyMatrix.options * @returns {object} */ -export function linkifyElement(element, options = linkifyMatrix.options) { +export function linkifyElement(element: HTMLElement, options = linkifyMatrix.options) { return _linkifyElement(element, options); } @@ -492,7 +507,7 @@ export function linkifyElement(element, options = linkifyMatrix.options) { * @param {object} [options] Options for linkifyString. Default: linkifyMatrix.options * @returns {string} */ -export function linkifyAndSanitizeHtml(dirtyHtml, options = linkifyMatrix.options) { +export function linkifyAndSanitizeHtml(dirtyHtml: string, options = linkifyMatrix.options) { return sanitizeHtml(linkifyString(dirtyHtml, options), sanitizeHtmlParams); } @@ -503,7 +518,7 @@ export function linkifyAndSanitizeHtml(dirtyHtml, options = linkifyMatrix.option * @param {Node} node * @returns {bool} */ -export function checkBlockNode(node) { +export function checkBlockNode(node: Node) { switch (node.nodeName) { case "H1": case "H2": diff --git a/src/KeyRequestHandler.js b/src/KeyRequestHandler.js deleted file mode 100644 index 30f3b7d50e..0000000000 --- a/src/KeyRequestHandler.js +++ /dev/null @@ -1,158 +0,0 @@ -/* -Copyright 2017 Vector Creations Ltd -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import * as sdk from './index'; -import Modal from './Modal'; -import SettingsStore from './settings/SettingsStore'; - -// TODO: We can remove this once cross-signing is the only way. -// https://github.com/vector-im/riot-web/issues/11908 -export default class KeyRequestHandler { - constructor(matrixClient) { - this._matrixClient = matrixClient; - - // the user/device for which we currently have a dialog open - this._currentUser = null; - this._currentDevice = null; - - // userId -> deviceId -> [keyRequest] - this._pendingKeyRequests = Object.create(null); - } - - handleKeyRequest(keyRequest) { - // Ignore own device key requests if cross-signing lab enabled - if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { - return; - } - - const userId = keyRequest.userId; - const deviceId = keyRequest.deviceId; - const requestId = keyRequest.requestId; - - if (!this._pendingKeyRequests[userId]) { - this._pendingKeyRequests[userId] = Object.create(null); - } - if (!this._pendingKeyRequests[userId][deviceId]) { - this._pendingKeyRequests[userId][deviceId] = []; - } - - // check if we already have this request - const requests = this._pendingKeyRequests[userId][deviceId]; - if (requests.find((r) => r.requestId === requestId)) { - console.log("Already have this key request, ignoring"); - return; - } - - requests.push(keyRequest); - - if (this._currentUser) { - // ignore for now - console.log("Key request, but we already have a dialog open"); - return; - } - - this._processNextRequest(); - } - - handleKeyRequestCancellation(cancellation) { - // Ignore own device key requests if cross-signing lab enabled - if (SettingsStore.isFeatureEnabled("feature_cross_signing")) { - return; - } - - // see if we can find the request in the queue - const userId = cancellation.userId; - const deviceId = cancellation.deviceId; - const requestId = cancellation.requestId; - - if (userId === this._currentUser && deviceId === this._currentDevice) { - console.log( - "room key request cancellation for the user we currently have a" - + " dialog open for", - ); - // TODO: update the dialog. For now, we just ignore the - // cancellation. - return; - } - - if (!this._pendingKeyRequests[userId]) { - return; - } - const requests = this._pendingKeyRequests[userId][deviceId]; - if (!requests) { - return; - } - const idx = requests.findIndex((r) => r.requestId === requestId); - if (idx < 0) { - return; - } - console.log("Forgetting room key request"); - requests.splice(idx, 1); - if (requests.length === 0) { - delete this._pendingKeyRequests[userId][deviceId]; - if (Object.keys(this._pendingKeyRequests[userId]).length === 0) { - delete this._pendingKeyRequests[userId]; - } - } - } - - _processNextRequest() { - const userId = Object.keys(this._pendingKeyRequests)[0]; - if (!userId) { - return; - } - const deviceId = Object.keys(this._pendingKeyRequests[userId])[0]; - if (!deviceId) { - return; - } - console.log(`Starting KeyShareDialog for ${userId}:${deviceId}`); - - const finished = (r) => { - this._currentUser = null; - this._currentDevice = null; - - if (!this._pendingKeyRequests[userId] || !this._pendingKeyRequests[userId][deviceId]) { - // request was removed in the time the dialog was displayed - this._processNextRequest(); - return; - } - - if (r) { - for (const req of this._pendingKeyRequests[userId][deviceId]) { - req.share(); - } - } - delete this._pendingKeyRequests[userId][deviceId]; - if (Object.keys(this._pendingKeyRequests[userId]).length === 0) { - delete this._pendingKeyRequests[userId]; - } - - this._processNextRequest(); - }; - - const KeyShareDialog = sdk.getComponent("dialogs.KeyShareDialog"); - Modal.appendTrackedDialog('Key Share', 'Process Next Request', KeyShareDialog, { - matrixClient: this._matrixClient, - userId: userId, - deviceId: deviceId, - onFinished: finished, - }); - this._currentUser = userId; - this._currentDevice = deviceId; - } -} - diff --git a/src/Keyboard.ts b/src/Keyboard.ts index 23e2bbf0d6..7040898872 100644 --- a/src/Keyboard.ts +++ b/src/Keyboard.ts @@ -43,6 +43,8 @@ export const Key = { BACKTICK: "`", SPACE: " ", SLASH: "/", + SQUARE_BRACKET_LEFT: "[", + SQUARE_BRACKET_RIGHT: "]", A: "a", B: "b", C: "c", diff --git a/src/Lifecycle.js b/src/Lifecycle.js index b9fbf4f1bc..a05392c3e9 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -26,7 +26,7 @@ import Analytics from './Analytics'; import Notifier from './Notifier'; import UserActivity from './UserActivity'; import Presence from './Presence'; -import dis from './dispatcher'; +import dis from './dispatcher/dispatcher'; import DMRoomMap from './utils/DMRoomMap'; import Modal from './Modal'; import * as sdk from './index'; @@ -40,6 +40,12 @@ import ToastStore from "./stores/ToastStore"; import {IntegrationManagers} from "./integrations/IntegrationManagers"; import {Mjolnir} from "./mjolnir/Mjolnir"; import DeviceListener from "./DeviceListener"; +import RebrandListener from "./RebrandListener"; +import {Jitsi} from "./widgets/Jitsi"; +import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "./BasePlatform"; + +const HOMESERVER_URL_KEY = "mx_hs_url"; +const ID_SERVER_URL_KEY = "mx_is_url"; /** * Called at startup, to attempt to build a logged-in Matrix session. It tries @@ -162,14 +168,16 @@ export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) { return Promise.resolve(false); } - if (!queryParams.homeserver) { + const homeserver = localStorage.getItem(SSO_HOMESERVER_URL_KEY); + const identityServer = localStorage.getItem(SSO_ID_SERVER_URL_KEY); + if (!homeserver) { console.warn("Cannot log in with token: can't determine HS URL to use"); return Promise.resolve(false); } return sendLoginRequest( - queryParams.homeserver, - queryParams.identityServer, + homeserver, + identityServer, "m.login.token", { token: queryParams.loginToken, initial_device_display_name: defaultDeviceDisplayName, @@ -255,8 +263,8 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) { * @returns {Object} Information about the session - see implementation for variables. */ export function getLocalStorageSessionVars() { - const hsUrl = localStorage.getItem("mx_hs_url"); - const isUrl = localStorage.getItem("mx_is_url"); + const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY); + const isUrl = localStorage.getItem(ID_SERVER_URL_KEY); const accessToken = localStorage.getItem("mx_access_token"); const userId = localStorage.getItem("mx_user_id"); const deviceId = localStorage.getItem("mx_device_id"); @@ -297,6 +305,8 @@ async function _restoreFromLocalStorage(opts) { return false; } + const pickleKey = await PlatformPeg.get().getPickleKey(userId, deviceId); + console.log(`Restoring session for ${userId}`); await _doSetLoggedIn({ userId: userId, @@ -305,6 +315,7 @@ async function _restoreFromLocalStorage(opts) { homeserverUrl: hsUrl, identityServerUrl: isUrl, guest: isGuest, + pickleKey: pickleKey, }, false); return true; } else { @@ -347,9 +358,13 @@ async function _handleLoadSessionFailure(e) { * * @returns {Promise} promise which resolves to the new MatrixClient once it has been started */ -export function setLoggedIn(credentials) { +export async function setLoggedIn(credentials) { stopMatrixClient(); - return _doSetLoggedIn(credentials, true); + const pickleKey = credentials.userId && credentials.deviceId + ? await PlatformPeg.get().createPickleKey(credentials.userId, credentials.deviceId) + : null; + + return _doSetLoggedIn(Object.assign({}, credentials, {pickleKey}), true); } /** @@ -478,9 +493,9 @@ function _showStorageEvictedDialog() { class AbortLoginAndRebuildStorage extends Error { } function _persistCredentialsToLocalStorage(credentials) { - localStorage.setItem("mx_hs_url", credentials.homeserverUrl); + localStorage.setItem(HOMESERVER_URL_KEY, credentials.homeserverUrl); if (credentials.identityServerUrl) { - localStorage.setItem("mx_is_url", credentials.identityServerUrl); + localStorage.setItem(ID_SERVER_URL_KEY, credentials.identityServerUrl); } localStorage.setItem("mx_user_id", credentials.userId); localStorage.setItem("mx_access_token", credentials.accessToken); @@ -515,7 +530,9 @@ export function logout() { } _isLoggingOut = true; - MatrixClientPeg.get().logout().then(onLoggedOut, + const client = MatrixClientPeg.get(); + PlatformPeg.get().destroyPickleKey(client.getUserId(), client.getDeviceId()); + client.logout().then(onLoggedOut, (err) => { // Just throwing an error here is going to be very unhelpful // if you're trying to log out because your server's down and @@ -574,13 +591,12 @@ async function startMatrixClient(startSyncing=true) { // to work). dis.dispatch({action: 'will_start_client'}, true); + // reset things first just in case + TypingStore.sharedInstance().reset(); + ToastStore.sharedInstance().reset(); + Notifier.start(); UserActivity.sharedInstance().start(); - TypingStore.sharedInstance().reset(); // just in case - ToastStore.sharedInstance().reset(); - if (!SettingsStore.getValue("lowBandwidth")) { - Presence.start(); - } DMRoomMap.makeShared().start(); IntegrationManagers.sharedInstance().startWatching(); ActiveWidgetStore.start(); @@ -603,6 +619,16 @@ async function startMatrixClient(startSyncing=true) { // This needs to be started after crypto is set up DeviceListener.sharedInstance().start(); + // Similarly, don't start sending presence updates until we've started + // the client + if (!SettingsStore.getValue("lowBandwidth")) { + Presence.start(); + } + + // Now that we have a MatrixClientPeg, update the Jitsi info + await Jitsi.getInstance().start(); + + RebrandListener.sharedInstance().start(); // dispatch that we finished starting up to wire up any other bits // of the matrix client that cannot be set prior to starting up. @@ -637,6 +663,10 @@ async function _clearStorage() { window.localStorage.clear(); } + if (window.sessionStorage) { + window.sessionStorage.clear(); + } + // create a temporary client to clear out the persistent stores. const cli = createMatrixClient({ // we'll never make any requests, so can pass a bogus HS URL @@ -661,6 +691,7 @@ export function stopMatrixClient(unsetClient=true) { IntegrationManagers.sharedInstance().stopWatching(); Mjolnir.sharedInstance().stop(); DeviceListener.sharedInstance().stop(); + RebrandListener.sharedInstance().stop(); if (DMRoomMap.shared()) DMRoomMap.shared().stop(); EventIndexPeg.stop(); const cli = MatrixClientPeg.get(); diff --git a/src/Login.js b/src/Login.js index 1590e5ac28..04805b4af9 100644 --- a/src/Login.js +++ b/src/Login.js @@ -95,6 +95,8 @@ export default class Login { identifier = { type: 'm.id.phone', country: phoneCountry, + phone: phoneNumber, + // XXX: Synapse historically wanted `number` and not `phone` number: phoneNumber, }; } else if (isEmail) { diff --git a/src/Markdown.js b/src/Markdown.js index fb1f8bf0ea..d312b7c5bd 100644 --- a/src/Markdown.js +++ b/src/Markdown.js @@ -175,14 +175,6 @@ export default class Markdown { const renderer = new commonmark.HtmlRenderer({safe: false}); const real_paragraph = renderer.paragraph; - // The default `out` function only sends the input through an XML - // escaping function, which causes messages to be entity encoded, - // which we don't want in this case. - renderer.out = function(s) { - // The `lit` function adds a string literal to the output buffer. - this.lit(s); - }; - renderer.paragraph = function(node, entering) { // as with toHTML, only append lines to paragraphs if there are // multiple paragraphs diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.ts similarity index 73% rename from src/MatrixClientPeg.js rename to src/MatrixClientPeg.ts index 21f05b9759..5f334a639c 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.ts @@ -17,8 +17,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {MatrixClient, MemoryStore} from 'matrix-js-sdk'; - +import {MatrixClient} from 'matrix-js-sdk/src/client'; +import {MemoryStore} from 'matrix-js-sdk/src/store/memory'; import * as utils from 'matrix-js-sdk/src/utils'; import {EventTimeline} from 'matrix-js-sdk/src/models/event-timeline'; import {EventTimelineSet} from 'matrix-js-sdk/src/models/event-timeline-set'; @@ -34,37 +34,26 @@ import IdentityAuthClient from './IdentityAuthClient'; import { crossSigningCallbacks } from './CrossSigningManager'; import {SHOW_QR_CODE_METHOD} from "matrix-js-sdk/src/crypto/verification/QRCode"; -interface MatrixClientCreds { - homeserverUrl: string, - identityServerUrl: string, - userId: string, - deviceId: string, - accessToken: string, - guest: boolean, +export interface IMatrixClientCreds { + homeserverUrl: string; + identityServerUrl: string; + userId: string; + deviceId: string; + accessToken: string; + guest: boolean; + pickleKey?: string; } -/** - * Wrapper object for handling the js-sdk Matrix Client object in the react-sdk - * Handles the creation/initialisation of client objects. - * This module provides a singleton instance of this class so the 'current' - * Matrix Client object is available easily. - */ -class _MatrixClientPeg { - constructor() { - this.matrixClient = null; - this._justRegisteredUserId = null; +// TODO: Move this to the js-sdk +export interface IOpts { + initialSyncLimit?: number; + pendingEventOrdering?: "detached" | "chronological"; + lazyLoadMembers?: boolean; + clientWellKnownPollPeriod?: number; +} - // These are the default options used when when the - // client is started in 'start'. These can be altered - // at any time up to after the 'will_start_client' - // event is finished processing. - this.opts = { - initialSyncLimit: 20, - }; - // the credentials used to init the current client object. - // used if we tear it down & recreate it with a different store - this._currentClientCreds = null; - } +export interface IMatrixClientPeg { + opts: IOpts; /** * Sets the script href passed to the IndexedDB web worker @@ -73,19 +62,23 @@ class _MatrixClientPeg { * * @param {string} script href to the script to be passed to the web worker */ - setIndexedDbWorkerScript(script) { - createMatrixClient.indexedDbWorkerScript = script; - } + setIndexedDbWorkerScript(script: string): void; - get(): MatrixClient { - return this.matrixClient; - } + /** + * Return the server name of the user's homeserver + * Throws an error if unable to deduce the homeserver name + * (eg. if the user is not logged in) + * + * @returns {string} The homeserver name, if present. + */ + getHomeserverName(): string; - unset() { - this.matrixClient = null; + get(): MatrixClient; + unset(): void; + assign(): Promise; + start(): Promise; - MatrixActionCreators.stop(); - } + getCredentials(): IMatrixClientCreds; /** * If we've registered a user ID we set this to the ID of the @@ -95,9 +88,7 @@ class _MatrixClientPeg { * * @param {string} uid The user ID of the user we've just registered */ - setJustRegisteredUserId(uid) { - this._justRegisteredUserId = uid; - } + setJustRegisteredUserId(uid: string): void; /** * Returns true if the current user has just been registered by this @@ -105,23 +96,73 @@ class _MatrixClientPeg { * * @returns {bool} True if user has just been registered */ - currentUserIsJustRegistered() { + currentUserIsJustRegistered(): boolean; + + /** + * Replace this MatrixClientPeg's client with a client instance that has + * homeserver / identity server URLs and active credentials + * + * @param {IMatrixClientCreds} creds The new credentials to use. + */ + replaceUsingCreds(creds: IMatrixClientCreds): void; +} + +/** + * Wrapper object for handling the js-sdk Matrix Client object in the react-sdk + * Handles the creation/initialisation of client objects. + * This module provides a singleton instance of this class so the 'current' + * Matrix Client object is available easily. + */ +class _MatrixClientPeg implements IMatrixClientPeg { + // These are the default options used when when the + // client is started in 'start'. These can be altered + // at any time up to after the 'will_start_client' + // event is finished processing. + public opts: IOpts = { + initialSyncLimit: 20, + }; + + private matrixClient: MatrixClient = null; + private justRegisteredUserId: string; + + // the credentials used to init the current client object. + // used if we tear it down & recreate it with a different store + private currentClientCreds: IMatrixClientCreds; + + constructor() { + } + + public setIndexedDbWorkerScript(script: string): void { + createMatrixClient.indexedDbWorkerScript = script; + } + + public get(): MatrixClient { + return this.matrixClient; + } + + public unset(): void { + this.matrixClient = null; + + MatrixActionCreators.stop(); + } + + public setJustRegisteredUserId(uid: string): void { + this.justRegisteredUserId = uid; + } + + public currentUserIsJustRegistered(): boolean { return ( this.matrixClient && - this.matrixClient.credentials.userId === this._justRegisteredUserId + this.matrixClient.credentials.userId === this.justRegisteredUserId ); } - /* - * Replace this MatrixClientPeg's client with a client instance that has - * homeserver / identity server URLs and active credentials - */ - replaceUsingCreds(creds: MatrixClientCreds) { - this._currentClientCreds = creds; - this._createClient(creds); + public replaceUsingCreds(creds: IMatrixClientCreds): void { + this.currentClientCreds = creds; + this.createClient(creds); } - async assign() { + public async assign(): Promise { for (const dbType of ['indexeddb', 'memory']) { try { const promise = this.matrixClient.store.startup(); @@ -132,7 +173,7 @@ class _MatrixClientPeg { if (dbType === 'indexeddb') { console.error('Error starting matrixclient store - falling back to memory store', err); this.matrixClient.store = new MemoryStore({ - localStorage: global.localStorage, + localStorage: localStorage, }); } else { console.error('Failed to start memory store!', err); @@ -158,9 +199,7 @@ class _MatrixClientPeg { // The js-sdk found a crypto DB too new for it to use const CryptoStoreTooNewDialog = sdk.getComponent("views.dialogs.CryptoStoreTooNewDialog"); - Modal.createDialog(CryptoStoreTooNewDialog, { - host: window.location.host, - }); + Modal.createDialog(CryptoStoreTooNewDialog); } // this can happen for a number of reasons, the most likely being // that the olm library was missing. It's not fatal. @@ -171,6 +210,7 @@ class _MatrixClientPeg { // the react sdk doesn't work without this, so don't allow opts.pendingEventOrdering = "detached"; opts.lazyLoadMembers = true; + opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours // Connect the matrix client to the dispatcher and setting handlers MatrixActionCreators.start(this.matrixClient); @@ -179,7 +219,7 @@ class _MatrixClientPeg { return opts; } - async start() { + public async start(): Promise { const opts = await this.assign(); console.log(`MatrixClientPeg: really starting MatrixClient`); @@ -187,7 +227,7 @@ class _MatrixClientPeg { console.log(`MatrixClientPeg: MatrixClient started`); } - getCredentials(): MatrixClientCreds { + public getCredentials(): IMatrixClientCreds { return { homeserverUrl: this.matrixClient.baseUrl, identityServerUrl: this.matrixClient.idBaseUrl, @@ -198,12 +238,7 @@ class _MatrixClientPeg { }; } - /* - * Return the server name of the user's homeserver - * Throws an error if unable to deduce the homeserver name - * (eg. if the user is not logged in) - */ - getHomeserverName() { + public getHomeserverName(): string { const matches = /^@.+:(.+)$/.exec(this.matrixClient.credentials.userId); if (matches === null || matches.length < 1) { throw new Error("Failed to derive homeserver name from user ID!"); @@ -211,13 +246,15 @@ class _MatrixClientPeg { return matches[1]; } - _createClient(creds: MatrixClientCreds) { + private createClient(creds: IMatrixClientCreds): void { + // TODO: Make these opts typesafe with the js-sdk const opts = { baseUrl: creds.homeserverUrl, idBaseUrl: creds.identityServerUrl, accessToken: creds.accessToken, userId: creds.userId, deviceId: creds.deviceId, + pickleKey: creds.pickleKey, timelineSupport: true, forceTURN: !SettingsStore.getValue('webRtcAllowPeerToPeer', false), fallbackICEServerAllowed: !!SettingsStore.getValue('fallbackICEServerAllowed'), @@ -228,9 +265,9 @@ class _MatrixClientPeg { ], unstableClientRelationAggregation: true, identityServer: new IdentityAuthClient(), + cryptoCallbacks: {}, }; - opts.cryptoCallbacks = {}; // These are always installed regardless of the labs flag so that // cross-signing features can toggle on without reloading and also be // accessed immediately after login. @@ -253,8 +290,8 @@ class _MatrixClientPeg { } } -if (!global.mxMatrixClientPeg) { - global.mxMatrixClientPeg = new _MatrixClientPeg(); +if (!window.mxMatrixClientPeg) { + window.mxMatrixClientPeg = new _MatrixClientPeg(); } -export const MatrixClientPeg = global.mxMatrixClientPeg; +export const MatrixClientPeg = window.mxMatrixClientPeg; diff --git a/src/Modal.js b/src/Modal.js deleted file mode 100644 index de441740f1..0000000000 --- a/src/Modal.js +++ /dev/null @@ -1,322 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - - -import React from 'react'; -import ReactDOM from 'react-dom'; -import Analytics from './Analytics'; -import dis from './dispatcher'; -import {defer} from './utils/promise'; -import AsyncWrapper from './AsyncWrapper'; - -const DIALOG_CONTAINER_ID = "mx_Dialog_Container"; -const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer"; - -class ModalManager { - constructor() { - this._counter = 0; - - // The modal to prioritise over all others. If this is set, only show - // this modal. Remove all other modals from the stack when this modal - // is closed. - this._priorityModal = null; - // The modal to keep open underneath other modals if possible. Useful - // for cases like Settings where the modal should remain open while the - // user is prompted for more information/errors. - this._staticModal = null; - // A list of the modals we have stacked up, with the most recent at [0] - // Neither the static nor priority modal will be in this list. - this._modals = [ - /* { - elem: React component for this dialog - onFinished: caller-supplied onFinished callback - className: CSS class for the dialog wrapper div - } */ - ]; - - this.onBackgroundClick = this.onBackgroundClick.bind(this); - } - - hasDialogs() { - return this._priorityModal || this._staticModal || this._modals.length > 0; - } - - getOrCreateContainer() { - let container = document.getElementById(DIALOG_CONTAINER_ID); - - if (!container) { - container = document.createElement("div"); - container.id = DIALOG_CONTAINER_ID; - document.body.appendChild(container); - } - - return container; - } - - getOrCreateStaticContainer() { - let container = document.getElementById(STATIC_DIALOG_CONTAINER_ID); - - if (!container) { - container = document.createElement("div"); - container.id = STATIC_DIALOG_CONTAINER_ID; - document.body.appendChild(container); - } - - return container; - } - - createTrackedDialog(analyticsAction, analyticsInfo, ...rest) { - Analytics.trackEvent('Modal', analyticsAction, analyticsInfo); - return this.createDialog(...rest); - } - - appendTrackedDialog(analyticsAction, analyticsInfo, ...rest) { - Analytics.trackEvent('Modal', analyticsAction, analyticsInfo); - return this.appendDialog(...rest); - } - - createDialog(Element, ...rest) { - return this.createDialogAsync(Promise.resolve(Element), ...rest); - } - - appendDialog(Element, ...rest) { - return this.appendDialogAsync(Promise.resolve(Element), ...rest); - } - - createTrackedDialogAsync(analyticsAction, analyticsInfo, ...rest) { - Analytics.trackEvent('Modal', analyticsAction, analyticsInfo); - return this.createDialogAsync(...rest); - } - - appendTrackedDialogAsync(analyticsAction, analyticsInfo, ...rest) { - Analytics.trackEvent('Modal', analyticsAction, analyticsInfo); - return this.appendDialogAsync(...rest); - } - - _buildModal(prom, props, className, options) { - const modal = {}; - - // never call this from onFinished() otherwise it will loop - const [closeDialog, onFinishedProm] = this._getCloseFn(modal, props); - - // don't attempt to reuse the same AsyncWrapper for different dialogs, - // otherwise we'll get confused. - const modalCount = this._counter++; - - // FIXME: If a dialog uses getDefaultProps it clobbers the onFinished - // property set here so you can't close the dialog from a button click! - modal.elem = ( - - ); - modal.onFinished = props ? props.onFinished : null; - modal.className = className; - modal.onBeforeClose = options.onBeforeClose; - modal.beforeClosePromise = null; - modal.close = closeDialog; - modal.closeReason = null; - - return {modal, closeDialog, onFinishedProm}; - } - - _getCloseFn(modal, props) { - const deferred = defer(); - return [async (...args) => { - if (modal.beforeClosePromise) { - await modal.beforeClosePromise; - } else if (modal.onBeforeClose) { - modal.beforeClosePromise = modal.onBeforeClose(modal.closeReason); - const shouldClose = await modal.beforeClosePromise; - modal.beforeClosePromise = null; - if (!shouldClose) { - return; - } - } - deferred.resolve(args); - if (props && props.onFinished) props.onFinished.apply(null, args); - const i = this._modals.indexOf(modal); - if (i >= 0) { - this._modals.splice(i, 1); - } - - if (this._priorityModal === modal) { - this._priorityModal = null; - - // XXX: This is destructive - this._modals = []; - } - - if (this._staticModal === modal) { - this._staticModal = null; - - // XXX: This is destructive - this._modals = []; - } - - this._reRender(); - }, deferred.promise]; - } - - /** - * @callback onBeforeClose - * @param {string?} reason either "backgroundClick" or null - * @return {Promise} whether the dialog should close - */ - - /** - * Open a modal view. - * - * This can be used to display a react component which is loaded as an asynchronous - * webpack component. To do this, set 'loader' as: - * - * (cb) => { - * require([''], cb); - * } - * - * @param {Promise} prom a promise which resolves with a React component - * which will be displayed as the modal view. - * - * @param {Object} props properties to pass to the displayed - * component. (We will also pass an 'onFinished' property.) - * - * @param {String} className CSS class to apply to the modal wrapper - * - * @param {boolean} isPriorityModal if true, this modal will be displayed regardless - * of other modals that are currently in the stack. - * Also, when closed, all modals will be removed - * from the stack. - * @param {boolean} isStaticModal if true, this modal will be displayed under other - * modals in the stack. When closed, all modals will - * also be removed from the stack. This is not compatible - * with being a priority modal. Only one modal can be - * static at a time. - * @param {Object} options? extra options for the dialog - * @param {onBeforeClose} options.onBeforeClose a callback to decide whether to close the dialog - * @returns {object} Object with 'close' parameter being a function that will close the dialog - */ - createDialogAsync(prom, props, className, isPriorityModal, isStaticModal, options = {}) { - const {modal, closeDialog, onFinishedProm} = this._buildModal(prom, props, className, options); - if (isPriorityModal) { - // XXX: This is destructive - this._priorityModal = modal; - } else if (isStaticModal) { - // This is intentionally destructive - this._staticModal = modal; - } else { - this._modals.unshift(modal); - } - - this._reRender(); - return { - close: closeDialog, - finished: onFinishedProm, - }; - } - - appendDialogAsync(prom, props, className) { - const {modal, closeDialog, onFinishedProm} = this._buildModal(prom, props, className, {}); - - this._modals.push(modal); - this._reRender(); - return { - close: closeDialog, - finished: onFinishedProm, - }; - } - - onBackgroundClick() { - const modal = this._getCurrentModal(); - if (!modal) { - return; - } - // we want to pass a reason to the onBeforeClose - // callback, but close is currently defined to - // pass all number of arguments to the onFinished callback - // so, pass the reason to close through a member variable - modal.closeReason = "backgroundClick"; - modal.close(); - modal.closeReason = null; - } - - _getCurrentModal() { - return this._priorityModal ? this._priorityModal : (this._modals[0] || this._staticModal); - } - - _reRender() { - if (this._modals.length === 0 && !this._priorityModal && !this._staticModal) { - // If there is no modal to render, make all of Riot available - // to screen reader users again - dis.dispatch({ - action: 'aria_unhide_main_app', - }); - ReactDOM.unmountComponentAtNode(this.getOrCreateContainer()); - ReactDOM.unmountComponentAtNode(this.getOrCreateStaticContainer()); - return; - } - - // Hide the content outside the modal to screen reader users - // so they won't be able to navigate into it and act on it using - // screen reader specific features - dis.dispatch({ - action: 'aria_hide_main_app', - }); - - if (this._staticModal) { - const classes = "mx_Dialog_wrapper mx_Dialog_staticWrapper " - + (this._staticModal.className ? this._staticModal.className : ''); - - const staticDialog = ( -
-
- { this._staticModal.elem } -
-
-
- ); - - ReactDOM.render(staticDialog, this.getOrCreateStaticContainer()); - } else { - // This is safe to call repeatedly if we happen to do that - ReactDOM.unmountComponentAtNode(this.getOrCreateStaticContainer()); - } - - const modal = this._getCurrentModal(); - if (modal !== this._staticModal) { - const classes = "mx_Dialog_wrapper " - + (this._staticModal ? "mx_Dialog_wrapperWithStaticUnder " : '') - + (modal.className ? modal.className : ''); - - const dialog = ( -
-
- {modal.elem} -
-
-
- ); - - ReactDOM.render(dialog, this.getOrCreateContainer()); - } else { - // This is safe to call repeatedly if we happen to do that - ReactDOM.unmountComponentAtNode(this.getOrCreateContainer()); - } - } -} - -if (!global.singletonModalManager) { - global.singletonModalManager = new ModalManager(); -} -export default global.singletonModalManager; diff --git a/src/Modal.tsx b/src/Modal.tsx new file mode 100644 index 0000000000..b744dbacf4 --- /dev/null +++ b/src/Modal.tsx @@ -0,0 +1,383 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + + +import React from 'react'; +import ReactDOM from 'react-dom'; +import classNames from 'classnames'; + +import Analytics from './Analytics'; +import dis from './dispatcher/dispatcher'; +import {defer} from './utils/promise'; +import AsyncWrapper from './AsyncWrapper'; + +const DIALOG_CONTAINER_ID = "mx_Dialog_Container"; +const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer"; + +interface IModal { + elem: React.ReactNode; + className?: string; + beforeClosePromise?: Promise; + closeReason?: string; + onBeforeClose?(reason?: string): Promise; + onFinished(...args: T): void; + close(...args: T): void; +} + +interface IHandle { + finished: Promise; + close(...args: T): void; +} + +interface IProps { + onFinished?(...args: T): void; + // TODO improve typing here once all Modals are TS and we can exhaustively check the props + [key: string]: any; +} + +interface IOptions { + onBeforeClose?: IModal["onBeforeClose"]; +} + +type ParametersWithoutFirst any> = T extends (a: any, ...args: infer P) => any ? P : never; + +export class ModalManager { + private counter = 0; + // The modal to prioritise over all others. If this is set, only show + // this modal. Remove all other modals from the stack when this modal + // is closed. + private priorityModal: IModal = null; + // The modal to keep open underneath other modals if possible. Useful + // for cases like Settings where the modal should remain open while the + // user is prompted for more information/errors. + private staticModal: IModal = null; + // A list of the modals we have stacked up, with the most recent at [0] + // Neither the static nor priority modal will be in this list. + private modals: IModal[] = []; + + private static getOrCreateContainer() { + let container = document.getElementById(DIALOG_CONTAINER_ID); + + if (!container) { + container = document.createElement("div"); + container.id = DIALOG_CONTAINER_ID; + document.body.appendChild(container); + } + + return container; + } + + private static getOrCreateStaticContainer() { + let container = document.getElementById(STATIC_DIALOG_CONTAINER_ID); + + if (!container) { + container = document.createElement("div"); + container.id = STATIC_DIALOG_CONTAINER_ID; + document.body.appendChild(container); + } + + return container; + } + + public hasDialogs() { + return this.priorityModal || this.staticModal || this.modals.length > 0; + } + + public createTrackedDialog( + analyticsAction: string, + analyticsInfo: string, + ...rest: Parameters + ) { + Analytics.trackEvent('Modal', analyticsAction, analyticsInfo); + return this.createDialog(...rest); + } + + public appendTrackedDialog( + analyticsAction: string, + analyticsInfo: string, + ...rest: Parameters + ) { + Analytics.trackEvent('Modal', analyticsAction, analyticsInfo); + return this.appendDialog(...rest); + } + + public createDialog( + Element: React.ComponentType, + ...rest: ParametersWithoutFirst + ) { + return this.createDialogAsync(Promise.resolve(Element), ...rest); + } + + public appendDialog( + Element: React.ComponentType, + ...rest: ParametersWithoutFirst + ) { + return this.appendDialogAsync(Promise.resolve(Element), ...rest); + } + + public createTrackedDialogAsync( + analyticsAction: string, + analyticsInfo: string, + ...rest: Parameters + ) { + Analytics.trackEvent('Modal', analyticsAction, analyticsInfo); + return this.createDialogAsync(...rest); + } + + public appendTrackedDialogAsync( + analyticsAction: string, + analyticsInfo: string, + ...rest: Parameters + ) { + Analytics.trackEvent('Modal', analyticsAction, analyticsInfo); + return this.appendDialogAsync(...rest); + } + + private buildModal( + prom: Promise, + props?: IProps, + className?: string, + options?: IOptions + ) { + const modal: IModal = { + onFinished: props ? props.onFinished : null, + onBeforeClose: options.onBeforeClose, + beforeClosePromise: null, + closeReason: null, + className, + + // these will be set below but we need an object reference to pass to getCloseFn before we can do that + elem: null, + close: null, + }; + + // never call this from onFinished() otherwise it will loop + const [closeDialog, onFinishedProm] = this.getCloseFn(modal, props); + + // don't attempt to reuse the same AsyncWrapper for different dialogs, + // otherwise we'll get confused. + const modalCount = this.counter++; + + // FIXME: If a dialog uses getDefaultProps it clobbers the onFinished + // property set here so you can't close the dialog from a button click! + modal.elem = ; + modal.close = closeDialog; + + return {modal, closeDialog, onFinishedProm}; + } + + private getCloseFn( + modal: IModal, + props: IProps + ): [IHandle["close"], IHandle["finished"]] { + const deferred = defer(); + return [async (...args: T) => { + if (modal.beforeClosePromise) { + await modal.beforeClosePromise; + } else if (modal.onBeforeClose) { + modal.beforeClosePromise = modal.onBeforeClose(modal.closeReason); + const shouldClose = await modal.beforeClosePromise; + modal.beforeClosePromise = null; + if (!shouldClose) { + return; + } + } + deferred.resolve(args); + if (props && props.onFinished) props.onFinished.apply(null, args); + const i = this.modals.indexOf(modal); + if (i >= 0) { + this.modals.splice(i, 1); + } + + if (this.priorityModal === modal) { + this.priorityModal = null; + + // XXX: This is destructive + this.modals = []; + } + + if (this.staticModal === modal) { + this.staticModal = null; + + // XXX: This is destructive + this.modals = []; + } + + this.reRender(); + }, deferred.promise]; + } + + /** + * @callback onBeforeClose + * @param {string?} reason either "backgroundClick" or null + * @return {Promise} whether the dialog should close + */ + + /** + * Open a modal view. + * + * This can be used to display a react component which is loaded as an asynchronous + * webpack component. To do this, set 'loader' as: + * + * (cb) => { + * require([''], cb); + * } + * + * @param {Promise} prom a promise which resolves with a React component + * which will be displayed as the modal view. + * + * @param {Object} props properties to pass to the displayed + * component. (We will also pass an 'onFinished' property.) + * + * @param {String} className CSS class to apply to the modal wrapper + * + * @param {boolean} isPriorityModal if true, this modal will be displayed regardless + * of other modals that are currently in the stack. + * Also, when closed, all modals will be removed + * from the stack. + * @param {boolean} isStaticModal if true, this modal will be displayed under other + * modals in the stack. When closed, all modals will + * also be removed from the stack. This is not compatible + * with being a priority modal. Only one modal can be + * static at a time. + * @param {Object} options? extra options for the dialog + * @param {onBeforeClose} options.onBeforeClose a callback to decide whether to close the dialog + * @returns {object} Object with 'close' parameter being a function that will close the dialog + */ + private createDialogAsync( + prom: Promise, + props?: IProps, + className?: string, + isPriorityModal = false, + isStaticModal = false, + options: IOptions = {} + ): IHandle { + const {modal, closeDialog, onFinishedProm} = this.buildModal(prom, props, className, options); + if (isPriorityModal) { + // XXX: This is destructive + this.priorityModal = modal; + } else if (isStaticModal) { + // This is intentionally destructive + this.staticModal = modal; + } else { + this.modals.unshift(modal); + } + + this.reRender(); + return { + close: closeDialog, + finished: onFinishedProm, + }; + } + + private appendDialogAsync( + prom: Promise, + props?: IProps, + className?: string + ): IHandle { + const {modal, closeDialog, onFinishedProm} = this.buildModal(prom, props, className, {}); + + this.modals.push(modal); + this.reRender(); + return { + close: closeDialog, + finished: onFinishedProm, + }; + } + + private onBackgroundClick = () => { + const modal = this.getCurrentModal(); + if (!modal) { + return; + } + // we want to pass a reason to the onBeforeClose + // callback, but close is currently defined to + // pass all number of arguments to the onFinished callback + // so, pass the reason to close through a member variable + modal.closeReason = "backgroundClick"; + modal.close(); + modal.closeReason = null; + }; + + private getCurrentModal(): IModal { + return this.priorityModal ? this.priorityModal : (this.modals[0] || this.staticModal); + } + + private reRender() { + if (this.modals.length === 0 && !this.priorityModal && !this.staticModal) { + // If there is no modal to render, make all of Riot available + // to screen reader users again + dis.dispatch({ + action: 'aria_unhide_main_app', + }); + ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer()); + ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateStaticContainer()); + return; + } + + // Hide the content outside the modal to screen reader users + // so they won't be able to navigate into it and act on it using + // screen reader specific features + dis.dispatch({ + action: 'aria_hide_main_app', + }); + + if (this.staticModal) { + const classes = classNames("mx_Dialog_wrapper mx_Dialog_staticWrapper", this.staticModal.className); + + const staticDialog = ( +
+
+ { this.staticModal.elem } +
+
+
+ ); + + ReactDOM.render(staticDialog, ModalManager.getOrCreateStaticContainer()); + } else { + // This is safe to call repeatedly if we happen to do that + ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateStaticContainer()); + } + + const modal = this.getCurrentModal(); + if (modal !== this.staticModal) { + const classes = classNames("mx_Dialog_wrapper", modal.className, { + mx_Dialog_wrapperWithStaticUnder: this.staticModal, + }); + + const dialog = ( +
+
+ {modal.elem} +
+
+
+ ); + + ReactDOM.render(dialog, ModalManager.getOrCreateContainer()); + } else { + // This is safe to call repeatedly if we happen to do that + ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer()); + } + } +} + +if (!window.singletonModalManager) { + window.singletonModalManager = new ModalManager(); +} +export default window.singletonModalManager; diff --git a/src/Notifier.js b/src/Notifier.js index 36a6f13bb6..c6fc7d7985 100644 --- a/src/Notifier.js +++ b/src/Notifier.js @@ -2,6 +2,7 @@ Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd Copyright 2017 New Vector Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,16 +17,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {MatrixClientPeg} from './MatrixClientPeg'; +import { MatrixClientPeg } from './MatrixClientPeg'; +import SdkConfig from './SdkConfig'; import PlatformPeg from './PlatformPeg'; import * as TextForEvent from './TextForEvent'; import Analytics from './Analytics'; import * as Avatar from './Avatar'; -import dis from './dispatcher'; +import dis from './dispatcher/dispatcher'; import * as sdk from './index'; import { _t } from './languageHandler'; import Modal from './Modal'; import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; +import { + hideToast as hideNotificationsToast, +} from "./toasts/DesktopNotificationsToast"; /* * Dispatches: @@ -37,6 +42,18 @@ import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; const MAX_PENDING_ENCRYPTED = 20; +/* +Override both the content body and the TextForEvent handler for specific msgtypes, in notifications. +This is useful when the content body contains fallback text that would explain that the client can't handle a particular +type of tile. +*/ +const typehandlers = { + "m.key.verification.request": (event) => { + const name = (event.sender || {}).name; + return _t("%(name)s is requesting verification", { name }); + }, +}; + const Notifier = { notifsByRoom: {}, @@ -46,6 +63,9 @@ const Notifier = { pendingEncryptedEventIds: [], notificationMessageForEvent: function(ev) { + if (typehandlers.hasOwnProperty(ev.getContent().msgtype)) { + return typehandlers[ev.getContent().msgtype](ev); + } return TextForEvent.textForEvent(ev); }, @@ -69,7 +89,9 @@ const Notifier = { title = room.name; // notificationMessageForEvent includes sender, // but we already have the sender here - if (ev.getContent().body) msg = ev.getContent().body; + if (ev.getContent().body && !typehandlers.hasOwnProperty(ev.getContent().msgtype)) { + msg = ev.getContent().body; + } } else if (ev.getType() === 'm.room.member') { // context is all in the message here, we don't need // to display sender info @@ -78,7 +100,9 @@ const Notifier = { title = ev.sender.name + " (" + room.name + ")"; // notificationMessageForEvent includes sender, // but we've just out sender in the title - if (ev.getContent().body) msg = ev.getContent().body; + if (ev.getContent().body && !typehandlers.hasOwnProperty(ev.getContent().msgtype)) { + msg = ev.getContent().body; + } } if (!this.isBodyEnabled()) { @@ -100,7 +124,7 @@ const Notifier = { } }, - getSoundForRoom: async function(roomId) { + getSoundForRoom: function(roomId) { // We do no caching here because the SDK caches setting // and the browser will cache the sound. const content = SettingsStore.getValue("notificationSound", roomId); @@ -129,7 +153,7 @@ const Notifier = { }, _playAudioNotification: async function(ev, room) { - const sound = await this.getSoundForRoom(room.roomId); + const sound = this.getSoundForRoom(room.roomId); console.log(`Got sound ${sound && sound.name || "default"} for ${room.roomId}`); try { @@ -204,10 +228,11 @@ const Notifier = { if (result !== 'granted') { // The permission request was dismissed or denied // TODO: Support alternative branding in messaging + const brand = SdkConfig.get().brand; const description = result === 'denied' - ? _t('Riot does not have permission to send you notifications - ' + - 'please check your browser settings') - : _t('Riot was not given permission to send notifications - please try again'); + ? _t('%(brand)s does not have permission to send you notifications - ' + + 'please check your browser settings', { brand }) + : _t('%(brand)s was not given permission to send notifications - please try again', { brand }); const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); Modal.createTrackedDialog('Unable to enable Notifications', result, ErrorDialog, { title: _t('Unable to enable Notifications'), @@ -259,12 +284,7 @@ const Notifier = { Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', hidden); - // XXX: why are we dispatching this here? - // this is nothing to do with notifier_enabled - dis.dispatch({ - action: "notifier_enabled", - value: this.isEnabled(), - }); + hideNotificationsToast(); // update the info to localStorage for persistent settings if (persistent && global.localStorage) { diff --git a/src/PasswordReset.js b/src/PasswordReset.js index 320599f6d9..9472ddc633 100644 --- a/src/PasswordReset.js +++ b/src/PasswordReset.js @@ -84,8 +84,14 @@ export default class PasswordReset { try { await this.client.setPassword({ + // Note: Though this sounds like a login type for identity servers only, it + // has a dual purpose of being used for homeservers too. type: "m.login.email.identity", + // TODO: Remove `threepid_creds` once servers support proper UIA + // See https://github.com/matrix-org/synapse/issues/5665 + // See https://github.com/matrix-org/matrix-doc/issues/2220 threepid_creds: creds, + threepidCreds: creds, }, this.password); } catch (err) { if (err.httpStatus === 401) { diff --git a/src/PlatformPeg.js b/src/PlatformPeg.ts similarity index 80% rename from src/PlatformPeg.js rename to src/PlatformPeg.ts index 34131fde7d..1d2b813ebc 100644 --- a/src/PlatformPeg.js +++ b/src/PlatformPeg.ts @@ -1,5 +1,6 @@ /* Copyright 2016 OpenMarket Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,6 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import BasePlatform from "./BasePlatform"; + /* * Holds the current Platform object used by the code to do anything * specific to the platform we're running on (eg. web, electron) @@ -21,10 +24,8 @@ limitations under the License. * This allows the app layer to set a Platform without necessarily * having to have a MatrixChat object */ -class PlatformPeg { - constructor() { - this.platform = null; - } +export class PlatformPeg { + platform: BasePlatform = null; /** * Returns the current Platform object for the application. @@ -39,12 +40,12 @@ class PlatformPeg { * application. * This should be an instance of a class extending BasePlatform. */ - set(plaf) { + set(plaf: BasePlatform) { this.platform = plaf; } } -if (!global.mxPlatformPeg) { - global.mxPlatformPeg = new PlatformPeg(); +if (!window.mxPlatformPeg) { + window.mxPlatformPeg = new PlatformPeg(); } -export default global.mxPlatformPeg; +export default window.mxPlatformPeg; diff --git a/src/Presence.js b/src/Presence.js index 2fc13a090b..42bca35f96 100644 --- a/src/Presence.js +++ b/src/Presence.js @@ -17,7 +17,7 @@ limitations under the License. */ import {MatrixClientPeg} from "./MatrixClientPeg"; -import dis from "./dispatcher"; +import dis from "./dispatcher/dispatcher"; import Timer from './utils/Timer'; // Time in ms after that a user is considered as unavailable/away diff --git a/src/RebrandListener.tsx b/src/RebrandListener.tsx new file mode 100644 index 0000000000..283f74d743 --- /dev/null +++ b/src/RebrandListener.tsx @@ -0,0 +1,173 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import SdkConfig from "./SdkConfig"; +import ToastStore from "./stores/ToastStore"; +import GenericToast from "./components/views/toasts/GenericToast"; +import RebrandDialog from "./components/views/dialogs/RebrandDialog"; +import { RebrandDialogKind } from "./components/views/dialogs/RebrandDialog"; +import Modal from './Modal'; +import { _t } from './languageHandler'; + +const TOAST_KEY = 'rebrand'; +const NAG_INTERVAL = 24 * 60 * 60 * 1000; + +function getRedirectUrl(url): string { + const redirectUrl = new URL(url); + redirectUrl.hash = ''; + + if (SdkConfig.get()['redirectToNewBrandUrl']) { + const newUrl = new URL(SdkConfig.get()['redirectToNewBrandUrl']); + if (url.hostname !== newUrl.hostname || url.pathname !== newUrl.pathname) { + redirectUrl.hostname = newUrl.hostname; + redirectUrl.pathname = newUrl.pathname; + return redirectUrl.toString(); + } + return null; + } else if (url.hostname === 'riot.im') { + if (url.pathname.startsWith('/app')) { + redirectUrl.hostname = 'app.element.io'; + redirectUrl.pathname = '/'; + } else if (url.pathname.startsWith('/staging')) { + redirectUrl.hostname = 'staging.element.io'; + redirectUrl.pathname = '/'; + } else if (url.pathname.startsWith('/develop')) { + redirectUrl.hostname = 'develop.element.io'; + redirectUrl.pathname = '/'; + } + + return redirectUrl.href; + } else if (url.hostname.endsWith('.riot.im')) { + redirectUrl.hostname = url.hostname.substr(0, url.hostname.length - '.riot.im'.length) + '.element.io'; + return redirectUrl.href; + } else { + return null; + } +} + +/** + * Shows toasts informing the user that the name of the app has changed and, + * potentially, that they should head to a different URL and log in there + */ +export default class RebrandListener { + private _reshowTimer?: number; + private nagAgainAt?: number = null; + + static sharedInstance() { + if (!window.mxRebrandListener) window.mxRebrandListener = new RebrandListener(); + return window.mxRebrandListener; + } + + constructor() { + this._reshowTimer = null; + } + + start() { + this.recheck(); + } + + stop() { + if (this._reshowTimer) { + clearTimeout(this._reshowTimer); + this._reshowTimer = null; + } + } + + onNagToastLearnMore = async () => { + const [doneClicked] = await Modal.createDialog(RebrandDialog, { + kind: RebrandDialogKind.NAG, + targetUrl: getRedirectUrl(window.location), + }).finished; + if (doneClicked) { + // open in new tab: they should come back here & log out + window.open(getRedirectUrl(window.location), '_blank'); + } + + // whatever the user clicks, we go away & nag again after however long: + // If they went to the new URL, we want to nag them to log out if they + // come back to this tab, and if they clicked, 'remind me later' we want + // to, well, remind them later. + this.nagAgainAt = Date.now() + NAG_INTERVAL; + this.recheck(); + }; + + onOneTimeToastLearnMore = async () => { + const [doneClicked] = await Modal.createDialog(RebrandDialog, { + kind: RebrandDialogKind.ONE_TIME, + }).finished; + if (doneClicked) { + localStorage.setItem('mx_rename_dialog_dismissed', 'true'); + this.recheck(); + } + }; + + onNagTimerFired = () => { + this._reshowTimer = null; + this.nagAgainAt = null; + this.recheck(); + }; + + private async recheck() { + // There are two types of toast/dialog we show: a 'one time' informing the user that + // the app is now called a different thing but no action is required from them (they + // may need to look for a different name name/icon to launch the app but don't need to + // log in again) and a nag toast where they need to log in to the app on a different domain. + let nagToast = false; + let oneTimeToast = false; + + if (getRedirectUrl(window.location)) { + if (!this.nagAgainAt) { + // if we have redirectUrl, show the nag toast + nagToast = true; + } + } else { + // otherwise we show the 'one time' toast / dialog + const renameDialogDismissed = localStorage.getItem('mx_rename_dialog_dismissed'); + if (renameDialogDismissed !== 'true') { + oneTimeToast = true; + } + } + + if (nagToast || oneTimeToast) { + let description; + if (nagToast) { + description = _t("Use your account to sign in to the latest version"); + } else { + description = _t("We’re excited to announce Riot is now Element"); + } + + ToastStore.sharedInstance().addOrReplaceToast({ + key: TOAST_KEY, + title: _t("Riot is now Element!"), + icon: 'element_logo', + props: { + description, + acceptLabel: _t("Learn More"), + onAccept: nagToast ? this.onNagToastLearnMore : this.onOneTimeToastLearnMore, + }, + component: GenericToast, + priority: 20, + }); + } else { + ToastStore.sharedInstance().dismissToast(TOAST_KEY); + } + + if (!this._reshowTimer && this.nagAgainAt) { + // XXX: Our build system picks up NodeJS bindings when we need browser bindings. + this._reshowTimer = setTimeout(this.onNagTimerFired, (this.nagAgainAt - Date.now()) + 100) as any as number; + } + } +} diff --git a/src/Registration.js b/src/Registration.js index ca162bac03..32c3d9cc35 100644 --- a/src/Registration.js +++ b/src/Registration.js @@ -20,7 +20,7 @@ limitations under the License. * registration code. */ -import dis from './dispatcher'; +import dis from './dispatcher/dispatcher'; import * as sdk from './index'; import Modal from './Modal'; import { _t } from './languageHandler'; diff --git a/src/Resend.js b/src/Resend.js index 6d6c18cf27..f5f24bffa5 100644 --- a/src/Resend.js +++ b/src/Resend.js @@ -16,7 +16,7 @@ limitations under the License. */ import {MatrixClientPeg} from './MatrixClientPeg'; -import dis from './dispatcher'; +import dis from './dispatcher/dispatcher'; import { EventStatus } from 'matrix-js-sdk'; export default class Resend { diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index c67acaf314..4614bef378 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -56,10 +56,11 @@ export function countRoomsWithNotif(rooms) { } export function aggregateNotificationCount(rooms) { - return rooms.reduce((result, room, index) => { + return rooms.reduce((result, room) => { const roomNotifState = getRoomNotifsState(room.roomId); const highlight = room.getUnreadNotificationCount('highlight') > 0; - const notificationCount = room.getUnreadNotificationCount(); + // use helper method to include highlights in the previous version of the room + const notificationCount = getUnreadNotificationCount(room); const notifBadges = notificationCount > 0 && shouldShowNotifBadge(roomNotifState); const mentionBadges = highlight && shouldShowMentionBadge(roomNotifState); diff --git a/src/RoomNotifsTypes.ts b/src/RoomNotifsTypes.ts new file mode 100644 index 0000000000..0e7093e434 --- /dev/null +++ b/src/RoomNotifsTypes.ts @@ -0,0 +1,24 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { + ALL_MESSAGES, + ALL_MESSAGES_LOUD, + MENTIONS_ONLY, + MUTE, +} from "./RoomNotifs"; + +export type Volume = ALL_MESSAGES_LOUD | ALL_MESSAGES | MENTIONS_ONLY | MUTE; diff --git a/src/ScalarAuthClient.js b/src/ScalarAuthClient.js index 819fe3c998..1ea9d39e2f 100644 --- a/src/ScalarAuthClient.js +++ b/src/ScalarAuthClient.js @@ -23,10 +23,13 @@ import request from "browser-request"; import * as Matrix from 'matrix-js-sdk'; import SdkConfig from "./SdkConfig"; +import {WidgetType} from "./widgets/WidgetType"; // The version of the integration manager API we're intending to work with const imApiVersion = "1.1"; +// TODO: Generify the name of this class and all components within - it's not just for Scalar. + export default class ScalarAuthClient { constructor(apiUrl, uiUrl) { this.apiUrl = apiUrl; @@ -233,20 +236,20 @@ export default class ScalarAuthClient { * Mark all assets associated with the specified widget as "disabled" in the * integration manager database. * This can be useful to temporarily prevent purchased assets from being displayed. - * @param {string} widgetType [description] - * @param {string} widgetId [description] + * @param {WidgetType} widgetType The Widget Type to disable assets for + * @param {string} widgetId The widget ID to disable assets for * @return {Promise} Resolves on completion */ - disableWidgetAssets(widgetType, widgetId) { + disableWidgetAssets(widgetType: WidgetType, widgetId) { let url = this.apiUrl + '/widgets/set_assets_state'; url = this.getStarterLink(url); return new Promise((resolve, reject) => { request({ - method: 'GET', + method: 'GET', // XXX: Actions shouldn't be GET requests uri: url, json: true, qs: { - 'widget_type': widgetType, + 'widget_type': widgetType.preferred, 'widget_id': widgetId, 'state': 'disable', }, diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index 2211e513c3..b33aa57359 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -16,6 +16,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// TODO: Generify the name of this and all components within - it's not just for scalar. + /* Listens for incoming postMessage requests from the integrations UI URL. The following API is exposed: { @@ -172,6 +174,7 @@ Request: Response: [ { + // TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111) type: "im.vector.modular.widgets", state_key: "wid1", content: { @@ -190,6 +193,7 @@ Example: room_id: "!foo:bar", response: [ { + // TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111) type: "im.vector.modular.widgets", state_key: "wid1", content: { @@ -234,21 +238,23 @@ Example: import {MatrixClientPeg} from './MatrixClientPeg'; import { MatrixEvent } from 'matrix-js-sdk'; -import dis from './dispatcher'; +import dis from './dispatcher/dispatcher'; import WidgetUtils from './utils/WidgetUtils'; import RoomViewStore from './stores/RoomViewStore'; import { _t } from './languageHandler'; import {IntegrationManagers} from "./integrations/IntegrationManagers"; +import {WidgetType} from "./widgets/WidgetType"; +import {objectClone} from "./utils/objects"; function sendResponse(event, res) { - const data = JSON.parse(JSON.stringify(event.data)); + const data = objectClone(event.data); data.response = res; event.source.postMessage(data, event.origin); } function sendError(event, msg, nestedError) { console.error("Action:" + event.data.action + " failed with message: " + msg); - const data = JSON.parse(JSON.stringify(event.data)); + const data = objectClone(event.data); data.response = { error: { message: msg, @@ -290,7 +296,7 @@ function inviteUser(event, roomId, userId) { function setWidget(event, roomId) { const widgetId = event.data.widget_id; - const widgetType = event.data.type; + let widgetType = event.data.type; const widgetUrl = event.data.url; const widgetName = event.data.name; // optional const widgetData = event.data.data; // optional @@ -322,6 +328,9 @@ function setWidget(event, roomId) { } } + // convert the widget type to a known widget type + widgetType = WidgetType.fromString(widgetType); + if (userWidget) { WidgetUtils.setUserWidget(widgetId, widgetType, widgetUrl, widgetName, widgetData).then(() => { sendResponse(event, { diff --git a/src/SdkConfig.ts b/src/SdkConfig.ts index 34f3402334..b914aaaf6d 100644 --- a/src/SdkConfig.ts +++ b/src/SdkConfig.ts @@ -1,6 +1,6 @@ /* Copyright 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,6 +20,8 @@ export interface ConfigOptions { } export const DEFAULTS: ConfigOptions = { + // Brand name of the app + brand: "Element", // URL to a page we show in an iframe to configure integrations integrations_ui_url: "https://scalar.vector.im/", // Base URL to the REST interface of the integrations server @@ -30,8 +32,6 @@ export const DEFAULTS: ConfigOptions = { jitsi: { // Default conference domain preferredDomain: "jitsi.riot.im", - // Default Jitsi Meet API location - externalApiUrl: "https://jitsi.riot.im/libs/external_api.min.js", }, }; diff --git a/src/Searching.js b/src/Searching.js index 663328fe41..b1507e6a49 100644 --- a/src/Searching.js +++ b/src/Searching.js @@ -17,25 +17,71 @@ limitations under the License. import EventIndexPeg from "./indexing/EventIndexPeg"; import {MatrixClientPeg} from "./MatrixClientPeg"; -function serverSideSearch(term, roomId = undefined) { - let filter; - if (roomId !== undefined) { - // XXX: it's unintuitive that the filter for searching doesn't have - // the same shape as the v2 filter API :( - filter = { - rooms: [roomId], - }; - } +const SEARCH_LIMIT = 10; - const searchPromise = MatrixClientPeg.get().searchRoomEvents({ - filter, - term, - }); +async function serverSideSearch(term, roomId = undefined) { + const client = MatrixClientPeg.get(); - return searchPromise; + const filter = { + limit: SEARCH_LIMIT, + }; + + if (roomId !== undefined) filter.rooms = [roomId]; + + const body = { + search_categories: { + room_events: { + search_term: term, + filter: filter, + order_by: "recent", + event_context: { + before_limit: 1, + after_limit: 1, + include_profile: true, + }, + }, + }, + }; + + const response = await client.search({body: body}); + + const result = { + response: response, + query: body, + }; + + return result; +} + +async function serverSideSearchProcess(term, roomId = undefined) { + const client = MatrixClientPeg.get(); + const result = await serverSideSearch(term, roomId); + + // The js-sdk method backPaginateRoomEventsSearch() uses _query internally + // so we're reusing the concept here since we wan't to delegate the + // pagination back to backPaginateRoomEventsSearch() in some cases. + const searchResult = { + _query: result.query, + results: [], + highlights: [], + }; + + return client._processRoomEventsSearch(searchResult, result.response); +} + +function compareEvents(a, b) { + const aEvent = a.result; + const bEvent = b.result; + + if (aEvent.origin_server_ts > bEvent.origin_server_ts) return -1; + if (aEvent.origin_server_ts < bEvent.origin_server_ts) return 1; + + return 0; } async function combinedSearch(searchTerm) { + const client = MatrixClientPeg.get(); + // Create two promises, one for the local search, one for the // server-side search. const serverSidePromise = serverSideSearch(searchTerm); @@ -48,37 +94,59 @@ async function combinedSearch(searchTerm) { const localResult = await localPromise; const serverSideResult = await serverSidePromise; - // Combine the search results into one result. - const result = {}; + const serverQuery = serverSideResult.query; + const serverResponse = serverSideResult.response; - // Our localResult and serverSideResult are both ordered by - // recency separately, when we combine them the order might not - // be the right one so we need to sort them. - const compare = (a, b) => { - const aEvent = a.context.getEvent().event; - const bEvent = b.context.getEvent().event; + const localQuery = localResult.query; + const localResponse = localResult.response; - if (aEvent.origin_server_ts > - bEvent.origin_server_ts) return -1; - if (aEvent.origin_server_ts < - bEvent.origin_server_ts) return 1; - return 0; + // Store our queries for later on so we can support pagination. + // + // We're reusing _query here again to not introduce separate code paths and + // concepts for our different pagination methods. We're storing the + // server-side next batch separately since the query is the json body of + // the request and next_batch needs to be a query parameter. + // + // We can't put it in the final result that _processRoomEventsSearch() + // returns since that one can be either a server-side one, a local one or a + // fake one to fetch the remaining cached events. See the docs for + // combineEvents() for an explanation why we need to cache events. + const emptyResult = { + seshatQuery: localQuery, + _query: serverQuery, + serverSideNextBatch: serverResponse.next_batch, + cachedEvents: [], + oldestEventFrom: "server", + results: [], + highlights: [], }; - result.count = localResult.count + serverSideResult.count; - result.results = localResult.results.concat( - serverSideResult.results).sort(compare); - result.highlights = localResult.highlights.concat( - serverSideResult.highlights); + // Combine our results. + const combinedResult = combineResponses(emptyResult, localResponse, serverResponse.search_categories.room_events); + + // Let the client process the combined result. + const response = { + search_categories: { + room_events: combinedResult, + }, + }; + + const result = client._processRoomEventsSearch(emptyResult, response); + + // Restore our encryption info so we can properly re-verify the events. + restoreEncryptionInfo(result.results); return result; } -async function localSearch(searchTerm, roomId = undefined) { +async function localSearch(searchTerm, roomId = undefined, processResult = true) { + const eventIndex = EventIndexPeg.get(); + const searchArgs = { search_term: searchTerm, before_limit: 1, after_limit: 1, + limit: SEARCH_LIMIT, order_by_recency: true, room_id: undefined, }; @@ -87,6 +155,19 @@ async function localSearch(searchTerm, roomId = undefined) { searchArgs.room_id = roomId; } + const localResult = await eventIndex.search(searchArgs); + + searchArgs.next_batch = localResult.next_batch; + + const result = { + response: localResult, + query: searchArgs, + }; + + return result; +} + +async function localSearchProcess(searchTerm, roomId = undefined) { const emptyResult = { results: [], highlights: [], @@ -94,9 +175,34 @@ async function localSearch(searchTerm, roomId = undefined) { if (searchTerm === "") return emptyResult; + const result = await localSearch(searchTerm, roomId); + + emptyResult.seshatQuery = result.query; + + const response = { + search_categories: { + room_events: result.response, + }, + }; + + const processedResult = MatrixClientPeg.get()._processRoomEventsSearch(emptyResult, response); + // Restore our encryption info so we can properly re-verify the events. + restoreEncryptionInfo(processedResult.results); + + return processedResult; +} + +async function localPagination(searchResult) { const eventIndex = EventIndexPeg.get(); + const searchArgs = searchResult.seshatQuery; + const localResult = await eventIndex.search(searchArgs); + searchResult.seshatQuery.next_batch = localResult.next_batch; + + // We only need to restore the encryption state for the new results, so + // remember how many of them we got. + const newResultCount = localResult.results.length; const response = { search_categories: { @@ -104,8 +210,324 @@ async function localSearch(searchTerm, roomId = undefined) { }, }; - const result = MatrixClientPeg.get()._processRoomEventsSearch( - emptyResult, response); + const result = MatrixClientPeg.get()._processRoomEventsSearch(searchResult, response); + + // Restore our encryption info so we can properly re-verify the events. + const newSlice = result.results.slice(Math.max(result.results.length - newResultCount, 0)); + restoreEncryptionInfo(newSlice); + + searchResult.pendingRequest = null; + + return result; +} + +function compareOldestEvents(firstResults, secondResults) { + try { + const oldestFirstEvent = firstResults.results[firstResults.results.length - 1].result; + const oldestSecondEvent = secondResults.results[secondResults.results.length - 1].result; + + if (oldestFirstEvent.origin_server_ts <= oldestSecondEvent.origin_server_ts) { + return -1; + } else { + return 1; + } + } catch { + return 0; + } +} + +function combineEventSources(previousSearchResult, response, a, b) { + // Merge event sources and sort the events. + const combinedEvents = a.concat(b).sort(compareEvents); + // Put half of the events in the response, and cache the other half. + response.results = combinedEvents.slice(0, SEARCH_LIMIT); + previousSearchResult.cachedEvents = combinedEvents.slice(SEARCH_LIMIT); +} + +/** + * Combine the events from our event sources into a sorted result + * + * This method will first be called from the combinedSearch() method. In this + * case we will fetch SEARCH_LIMIT events from the server and the local index. + * + * The method will put the SEARCH_LIMIT newest events from the server and the + * local index in the results part of the response, the rest will be put in the + * cachedEvents field of the previousSearchResult (in this case an empty search + * result). + * + * Every subsequent call will be made from the combinedPagination() method, in + * this case we will combine the cachedEvents and the next SEARCH_LIMIT events + * from either the server or the local index. + * + * Since we have two event sources and we need to sort the results by date we + * need keep on looking for the oldest event. We are implementing a variation of + * a sliding window. + * + * The event sources are here represented as two sorted lists where the smallest + * number represents the newest event. The two lists need to be merged in a way + * that preserves the sorted property so they can be shown as one search result. + * We first fetch SEARCH_LIMIT events from both sources. + * + * If we set SEARCH_LIMIT to 3: + * + * Server events [01, 02, 04, 06, 07, 08, 11, 13] + * |01, 02, 04| + * Local events [03, 05, 09, 10, 12, 14, 15, 16] + * |03, 05, 09| + * + * We note that the oldest event is from the local index, and we combine the + * results: + * + * Server window [01, 02, 04] + * Local window [03, 05, 09] + * + * Combined events [01, 02, 03, 04, 05, 09] + * + * We split the combined result in the part that we want to present and a part + * that will be cached. + * + * Presented events [01, 02, 03] + * Cached events [04, 05, 09] + * + * We slide the window for the server since the oldest event is from the local + * index. + * + * Server events [01, 02, 04, 06, 07, 08, 11, 13] + * |06, 07, 08| + * Local events [03, 05, 09, 10, 12, 14, 15, 16] + * |XX, XX, XX| + * Cached events [04, 05, 09] + * + * We note that the oldest event is from the server and we combine the new + * server events with the cached ones. + * + * Cached events [04, 05, 09] + * Server events [06, 07, 08] + * + * Combined events [04, 05, 06, 07, 08, 09] + * + * We split again. + * + * Presented events [04, 05, 06] + * Cached events [07, 08, 09] + * + * We slide the local window, the oldest event is on the server. + * + * Server events [01, 02, 04, 06, 07, 08, 11, 13] + * |XX, XX, XX| + * Local events [03, 05, 09, 10, 12, 14, 15, 16] + * |10, 12, 14| + * + * Cached events [07, 08, 09] + * Local events [10, 12, 14] + * Combined events [07, 08, 09, 10, 12, 14] + * + * Presented events [07, 08, 09] + * Cached events [10, 12, 14] + * + * Next up we slide the server window again. + * + * Server events [01, 02, 04, 06, 07, 08, 11, 13] + * |11, 13| + * Local events [03, 05, 09, 10, 12, 14, 15, 16] + * |XX, XX, XX| + * + * Cached events [10, 12, 14] + * Server events [11, 13] + * Combined events [10, 11, 12, 13, 14] + * + * Presented events [10, 11, 12] + * Cached events [13, 14] + * + * We have one source exhausted, we fetch the rest of our events from the other + * source and combine it with our cached events. + * + * + * @param {object} previousSearchResult A search result from a previous search + * call. + * @param {object} localEvents An unprocessed search result from the event + * index. + * @param {object} serverEvents An unprocessed search result from the server. + * + * @return {object} A response object that combines the events from the + * different event sources. + * + */ +function combineEvents(previousSearchResult, localEvents = undefined, serverEvents = undefined) { + const response = {}; + + const cachedEvents = previousSearchResult.cachedEvents; + let oldestEventFrom = previousSearchResult.oldestEventFrom; + response.highlights = previousSearchResult.highlights; + + if (localEvents && serverEvents) { + // This is a first search call, combine the events from the server and + // the local index. Note where our oldest event came from, we shall + // fetch the next batch of events from the other source. + if (compareOldestEvents(localEvents, serverEvents) < 0) { + oldestEventFrom = "local"; + } + + combineEventSources(previousSearchResult, response, localEvents.results, serverEvents.results); + response.highlights = localEvents.highlights.concat(serverEvents.highlights); + } else if (localEvents) { + // This is a pagination call fetching more events from the local index, + // meaning that our oldest event was on the server. + // Change the source of the oldest event if our local event is older + // than the cached one. + if (compareOldestEvents(localEvents, cachedEvents) < 0) { + oldestEventFrom = "local"; + } + combineEventSources(previousSearchResult, response, localEvents.results, cachedEvents); + } else if (serverEvents) { + // This is a pagination call fetching more events from the server, + // meaning that our oldest event was in the local index. + // Change the source of the oldest event if our server event is older + // than the cached one. + if (compareOldestEvents(serverEvents, cachedEvents) < 0) { + oldestEventFrom = "server"; + } + combineEventSources(previousSearchResult, response, serverEvents.results, cachedEvents); + } else { + // This is a pagination call where we exhausted both of our event + // sources, let's push the remaining cached events. + response.results = cachedEvents; + previousSearchResult.cachedEvents = []; + } + + previousSearchResult.oldestEventFrom = oldestEventFrom; + + return response; +} + +/** + * Combine the local and server search responses + * + * @param {object} previousSearchResult A search result from a previous search + * call. + * @param {object} localEvents An unprocessed search result from the event + * index. + * @param {object} serverEvents An unprocessed search result from the server. + * + * @return {object} A response object that combines the events from the + * different event sources. + */ +function combineResponses(previousSearchResult, localEvents = undefined, serverEvents = undefined) { + // Combine our events first. + const response = combineEvents(previousSearchResult, localEvents, serverEvents); + + // Our first search will contain counts from both sources, subsequent + // pagination requests will fetch responses only from one of the sources, so + // reuse the first count when we're paginating. + if (previousSearchResult.count) { + response.count = previousSearchResult.count; + } else { + response.count = localEvents.count + serverEvents.count; + } + + // Update our next batch tokens for the given search sources. + if (localEvents) { + previousSearchResult.seshatQuery.next_batch = localEvents.next_batch; + } + if (serverEvents) { + previousSearchResult.serverSideNextBatch = serverEvents.next_batch; + } + + // Set the response next batch token to one of the tokens from the sources, + // this makes sure that if we exhaust one of the sources we continue with + // the other one. + if (previousSearchResult.seshatQuery.next_batch) { + response.next_batch = previousSearchResult.seshatQuery.next_batch; + } else if (previousSearchResult.serverSideNextBatch) { + response.next_batch = previousSearchResult.serverSideNextBatch; + } + + // We collected all search results from the server as well as from Seshat, + // we still have some events cached that we'll want to display on the next + // pagination request. + // + // Provide a fake next batch token for that case. + if (!response.next_batch && previousSearchResult.cachedEvents.length > 0) { + response.next_batch = "cached"; + } + + return response; +} + +function restoreEncryptionInfo(searchResultSlice) { + for (let i = 0; i < searchResultSlice.length; i++) { + const timeline = searchResultSlice[i].context.getTimeline(); + + for (let j = 0; j < timeline.length; j++) { + const ev = timeline[j]; + + if (ev.event.curve25519Key) { + ev.makeEncrypted( + "m.room.encrypted", + { algorithm: ev.event.algorithm }, + ev.event.curve25519Key, + ev.event.ed25519Key, + ); + ev._forwardingCurve25519KeyChain = ev.event.forwardingCurve25519KeyChain; + + delete ev.event.curve25519Key; + delete ev.event.ed25519Key; + delete ev.event.algorithm; + delete ev.event.forwardingCurve25519KeyChain; + } + } + } +} + +async function combinedPagination(searchResult) { + const eventIndex = EventIndexPeg.get(); + const client = MatrixClientPeg.get(); + + const searchArgs = searchResult.seshatQuery; + const oldestEventFrom = searchResult.oldestEventFrom; + + let localResult; + let serverSideResult; + + // Fetch events from the local index if we have a token for itand if it's + // the local indexes turn or the server has exhausted its results. + if (searchArgs.next_batch && (!searchResult.serverSideNextBatch || oldestEventFrom === "server")) { + localResult = await eventIndex.search(searchArgs); + } + + // Fetch events from the server if we have a token for it and if it's the + // local indexes turn or the local index has exhausted its results. + if (searchResult.serverSideNextBatch && (oldestEventFrom === "local" || !searchArgs.next_batch)) { + const body = {body: searchResult._query, next_batch: searchResult.serverSideNextBatch}; + serverSideResult = await client.search(body); + } + + let serverEvents; + + if (serverSideResult) { + serverEvents = serverSideResult.search_categories.room_events; + } + + // Combine our events. + const combinedResult = combineResponses(searchResult, localResult, serverEvents); + + const response = { + search_categories: { + room_events: combinedResult, + }, + }; + + const oldResultCount = searchResult.results.length; + + // Let the client process the combined result. + const result = client._processRoomEventsSearch(searchResult, response); + + // Restore our encryption info so we can properly re-verify the events. + const newResultCount = result.results.length - oldResultCount; + const newSlice = result.results.slice(Math.max(result.results.length - newResultCount, 0)); + restoreEncryptionInfo(newSlice); + + searchResult.pendingRequest = null; return result; } @@ -117,11 +539,11 @@ function eventIndexSearch(term, roomId = undefined) { if (MatrixClientPeg.get().isRoomEncrypted(roomId)) { // The search is for a single encrypted room, use our local // search method. - searchPromise = localSearch(term, roomId); + searchPromise = localSearchProcess(term, roomId); } else { // The search is for a single non-encrypted room, use the // server-side search. - searchPromise = serverSideSearch(term, roomId); + searchPromise = serverSideSearchProcess(term, roomId); } } else { // Search across all rooms, combine a server side search and a @@ -132,9 +554,45 @@ function eventIndexSearch(term, roomId = undefined) { return searchPromise; } +function eventIndexSearchPagination(searchResult) { + const client = MatrixClientPeg.get(); + + const seshatQuery = searchResult.seshatQuery; + const serverQuery = searchResult._query; + + if (!seshatQuery) { + // This is a search in a non-encrypted room. Do the normal server-side + // pagination. + return client.backPaginateRoomEventsSearch(searchResult); + } else if (!serverQuery) { + // This is a search in a encrypted room. Do a local pagination. + const promise = localPagination(searchResult); + searchResult.pendingRequest = promise; + + return promise; + } else { + // We have both queries around, this is a search across all rooms so a + // combined pagination needs to be done. + const promise = combinedPagination(searchResult); + searchResult.pendingRequest = promise; + + return promise; + } +} + +export function searchPagination(searchResult) { + const eventIndex = EventIndexPeg.get(); + const client = MatrixClientPeg.get(); + + if (searchResult.pendingRequest) return searchResult.pendingRequest; + + if (eventIndex === null) return client.backPaginateRoomEventsSearch(searchResult); + else return eventIndexSearchPagination(searchResult); +} + export default function eventSearch(term, roomId = undefined) { const eventIndex = EventIndexPeg.get(); - if (eventIndex === null) return serverSideSearch(term, roomId); + if (eventIndex === null) return serverSideSearchProcess(term, roomId); else return eventIndexSearch(term, roomId); } diff --git a/src/SlashCommands.js b/src/SlashCommands.tsx similarity index 75% rename from src/SlashCommands.js rename to src/SlashCommands.tsx index d306978f78..ad3dc7002a 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.tsx @@ -2,6 +2,7 @@ Copyright 2015, 2016 OpenMarket Ltd Copyright 2018 New Vector Ltd Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,9 +18,10 @@ limitations under the License. */ -import React from 'react'; +import * as React from 'react'; + import {MatrixClientPeg} from './MatrixClientPeg'; -import dis from './dispatcher'; +import dis from './dispatcher/dispatcher'; import * as sdk from './index'; import {_t, _td} from './languageHandler'; import Modal from './Modal'; @@ -33,12 +35,26 @@ import { abbreviateUrl } from './utils/UrlUtils'; import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from './utils/IdentityServerUtils'; import {isPermalinkHost, parsePermalink} from "./utils/permalinks/Permalinks"; import {inviteUsersToRoom} from "./RoomInvite"; +import { WidgetType } from "./widgets/WidgetType"; +import { Jitsi } from "./widgets/Jitsi"; +import { parseFragment as parseHtml } from "parse5"; +import sendBugReport from "./rageshake/submit-rageshake"; +import SdkConfig from "./SdkConfig"; +import { ensureDMExists } from "./createRoom"; +import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload"; +import { Action } from "./dispatcher/actions"; +import { EffectiveMembership, getEffectiveMembership } from "./utils/membership"; -const singleMxcUpload = async () => { +// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 +interface HTMLInputEvent extends Event { + target: HTMLInputElement & EventTarget; +} + +const singleMxcUpload = async (): Promise => { return new Promise((resolve) => { const fileSelector = document.createElement('input'); fileSelector.setAttribute('type', 'file'); - fileSelector.onchange = (ev) => { + fileSelector.onchange = (ev: HTMLInputEvent) => { const file = ev.target.files[0]; const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog"); @@ -62,28 +78,49 @@ export const CommandCategories = { "other": _td("Other"), }; -class Command { - constructor({name, args='', description, runFn, category=CommandCategories.other, hideCompletionAfterSpace=false}) { - this.command = '/' + name; - this.args = args; - this.description = description; - this.runFn = runFn; - this.category = category; - this.hideCompletionAfterSpace = hideCompletionAfterSpace; +type RunFn = ((roomId: string, args: string, cmd: string) => {error: any} | {promise: Promise}); + +interface ICommandOpts { + command: string; + aliases?: string[]; + args?: string; + description: string; + runFn?: RunFn; + category: string; + hideCompletionAfterSpace?: boolean; +} + +export class Command { + command: string; + aliases: string[]; + args: undefined | string; + description: string; + runFn: undefined | RunFn; + category: string; + hideCompletionAfterSpace: boolean; + + constructor(opts: ICommandOpts) { + this.command = opts.command; + this.aliases = opts.aliases || []; + this.args = opts.args || ""; + this.description = opts.description; + this.runFn = opts.runFn; + this.category = opts.category || CommandCategories.other; + this.hideCompletionAfterSpace = opts.hideCompletionAfterSpace || false; } getCommand() { - return this.command; + return `/${this.command}`; } getCommandWithArgs() { return this.getCommand() + " " + this.args; } - run(roomId, args) { + run(roomId: string, args: string, cmd: string) { // if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me` - if (!this.runFn) return; - return this.runFn.bind(this)(roomId, args); + if (!this.runFn) return reject(_t("Command error")); + return this.runFn.bind(this)(roomId, args, cmd); } getUsage() { @@ -95,7 +132,7 @@ function reject(error) { return {error}; } -function success(promise) { +function success(promise?: Promise) { return {promise}; } @@ -103,11 +140,9 @@ function success(promise) { * functions are called with `this` bound to the Command instance. */ -/* eslint-disable babel/no-invalid-this */ - -export const CommandMap = { - shrug: new Command({ - name: 'shrug', +export const Commands = [ + new Command({ + command: 'shrug', args: '', description: _td('Prepends ¯\\_(ツ)_/¯ to a plain-text message'), runFn: function(roomId, args) { @@ -119,8 +154,8 @@ export const CommandMap = { }, category: CommandCategories.messages, }), - plain: new Command({ - name: 'plain', + new Command({ + command: 'plain', args: '', description: _td('Sends a message as plain text, without interpreting it as markdown'), runFn: function(roomId, messages) { @@ -128,11 +163,20 @@ export const CommandMap = { }, category: CommandCategories.messages, }), - ddg: new Command({ - name: 'ddg', + new Command({ + command: 'html', + args: '', + description: _td('Sends a message as html, without interpreting it as markdown'), + runFn: function(roomId, messages) { + return success(MatrixClientPeg.get().sendHtmlMessage(roomId, messages, messages)); + }, + category: CommandCategories.messages, + }), + new Command({ + command: 'ddg', args: '', description: _td('Searches DuckDuckGo for results'), - runFn: function(roomId, args) { + runFn: function() { const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); // TODO Don't explain this away, actually show a search UI here. Modal.createTrackedDialog('Slash Commands', '/ddg is not a command', ErrorDialog, { @@ -144,9 +188,8 @@ export const CommandMap = { category: CommandCategories.actions, hideCompletionAfterSpace: true, }), - - upgraderoom: new Command({ - name: 'upgraderoom', + new Command({ + command: 'upgraderoom', args: '', description: _td('Upgrades a room to a new version'), runFn: function(roomId, args) { @@ -215,9 +258,8 @@ export const CommandMap = { }, category: CommandCategories.admin, }), - - nick: new Command({ - name: 'nick', + new Command({ + command: 'nick', args: '', description: _td('Changes your display nickname'), runFn: function(roomId, args) { @@ -228,9 +270,9 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - myroomnick: new Command({ - name: 'myroomnick', + new Command({ + command: 'myroomnick', + aliases: ['roomnick'], args: '', description: _td('Changes your display nickname in the current room only'), runFn: function(roomId, args) { @@ -247,9 +289,8 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - roomavatar: new Command({ - name: 'roomavatar', + new Command({ + command: 'roomavatar', args: '[]', description: _td('Changes the avatar of the current room'), runFn: function(roomId, args) { @@ -265,9 +306,8 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - myroomavatar: new Command({ - name: 'myroomavatar', + new Command({ + command: 'myroomavatar', args: '[]', description: _td('Changes your avatar in this current room only'), runFn: function(roomId, args) { @@ -292,9 +332,8 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - myavatar: new Command({ - name: 'myavatar', + new Command({ + command: 'myavatar', args: '[]', description: _td('Changes your avatar in all rooms'), runFn: function(roomId, args) { @@ -310,9 +349,8 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - topic: new Command({ - name: 'topic', + new Command({ + command: 'topic', args: '[]', description: _td('Gets or sets the room topic'), runFn: function(roomId, args) { @@ -321,7 +359,7 @@ export const CommandMap = { return success(cli.setRoomTopic(roomId, args)); } const room = cli.getRoom(roomId); - if (!room) return reject('Bad room ID: ' + roomId); + if (!room) return reject(_t("Failed to set topic")); const topicEvents = room.currentState.getStateEvents('m.room.topic', ''); const topic = topicEvents && topicEvents.getContent().topic; @@ -331,14 +369,14 @@ export const CommandMap = { Modal.createTrackedDialog('Slash Commands', 'Topic', InfoDialog, { title: room.name, description:
, + hasCloseButton: true, }); return success(); }, category: CommandCategories.admin, }), - - roomname: new Command({ - name: 'roomname', + new Command({ + command: 'roomname', args: '', description: _td('Sets the room name'), runFn: function(roomId, args) { @@ -349,9 +387,8 @@ export const CommandMap = { }, category: CommandCategories.admin, }), - - invite: new Command({ - name: 'invite', + new Command({ + command: 'invite', args: '', description: _td('Invites user with given id to current room'), runFn: function(roomId, args) { @@ -364,14 +401,16 @@ export const CommandMap = { // If we need an identity server but don't have one, things // get a bit more complex here, but we try to show something // meaningful. - let finished = Promise.resolve(); + let prom = Promise.resolve(); if ( getAddressType(address) === 'email' && !MatrixClientPeg.get().getIdentityServerUrl() ) { const defaultIdentityServerUrl = getDefaultIdentityServerUrl(); if (defaultIdentityServerUrl) { - ({ finished } = Modal.createTrackedDialog('Slash Commands', 'Identity server', + const { finished } = Modal.createTrackedDialog<[boolean]>( + 'Slash Commands', + 'Identity server', QuestionDialog, { title: _t("Use an identity server"), description:

{_t( @@ -384,18 +423,21 @@ export const CommandMap = { )}

, button: _t("Continue"), }, - )); + ); + + prom = finished.then(([useDefault]) => { + if (useDefault) { + useDefaultIdentityServer(); + return; + } + throw new Error(_t("Use an identity server to invite by email. Manage in Settings.")); + }); } else { return reject(_t("Use an identity server to invite by email. Manage in Settings.")); } } const inviter = new MultiInviter(roomId); - return success(finished.then(([useDefault] = []) => { - if (useDefault) { - useDefaultIdentityServer(); - } else if (useDefault === false) { - throw new Error(_t("Use an identity server to invite by email. Manage in Settings.")); - } + return success(prom.then(() => { return inviter.invite([address]); }).then(() => { if (inviter.getCompletionState(address) !== "invited") { @@ -408,12 +450,12 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - join: new Command({ - name: 'join', - args: '', - description: _td('Joins room with given alias'), - runFn: function(roomId, args) { + new Command({ + command: 'join', + aliases: ['j', 'goto'], + args: '', + description: _td('Joins room with given address'), + runFn: function(_, args) { if (args) { // Note: we support 2 versions of this command. The first is // the public-facing one for most users and the other is a @@ -456,8 +498,7 @@ export const CommandMap = { }); return success(); } else if (params[0][0] === '!') { - const roomId = params[0]; - const viaServers = params.splice(0); + const [roomId, ...viaServers] = params; dis.dispatch({ action: 'view_room', @@ -521,10 +562,9 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - part: new Command({ - name: 'part', - args: '[]', + new Command({ + command: 'part', + args: '[]', description: _td('Leave room'), runFn: function(roomId, args) { const cli = MatrixClientPeg.get(); @@ -556,7 +596,7 @@ export const CommandMap = { } if (targetRoomId) break; } - if (!targetRoomId) return reject(_t('Unrecognised room alias:') + ' ' + roomAlias); + if (!targetRoomId) return reject(_t('Unrecognised room address:') + ' ' + roomAlias); } } @@ -569,9 +609,8 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - kick: new Command({ - name: 'kick', + new Command({ + command: 'kick', args: ' [reason]', description: _td('Kicks user with given id'), runFn: function(roomId, args) { @@ -585,10 +624,8 @@ export const CommandMap = { }, category: CommandCategories.admin, }), - - // Ban a user from the room with an optional reason - ban: new Command({ - name: 'ban', + new Command({ + command: 'ban', args: ' [reason]', description: _td('Bans user with given id'), runFn: function(roomId, args) { @@ -602,10 +639,8 @@ export const CommandMap = { }, category: CommandCategories.admin, }), - - // Unban a user from ythe room - unban: new Command({ - name: 'unban', + new Command({ + command: 'unban', args: '', description: _td('Unbans user with given ID'), runFn: function(roomId, args) { @@ -620,16 +655,15 @@ export const CommandMap = { }, category: CommandCategories.admin, }), - - ignore: new Command({ - name: 'ignore', + new Command({ + command: 'ignore', args: '', description: _td('Ignores a user, hiding their messages from you'), runFn: function(roomId, args) { if (args) { const cli = MatrixClientPeg.get(); - const matches = args.match(/^(\S+)$/); + const matches = args.match(/^(@[^:]+:\S+)$/); if (matches) { const userId = matches[1]; const ignoredUsers = cli.getIgnoredUsers(); @@ -651,16 +685,15 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - unignore: new Command({ - name: 'unignore', + new Command({ + command: 'unignore', args: '', description: _td('Stops ignoring a user, showing their messages going forward'), runFn: function(roomId, args) { if (args) { const cli = MatrixClientPeg.get(); - const matches = args.match(/^(\S+)$/); + const matches = args.match(/(^@[^:]+:\S+$)/); if (matches) { const userId = matches[1]; const ignoredUsers = cli.getIgnoredUsers(); @@ -683,10 +716,8 @@ export const CommandMap = { }, category: CommandCategories.actions, }), - - // Define the power level of a user - op: new Command({ - name: 'op', + new Command({ + command: 'op', args: ' []', description: _td('Define the power level of a user'), runFn: function(roomId, args) { @@ -696,13 +727,16 @@ export const CommandMap = { if (matches) { const userId = matches[1]; if (matches.length === 4 && undefined !== matches[3]) { - powerLevel = parseInt(matches[3]); + powerLevel = parseInt(matches[3], 10); } if (!isNaN(powerLevel)) { const cli = MatrixClientPeg.get(); const room = cli.getRoom(roomId); - if (!room) return reject('Bad room ID: ' + roomId); - + if (!room) return reject(_t("Command failed")); + const member = room.getMember(args); + if (!member || getEffectiveMembership(member.membership) === EffectiveMembership.Leave) { + return reject(_t("Could not find user in room")); + } const powerLevelEvent = room.currentState.getStateEvents('m.room.power_levels', ''); return success(cli.setPowerLevel(roomId, userId, powerLevel, powerLevelEvent)); } @@ -712,10 +746,8 @@ export const CommandMap = { }, category: CommandCategories.admin, }), - - // Reset the power level of a user - deop: new Command({ - name: 'deop', + new Command({ + command: 'deop', args: '', description: _td('Deops user with given id'), runFn: function(roomId, args) { @@ -724,9 +756,10 @@ export const CommandMap = { if (matches) { const cli = MatrixClientPeg.get(); const room = cli.getRoom(roomId); - if (!room) return reject('Bad room ID: ' + roomId); + if (!room) return reject(_t("Command failed")); const powerLevelEvent = room.currentState.getStateEvents('m.room.power_levels', ''); + if (!powerLevelEvent.getContent().users[args]) return reject(_t("Could not find user in room")); return success(cli.setPowerLevel(roomId, args, undefined, powerLevelEvent)); } } @@ -734,9 +767,8 @@ export const CommandMap = { }, category: CommandCategories.admin, }), - - devtools: new Command({ - name: 'devtools', + new Command({ + command: 'devtools', description: _td('Opens the Developer Tools dialog'), runFn: function(roomId) { const DevtoolsDialog = sdk.getComponent('dialogs.DevtoolsDialog'); @@ -745,31 +777,60 @@ export const CommandMap = { }, category: CommandCategories.advanced, }), - - addwidget: new Command({ - name: 'addwidget', - args: '', + new Command({ + command: 'addwidget', + args: '', description: _td('Adds a custom widget by URL to the room'), - runFn: function(roomId, args) { - if (!args || (!args.startsWith("https://") && !args.startsWith("http://"))) { + runFn: function(roomId, widgetUrl) { + if (!widgetUrl) { + return reject(_t("Please supply a widget URL or embed code")); + } + + // Try and parse out a widget URL from iframes + if (widgetUrl.toLowerCase().startsWith("
{ _t(customVariables[row[0]].expl) }{_t( + customVariables[row[0]].expl, + customVariables[row[0]].getTextVariables ? + customVariables[row[0]].getTextVariables() : + null, + )}{ row[1] }