Merge branches 'develop' and 'devtools_serverlist' of github.com:matrix-org/matrix-react-sdk into devtools_serverlist

pull/21833/head
Michael Telatynski 2019-05-09 22:27:37 +01:00
commit 16c027dc61
208 changed files with 10738 additions and 4783 deletions
.buildkite

View File

@ -1,4 +1,21 @@
{
"presets": ["react", "es2015", "es2016"],
"plugins": ["transform-class-properties", "transform-object-rest-spread", "transform-async-to-bluebird", "transform-runtime", "add-module-exports", "syntax-dynamic-import"]
"presets": [
"react",
"es2015",
"es2016"
],
"plugins": [
[
"transform-builtin-extend",
{
"globals": ["Error"]
}
],
"transform-class-properties",
"transform-object-rest-spread",
"transform-async-to-bluebird",
"transform-runtime",
"add-module-exports",
"syntax-dynamic-import"
]
}

View File

@ -3,25 +3,37 @@ steps:
command:
- "yarn install"
- "yarn lintwithexclusions"
- "yarn stylelint"
plugins:
- docker#v3.0.1:
image: "node:10"
# - label: ":chains: End-to-End Tests"
# command:
# # TODO: Remove hacky chmod for BuildKite
# - "chmod +x ./scripts/ci/*.sh"
# - "chmod +x ./scripts/*"
# - "sudo apt-get install build-essential python2.7-dev libffi-dev python-pip python-setuptools sqlite3 libssl-dev python-virtualenv libjpeg-dev libxslt1-dev"
# - "./scripts/ci/install-deps.sh"
# - "./scripts/ci/end-to-end-tests.sh"
# plugins:
# - docker#v3.0.1:
# image: "node:10"
- label: ":chains: End-to-End Tests"
agents:
# We use a medium sized instance instead of the normal small ones because
# e2e tests otherwise take +-8min
queue: "medium"
command:
# TODO: Remove hacky chmod for BuildKite
- "echo '--- Setup'"
- "chmod +x ./scripts/ci/*.sh"
- "chmod +x ./scripts/*"
- "echo '--- Install js-sdk'"
- "./scripts/ci/install-deps.sh"
- "./scripts/ci/end-to-end-tests.sh"
plugins:
- docker#v3.0.1:
image: "matrixdotorg/riotweb-ci-e2etests-env:latest"
propagate-environment: true
- label: ":karma: Tests"
agents:
# We use a medium sized instance instead of the normal small ones because
# webpack loves to gorge itself on resources.
queue: "medium"
command:
# Install chrome
- "echo '--- Installing Chrome'"
- "wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -"
- "sh -c 'echo \"deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main\" >> /etc/apt/sources.list.d/google.list'"
- "apt-get update"
@ -30,7 +42,9 @@ steps:
# TODO: Remove hacky chmod for BuildKite
- "chmod +x ./scripts/ci/*.sh"
- "chmod +x ./scripts/*"
- "echo '--- Installing Dependencies'"
- "./scripts/ci/install-deps.sh"
- "echo '+++ Running Tests'"
- "./scripts/ci/unit-tests.sh"
env:
CHROME_BIN: "/usr/bin/google-chrome-stable"
@ -40,8 +54,13 @@ steps:
propagate-environment: true
- label: "🔧 Riot Tests"
agents:
# We use a medium sized instance instead of the normal small ones because
# webpack loves to gorge itself on resources.
queue: "medium"
command:
# Install chrome
- "echo '--- Installing Chrome'"
- "wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -"
- "sh -c 'echo \"deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main\" >> /etc/apt/sources.list.d/google.list'"
- "apt-get update"
@ -50,7 +69,9 @@ steps:
# TODO: Remove hacky chmod for BuildKite
- "chmod +x ./scripts/ci/*.sh"
- "chmod +x ./scripts/*"
- "echo '--- Installing Dependencies'"
- "./scripts/ci/install-deps.sh"
- "echo '+++ Running Tests'"
- "./scripts/ci/riot-unit-tests.sh"
env:
CHROME_BIN: "/usr/bin/google-chrome-stable"
@ -58,3 +79,13 @@ steps:
- docker#v3.0.1:
image: "node:10"
propagate-environment: true
- wait
- label: "🐴 Trigger riot-web"
trigger: "riot-web"
branches: "develop"
build:
branch: "develop"
message: "[react-sdk] ${BUILDKITE_MESSAGE}"
async: true

View File

@ -10,7 +10,6 @@ src/components/structures/RoomStatusBar.js
src/components/structures/RoomView.js
src/components/structures/ScrollPanel.js
src/components/structures/SearchBox.js
src/components/structures/TimelinePanel.js
src/components/structures/UploadBar.js
src/components/views/avatars/BaseAvatar.js
src/components/views/avatars/MemberAvatar.js
@ -52,7 +51,6 @@ src/components/views/settings/ChangePassword.js
src/components/views/settings/DevicesPanel.js
src/components/views/settings/IntegrationsManager.js
src/components/views/settings/Notifications.js
src/ContentMessages.js
src/GroupAddressPicker.js
src/HtmlUtils.js
src/ImageUtils.js
@ -60,7 +58,6 @@ src/languageHandler.js
src/linkify-matrix.js
src/Markdown.js
src/MatrixClientPeg.js
src/Modal.js
src/notifications/ContentRules.js
src/notifications/PushRuleVectorState.js
src/notifications/VectorPushRulesDefinitions.js

3
.gitignore vendored
View File

@ -9,9 +9,6 @@ package-lock.json
/git-revision.txt
/matrix-react-sdk-*.tgz
# test reports created by karma
/karma-reports
/.idea
/src/component-index.js

15
.stylelintrc.js Normal file
View File

@ -0,0 +1,15 @@
module.exports = {
"extends": "stylelint-config-standard",
"rules": {
"indentation": 4,
"comment-empty-line-before": null,
"declaration-empty-line-before": null,
"length-zero-no-unit": null,
"rule-empty-line-before": null,
"color-hex-length": null,
"max-empty-lines": null,
"number-no-trailing-zeros": null,
"number-leading-zero": null,
"selector-list-comma-newline-after": null,
}
}

View File

@ -1,3 +1,313 @@
Changes in [1.1.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.1.0) (2019-05-07)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.1.0-rc.1...v1.1.0)
* Relax password requirements to score of 3 out of 4
[\#2949](https://github.com/matrix-org/matrix-react-sdk/pull/2949)
* Restore access to message quote option on first click
[\#2948](https://github.com/matrix-org/matrix-react-sdk/pull/2948)
* Check for `room` in all `Room.timeline*` handlers
[\#2946](https://github.com/matrix-org/matrix-react-sdk/pull/2946)
Changes in [1.1.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.1.0-rc.1) (2019-04-30)
=============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.0.7...v1.1.0-rc.1)
* Add important info to new preview bar
[\#2936](https://github.com/matrix-org/matrix-react-sdk/pull/2936)
* Add a message action bar
[\#2935](https://github.com/matrix-org/matrix-react-sdk/pull/2935)
* Trigger riot-web build
[\#2934](https://github.com/matrix-org/matrix-react-sdk/pull/2934)
* Input validation tooltips for registration
[\#2933](https://github.com/matrix-org/matrix-react-sdk/pull/2933)
* Also say "Connect ..." on remaining key backup buttons
[\#2931](https://github.com/matrix-org/matrix-react-sdk/pull/2931)
* Mark a few CSS classes as not selectable
[\#2929](https://github.com/matrix-org/matrix-react-sdk/pull/2929)
* Cleanup message composer render() method
[\#2883](https://github.com/matrix-org/matrix-react-sdk/pull/2883)
* Redesigned room preview bar
[\#2925](https://github.com/matrix-org/matrix-react-sdk/pull/2925)
* Prevent user pills containing only emoji from embiggening
[\#2907](https://github.com/matrix-org/matrix-react-sdk/pull/2907)
* Make alt-enter insert new line on macOS
[\#2923](https://github.com/matrix-org/matrix-react-sdk/pull/2923)
* Test `defaultServerName` before showing it on forgot password
[\#2924](https://github.com/matrix-org/matrix-react-sdk/pull/2924)
* Add a function to append/overwrite objects in the config on the fly
[\#2922](https://github.com/matrix-org/matrix-react-sdk/pull/2922)
* use SdkConfig brand name instead of static "Riot"
[\#2921](https://github.com/matrix-org/matrix-react-sdk/pull/2921)
* Use dedicated permalink creators in search results with multiple rooms
[\#2898](https://github.com/matrix-org/matrix-react-sdk/pull/2898)
* Clarify that use backup means restore
[\#2917](https://github.com/matrix-org/matrix-react-sdk/pull/2917)
* Fix key backup status when missing device
[\#2919](https://github.com/matrix-org/matrix-react-sdk/pull/2919)
* Ensure `<b>` tags appear bold for all browsers
[\#2918](https://github.com/matrix-org/matrix-react-sdk/pull/2918)
* Add a link in room settings to get at the tombstoned room if it exists
[\#2908](https://github.com/matrix-org/matrix-react-sdk/pull/2908)
* Add a generic error page element for startup errors
[\#2915](https://github.com/matrix-org/matrix-react-sdk/pull/2915)
* Add strings for js-sdk autodiscovery errors
[\#2916](https://github.com/matrix-org/matrix-react-sdk/pull/2916)
* Focus the composer view on file upload
[\#2914](https://github.com/matrix-org/matrix-react-sdk/pull/2914)
* use medium agent for e2e tests
[\#2911](https://github.com/matrix-org/matrix-react-sdk/pull/2911)
* adjust prop in HeaderButton
[\#2912](https://github.com/matrix-org/matrix-react-sdk/pull/2912)
* Remove breadcrumb scroll tolerances and use sensible defaults
[\#2913](https://github.com/matrix-org/matrix-react-sdk/pull/2913)
* Fix having to click the member list button twice to show it after having
changed room.
[\#2906](https://github.com/matrix-org/matrix-react-sdk/pull/2906)
* Add period to the end of upgrade notice
[\#2909](https://github.com/matrix-org/matrix-react-sdk/pull/2909)
* Remove duplicate space in credits
[\#2889](https://github.com/matrix-org/matrix-react-sdk/pull/2889)
* Handle M_UNSUPPORTED_ROOM_VERSION in invites and room creation
[\#2905](https://github.com/matrix-org/matrix-react-sdk/pull/2905)
* Re-enable E2E tests
[\#2867](https://github.com/matrix-org/matrix-react-sdk/pull/2867)
* Remove BottomLeftMenu and supporting bits
[\#2903](https://github.com/matrix-org/matrix-react-sdk/pull/2903)
* Fix for retina thumbnails being massive
[\#2439](https://github.com/matrix-org/matrix-react-sdk/pull/2439)
* Send breadcrumb updates only when they change
[\#2894](https://github.com/matrix-org/matrix-react-sdk/pull/2894)
* Add some tolerances to breadcrumb scrolling
[\#2892](https://github.com/matrix-org/matrix-react-sdk/pull/2892)
* Fix validation to avoid `undefined` class on fields
[\#2902](https://github.com/matrix-org/matrix-react-sdk/pull/2902)
* Always return a client from onRegistered
[\#2895](https://github.com/matrix-org/matrix-react-sdk/pull/2895)
* Fix room upgrade warnings popping up in upgraded rooms
[\#2897](https://github.com/matrix-org/matrix-react-sdk/pull/2897)
* Fix style lint errors & enable on CI
[\#2901](https://github.com/matrix-org/matrix-react-sdk/pull/2901)
* Add stylelint
[\#2900](https://github.com/matrix-org/matrix-react-sdk/pull/2900)
* Key backup: Handle case where your onw sig is invalid
[\#2899](https://github.com/matrix-org/matrix-react-sdk/pull/2899)
* Simplify settings dialog CSS
[\#2891](https://github.com/matrix-org/matrix-react-sdk/pull/2891)
* Fix upload cancel in e2e rooms
[\#2893](https://github.com/matrix-org/matrix-react-sdk/pull/2893)
* Set E2E room status to warning when crypto is disabled
[\#2890](https://github.com/matrix-org/matrix-react-sdk/pull/2890)
* Move SettingsDialog width override to fixedWidth
[\#2888](https://github.com/matrix-org/matrix-react-sdk/pull/2888)
* Prevent the permalink creator from causing cascading failure
[\#2882](https://github.com/matrix-org/matrix-react-sdk/pull/2882)
* Don't include all networks by default in the room directory
[\#2881](https://github.com/matrix-org/matrix-react-sdk/pull/2881)
* Fix fixed width dialogs
[\#2886](https://github.com/matrix-org/matrix-react-sdk/pull/2886)
* Fix settings dialog layout
[\#2885](https://github.com/matrix-org/matrix-react-sdk/pull/2885)
* Update from Weblate
[\#2884](https://github.com/matrix-org/matrix-react-sdk/pull/2884)
* Design tweaks to dialogs
[\#2868](https://github.com/matrix-org/matrix-react-sdk/pull/2868)
* Remove 'try the app' link from login
[\#2880](https://github.com/matrix-org/matrix-react-sdk/pull/2880)
* Track store failures after startup
[\#2870](https://github.com/matrix-org/matrix-react-sdk/pull/2870)
* Translate vertical scrolling to horizontal movement in breadcrumbs
[\#2877](https://github.com/matrix-org/matrix-react-sdk/pull/2877)
* Add telemetry for breadcrumbs and have the setting apply without refresh
[\#2873](https://github.com/matrix-org/matrix-react-sdk/pull/2873)
* Fix a few bugs introduced in file upload rework
[\#2879](https://github.com/matrix-org/matrix-react-sdk/pull/2879)
* Sync breadcrumb rooms through account data
[\#2875](https://github.com/matrix-org/matrix-react-sdk/pull/2875)
* Scroll breadcrumbs to the left when they change
[\#2878](https://github.com/matrix-org/matrix-react-sdk/pull/2878)
* Add an indicator to show a room is a direct chat in breadcrumbs
[\#2874](https://github.com/matrix-org/matrix-react-sdk/pull/2874)
* Use the most recent version of the room in breadcrumbs
[\#2872](https://github.com/matrix-org/matrix-react-sdk/pull/2872)
* Autohide the scrollbar on breadcrumbs
[\#2876](https://github.com/matrix-org/matrix-react-sdk/pull/2876)
* Ensure the page URL is redacted before tracking analytics events
[\#2871](https://github.com/matrix-org/matrix-react-sdk/pull/2871)
* fix NPE for rooms with redacted tombstones
[\#2869](https://github.com/matrix-org/matrix-react-sdk/pull/2869)
* Don't re-init the stickerpicker unless something actually changes
[\#2862](https://github.com/matrix-org/matrix-react-sdk/pull/2862)
* Add option to rotate images
[\#2855](https://github.com/matrix-org/matrix-react-sdk/pull/2855)
* Add badges to breadcrumb rooms
[\#2861](https://github.com/matrix-org/matrix-react-sdk/pull/2861)
* Include the current power level in the selector
[\#2866](https://github.com/matrix-org/matrix-react-sdk/pull/2866)
* Apply 50% opacity to left breadcrumbs
[\#2860](https://github.com/matrix-org/matrix-react-sdk/pull/2860)
* Small scroll fixes
[\#2865](https://github.com/matrix-org/matrix-react-sdk/pull/2865)
* Put the stickerpicker below dialogs
[\#2863](https://github.com/matrix-org/matrix-react-sdk/pull/2863)
* Logging tweaks
[\#2864](https://github.com/matrix-org/matrix-react-sdk/pull/2864)
* Implement redesigned upload confirmation screens
[\#2858](https://github.com/matrix-org/matrix-react-sdk/pull/2858)
* Use Field component in bug report dialog
[\#2859](https://github.com/matrix-org/matrix-react-sdk/pull/2859)
* Notify user when crypto data is missing
[\#2841](https://github.com/matrix-org/matrix-react-sdk/pull/2841)
* Update from Weblate
[\#2857](https://github.com/matrix-org/matrix-react-sdk/pull/2857)
* Download PDFs as blobs to avoid empty grey screens
[\#2847](https://github.com/matrix-org/matrix-react-sdk/pull/2847)
* Set title attribute on images in lightbox
[\#2848](https://github.com/matrix-org/matrix-react-sdk/pull/2848)
* Add MemberInfo for 3pid invites and support revoking those invites
[\#2843](https://github.com/matrix-org/matrix-react-sdk/pull/2843)
* round scrollTop upwards to prevent never detecting bottom
[\#2846](https://github.com/matrix-org/matrix-react-sdk/pull/2846)
* Notifier is how singleton is known outside of this module
[\#2845](https://github.com/matrix-org/matrix-react-sdk/pull/2845)
* Delay `Notifier` check until we have push rules
[\#2844](https://github.com/matrix-org/matrix-react-sdk/pull/2844)
* BACAT Scrolling
[\#2842](https://github.com/matrix-org/matrix-react-sdk/pull/2842)
* Handle storage fallback cases in consistency check
[\#2840](https://github.com/matrix-org/matrix-react-sdk/pull/2840)
* Handle all the segments of a v3 event ID
[\#2827](https://github.com/matrix-org/matrix-react-sdk/pull/2827)
* Add custom tooltips and scrolling to breadcrumbs
[\#2839](https://github.com/matrix-org/matrix-react-sdk/pull/2839)
* Check if the message panel is at the end of the timeline on init
[\#2829](https://github.com/matrix-org/matrix-react-sdk/pull/2829)
* Persist breadcrumb state between sessions
[\#2837](https://github.com/matrix-org/matrix-react-sdk/pull/2837)
* Always append the current room to the breadcrumbs
[\#2838](https://github.com/matrix-org/matrix-react-sdk/pull/2838)
* Alert the user to unread notifications in prior versions of rooms
[\#2831](https://github.com/matrix-org/matrix-react-sdk/pull/2831)
* Filter out upgraded rooms from autocomplete results
[\#2830](https://github.com/matrix-org/matrix-react-sdk/pull/2830)
Changes in [1.0.7](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.0.7) (2019-04-08)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.0.6...v1.0.7)
* Hotfix: bump js-sdk to 1.0.4, see https://github.com/matrix-org/matrix-js-sdk/releases/tag/v1.0.4
Changes in [1.0.6](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.0.6) (2019-04-01)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.0.6-rc.1...v1.0.6)
* Handle storage fallback cases in consistency check
[\#2853](https://github.com/matrix-org/matrix-react-sdk/pull/2853)
* Set title attribute on images in lightbox
[\#2852](https://github.com/matrix-org/matrix-react-sdk/pull/2852)
* Download PDFs as blobs to avoid empty grey screens
[\#2851](https://github.com/matrix-org/matrix-react-sdk/pull/2851)
* Add MemberInfo for 3pid invites and support revoking those invites
[\#2850](https://github.com/matrix-org/matrix-react-sdk/pull/2850)
Changes in [1.0.6-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.0.6-rc.1) (2019-03-27)
=============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.0.5...v1.0.6-rc.1)
* Catch errors when checking IndexedDB
[\#2836](https://github.com/matrix-org/matrix-react-sdk/pull/2836)
* Remove noreferrer on widget pop-out
[\#2835](https://github.com/matrix-org/matrix-react-sdk/pull/2835)
* Rework room directory so that new room is always available
[\#2834](https://github.com/matrix-org/matrix-react-sdk/pull/2834)
* Send telemetry about storage consistency
[\#2832](https://github.com/matrix-org/matrix-react-sdk/pull/2832)
* Widget OpenID reauth implementation
[\#2781](https://github.com/matrix-org/matrix-react-sdk/pull/2781)
* Log results of basic storage consistency check
[\#2826](https://github.com/matrix-org/matrix-react-sdk/pull/2826)
* Clarify devices affected by notification settings
[\#2828](https://github.com/matrix-org/matrix-react-sdk/pull/2828)
* Add a command for creating custom widgets without an integration manager
[\#2824](https://github.com/matrix-org/matrix-react-sdk/pull/2824)
* Minimize stickerpicker when the title is clicked
[\#2822](https://github.com/matrix-org/matrix-react-sdk/pull/2822)
* Add <code> blocks around homeserver and identity server urls
[\#2825](https://github.com/matrix-org/matrix-react-sdk/pull/2825)
* Fixed drop shadow for tooltip.
[\#2815](https://github.com/matrix-org/matrix-react-sdk/pull/2815)
* Ask the user for debug logs when the timeline explodes
[\#2820](https://github.com/matrix-org/matrix-react-sdk/pull/2820)
* Fix typo preventing users from adding more widgets easily
[\#2823](https://github.com/matrix-org/matrix-react-sdk/pull/2823)
* Attach an onChange listener to the room's blacklist devices option
[\#2817](https://github.com/matrix-org/matrix-react-sdk/pull/2817)
* Use leaveRoomChain when leaving a room
[\#2818](https://github.com/matrix-org/matrix-react-sdk/pull/2818)
* Fix bug with NetworkList dropdown
[\#2821](https://github.com/matrix-org/matrix-react-sdk/pull/2821)
* Trim the logging for URL previews
[\#2816](https://github.com/matrix-org/matrix-react-sdk/pull/2816)
* Explicitly create `cryptoStore` in React SDK
[\#2814](https://github.com/matrix-org/matrix-react-sdk/pull/2814)
* Change to new consistent name for `MemoryStore`
[\#2812](https://github.com/matrix-org/matrix-react-sdk/pull/2812)
* Use medium agents for the more resource intensive builds
[\#2813](https://github.com/matrix-org/matrix-react-sdk/pull/2813)
* Add log grouping to buildkite
[\#2810](https://github.com/matrix-org/matrix-react-sdk/pull/2810)
* Switch to `git` protocol for CI dependencies
[\#2809](https://github.com/matrix-org/matrix-react-sdk/pull/2809)
* Go back to using mainine velocity
[\#2808](https://github.com/matrix-org/matrix-react-sdk/pull/2808)
* Warn that members won't be autojoined to upgraded rooms
[\#2796](https://github.com/matrix-org/matrix-react-sdk/pull/2796)
* Support CI for matching branches on forks
[\#2807](https://github.com/matrix-org/matrix-react-sdk/pull/2807)
* Discard old sticker picker when the URL changes
[\#2801](https://github.com/matrix-org/matrix-react-sdk/pull/2801)
* Reload widget messaging when widgets reload
[\#2799](https://github.com/matrix-org/matrix-react-sdk/pull/2799)
* Don't show calculated room name in room settings name input field
[\#2806](https://github.com/matrix-org/matrix-react-sdk/pull/2806)
* Disable big emoji for m.emote messages as it looks weird
[\#2805](https://github.com/matrix-org/matrix-react-sdk/pull/2805)
* Remove Edge from browser support statements
[\#2803](https://github.com/matrix-org/matrix-react-sdk/pull/2803)
* Update from Weblate
[\#2802](https://github.com/matrix-org/matrix-react-sdk/pull/2802)
* Really fix tag panel
[\#2800](https://github.com/matrix-org/matrix-react-sdk/pull/2800)
* Update CompatibilityPage to match officially supported browsers
[\#2793](https://github.com/matrix-org/matrix-react-sdk/pull/2793)
* Use Buildkite for CI
[\#2788](https://github.com/matrix-org/matrix-react-sdk/pull/2788)
* Fix CSS syntax errors preventing offline member opacity from working
[\#2794](https://github.com/matrix-org/matrix-react-sdk/pull/2794)
* Make the EntityTile chevron a masked SVG for theming
[\#2795](https://github.com/matrix-org/matrix-react-sdk/pull/2795)
* Remove refs from `RegistrationForm`
[\#2791](https://github.com/matrix-org/matrix-react-sdk/pull/2791)
* Fix initial letter avatar vertical offset in Firefox
[\#2792](https://github.com/matrix-org/matrix-react-sdk/pull/2792)
* Fix the custom tag panel
[\#2797](https://github.com/matrix-org/matrix-react-sdk/pull/2797)
* Ensure freshly invited members don't count towards the alone warning
[\#2786](https://github.com/matrix-org/matrix-react-sdk/pull/2786)
* Fix 'forgot password' warning to represent the reality of e2ee
[\#2787](https://github.com/matrix-org/matrix-react-sdk/pull/2787)
* Restore `Field` value getter for `RegistrationForm`
[\#2790](https://github.com/matrix-org/matrix-react-sdk/pull/2790)
* Initial portions of support for Field validation
[\#2780](https://github.com/matrix-org/matrix-react-sdk/pull/2780)
Changes in [1.0.5](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.0.5) (2019-03-21)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.0.4...v1.0.5)
* Hotfix: disable typing notifs jumping prevention for now
[\#2811](https://github.com/matrix-org/matrix-react-sdk/pull/2811)
Changes in [1.0.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.0.4) (2019-03-18)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.0.4-rc.1...v1.0.4)

View File

@ -229,7 +229,7 @@ Controllers are notified of changes by the `SettingsStore`, and are given the op
### Features
Features automatically get considered as `disabled` if they are not listed in the `SdkConfig` or `enable_labs` is
Features automatically get considered as `disabled` if they are not listed in the `SdkConfig` or `enableLabs` is
false/not set. Features are always checked against the configuration before going through the level order as they have
the option of being forced-on or forced-off for the application. This is done by the `features` section and looks
something like this:
@ -260,4 +260,4 @@ In practice, handlers which rely on remote changes (account data, room events, e
generalized `WatchManager` - a class specifically designed to deduplicate the logic of managing watchers. The handlers
which are localized to the local client (device) generally just trigger the `WatchManager` when they manipulate the
setting themselves as there's nothing to really 'watch'.

View File

@ -29,6 +29,9 @@ yarn lintall -f checkstyle -o eslint.xml || true
# re-run the linter, excluding any files known to have errors or warnings.
yarn lintwithexclusions
# lint styles
yarn stylelint
# delete the old tarball, if it exists
rm -f matrix-react-sdk-*.tgz

View File

@ -94,7 +94,7 @@ module.exports = function (config) {
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['logcapture', 'spec', 'junit', 'summary'],
reporters: ['logcapture', 'spec', 'summary'],
specReporter: {
suppressErrorSummary: false, // do print error summary
@ -156,10 +156,6 @@ module.exports = function (config) {
// how many browser should be started simultaneous
concurrency: Infinity,
junitReporter: {
outputDir: 'karma-reports',
},
webpack: {
module: {
rules: [

View File

@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
"version": "1.0.4",
"version": "1.1.0",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
@ -49,6 +49,7 @@
"lint": "eslint src/",
"lintall": "eslint src/ test/",
"lintwithexclusions": "eslint --max-warnings 0 --ignore-path .eslintignore.errorfiles src test",
"stylelint": "stylelint res/css/**/*.scss",
"clean": "rimraf lib",
"prepare": "yarn clean && yarn build && git rev-parse HEAD > git-revision.txt",
"test": "karma start --single-run=true --browsers VectorChromeHeadless",
@ -79,9 +80,10 @@
"linkifyjs": "^2.1.6",
"lodash": "^4.13.1",
"lolex": "2.3.2",
"matrix-js-sdk": "1.0.2",
"matrix-js-sdk": "1.1.0",
"optimist": "^0.6.1",
"pako": "^1.0.5",
"png-chunks-extract": "^1.0.0",
"prop-types": "^15.5.8",
"qrcode-react": "^0.1.16",
"qs": "^6.6.0",
@ -99,7 +101,7 @@
"slate-react": "^0.18.10",
"text-encoding-utf-8": "^1.0.1",
"url": "^0.11.0",
"velocity-vector": "github:vector-im/velocity#059e3b2",
"velocity-animate": "^1.5.2",
"whatwg-fetch": "^1.1.1",
"zxcvbn": "^4.4.2"
},
@ -110,6 +112,7 @@
"babel-loader": "^7.1.5",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-transform-async-to-bluebird": "^1.1.1",
"babel-plugin-transform-builtin-extend": "^1.1.2",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
@ -133,14 +136,13 @@
"karma": "^4.0.1",
"karma-chrome-launcher": "^2.2.0",
"karma-cli": "^1.0.1",
"karma-junit-reporter": "^2.0.0",
"karma-logcapture-reporter": "0.0.1",
"karma-mocha": "^1.3.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "^0.0.31",
"karma-summary-reporter": "^1.5.1",
"karma-webpack": "^4.0.0-beta.0",
"matrix-mock-request": "^1.2.1",
"matrix-mock-request": "^1.2.3",
"matrix-react-test-utils": "^0.1.1",
"mocha": "^5.0.5",
"react-addons-test-utils": "^15.4.0",
@ -148,6 +150,8 @@
"rimraf": "^2.4.3",
"sinon": "^5.0.7",
"source-map-loader": "^0.2.3",
"stylelint": "^9.10.1",
"stylelint-config-standard": "^18.2.0",
"walk": "^2.3.9",
"webpack": "^4.20.2",
"webpack-cli": "^3.1.1"

View File

@ -36,6 +36,12 @@ body {
color: $warning-color;
}
b {
// On Firefox, the default weight for `<b>` is `bolder` which results in no bold
// effect since we only have specific weights of our fonts available.
font-weight: bold;
}
h2 {
color: $primary-fg-color;
font-weight: 400;
@ -118,7 +124,7 @@ textarea {
background-color: transparent;
color: $input-darker-fg-color;
border-radius: 4px;
border: 1px solid #c1c1c1;
border: 1px solid $dialog-close-fg-color;
// these things should probably not be defined
// globally
margin: 9px;
@ -267,14 +273,18 @@ textarea {
font-weight: 300;
font-size: 15px;
position: relative;
padding: 40px 58px 36px 58px;
width: 60%;
max-width: 704px;
box-shadow: 2px 15px 30px 0 $dialog-shadow-color;
padding: 25px 30px 30px 30px;
max-height: 80%;
box-shadow: 2px 15px 30px 0 $dialog-shadow-color;
border-radius: 4px;
overflow-y: auto;
}
.mx_Dialog_fixedWidth {
width: 60vw;
max-width: 704px;
}
.mx_Dialog_staticWrapper .mx_Dialog {
z-index: 4010;
}
@ -317,13 +327,13 @@ textarea {
.mx_Dialog_header {
position: relative;
margin-bottom: 20px;
}
.mx_Dialog_title {
font-weight: bold;
font-size: 22px;
line-height: 36px;
color: $primary-fg-color;
color: $dialog-title-fg-color;
}
.mx_Dialog_header.mx_Dialog_headerWithButton > .mx_Dialog_title {
@ -338,13 +348,14 @@ textarea {
mask: url('$(res)/img/feather-customised/cancel.svg');
mask-repeat: no-repeat;
mask-position: center;
width: 36px;
height: 36px;
background-color: $primary-fg-color;
mask-size: cover;
width: 14px;
height: 14px;
background-color: $dialog-close-fg-color;
cursor: pointer;
position: absolute;
top: 20px;
right: 20px;
top: 4px;
right: 0px;
}
.mx_Dialog_content {
@ -355,6 +366,7 @@ textarea {
}
.mx_Dialog_buttons {
margin-top: 20px;
text-align: right;
}
@ -370,6 +382,10 @@ textarea {
background-color: $button-secondary-bg-color;
}
.mx_Dialog button:last-child {
margin-right: 0px;
}
.mx_Dialog button:hover, .mx_Dialog input[type="submit"]:hover {
@mixin mx_DialogButton_hover;
}
@ -381,6 +397,7 @@ textarea {
.mx_Dialog button.mx_Dialog_primary, .mx_Dialog input[type="submit"].mx_Dialog_primary {
color: $accent-fg-color;
background-color: $accent-color;
min-width: 156px;
}
.mx_Dialog button.danger, .mx_Dialog input[type="submit"].danger {
@ -506,3 +523,38 @@ textarea {
opacity: 0;
cursor: pointer;
}
// username colors
// used by SenderProfile & RoomPreviewBar
.mx_Username_color1 {
color: $username-variant1-color;
}
.mx_Username_color2 {
color: $username-variant2-color;
}
.mx_Username_color3 {
color: $username-variant3-color;
}
.mx_Username_color4 {
color: $username-variant4-color;
}
.mx_Username_color5 {
color: $username-variant5-color;
}
.mx_Username_color6 {
color: $username-variant6-color;
}
.mx_Username_color7 {
color: $username-variant7-color;
}
.mx_Username_color8 {
color: $username-variant8-color;
}

View File

@ -6,6 +6,7 @@
@import "./structures/_CreateRoom.scss";
@import "./structures/_CustomRoomTagPanel.scss";
@import "./structures/_FilePanel.scss";
@import "./structures/_GenericErrorPage.scss";
@import "./structures/_GroupView.scss";
@import "./structures/_HeaderButtons.scss";
@import "./structures/_HomePage.scss";
@ -19,6 +20,7 @@
@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";
@ -46,11 +48,11 @@
@import "./views/context_menus/_StatusMessageContextMenu.scss";
@import "./views/context_menus/_TagTileContextMenu.scss";
@import "./views/context_menus/_TopLeftMenu.scss";
@import "./views/dialogs/_AddressPickerDialog.scss";
@import "./views/dialogs/_Analytics.scss";
@import "./views/dialogs/_BugReportDialog.scss";
@import "./views/dialogs/_ChangelogDialog.scss";
@import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss";
@import "./views/dialogs/_ChatInviteDialog.scss";
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
@import "./views/dialogs/_CreateGroupDialog.scss";
@import "./views/dialogs/_CreateRoomDialog.scss";
@ -69,7 +71,9 @@
@import "./views/dialogs/_SettingsDialog.scss";
@import "./views/dialogs/_ShareDialog.scss";
@import "./views/dialogs/_UnknownDeviceDialog.scss";
@import "./views/dialogs/_UploadConfirmDialog.scss";
@import "./views/dialogs/_UserSettingsDialog.scss";
@import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss";
@import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss";
@import "./views/dialogs/keybackup/_KeyBackupFailedDialog.scss";
@import "./views/dialogs/keybackup/_RestoreKeyBackupDialog.scss";
@ -96,6 +100,7 @@
@import "./views/elements/_ToggleSwitch.scss";
@import "./views/elements/_ToolTipButton.scss";
@import "./views/elements/_Tooltip.scss";
@import "./views/elements/_Validation.scss";
@import "./views/globals/_MatrixToolbar.scss";
@import "./views/groups/_GroupPublicityToggle.scss";
@import "./views/groups/_GroupRoomList.scss";
@ -108,7 +113,11 @@
@import "./views/messages/_MNoticeBody.scss";
@import "./views/messages/_MStickerBody.scss";
@import "./views/messages/_MTextBody.scss";
@import "./views/messages/_MessageActionBar.scss";
@import "./views/messages/_MessageTimestamp.scss";
@import "./views/messages/_ReactionDimension.scss";
@import "./views/messages/_ReactionsRow.scss";
@import "./views/messages/_ReactionsRowButton.scss";
@import "./views/messages/_RoomAvatarEvent.scss";
@import "./views/messages/_SenderProfile.scss";
@import "./views/messages/_TextualEvent.scss";

View File

@ -14,6 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
/* This file has CSS for both native and non-native scrollbars in an
* order that's fairly logic to read but violates stylelints descending
* specificity rule, so turn it off for this file. It also duplicates
* a selector to separate the hiding/showing from the sizing.
*/
/* stylelint-disable no-descending-specificity, no-duplicate-selectors */
/*
1. for browsers that support native overlay auto-hiding scrollbars
*/
@ -59,8 +66,7 @@ body.mx_scrollbar_nooverlay {
*/
.mx_AutoHideScrollbar:hover.mx_AutoHideScrollbar_overflow > .mx_AutoHideScrollbar_offset,
.mx_AutoHideScrollbar:hover.mx_AutoHideScrollbar_overflow::before,
.mx_AutoHideScrollbar:hover.mx_AutoHideScrollbar_overflow::after
{
.mx_AutoHideScrollbar:hover.mx_AutoHideScrollbar_overflow::after {
margin-right: calc(-1 * var(--scrollbar-width));
}
}

View File

@ -16,4 +16,4 @@
border: 1px solid;
padding: 10px;
background-color: #fcc;
}
}

View File

@ -35,7 +35,7 @@ limitations under the License.
background-color: $menu-bg-color;
color: $primary-fg-color;
position: absolute;
font-size: 14px;
font-size: 14px;
z-index: 5001;
}
@ -54,14 +54,14 @@ limitations under the License.
border-bottom: 8px solid transparent;
}
.mx_ContextualMenu_chevron_right:after {
content:'';
.mx_ContextualMenu_chevron_right::after {
content: '';
width: 0;
height: 0;
border-top: 7px solid transparent;
border-left: 7px solid $menu-bg-color;
border-bottom: 7px solid transparent;
position:absolute;
position: absolute;
top: -7px;
right: 1px;
}
@ -81,14 +81,14 @@ limitations under the License.
border-bottom: 8px solid transparent;
}
.mx_ContextualMenu_chevron_left:after{
content:'';
.mx_ContextualMenu_chevron_left::after {
content: '';
width: 0;
height: 0;
border-top: 7px solid transparent;
border-right: 7px solid $menu-bg-color;
border-bottom: 7px solid transparent;
position:absolute;
position: absolute;
top: -7px;
left: 1px;
}
@ -108,14 +108,14 @@ limitations under the License.
border-right: 8px solid transparent;
}
.mx_ContextualMenu_chevron_top:after{
content:'';
.mx_ContextualMenu_chevron_top::after {
content: '';
width: 0;
height: 0;
border-left: 7px solid transparent;
border-bottom: 7px solid $menu-bg-color;
border-right: 7px solid transparent;
position:absolute;
position: absolute;
left: -7px;
top: 1px;
}
@ -135,14 +135,14 @@ limitations under the License.
border-right: 8px solid transparent;
}
.mx_ContextualMenu_chevron_bottom:after{
content:'';
.mx_ContextualMenu_chevron_bottom::after {
content: '';
width: 0;
height: 0;
border-left: 7px solid transparent;
border-top: 7px solid $menu-bg-color;
border-right: 7px solid transparent;
position:absolute;
position: absolute;
left: -7px;
bottom: 1px;
}

View File

@ -22,7 +22,7 @@ limitations under the License.
}
.mx_CreateRoom input,
.mx_CreateRoom textarea {
.mx_CreateRoom textarea {
border-radius: 3px;
border: 1px solid $strong-input-border-color;
font-weight: 300;

View File

@ -101,10 +101,10 @@ limitations under the License.
padding-left: 0px;
}
.mx_FilePanel .mx_EventTile:hover .mx_EventTile_line {
background-color: $primary-bg-color;
}
.mx_FilePanel .mx_EventTile_selected .mx_EventTile_line {
padding-left: 0px;
}
.mx_FilePanel .mx_EventTile:hover .mx_EventTile_line {
background-color: $primary-bg-color;
}

View File

@ -0,0 +1,19 @@
.mx_GenericErrorPage {
width: 100%;
height: 100%;
background-color: #fff;
}
.mx_GenericErrorPage_box {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
width: 500px;
height: 200px;
border: 1px solid #f22;
padding: 10px;
background-color: #fcc;
}

View File

@ -67,13 +67,13 @@ limitations under the License.
}
.mx_GroupView_editable {
border-bottom: 1px solid $strong-input-border-color ! important;
border-bottom: 1px solid $strong-input-border-color !important;
min-width: 150px;
cursor: text;
}
.mx_GroupView_editable:focus {
border-bottom: 1px solid $accent-color ! important;
border-bottom: 1px solid $accent-color !important;
outline: none;
box-shadow: none;
}
@ -95,7 +95,7 @@ limitations under the License.
.mx_GroupView_avatarPicker .mx_Spinner {
width: 48px;
height: 48px ! important;
height: 48px !important;
}
.mx_GroupView_header_leftCol {
@ -176,7 +176,7 @@ limitations under the License.
flex: 1;
}
.mx_GroupView_body {
.mx_GroupView_body {
flex-grow: 1;
}
@ -333,7 +333,7 @@ limitations under the License.
display: none;
}
.mx_GroupView_body .gm-scroll-view > *{
.mx_GroupView_body .gm-scroll-view > * {
margin: 11px 50px 0px 68px;
}

View File

@ -67,11 +67,6 @@ limitations under the License.
z-index: 6;
}
.mx_LeftPanel_container.collapsed .mx_BottomLeftMenu {
flex: 0 0 160px;
margin-bottom: 9px;
}
.mx_LeftPanel .mx_BottomLeftMenu {
order: 3;
@ -82,6 +77,11 @@ limitations under the License.
z-index: 1;
}
.mx_LeftPanel_container.collapsed .mx_BottomLeftMenu {
flex: 0 0 160px;
margin-bottom: 9px;
}
.mx_LeftPanel .mx_BottomLeftMenu_options {
margin-top: 18px;
}

View File

@ -19,3 +19,9 @@ limitations under the License.
flex-direction: row;
min-width: 0;
}
// move hit area 5px to the right so it doesn't overlap with the timeline scrollbar
.mx_MainSplit > .mx_ResizeHandle.mx_ResizeHandle_horizontal {
margin: 0 -10px 0 0;
padding: 0 10px 0 0;
}

View File

@ -52,7 +52,7 @@ limitations under the License.
background-color: $roomheader-addroom-bg-color;
position: relative;
&:before {
&::before {
background-color: $roomheader-addroom-fg-color;
mask: url('$(res)/img/icons-create-room.svg');
mask-repeat: no-repeat;
@ -113,8 +113,7 @@ limitations under the License.
overflow-x: hidden;
display: flex;
flex-direction: row;
flex-flow: wrap;
flex-flow: row wrap;
align-content: flex-start;
}
@ -153,6 +152,7 @@ limitations under the License.
.mx_GroupTile_profile .mx_GroupTile_groupId {
font-size: 13px;
opacity: 0.7;
}
.mx_GroupTile_profile .mx_GroupTile_desc {
@ -163,7 +163,3 @@ limitations under the License.
max-height: 36px;
overflow: hidden;
}
.mx_GroupTile_profile .mx_GroupTile_groupId {
opacity: 0.7;
}

View File

@ -66,7 +66,7 @@ limitations under the License.
.mx_NotificationPanel .mx_EventTile_roomName a,
.mx_NotificationPanel .mx_EventTile_senderDetails a {
text-decoration: none ! important;
text-decoration: none !important;
}
.mx_NotificationPanel .mx_EventTile .mx_MessageTimestamp {
@ -83,14 +83,14 @@ limitations under the License.
padding-right: 0px;
}
.mx_NotificationPanel .mx_EventTile:hover .mx_EventTile_line {
background-color: $primary-bg-color;
}
.mx_NotificationPanel .mx_EventTile_selected .mx_EventTile_line {
padding-left: 0px;
}
.mx_NotificationPanel .mx_EventTile:hover .mx_EventTile_line {
background-color: $primary-bg-color;
}
.mx_NotificationPanel .mx_EventTile_content {
margin-right: 0px;
}

View File

@ -40,12 +40,12 @@ limitations under the License.
opacity: 0.5;
position: relative;
top: -4px;
/*
/*
animation-duration: 1s;
animation-name: bounce;
animation-direction: alternate;
animation-iteration-count: infinite;
*/
*/
}
.mx_RoomStatusBar_placeholderIndicator span:nth-child(1) {
@ -138,8 +138,8 @@ limitations under the License.
}
.mx_RoomStatusBar_resend_link {
color: $primary-fg-color ! important;
text-decoration: underline ! important;
color: $primary-fg-color !important;
text-decoration: underline !important;
cursor: pointer;
}
@ -173,12 +173,12 @@ limitations under the License.
}
.mx_RoomStatusBar_callBar {
height: 40px;
line-height: 40px;
height: 40px;
line-height: 40px;
}
.mx_RoomStatusBar_typingBar {
height: 40px;
line-height: 40px;
height: 40px;
line-height: 40px;
}
}

View File

@ -89,7 +89,7 @@ limitations under the License.
flex: 0 0 16px;
position: relative;
&:before {
&::before {
background-color: $roomheader-addroom-fg-color;
mask: url('$(res)/img/icons-room-add.svg');
mask-repeat: no-repeat;
@ -137,6 +137,26 @@ limitations under the License.
padding: 0 8px;
}
.collapsed {
.mx_RoomSubList_scroll {
padding: 0;
}
.mx_RoomSubList_labelContainer {
margin-right: 14px;
margin-left: 2px;
}
.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,
@ -164,7 +184,7 @@ limitations under the License.
background: linear-gradient(to top, $panel-gradient);
}
/*
/*
// for now, we remove the bottomOverflow entirely as we don't want to
// lose the screen real-estate due to a bg-colored gradient, but we also
// don't want to use drop shadows and risk a confusing hierarchy of cards.
@ -175,26 +195,5 @@ limitations under the License.
margin: 0px -8px;
background: linear-gradient(to bottom, rgba(0,0,0,0.1), rgba(0,0,0,0.0));
}
*/
}
.collapsed {
.mx_RoomSubList_scroll {
padding: 0;
}
.mx_RoomSubList_labelContainer {
margin-right: 14px;
margin-left: 2px;
}
.mx_RoomSubList_addRoom {
margin-left: 3px;
margin-right: 10px;
}
.mx_RoomSubList_label > span {
display: none;
}
*/
}

View File

@ -70,8 +70,15 @@ limitations under the License.
background-color: $primary-bg-color;
}
.mx_RoomView_auxPanel_hiddenHighlights {
border-bottom: 1px solid $primary-hairline-color;
padding: 10px 26px;
color: $warning-color;
cursor: pointer;
}
.mx_RoomView_auxPanel_apps {
max-width: 1920px ! important;
max-width: 1920px !important;
}
@ -79,38 +86,11 @@ limitations under the License.
flex: 1 1 0;
}
.mx_RoomView_body {
position: relative; //for .mx_RoomView_auxPanel_fullHeight
display: flex;
flex-direction: column;
flex: 1;
}
.mx_RoomView_body .mx_RoomView_timeline {
/* offset parent for mx_RoomView_topUnreadMessagesBar */
position: relative;
flex: 1;
display: flex;
flex-direction: column;
}
.mx_RoomView_body {
.mx_RoomView_messagePanel, .mx_RoomView_messagePanelSpinner, .mx_RoomView_messagePanelSearchSpinner{
order: 2;
}
}
.mx_RoomView_body .mx_RoomView_statusArea {
order: 3;
}
.mx_RoomView_body .mx_MessageComposer {
order: 4;
}
.mx_RoomView_messagePanel {
width: 100%;
overflow-y: auto;
flex: 1 1 0;
overflow-anchor: none;
}
.mx_RoomView_messagePanelSearchSpinner {
@ -122,7 +102,7 @@ limitations under the License.
position: relative;
}
.mx_RoomView_messagePanelSearchSpinner:before {
.mx_RoomView_messagePanelSearchSpinner::before {
background-color: $greyed-fg-color;
mask: url('$(res)/img/feather-customised/search-input.svg');
mask-repeat: no-repeat;
@ -136,6 +116,53 @@ limitations under the License.
height: 50px;
}
.mx_RoomView_body {
position: relative; //for .mx_RoomView_auxPanel_fullHeight
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
.mx_RoomView_messagePanel, .mx_RoomView_messagePanelSpinner, .mx_RoomView_messagePanelSearchSpinner {
order: 2;
}
}
.mx_RoomView_body .mx_RoomView_timeline {
/* offset parent for mx_RoomView_topUnreadMessagesBar */
position: relative;
flex: 1;
display: flex;
flex-direction: column;
}
.mx_RoomView_statusArea {
width: 100%;
flex: 0 0 auto;
max-height: 0px;
background-color: $primary-bg-color;
z-index: 1000;
overflow: hidden;
transition: all .2s ease-out;
}
.mx_RoomView_statusArea_expanded {
max-height: 100px;
}
.mx_RoomView_statusAreaBox {
margin: auto;
min-height: 50px;
}
.mx_RoomView_statusAreaBox_line {
margin-left: 65px;
border-top: 1px solid $primary-hairline-color;
height: 1px;
}
.mx_RoomView_messageListWrapper {
min-height: 100%;
@ -196,33 +223,6 @@ hr.mx_RoomView_myReadMarker {
z-index: 1;
}
.mx_RoomView_statusArea {
width: 100%;
flex: 0 0 auto;
max-height: 0px;
background-color: $primary-bg-color;
z-index: 1000;
overflow: hidden;
transition: all .2s ease-out;
}
.mx_RoomView_statusArea_expanded {
max-height: 100px;
}
.mx_RoomView_statusAreaBox {
margin: auto;
min-height: 50px;
}
.mx_RoomView_statusAreaBox_line {
margin-left: 65px;
border-top: 1px solid $primary-hairline-color;
height: 1px;
}
.mx_RoomView_callStatusBar .mx_UploadBar_uploadProgressInner {
background-color: $primary-bg-color;
}
@ -283,7 +283,7 @@ hr.mx_RoomView_myReadMarker {
}
.mx_RoomView_ongoingConfCallNotification a {
color: $accent-fg-color ! important;
color: $accent-fg-color !important;
}
.mx_MatrixChat_useCompactLayout {

View File

@ -0,0 +1,26 @@
/*
Copyright 2019 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_ScrollPanel {
.mx_RoomView_MessageList {
position: relative;
display: flex;
flex-direction: column;
justify-content: flex-end;
overflow-y: hidden;
}
}

View File

@ -50,7 +50,7 @@ limitations under the License.
color: $tab-label-active-fg-color;
}
.mx_TabbedView_maskedIcon {;
.mx_TabbedView_maskedIcon {
margin-left: 6px;
margin-right: 9px;
margin-top: 1px;
@ -59,7 +59,7 @@ limitations under the License.
display: inline-block;
}
.mx_TabbedView_maskedIcon:before {
.mx_TabbedView_maskedIcon::before {
display: inline-block;
background-color: $tab-label-icon-bg-color;
mask-repeat: no-repeat;
@ -71,7 +71,7 @@ limitations under the License.
vertical-align: middle;
}
.mx_TabbedView_tabLabel_active .mx_TabbedView_maskedIcon:before {
.mx_TabbedView_tabLabel_active .mx_TabbedView_maskedIcon::before {
background-color: $tab-label-active-icon-bg-color;
}
@ -91,4 +91,4 @@ limitations under the License.
flex-grow: 1;
overflow: auto;
min-height: 0; // firefox
}
}

View File

@ -36,7 +36,6 @@ limitations under the License.
flex: none;
display: flex;
justify-content: center;
align-items: flex-start;
@ -75,13 +74,13 @@ limitations under the License.
.mx_TagPanel .mx_TagTile {
margin: 9px 0;
// opacity: 0.5;
// opacity: 0.5;
position: relative;
}
.mx_TagPanel .mx_TagTile:focus,
.mx_TagPanel .mx_TagTile:hover,
.mx_TagPanel .mx_TagTile.mx_TagTile_selected {
// opacity: 1;
// opacity: 1;
}
.mx_TagPanel .mx_TagTile.mx_TagTile_selected .mx_TagTile_avatar .mx_BaseAvatar {

View File

@ -23,12 +23,12 @@ limitations under the License.
padding: 17px 0 3px 0;
}
.mx_TagPanelButtons > .mx_GroupsButton:before {
.mx_TagPanelButtons > .mx_GroupsButton::before {
mask: url('$(res)/img/feather-customised/users.svg');
mask-position: center 11px;
}
.mx_TagPanelButtons > .mx_TagPanelButtons_report:before {
.mx_TagPanelButtons > .mx_TagPanelButtons_report::before {
mask: url('$(res)/img/feather-customised/life-buoy.svg');
mask-position: center 9px;
}
@ -43,7 +43,7 @@ limitations under the License.
/* overwrite mx_RoleButton inline-block */
display: block !important;
&:before {
&::before {
background-color: $tagpanel-bg-color;
mask-repeat: no-repeat;
content: '';

View File

@ -130,3 +130,27 @@ limitations under the License.
.mx_AuthBody_spinner {
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;
}
}

View File

@ -26,6 +26,7 @@ limitations under the License.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1535053
// https://bugzilla.mozilla.org/show_bug.cgi?id=255139
display: inline-block;
user-select: none;
}
.mx_BaseAvatar_initial {

View File

@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2019 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.
@ -15,8 +16,8 @@ limitations under the License.
*/
/* Using a textarea for this element, to circumvent autofill */
.mx_ChatInviteDialog_input,
.mx_ChatInviteDialog_input:focus
.mx_AddressPickerDialog_input,
.mx_AddressPickerDialog_input:focus
{
height: 26px;
font-size: 14px;
@ -34,11 +35,11 @@ limitations under the License.
word-wrap: nowrap;
}
.mx_ChatInviteDialog .mx_Dialog_content {
.mx_AddressPickerDialog .mx_Dialog_content {
min-height: 50px
}
.mx_ChatInviteDialog_inputContainer {
.mx_AddressPickerDialog_inputContainer {
border-radius: 3px;
border: solid 1px $input-border-color;
line-height: 36px;
@ -51,19 +52,19 @@ limitations under the License.
overflow-y: auto;
}
.mx_ChatInviteDialog_error {
.mx_AddressPickerDialog_error {
margin-top: 10px;
color: $warning-color;
}
.mx_ChatInviteDialog_cancel {
.mx_AddressPickerDialog_cancel {
position: absolute;
right: 11px;
top: 13px;
cursor: pointer;
}
.mx_ChatInviteDialog_cancel object {
.mx_AddressPickerDialog_cancel object {
pointer-events: none;
}

View File

@ -14,39 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_BugReportDialog_field_container {
display: flex;
}
.mx_BugReportDialog_field_label {
flex-basis: 150px;
text-align: right;
padding-top: 9px;
padding-right: 4px;
line-height: 18px;
.mx_BugReportDialog .mx_Field {
flex: 1;
}
.mx_BugReportDialog_field_input {
flex-grow: 1;
/* taken from mx_ChatInviteDialog_inputContainer */
border-radius: 3px;
border: solid 1px $input-border-color;
font-size: 14px;
padding-left: 4px;
padding-right: 4px;
padding-top: 7px;
padding-bottom: 7px;
margin-bottom: 4px;
}
.mx_BugReportDialog_field_input[type="text" i] {
padding-top: 9px;
padding-bottom: 9px;
// TODO: We should really apply this to all .mx_Field inputs.
// See https://github.com/vector-im/riot-web/issues/9344.
flex: 1;
}

View File

@ -14,34 +14,29 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_SettingsDialog {
.mx_Dialog {
max-width: 1000px;
width: 90%;
height: 80%;
border-radius: 4px;
padding-top: 0;
padding-right: 0;
padding-left: 0;
// Not actually a component but things shared by settings components
.mx_UserSettingsDialog, .mx_RoomSettingsDialog {
width: 90vw;
max-width: 1000px;
// set the height too since tabbed view scrolls itself.
height: 80vh;
.mx_TabbedView {
top: 65px;
}
.mx_TabbedView {
top: 65px;
}
.mx_TabbedView .mx_SettingsTab {
box-sizing: border-box;
min-width: 580px;
padding-right: 130px;
.mx_TabbedView .mx_SettingsTab {
box-sizing: border-box;
min-width: 580px;
padding-right: 100px;
// Put some padding on the bottom to avoid the settings tab from
// colliding harshly with the dialog when scrolled down.
padding-bottom: 100px;
}
// Put some padding on the bottom to avoid the settings tab from
// colliding harshly with the dialog when scrolled down.
padding-bottom: 100px;
}
.mx_Dialog_title {
text-align: center;
margin-top: 16px;
margin-bottom: 24px;
}
.mx_Dialog_title {
text-align: center;
margin-bottom: 24px;
}
}

View File

@ -0,0 +1,35 @@
/*
Copyright 2019 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_UploadConfirmDialog_fileIcon {
margin-right: 5px;
}
.mx_UploadConfirmDialog_previewOuter {
text-align: center;
}
.mx_UploadConfirmDialog_previewInner {
display: inline-block;
text-align: left;
}
.mx_UploadConfirmDialog_imagePreview {
max-height: 300px;
max-width: 100%;
border-radius: 4px;
border: 1px solid $dialog-close-fg-color;
}

View File

@ -0,0 +1,28 @@
/*
Copyright 2019 Travis Ralston
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_WidgetOpenIDPermissionsDialog .mx_SettingsFlag {
.mx_ToggleSwitch {
display: inline-block;
vertical-align: middle;
margin-right: 8px;
}
.mx_SettingsFlag_label {
display: inline-block;
vertical-align: middle;
}
}

View File

@ -37,6 +37,12 @@ limitations under the License.
.mx_AccessibleButton_kind_primary {
color: $button-primary-fg-color;
background-color: $button-primary-bg-color;
font-weight: 600;
}
.mx_AccessibleButton_kind_secondary {
color: $accent-color;
font-weight: 600;
}
.mx_AccessibleButton_kind_primary.mx_AccessibleButton_disabled {

View File

@ -168,6 +168,7 @@ limitations under the License.
.mx_Field_tooltip {
margin-top: -12px;
margin-left: 4px;
width: 200px;
}
.mx_Field_tooltip.mx_Field_valid {

View File

@ -80,7 +80,24 @@ limitations under the License.
// hack for mx_Dialog having a top padding of 40px
top: 40px;
right: 0px;
padding: 35px;
padding-top: 35px;
padding-right: 35px;
cursor: pointer;
}
.mx_ImageView_rotateClockwise {
position: absolute;
top: 40px;
right: 70px;
padding-top: 35px;
cursor: pointer;
}
.mx_ImageView_rotateCounterClockwise {
position: absolute;
top: 40px;
right: 105px;
padding-top: 35px;
cursor: pointer;
}

View File

@ -50,11 +50,10 @@ limitations under the License.
.mx_Tooltip {
display: none;
animation: mx_fadein 0.2s;
position: fixed;
border: 1px solid $menu-border-color;
border-radius: 4px;
box-shadow: 4px 4px 12px 0 rgba(118, 131, 156, 0.6);
box-shadow: 4px 4px 12px 0 $menu-box-shadow-color;
background-color: $menu-bg-color;
z-index: 2000;
padding: 10px;
@ -66,4 +65,12 @@ limitations under the License.
max-width: 200px;
word-break: break-word;
margin-right: 50px;
&.mx_Tooltip_visible {
animation: mx_fadein 0.2s forwards;
}
&.mx_Tooltip_invisible {
animation: mx_fadeout 0.1s forwards;
}
}

View File

@ -0,0 +1,69 @@
/*
Copyright 2019 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_Validation {
position: relative;
}
.mx_Validation_details {
padding-left: 20px;
margin: 0;
}
.mx_Validation_description + .mx_Validation_details {
margin: 1em 0 0;
}
.mx_Validation_detail {
position: relative;
font-weight: normal;
list-style: none;
margin-bottom: 0.5em;
&:last-child {
margin-bottom: 0;
}
&::before {
content: "";
position: absolute;
width: 14px;
height: 14px;
top: 0;
left: -18px;
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
}
&.mx_Validation_valid {
color: $input-valid-border-color;
&::before {
mask-image: url('$(res)/img/feather-customised/check.svg');
background-color: $input-valid-border-color;
}
}
&.mx_Validation_invalid {
color: $input-invalid-border-color;
&::before {
mask-image: url('$(res)/img/feather-customised/x.svg');
background-color: $input-invalid-border-color;
}
}
}

View File

@ -0,0 +1,74 @@
/*
Copyright 2019 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_MessageActionBar {
position: absolute;
visibility: hidden;
cursor: pointer;
display: flex;
height: 24px;
line-height: 24px;
border-radius: 4px;
background: $message-action-bar-bg-color;
top: -13px;
right: 8px;
user-select: none;
> * {
display: inline-block;
position: relative;
width: 27px;
border: 1px solid $message-action-bar-border-color;
margin-left: -1px;
&:hover {
border-color: $message-action-bar-hover-border-color;
z-index: 1;
}
&:first-child {
border-radius: 3px 0 0 3px;
}
&:last-child {
border-radius: 0 3px 3px 0;
}
&:only-child {
border-radius: 3px;
}
}
}
.mx_MessageActionBar_maskButton::after {
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
mask-repeat: no-repeat;
mask-position: center;
background-color: $message-action-bar-fg-color;
}
.mx_MessageActionBar_replyButton::after {
mask-image: url('$(res)/img/reply.svg');
}
.mx_MessageActionBar_optionsButton::after {
mask-image: url('$(res)/img/icon_context.svg');
}

View File

@ -0,0 +1,25 @@
/*
Copyright 2019 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_ReactionDimension {
width: 42px;
display: flex;
justify-content: space-evenly;
}
.mx_ReactionDimension_disabled {
opacity: 0.4;
}

View File

@ -0,0 +1,19 @@
/*
Copyright 2019 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_ReactionsRow {
margin: 6px 0;
}

View File

@ -0,0 +1,36 @@
/*
Copyright 2019 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_ReactionsRowButton {
display: inline-block;
height: 20px;
line-height: 21px;
margin-right: 6px;
padding: 0 6px;
border: 1px solid $reaction-row-button-border-color;
border-radius: 10px;
background-color: $reaction-row-button-bg-color;
cursor: pointer;
&:hover {
border-color: $reaction-row-button-hover-border-color;
}
&.mx_ReactionsRowButton_selected {
background-color: $reaction-row-button-selected-bg-color;
border-color: $reaction-row-button-selected-border-color;
}
}

View File

@ -18,36 +18,3 @@ limitations under the License.
font-weight: 600;
}
.mx_SenderProfile_color1 {
color: $username-variant1-color;
}
.mx_SenderProfile_color2 {
color: $username-variant2-color;
}
.mx_SenderProfile_color3 {
color: $username-variant3-color;
}
.mx_SenderProfile_color4 {
color: $username-variant4-color;
}
.mx_SenderProfile_color5 {
color: $username-variant5-color;
}
.mx_SenderProfile_color6 {
color: $username-variant6-color;
}
.mx_SenderProfile_color7 {
color: $username-variant7-color;
}
.mx_SenderProfile_color8 {
color: $username-variant8-color;
}

View File

@ -31,6 +31,7 @@ limitations under the License.
top: 14px;
left: 8px;
cursor: pointer;
user-select: none;
}
.mx_EventTile.mx_EventTile_info .mx_EventTile_avatar {
@ -62,6 +63,7 @@ limitations under the License.
vertical-align: top;
height: 16px;
overflow: hidden;
user-select: none;
img {
vertical-align: -2px;
@ -80,6 +82,7 @@ limitations under the License.
width: 46px; /* 8 + 30 (avatar) + 8 */
text-align: center;
position: absolute;
user-select: none;
}
.mx_EventTile_line, .mx_EventTile_reply {
@ -118,7 +121,7 @@ limitations under the License.
}
.mx_EventTile:hover .mx_EventTile_line,
.mx_EventTile.menu .mx_EventTile_line
.mx_EventTile.mx_EventTile_actionBarFocused .mx_EventTile_line
{
background-color: $event-selected-color;
}
@ -203,7 +206,7 @@ limitations under the License.
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
.mx_EventTile_last > div > a > .mx_MessageTimestamp,
.mx_EventTile:hover > div > a > .mx_MessageTimestamp,
.mx_EventTile.menu > div > a > .mx_MessageTimestamp {
.mx_EventTile.mx_EventTile_actionBarFocused > div > a > .mx_MessageTimestamp {
visibility: visible;
}
@ -216,24 +219,8 @@ limitations under the License.
width: auto;
}
.mx_EventTile_editButton {
position: absolute;
display: inline-block;
visibility: hidden;
cursor: pointer;
top: 6px;
right: 6px;
width: 19px;
height: 19px;
background-image: url($edit-button-url);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.mx_EventTile:hover .mx_EventTile_editButton,
.mx_EventTile.menu .mx_EventTile_editButton {
.mx_EventTile:hover .mx_MessageActionBar,
.mx_EventTile.mx_EventTile_actionBarFocused .mx_MessageActionBar {
visibility: visible;
}
@ -243,6 +230,7 @@ limitations under the License.
width: 14px;
height: 14px;
top: 29px;
user-select: none;
}
.mx_EventTile_continuation .mx_EventTile_readAvatars,
@ -550,10 +538,6 @@ limitations under the License.
top: 3px;
}
.mx_EventTile_editButton {
top: 3px;
}
.mx_EventTile_readAvatars {
top: 27px;
}

View File

@ -20,6 +20,7 @@ limitations under the License.
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
.mx_Spinner {
flex: 1 0 auto;
@ -35,6 +36,10 @@ limitations under the License.
margin-top: 8px;
margin-bottom: 4px;
}
.mx_AutoHideScrollbar {
flex: 1 1 0;
}
}
.mx_MemberList_chevron {

View File

@ -15,33 +15,47 @@ limitations under the License.
*/
.mx_RoomBreadcrumbs {
overflow-x: auto;
position: relative;
height: 32px;
margin: 8px;
margin-bottom: 0;
overflow-x: hidden;
height: 42px;
padding: 8px;
padding-bottom: 0;
display: flex;
flex-direction: row;
> * {
margin-left: 4px;
// Autohide the scrollbar
overflow-x: hidden;
&:hover {
overflow-x: visible;
}
&::after {
content: "";
position: absolute;
width: 15px;
top: 0;
right: 0;
.mx_AutoHideScrollbar_offset {
display: flex;
flex-direction: row;
height: 100%;
background: linear-gradient(to right, $panel-gradient);
}
.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;
transition: transform 0.3s, width 0.3s;
width: 32px;
transform: scale(1);
}
@ -50,5 +64,37 @@ limitations under the License.
width: 0;
transform: scale(0);
}
.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_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_IndicatorScrollbar_leftOverflowIndicator {
background: linear-gradient(to left, $panel-gradient);
}
.mx_IndicatorScrollbar_rightOverflowIndicator {
background: linear-gradient(to right, $panel-gradient);
}
}

View File

@ -15,48 +15,112 @@ limitations under the License.
*/
.mx_RoomPreviewBar {
text-align: center;
height: 176px;
background-color: $event-selected-color;
flex: 0 0 auto;
align-items: center;
flex-direction: column;
justify-content: center;
display: flex;
background-color: $preview-bar-bg-color;
-webkit-align-items: center;
h3 {
font-size: 18px;
font-weight: 600;
&.mx_RoomPreviewBar_spinnerTitle {
display: flex;
flex-direction: row;
align-items: center;
}
}
.mx_Spinner {
width: auto;
height: auto;
margin: 10px 10px 10px 0;
flex: 0 0 auto;
}
}
.mx_RoomPreviewBar_wrapper {
.mx_RoomPreviewBar_dark {
background-color: $tagpanel-bg-color;
color: $accent-fg-color;
}
.mx_RoomPreviewBar_invite_text {
color: $primary-fg-color;
.mx_RoomPreviewBar_actions {
display: flex;
}
.mx_RoomPreviewBar_join_text {
color: $warning-color;
.mx_RoomPreviewBar_message {
display: flex;
align-items: stretch;
p {
overflow-wrap: break-word;
}
}
.mx_RoomPreviewBar_preview_text {
margin-top: 25px;
color: $settings-grey-fg-color;
.mx_RoomPreviewBar_panel {
padding: 8px 8px 8px 20px;
border-top: 1px solid $panel-divider-color;
flex-direction: row;
.mx_RoomPreviewBar_actions {
flex: 0 0 auto;
flex-direction: row;
padding: 3px 8px;
&>* {
margin-left: 12px;
}
}
.mx_RoomPreviewBar_message {
flex: 1 0 0;
min-width: 0;
display: flex;
flex-direction: column;
&>* {
margin: 4px;
}
}
}
.mx_RoomPreviewBar_join_text a {
.mx_RoomPreviewBar_dialog {
margin: auto;
box-sizing: content;
width: 400px;
border-radius: 4px;
flex-direction: column;
padding: 20px;
text-align: center;
.mx_RoomPreviewBar_message {
flex-direction: column;
&>* {
margin: 5px 0 20px 0;
}
}
.mx_RoomPreviewBar_actions {
flex-direction: column-reverse;
.mx_AccessibleButton {
padding: 7px 50px;//extra wide
}
&>* {
margin-top: 12px;
}
}
}
.mx_RoomPreviewBar_inviter {
font-weight: 600;
}
a.mx_RoomPreviewBar_inviter {
text-decoration: underline;
cursor: pointer;
}
.mx_RoomPreviewBar_warning {
display: flex;
align-items: center;
padding: 8px;
}
.mx_RoomPreviewBar_warningIcon {
padding: 12px;
}
.mx_RoomPreviewBar_spinnerIntro {
margin-top: 50px;
}

View File

@ -144,11 +144,14 @@ limitations under the License.
font-size: 12px;
}
.mx_RoomTile_unreadNotify .mx_RoomTile_badge {
.mx_RoomTile_unreadNotify .mx_RoomTile_badge,
.mx_RoomTile_badge.mx_RoomTile_badgeUnread {
background-color: $roomtile-name-color;
}
.mx_RoomTile_highlight .mx_RoomTile_badge {
.mx_RoomTile_highlight .mx_RoomTile_badge,
.mx_RoomTile_badge.mx_RoomTile_badgeRed
{
background-color: $warning-color;
}

View File

@ -16,7 +16,7 @@ limitations under the License.
.mx_RoomUpgradeWarningBar {
text-align: center;
height: 176px;
height: 235px;
background-color: $event-selected-color;
align-items: center;
flex-direction: column;

View File

@ -14,6 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_SettingsTab_warningText {
color: $warning-color;
}
.mx_SettingsTab_heading {
font-size: 20px;
font-weight: 600;
@ -68,3 +72,7 @@ limitations under the License.
// give them more visual distinction between the sections.
margin-top: 30px;
}
.mx_SettingsTab a {
color: $accent-color-alt;
}

View File

@ -0,0 +1,3 @@
<svg fill="none" height="24" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m20 6-11 11-5-5"/>
</svg>

After

Width:  |  Height:  |  Size: 213 B

View File

@ -0,0 +1,4 @@
<svg fill="none" height="24" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m18 6-12 12"/>
<path d="m6 6 12 12"/>
</svg>

After

Width:  |  Height:  |  Size: 236 B

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="19px" height="19px" viewBox="0 0 19 19" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: sketchtool 39.1 (31720) - http://www.bohemiancoding.com/sketch -->
<title>ED5D3E59-2561-4AC1-9B43-82FBC51767FC</title>
<desc>Created with sketchtool.</desc>
<defs></defs>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="icon_context">
<g>
<path d="M9.5,19 C14.7467051,19 19,14.7467051 19,9.5 C19,4.25329488 14.7467051,0 9.5,0 C4.25329488,0 0,4.25329488 0,9.5 C0,14.7467051 4.25329488,19 9.5,19 Z" id="Oval-69" fill="#ECECEC"></path>
<path d="M4.5,9.50063771 C4.5,9.13148623 4.59887838,8.85242947 4.7966381,8.66345907 C4.99439782,8.47448867 5.28224377,8.38000488 5.66018457,8.38000488 C6.0249414,8.38000488 6.3072941,8.47668596 6.50725115,8.67005103 C6.70720821,8.86341609 6.80718523,9.14027555 6.80718523,9.50063771 C6.80718523,9.84781589 6.70610956,10.1213794 6.50395517,10.3213365 C6.30180079,10.5212935 6.02054674,10.6212705 5.66018457,10.6212705 C5.29103309,10.6212705 5.00538444,10.5234908 4.80323006,10.3279284 C4.60107568,10.132366 4.5,9.85660521 4.5,9.50063771 L4.5,9.50063771 Z M8.3431114,9.50063771 C8.3431114,9.13148623 8.44198978,8.85242947 8.63974951,8.66345907 C8.83750923,8.47448867 9.12755247,8.38000488 9.50988794,8.38000488 C9.87464476,8.38000488 10.1569975,8.47668596 10.3569545,8.67005103 C10.5569116,8.86341609 10.6568886,9.14027555 10.6568886,9.50063771 C10.6568886,9.84781589 10.5558129,10.1213794 10.3536585,10.3213365 C10.1515042,10.5212935 9.8702501,10.6212705 9.50988794,10.6212705 C9.13634179,10.6212705 8.84849585,10.5234908 8.64634146,10.3279284 C8.44418708,10.132366 8.3431114,9.85660521 8.3431114,9.50063771 L8.3431114,9.50063771 Z M12.1928148,9.50063771 C12.1928148,9.13148623 12.2916931,8.85242947 12.4894529,8.66345907 C12.6872126,8.47448867 12.9750585,8.38000488 13.3529993,8.38000488 C13.7177562,8.38000488 14.0001089,8.47668596 14.2000659,8.67005103 C14.400023,8.86341609 14.5,9.14027555 14.5,9.50063771 C14.5,9.84781589 14.3989243,10.1213794 14.1967699,10.3213365 C13.9946156,10.5212935 13.7133615,10.6212705 13.3529993,10.6212705 C12.9838479,10.6212705 12.6981992,10.5234908 12.4960448,10.3279284 C12.2938904,10.132366 12.1928148,9.85660521 12.1928148,9.50063771 L12.1928148,9.50063771 Z" id="…" fill="#9B9B9B"></path>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 25 25" style="enable-background:new 0 0 25 25;" xml:space="preserve">
<style type="text/css">
.st1{fill:none;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
</style>
<g id="Layer_1">
<title>81230A28-D944-4572-B5DB-C03CAA2B1FCA</title>
<desc>Created with sketchtool.</desc>
<g id="Symbols">
<g id="Left-nav-default" transform="translate(-50.000000, -725.000000)">
<g id="Left-panel">
<g>
<g id="icons_people" transform="translate(50.000000, 725.000000)">
<path id="Oval-1-Copy-7" fill="#76cfa6" d="M12.5,25C19.4,25,25,19.4,25,12.5S19.4,0,12.5,0S0,5.6,0,12.5S5.6,25,12.5,25z"/>
</g>
</g>
</g>
</g>
</g>
</g>
<g id="Layer_2">
<rect x="7.8" y="10.7" class="st1" stroke="#ffffff" width="9.4" height="7.4"/>
<polygon class="st1" stroke="#ffffff" points="12.5,6 6.2,10.7 18.8,10.7 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="25px" height="25px" viewBox="0 0 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: sketchtool 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
<title>4D42A2A7-7430-4D4F-A0A2-E19278CF66E3</title>
<desc>Created with sketchtool.</desc>
<defs></defs>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Room-list-Copy-3" transform="translate(-165.000000, -726.000000)">
<g id="icons_settings" transform="translate(165.000000, 726.000000)">
<path d="M12.5,25 C19.4035594,25 25,19.4035594 25,12.5 C25,5.59644063 19.4035594,0 12.5,0 C5.59644063,0 0,5.59644063 0,12.5 C0,19.4035594 5.59644063,25 12.5,25 Z" id="Oval-1-Copy-7" fill="#76CFA6"></path>
<path d="M15.625,12.5 C15.625,11.6373655 15.3198273,10.900882 14.7094727,10.2905273 C14.099118,9.68017273 13.3626345,9.375 12.5,9.375 C11.6373655,9.375 10.900882,9.68017273 10.2905273,10.2905273 C9.68017273,10.900882 9.375,11.6373655 9.375,12.5 C9.375,13.3626345 9.68017273,14.099118 10.2905273,14.7094727 C10.900882,15.3198273 11.6373655,15.625 12.5,15.625 C13.3626345,15.625 14.099118,15.3198273 14.7094727,14.7094727 C15.3198273,14.099118 15.625,13.3626345 15.625,12.5 L15.625,12.5 Z M19.7916667,11.465115 L19.7916667,13.5728624 C19.7916667,13.6488177 19.7663486,13.721607 19.7157118,13.7912326 C19.665075,13.8608583 19.6017799,13.9020001 19.5258247,13.9146593 L17.7693685,14.1805013 C17.649106,14.5222999 17.5256806,14.8102925 17.3990885,15.0444878 C17.6206247,15.360968 17.9592534,15.7977041 18.4149848,16.3547092 C18.4782808,16.4306644 18.5099284,16.5097833 18.5099284,16.5920681 C18.5099284,16.674353 18.4814456,16.7471423 18.4244792,16.8104384 C18.2535799,17.0446337 17.9402692,17.3864272 17.4845378,17.835829 C17.0288063,18.2852308 16.7313194,18.5099284 16.5920681,18.5099284 C16.5161129,18.5099284 16.4338293,18.4814456 16.3452148,18.4244792 L15.0349935,17.3990885 C14.7564909,17.5446694 14.4684983,17.6649301 14.1710069,17.7598741 C14.0697333,18.6207002 13.9779554,19.2093445 13.8956706,19.5258247 C13.8513633,19.7030535 13.7374322,19.7916667 13.5538737,19.7916667 L11.4461263,19.7916667 C11.3575119,19.7916667 11.2799754,19.7647663 11.2135145,19.7109646 C11.1470537,19.657163 11.110659,19.5891208 11.1043294,19.5068359 L10.8384874,17.7598741 C10.5283368,17.6586005 10.243509,17.5415046 9.98399523,17.4085829 L8.6452908,18.4244792 C8.58199476,18.4814456 8.50287591,18.5099284 8.40793186,18.5099284 C8.31931741,18.5099284 8.24019855,18.4751161 8.17057292,18.4054905 C7.37304289,17.6839157 6.85085844,17.152237 6.60400391,16.8104384 C6.55969668,16.7471423 6.5375434,16.674353 6.5375434,16.5920681 C6.5375434,16.5161129 6.56286144,16.4433236 6.61349826,16.3736979 C6.70844231,16.2407762 6.86984478,16.0303201 7.0977105,15.7423231 C7.32557623,15.4543262 7.49647295,15.231211 7.61040582,15.0729709 C7.43950652,14.7564907 7.3097516,14.4431801 7.22113715,14.1330295 L5.4836697,13.8766819 C5.40138486,13.8640227 5.33492502,13.8244632 5.28428819,13.7580024 C5.23365137,13.6915416 5.20833333,13.6171698 5.20833333,13.534885 L5.20833333,11.4271376 C5.20833333,11.3511823 5.23365137,11.278393 5.28428819,11.2087674 C5.33492502,11.1391417 5.39505535,11.0979999 5.46468099,11.0853407 L7.23063151,10.8194987 C7.31924596,10.5283369 7.44267137,10.2371796 7.60091146,9.9460178 C7.34772732,9.5852304 7.00909862,9.14849432 6.58501519,8.63579644 C6.52171916,8.5598412 6.49007161,8.4838871 6.49007161,8.40793186 C6.49007161,8.34463582 6.5185544,8.27184648 6.57552083,8.18956163 C6.74009052,7.96169591 7.05181881,7.62148483 7.51071506,7.16891819 C7.96961131,6.71635154 8.26868058,6.49007161 8.40793186,6.49007161 C8.4902167,6.49007161 8.57250031,6.52171916 8.65478516,6.58501519 L9.96500651,7.60091146 C10.2435091,7.45533058 10.5315017,7.33506992 10.8289931,7.24012587 C10.9302667,6.3792998 11.0220446,5.79065552 11.1043294,5.47417535 C11.1486367,5.29694645 11.2625678,5.20833333 11.4461263,5.20833333 L13.5538737,5.20833333 C13.6424881,5.20833333 13.7200246,5.23523374 13.7864855,5.28903537 C13.8529463,5.342837 13.889341,5.41087922 13.8956706,5.49316406 L14.1615126,7.24012587 C14.4716632,7.34139952 14.756491,7.45849543 15.0160048,7.5914171 L16.3642036,6.57552083 C16.42117,6.5185544 16.4971241,6.49007161 16.5920681,6.49007161 C16.674353,6.49007161 16.7534718,6.52171916 16.8294271,6.58501519 C17.6459459,7.338238 18.1681304,7.87624622 18.3959961,8.19905599 C18.4403033,8.24969282 18.4624566,8.31931741 18.4624566,8.40793186 C18.4624566,8.4838871 18.4371386,8.55667645 18.3865017,8.62630208 C18.2915577,8.75922375 18.1301552,8.96967991 17.9022895,9.25767687 C17.6744238,9.54567382 17.503527,9.76878899 17.3895942,9.92702908 C17.5541639,10.2435093 17.6839188,10.5536552 17.7788628,10.8574761 L19.5163303,11.1233181 C19.5986151,11.1359773 19.665075,11.1755368 19.7157118,11.2419976 C19.7663486,11.3084584 19.7916667,11.3828302 19.7916667,11.465115 L19.7916667,11.465115 Z" id="icons_settings-copy" stroke="#FFFFFF" opacity="0.8"></path>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.0 KiB

6
res/img/reply.svg Normal file
View File

@ -0,0 +1,6 @@
<svg width="13" height="13" xmlns="http://www.w3.org/2000/svg">
<g stroke="#2E2F32" stroke-width=".75" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<path d="M8.75 4.75L12.5 8.5l-3.75 3.75"/>
<path d="M.5.25V5.5a3 3 0 0 0 3 3h9"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 289 B

1
res/img/rotate-ccw.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-rotate-ccw"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg>

After

Width:  |  Height:  |  Size: 311 B

1
res/img/rotate-cw.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-rotate-cw"><polyline points="23 4 23 10 17 10"></polyline><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path></svg>

After

Width:  |  Height:  |  Size: 315 B

View File

@ -40,7 +40,7 @@ $tagpanel-bg-color: $base-color;
$selected-color: $room-highlight-color;
// selected for hoverover & selected event tiles
$event-selected-color: #111316;
$event-selected-color: $header-panel-bg-color;
// used for the hairline dividers in RoomView
$primary-hairline-color: $header-panel-border-color;
@ -72,7 +72,7 @@ $avatar-bg-color: $bg-color;
$h3-color: $primary-fg-color;
$dialog-title-fg-color: #454545;
$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;
@ -146,6 +146,17 @@ $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;
// ***** Mixins! *****
@define-mixin mx_DialogButton {

View File

@ -11,7 +11,7 @@ $font-family: 'Nunito', Arial, Helvetica, Sans-Serif;
$accent-color: #03b381;
$notice-primary-color: #ff4b55;
$notice-secondary-color: #61708b;
$header-panel-bg-color: #f2f5f8;
$header-panel-bg-color: #f3f8fd;
// typical text (dark-on-white in light skin)
$primary-fg-color: #2e2f32;
@ -66,14 +66,14 @@ $droptarget-bg-color: rgba(255,255,255,0.5);
$selected-color: $secondary-accent-color;
// selected for hoverover & selected event tiles
$event-selected-color: #f7f7f7;
$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: rgba(193, 201, 214, 0.29);
$input-darker-bg-color: #e3e8f0;
$input-darker-fg-color: #9fa9ba;
$input-lighter-bg-color: #f2f5f8;
$input-lighter-fg-color: $input-darker-fg-color;
@ -106,10 +106,10 @@ $avatar-bg-color: #ffffff;
$h3-color: #3d3b39;
$dialog-title-fg-color: #2e2f32;
$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: #9fa9ba;
$dialog-close-fg-color: #c1c1c1;
$dialog-background-bg-color: #e9e9e9;
$lightbox-background-bg-color: #000;
@ -153,7 +153,7 @@ $roomheader-button-color: #91A1C0;
$groupheader-button-color: #91A1C0;
$rightpanel-button-color: #91A1C0;
$composer-button-color: #91A1C0;
$roomtopic-color: #9fa9ba;
$roomtopic-color: #9e9e9e;
$eventtile-meta-color: $roomtopic-color;
$composer-e2e-icon-color: #c9ced6;
@ -203,7 +203,6 @@ $event-redacted-border-color: #cccccc;
// event timestamp
$event-timestamp-color: #acacac;
$edit-button-url: "$(res)/img/icon_context_message.svg";
$copy-button-url: "$(res)/img/icon_copy_message.svg";
// e2e
@ -255,6 +254,17 @@ $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: #b8c1d2;
$reaction-row-button-bg-color: $header-panel-bg-color;
$reaction-row-button-border-color: #e9edf1;
$reaction-row-button-hover-border-color: #bebebe;
$reaction-row-button-selected-bg-color: #e9fff9;
$reaction-row-button-selected-border-color: $accent-color;
// ***** Mixins! *****
@define-mixin mx_DialogButton {

9
scripts/ci/Dockerfile Normal file
View File

@ -0,0 +1,9 @@
# Update on docker hub with the following commands in the directory of this file:
# docker build -t matrixdotorg/riotweb-ci-e2etests-env:latest .
# docker log
# docker push matrixdotorg/riotweb-ci-e2etests-env:latest
FROM node:10
RUN apt-get update
RUN apt-get -y install build-essential python3-dev libffi-dev python-pip python-setuptools sqlite3 libssl-dev python-virtualenv libjpeg-dev libxslt1-dev uuid-runtime
# dependencies for chrome (installed by puppeteer)
RUN apt-get -y install gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget

View File

@ -6,16 +6,38 @@
set -ev
upload_logs() {
echo "--- Uploading logs"
buildkite-agent artifact upload "logs/**/*;synapse/installations/consent/homeserver.log"
}
handle_error() {
EXIT_CODE=$?
if [ $TESTS_STARTED -eq 1 ]; then
upload_logs
fi
exit $EXIT_CODE
}
trap 'handle_error' ERR
RIOT_WEB_DIR=riot-web
REACT_SDK_DIR=`pwd`
echo "--- Building Riot"
scripts/ci/build.sh
# run end to end tests
echo "--- Fetching end-to-end tests from master"
scripts/fetchdep.sh matrix-org matrix-react-end-to-end-tests master
pushd matrix-react-end-to-end-tests
ln -s $REACT_SDK_DIR/$RIOT_WEB_DIR riot/riot-web
# PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh
# CHROME_PATH=$(which google-chrome-stable) ./run.sh
echo "--- Install synapse & other dependencies"
./install.sh
./run.sh --travis
mkdir logs
echo "+++ Running end-to-end tests"
TESTS_STARTED=1
./run.sh --no-sandbox --log-directory logs/
popd

View File

@ -1,28 +1,40 @@
#!/bin/sh
#!/bin/bash
org="$1"
repo="$2"
set -x
deforg="$1"
defrepo="$2"
defbranch="$3"
[ -z "$defbranch" ] && defbranch="develop"
rm -r "$repo" || true
rm -r "$defrepo" || true
clone() {
branch=$1
org=$1
repo=$2
branch=$3
if [ -n "$branch" ]
then
echo "Trying to use the branch $branch"
git clone https://github.com/$org/$repo.git $repo --branch "$branch" && exit 0
echo "Trying to use $org/$repo#$branch"
git clone git://github.com/$org/$repo.git $repo --branch "$branch" && exit 0
fi
}
# Try the PR author's branch in case it exists on the deps as well.
clone $BUILDKITE_BRANCH
# If BUILDKITE_BRANCH is set, it will contain either:
# * "branch" when the author's branch and target branch are in the same repo
# * "author:branch" when the author's branch is in their fork
# We can split on `:` into an array to check.
BUILDKITE_BRANCH_ARRAY=(${BUILDKITE_BRANCH//:/ })
if [[ "${#BUILDKITE_BRANCH_ARRAY[@]}" == "1" ]]; then
clone $deforg $defrepo $BUILDKITE_BRANCH
elif [[ "${#BUILDKITE_BRANCH_ARRAY[@]}" == "2" ]]; then
clone ${BUILDKITE_BRANCH_ARRAY[0]} $defrepo ${BUILDKITE_BRANCH_ARRAY[1]}
fi
# Try the target branch of the push or PR.
clone $BUILDKITE_PULL_REQUEST_BASE_BRANCH
clone $deforg $defrepo $BUILDKITE_PULL_REQUEST_BASE_BRANCH
# Try the current branch from Jenkins.
clone `"echo $GIT_BRANCH" | sed -e 's/^origin\///'`
clone $deforg $defrepo `"echo $GIT_BRANCH" | sed -e 's/^origin\///'`
# Use the default branch as the last resort.
clone $defbranch
clone $deforg $defrepo $defbranch

View File

@ -84,6 +84,11 @@ const customVariables = {
expl: _td('Whether or not you\'re using the Richtext mode of the Rich Text Editor'),
example: 'off',
},
'Breadcrumbs': {
id: 9,
expl: _td("Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)"),
example: 'disabled',
},
'Homeserver URL': {
id: 7,
expl: _td('Your homeserver\'s URL'),
@ -201,6 +206,7 @@ class Analytics {
trackEvent(category, action, name, value) {
if (this.disabled) return;
this._paq.push(['setCustomUrl', getRedactedUrl()]);
this._paq.push(['trackEvent', category, action, name, value]);
}
@ -233,6 +239,11 @@ class Analytics {
this._setVisitVariable('RTE: Uses Richtext Mode', state ? 'on' : 'off');
}
setBreadcrumbs(state) {
if (this.disabled) return;
this._setVisitVariable('Breadcrumbs', state ? 'enabled' : 'disabled');
}
showDetailsModal() {
let rows = [];
if (window.Piwik) {

View File

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 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.
@ -17,21 +18,27 @@ limitations under the License.
'use strict';
import Promise from 'bluebird';
const extend = require('./extend');
const dis = require('./dispatcher');
const MatrixClientPeg = require('./MatrixClientPeg');
const sdk = require('./index');
import extend from './extend';
import dis from './dispatcher';
import MatrixClientPeg from './MatrixClientPeg';
import sdk from './index';
import { _t } from './languageHandler';
const Modal = require('./Modal');
const encrypt = require("browser-encrypt-attachment");
import Modal from './Modal';
import RoomViewStore from './stores/RoomViewStore';
import encrypt from "browser-encrypt-attachment";
import extractPngChunks from "png-chunks-extract";
// Polyfill for Canvas.toBlob API using Canvas.toDataURL
require("blueimp-canvas-to-blob");
import "blueimp-canvas-to-blob";
const MAX_WIDTH = 800;
const MAX_HEIGHT = 600;
// scraped out of a macOS hidpi (5660ppm) screenshot png
// 5669 px (x-axis) , 5669 px (y-axis) , per metre
const PHYS_HIDPI = [0x00, 0x00, 0x16, 0x25, 0x00, 0x00, 0x16, 0x25, 0x01];
export class UploadCanceledError extends Error {}
/**
* Create a thumbnail for a image DOM element.
@ -91,27 +98,51 @@ function createThumbnail(element, inputWidth, inputHeight, mimeType) {
/**
* Load a file into a newly created image element.
*
* @param {File} file The file to load in an image element.
* @param {File} imageFile The file to load in an image element.
* @return {Promise} A promise that resolves with the html image element.
*/
function loadImageElement(imageFile) {
const deferred = Promise.defer();
async function loadImageElement(imageFile) {
// Load the file into an html element
const img = document.createElement("img");
const objectUrl = URL.createObjectURL(imageFile);
const imgPromise = new Promise((resolve, reject) => {
img.onload = function() {
URL.revokeObjectURL(objectUrl);
resolve(img);
};
img.onerror = function(e) {
reject(e);
};
});
img.src = objectUrl;
// Once ready, create a thumbnail
img.onload = function() {
URL.revokeObjectURL(objectUrl);
deferred.resolve(img);
};
img.onerror = function(e) {
deferred.reject(e);
};
// check for hi-dpi PNGs and fudge display resolution as needed.
// this is mainly needed for macOS screencaps
let parsePromise;
if (imageFile.type === "image/png") {
// in practice macOS happens to order the chunks so they fall in
// the first 0x1000 bytes (thanks to a massive ICC header).
// Thus we could slice the file down to only sniff the first 0x1000
// bytes (but this makes extractPngChunks choke on the corrupt file)
const headers = imageFile; //.slice(0, 0x1000);
parsePromise = readFileAsArrayBuffer(headers).then(arrayBuffer => {
const buffer = new Uint8Array(arrayBuffer);
const chunks = extractPngChunks(buffer);
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 false;
});
}
return deferred.promise;
const [hidpi] = await Promise.all([parsePromise, imgPromise]);
const width = hidpi ? (img.width >> 1) : img.width;
const height = hidpi ? (img.height >> 1) : img.height;
return {width, height, img};
}
/**
@ -119,7 +150,7 @@ function loadImageElement(imageFile) {
*
* @param {MatrixClient} matrixClient A matrixClient to upload the thumbnail with.
* @param {String} roomId The ID of the room the image will be uploaded in.
* @param {File} The image to read and thumbnail.
* @param {File} imageFile The image to read and thumbnail.
* @return {Promise} A promise that resolves with the attachment info.
*/
function infoForImageFile(matrixClient, roomId, imageFile) {
@ -129,8 +160,8 @@ function infoForImageFile(matrixClient, roomId, imageFile) {
}
let imageInfo;
return loadImageElement(imageFile).then(function(img) {
return createThumbnail(img, img.width, img.height, thumbnailType);
return loadImageElement(imageFile).then(function(r) {
return createThumbnail(r.img, r.width, r.height, thumbnailType);
}).then(function(result) {
imageInfo = result.info;
return uploadFile(matrixClient, roomId, result.thumbnail);
@ -144,7 +175,7 @@ function infoForImageFile(matrixClient, roomId, imageFile) {
/**
* Load a file into a newly created video element.
*
* @param {File} file The file to load in an video element.
* @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) {
@ -179,7 +210,7 @@ function loadVideoElement(videoFile) {
*
* @param {MatrixClient} matrixClient A matrixClient to upload the thumbnail with.
* @param {String} roomId The ID of the room the video will be uploaded to.
* @param {File} The video to read and thumbnail.
* @param {File} videoFile The video to read and thumbnail.
* @return {Promise} A promise that resolves with the attachment info.
*/
function infoForVideoFile(matrixClient, roomId, videoFile) {
@ -200,6 +231,7 @@ function infoForVideoFile(matrixClient, roomId, videoFile) {
/**
* Read the file as an ArrayBuffer.
* @param {File} file The file to read
* @return {Promise} A promise that resolves with an ArrayBuffer when the file
* is read.
*/
@ -233,28 +265,40 @@ function uploadFile(matrixClient, roomId, file, progressHandler) {
if (matrixClient.isRoomEncrypted(roomId)) {
// If the room is encrypted then encrypt the file before uploading it.
// First read the file into memory.
return readFileAsArrayBuffer(file).then(function(data) {
let canceled = false;
let uploadPromise;
let encryptInfo;
const prom = readFileAsArrayBuffer(file).then(function(data) {
if (canceled) throw new UploadCanceledError();
// Then encrypt the file.
return encrypt.encryptAttachment(data);
}).then(function(encryptResult) {
if (canceled) throw new UploadCanceledError();
// Record the information needed to decrypt the attachment.
const encryptInfo = encryptResult.info;
encryptInfo = encryptResult.info;
// Pass the encrypted data as a Blob to the uploader.
const blob = new Blob([encryptResult.data]);
return matrixClient.uploadContent(blob, {
uploadPromise = matrixClient.uploadContent(blob, {
progressHandler: progressHandler,
includeFilename: false,
}).then(function(url) {
// 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.
encryptInfo.url = url;
if (file.type) {
encryptInfo.mimetype = file.type;
}
return {"file": encryptInfo};
});
return uploadPromise;
}).then(function(url) {
// 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.
encryptInfo.url = url;
if (file.type) {
encryptInfo.mimetype = file.type;
}
return {"file": encryptInfo};
});
prom.abort = () => {
canceled = true;
if (uploadPromise) MatrixClientPeg.get().cancelUpload(uploadPromise);
};
return prom;
} else {
const basePromise = matrixClient.uploadContent(file, {
progressHandler: progressHandler,
@ -269,11 +313,43 @@ function uploadFile(matrixClient, roomId, file, progressHandler) {
}
}
class ContentMessages {
export default class ContentMessages {
constructor() {
this.inprogress = [];
this.nextId = 0;
this._mediaConfig = 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) {
@ -283,7 +359,90 @@ class ContentMessages {
});
}
sendContentToRoom(file, roomId, matrixClient) {
getUploadLimit() {
if (this._mediaConfig !== null && this._mediaConfig["m.upload.size"] !== undefined) {
return this._mediaConfig["m.upload.size"];
} else {
return null;
}
}
async sendContentListToRoom(files, roomId, matrixClient) {
if (matrixClient.isGuest()) {
dis.dispatch({action: 'require_registration'});
return;
}
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: (
<div>{_t(
'At this time it is not possible to reply with a file. ' +
'Would you like to upload this file without replying?',
)}</div>
),
hasCancelButton: true,
button: _t("Continue"),
onFinished: (shouldUpload) => {
resolve(shouldUpload);
},
});
});
if (!shouldUpload) return;
}
await this._ensureMediaConfigFetched();
const tooBigFiles = [];
const okFiles = [];
for (let i = 0; i < files.length; ++i) {
if (this._isFileSizeAcceptable(files[i])) {
okFiles.push(files[i]);
} else {
tooBigFiles.push(files[i]);
}
}
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 shouldContinue = await uploadFailureDialogPromise;
if (!shouldContinue) return;
}
const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog");
for (let i = 0; i < okFiles.length; ++i) {
const file = okFiles[i];
const shouldContinue = await new Promise((resolve) => {
Modal.createTrackedDialog('Upload Files confirmation', '', UploadConfirmDialog, {
file,
currentIndex: i,
totalFiles: okFiles.length,
onFinished: (shouldContinue) => {
resolve(shouldContinue);
},
});
});
if (!shouldContinue) break;
this._sendContentToRoom(file, roomId, matrixClient);
}
}
_sendContentToRoom(file, roomId, matrixClient) {
const content = {
body: file.name || 'Attachment',
info: {
@ -333,6 +492,9 @@ class ContentMessages {
this.inprogress.push(upload);
dis.dispatch({action: 'upload_started'});
// Focus the composer view
dis.dispatch({action: 'focus_composer'});
let error;
function onProgress(ev) {
@ -357,9 +519,12 @@ class ContentMessages {
}, function(err) {
error = err;
if (!upload.canceled) {
let desc = _t('The file \'%(fileName)s\' failed to upload', {fileName: upload.fileName}) + '.';
let desc = _t("The file '%(fileName)s' failed to upload.", {fileName: upload.fileName});
if (err.http_status == 413) {
desc = _t('The file \'%(fileName)s\' exceeds this homeserver\'s size limit for uploads', {fileName: upload.fileName});
desc = _t(
"The file '%(fileName)s' exceeds this homeserver's size limit for uploads",
{fileName: upload.fileName},
);
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Upload failed', '', ErrorDialog, {
@ -377,15 +542,22 @@ class ContentMessages {
}
}
if (error) {
// 413: File was too big or upset the server in some way:
// 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;
}
dis.dispatch({action: 'upload_failed', upload, error});
} else {
dis.dispatch({action: 'upload_finished', upload});
dis.dispatch({action: 'message_sent'});
}
});
}
getCurrentUploads() {
return this.inprogress;
return this.inprogress.filter(u => !u.canceled);
}
cancelUpload(promise) {
@ -401,12 +573,7 @@ class ContentMessages {
if (upload) {
upload.canceled = true;
MatrixClientPeg.get().cancelUpload(upload.promise);
dis.dispatch({action: 'upload_canceled', upload});
}
}
}
if (global.mx_ContentMessage === undefined) {
global.mx_ContentMessage = new ContentMessages();
}
module.exports = global.mx_ContentMessage;

View File

@ -1,5 +1,6 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2019 Travis Ralston
Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
@ -20,17 +21,19 @@ import IntegrationManager from './IntegrationManager';
import WidgetMessagingEndpoint from './WidgetMessagingEndpoint';
import ActiveWidgetStore from './stores/ActiveWidgetStore';
const WIDGET_API_VERSION = '0.0.1'; // Current API version
const WIDGET_API_VERSION = '0.0.2'; // Current API version
const SUPPORTED_WIDGET_API_VERSIONS = [
'0.0.1',
'0.0.2',
];
const INBOUND_API_NAME = 'fromWidget';
// Listen for and handle incomming requests using the 'fromWidget' postMessage
// Listen for and handle incoming requests using the 'fromWidget' postMessage
// API and initiate responses
export default class FromWidgetPostMessageApi {
constructor() {
this.widgetMessagingEndpoints = [];
this.widgetListeners = {}; // {action: func[]}
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
@ -45,6 +48,32 @@ export default class FromWidgetPostMessageApi {
window.removeEventListener('message', this.onPostMessage);
}
/**
* Adds a listener for a given action
* @param {string} action The action to listen for.
* @param {Function} callbackFn A callback function to be called when the action is
* encountered. Called with two parameters: the interesting request information and
* the raw event received from the postMessage API. The raw event is meant to be used
* for sendResponse and similar functions.
*/
addListener(action, callbackFn) {
if (!this.widgetListeners[action]) this.widgetListeners[action] = [];
this.widgetListeners[action].push(callbackFn);
}
/**
* Removes a listener for a given action.
* @param {string} action The action that was subscribed to.
* @param {Function} callbackFn The original callback function that was used to subscribe
* to updates.
*/
removeListener(action, callbackFn) {
if (!this.widgetListeners[action]) return;
const idx = this.widgetListeners[action].indexOf(callbackFn);
if (idx !== -1) this.widgetListeners[action].splice(idx, 1);
}
/**
* Register a widget endpoint for trusted postMessage communication
* @param {string} widgetId Unique widget identifier
@ -87,10 +116,8 @@ export default class FromWidgetPostMessageApi {
const origin = u.protocol + '//' + u.host;
if (this.widgetMessagingEndpoints && this.widgetMessagingEndpoints.length > 0) {
const length = this.widgetMessagingEndpoints.length;
this.widgetMessagingEndpoints = this.widgetMessagingEndpoints.
filter(function(endpoint) {
return (endpoint.widgetId != widgetId || endpoint.endpointUrl != origin);
});
this.widgetMessagingEndpoints = this.widgetMessagingEndpoints
.filter((endpoint) => endpoint.widgetId !== widgetId || endpoint.endpointUrl !== origin);
return (length > this.widgetMessagingEndpoints.length);
}
return false;
@ -117,6 +144,13 @@ export default class FromWidgetPostMessageApi {
return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
}
// Call any listeners we have registered
if (this.widgetListeners[event.data.action]) {
for (const fn of this.widgetListeners[event.data.action]) {
fn(event.data, event);
}
}
// Although the requestId is required, we don't use it. We'll be nice and process the message
// if the property is missing, but with a warning for widget developers.
if (!event.data.requestId) {
@ -164,6 +198,8 @@ export default class FromWidgetPostMessageApi {
if (ActiveWidgetStore.widgetHasCapability(widgetId, 'm.always_on_screen')) {
ActiveWidgetStore.setWidgetPersistence(widgetId, val);
}
} else if (action === 'get_openid') {
// Handled by caller
} else {
console.warn('Widget postMessage event unhandled');
this.sendError(event, {message: 'The postMessage was unhandled'});

View File

@ -516,7 +516,11 @@ export function bodyToHtml(content, highlights, opts={}) {
contentBodyTrimmed = contentBodyTrimmed.replace(ZWJ_REGEX, '');
const match = EMOJI_REGEX.exec(contentBodyTrimmed);
emojiBody = match && match[0] && match[0].length === contentBodyTrimmed.length;
emojiBody = match && match[0] && match[0].length === contentBodyTrimmed.length
// Prevent user pills expanding for users with only emoji in
// their username
&& (content.formatted_body == undefined
|| !content.formatted_body.includes("https://matrix.to/"));
}
const className = classNames({

View File

@ -31,7 +31,8 @@ import Modal from './Modal';
import sdk from './index';
import ActiveWidgetStore from './stores/ActiveWidgetStore';
import PlatformPeg from "./PlatformPeg";
import {sendLoginRequest} from "./Login";
import { sendLoginRequest } from "./Login";
import * as StorageManager from './utils/StorageManager';
/**
* Called at startup, to attempt to build a logged-in Matrix session. It tries
@ -102,9 +103,14 @@ export async function loadSession(opts) {
return _registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName);
}
// fall back to login screen
// fall back to welcome screen
return false;
} catch (e) {
if (e instanceof AbortLoginAndRebuildStorage) {
// If we're aborting login because of a storage inconsistency, we don't
// need to show the general failure dialog. Instead, just go back to welcome.
return false;
}
return _handleLoadSessionFailure(e);
}
}
@ -197,9 +203,6 @@ export function handleInvalidStoreError(e) {
function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
console.log(`Doing guest login on ${hsUrl}`);
// TODO: we should probably de-duplicate this and Login.loginAsGuest.
// Not really sure where the right home for it is.
// create a temporary MatrixClient to do the login
const client = Matrix.createClient({
baseUrl: hsUrl,
@ -278,7 +281,7 @@ async function _restoreFromLocalStorage() {
}
function _handleLoadSessionFailure(e) {
console.log("Unable to load session", e);
console.error("Unable to load session", e);
const def = Promise.defer();
const SessionRestoreErrorDialog =
@ -353,6 +356,22 @@ async function _doSetLoggedIn(credentials, clearStorage) {
await _clearStorage();
}
const results = await StorageManager.checkConsistency();
// If there's an inconsistency between account data in local storage and the
// crypto store, we'll be generally confused when handling encrypted data.
// Show a modal recommending a full reset of storage.
if (results.dataInLocalStorage && !results.dataInCryptoStore) {
const signOut = await _showStorageEvictedDialog();
if (signOut) {
await _clearStorage();
// This error feels a bit clunky, but we want to make sure we don't go any
// further and instead head back to sign in.
throw new AbortLoginAndRebuildStorage(
"Aborting login in progress because of storage inconsistency",
);
}
}
Analytics.setLoggedIn(credentials.guest, credentials.homeserverUrl, credentials.identityServerUrl);
if (localStorage) {
@ -383,6 +402,19 @@ async function _doSetLoggedIn(credentials, clearStorage) {
return MatrixClientPeg.get();
}
function _showStorageEvictedDialog() {
const StorageEvictedDialog = sdk.getComponent('views.dialogs.StorageEvictedDialog');
return new Promise(resolve => {
Modal.createTrackedDialog('Storage evicted', '', StorageEvictedDialog, {
onFinished: resolve,
});
});
}
// Note: Babel 6 requires the `transform-builtin-extend` plugin for this to satisfy
// `instanceof`. Babel 7 supports this natively in their class handling.
class AbortLoginAndRebuildStorage extends Error { }
function _persistCredentialsToLocalStorage(credentials) {
localStorage.setItem("mx_hs_url", credentials.homeserverUrl);
localStorage.setItem("mx_is_url", credentials.identityServerUrl);

View File

@ -81,26 +81,6 @@ export default class Login {
return flowStep ? flowStep.type : null;
}
loginAsGuest() {
const client = this._createTemporaryClient();
return client.registerGuest({
body: {
initial_device_display_name: this._defaultDeviceDisplayName,
},
}).then((creds) => {
return {
userId: creds.user_id,
deviceId: creds.device_id,
accessToken: creds.access_token,
homeserverUrl: this._hsUrl,
identityServerUrl: this._isUrl,
guest: true,
};
}, (error) => {
throw error;
});
}
loginViaPassword(username, phoneCountry, phoneNumber, pass) {
const self = this;

View File

@ -31,6 +31,7 @@ import {phasedRollOutExpiredForUser} from "./PhasedRollOut";
import Modal from './Modal';
import {verificationMethods} from 'matrix-js-sdk/lib/crypto';
import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientBackedSettingsHandler";
import * as StorageManager from './utils/StorageManager';
interface MatrixClientCreds {
homeserverUrl: string,
@ -103,7 +104,7 @@ class MatrixClientPeg {
} catch (err) {
if (dbType === 'indexeddb') {
console.error('Error starting matrixclient store - falling back to memory store', err);
this.matrixClient.store = new Matrix.MatrixInMemoryStore({
this.matrixClient.store = new Matrix.MemoryStore({
localStorage: global.localStorage,
});
} else {
@ -113,6 +114,8 @@ class MatrixClientPeg {
}
}
StorageManager.trackStores(this.matrixClient);
// try to initialise e2e on the new client
try {
// check that we have a version of the js-sdk which includes initCrypto
@ -120,7 +123,7 @@ class MatrixClientPeg {
await this.matrixClient.initCrypto();
}
} catch (e) {
if (e.name === 'InvalidCryptoStoreError') {
if (e && e.name === 'InvalidCryptoStoreError') {
// The js-sdk found a crypto DB too new for it to use
const CryptoStoreTooNewDialog =
sdk.getComponent("views.dialogs.CryptoStoreTooNewDialog");
@ -130,7 +133,7 @@ class MatrixClientPeg {
}
// this can happen for a number of reasons, the most likely being
// that the olm library was missing. It's not fatal.
console.warn("Unable to initialise e2e: " + e);
console.warn("Unable to initialise e2e", e);
}
const opts = utils.deepCopy(this.opts);
@ -171,7 +174,7 @@ class MatrixClientPeg {
return matches[1];
}
_createClient(creds: MatrixClientCreds, useIndexedDb) {
_createClient(creds: MatrixClientCreds) {
const opts = {
baseUrl: creds.homeserverUrl,
idBaseUrl: creds.identityServerUrl,
@ -183,7 +186,7 @@ class MatrixClientPeg {
verificationMethods: [verificationMethods.SAS]
};
this.matrixClient = createMatrixClient(opts, useIndexedDb);
this.matrixClient = createMatrixClient(opts);
// we're going to add eventlisteners for each matrix event tile, so the
// potential number of event listeners is quite high.

View File

@ -75,10 +75,9 @@ const AsyncWrapper = React.createClass({
},
render: function() {
const {loader, ...otherProps} = this.props;
if (this.state.component) {
const Component = this.state.component;
return <Component {...otherProps} />;
return <Component {...this.props} />;
} else if (this.state.error) {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
@ -124,6 +123,10 @@ class ModalManager {
this.closeAll = this.closeAll.bind(this);
}
hasDialogs() {
return this._priorityModal || this._staticModal || this._modals.length > 0;
}
getOrCreateContainer() {
let container = document.getElementById(DIALOG_CONTAINER_ID);
@ -154,7 +157,7 @@ class ModalManager {
}
createDialog(Element, ...rest) {
return this.createDialogAsync(new Promise(resolve => resolve(Element)), ...rest);
return this.createDialogAsync(Promise.resolve(Element), ...rest);
}
createTrackedDialogAsync(analyticsAction, analyticsInfo, ...rest) {
@ -189,36 +192,35 @@ class ModalManager {
* also be removed from the stack. This is not compatible
* with being a priority modal. Only one modal can be
* static at a time.
* @returns {object} Object with 'close' parameter being a function that will close the dialog
*/
createDialogAsync(prom, props, className, isPriorityModal, isStaticModal) {
const self = this;
const modal = {};
// never call this from onFinished() otherwise it will loop
//
// nb explicit function() rather than arrow function, to get `arguments`
const closeDialog = function() {
if (props && props.onFinished) props.onFinished.apply(null, arguments);
const i = self._modals.indexOf(modal);
const closeDialog = (...args) => {
if (props && props.onFinished) props.onFinished.apply(null, args);
const i = this._modals.indexOf(modal);
if (i >= 0) {
self._modals.splice(i, 1);
this._modals.splice(i, 1);
}
if (self._priorityModal === modal) {
self._priorityModal = null;
if (this._priorityModal === modal) {
this._priorityModal = null;
// XXX: This is destructive
self._modals = [];
this._modals = [];
}
if (self._staticModal === modal) {
self._staticModal = null;
if (this._staticModal === modal) {
this._staticModal = null;
// XXX: This is destructive
self._modals = [];
this._modals = [];
}
self._reRender();
this._reRender();
};
// don't attempt to reuse the same AsyncWrapper for different dialogs,

View File

@ -220,7 +220,17 @@ const Notifier = {
}
},
isToolbarHidden: function() {
shouldShowToolbar: function() {
const client = MatrixClientPeg.get();
if (!client) {
return false;
}
const isGuest = client.isGuest();
return !isGuest && this.supportsDesktopNotifications() &&
!this.isEnabled() && !this._isToolbarHidden();
},
_isToolbarHidden: function() {
// Check localStorage for any such meta data
if (global.localStorage) {
return global.localStorage.getItem("notifications_hidden") === "true";

View File

@ -65,6 +65,24 @@ export function showRoomInviteDialog(roomId) {
});
}
/**
* Checks if the given MatrixEvent is a valid 3rd party user invite.
* @param {MatrixEvent} event The event to check
* @returns {boolean} True if valid, false otherwise
*/
export function isValid3pidInvite(event) {
if (!event || event.getType() !== "m.room.third_party_invite") return false;
// any events without these keys are not valid 3pid invites, so we ignore them
const requiredKeys = ['key_validity_url', 'public_key', 'display_name'];
for (let i = 0; i < requiredKeys.length; ++i) {
if (!event.getContent()[requiredKeys[i]]) return false;
}
// Valid enough by our standards
return true;
}
function _onStartChatFinished(shouldInvite, addrs) {
if (!shouldInvite) return;

View File

@ -23,6 +23,8 @@ export const ALL_MESSAGES = 'all_messages';
export const MENTIONS_ONLY = 'mentions_only';
export const MUTE = 'mute';
export const BADGE_STATES = [ALL_MESSAGES, ALL_MESSAGES_LOUD];
export const MENTION_BADGE_STATES = [...BADGE_STATES, MENTIONS_ONLY];
function _shouldShowNotifBadge(roomNotifState) {
const showBadgeInStates = [ALL_MESSAGES, ALL_MESSAGES_LOUD];
@ -107,6 +109,28 @@ export function setRoomNotifsState(roomId, newState) {
}
}
export function getUnreadNotificationCount(room, type=null) {
let notificationCount = room.getUnreadNotificationCount(type);
// Check notification counts in the old room just in case there's some lost
// there. We only go one level down to avoid performance issues, and theory
// is that 1st generation rooms will have already been read by the 3rd generation.
const createEvent = room.currentState.getStateEvents("m.room.create", "");
if (createEvent && createEvent.getContent()['predecessor']) {
const oldRoomId = createEvent.getContent()['predecessor']['room_id'];
const oldRoom = MatrixClientPeg.get().getRoom(oldRoomId);
if (oldRoom) {
// We only ever care if there's highlights in the old room. No point in
// notifying the user for unread messages because they would have extreme
// difficulty changing their notification preferences away from "All Messages"
// and "Noisy".
notificationCount += oldRoom.getUnreadNotificationCount("highlight");
}
}
return notificationCount;
}
function setRoomNotifsStateMuted(roomId) {
const cli = MatrixClientPeg.get();
const promises = [];
@ -204,4 +228,3 @@ function isRuleForRoom(roomId, rule) {
function isMuteRule(rule) {
return (rule.actions.length === 1 && rule.actions[0] === 'dont_notify');
}

View File

@ -41,6 +41,12 @@ class SdkConfig {
static unset() {
global.mxReactSdkConfig = undefined;
}
static add(cfg) {
const liveConfig = SdkConfig.get();
const newConfig = Object.assign({}, liveConfig, cfg);
SdkConfig.put(newConfig);
}
}
module.exports = SdkConfig;

View File

@ -28,6 +28,8 @@ import {MATRIXTO_URL_PATTERN} from "./linkify-matrix";
import * as querystring from "querystring";
import MultiInviter from './utils/MultiInviter';
import { linkifyAndSanitizeHtml } from './HtmlUtils';
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
import WidgetUtils from "./utils/WidgetUtils";
class Command {
constructor({name, args='', description, runFn, hideCompletionAfterSpace=false}) {
@ -105,7 +107,72 @@ export const CommandMap = {
description: _td('Upgrades a room to a new version'),
runFn: function(roomId, args) {
if (args) {
return success(MatrixClientPeg.get().upgradeRoom(roomId, args));
const room = MatrixClientPeg.get().getRoom(roomId);
Modal.createTrackedDialog('Slash Commands', 'upgrade room confirmation',
QuestionDialog, {
title: _t('Room upgrade confirmation'),
description: (
<div>
<p>{_t("Upgrading a room can be destructive and isn't always necessary.")}</p>
<p>
{_t(
"Room upgrades are usually recommended when a room version is considered " +
"<i>unstable</i>. Unstable room versions might have bugs, missing features, or " +
"security vulnerabilities.",
{}, {
"i": (sub) => <i>{sub}</i>,
},
)}
</p>
<p>
{_t(
"Room upgrades usually only affect <i>server-side</i> processing of the " +
"room. If you're having problems with your Riot client, please file an issue " +
"with <issueLink />.",
{}, {
"i": (sub) => <i>{sub}</i>,
"issueLink": () => {
return <a href="https://github.com/vector-im/riot-web/issues/new/choose"
target="_blank" rel="noopener">
https://github.com/vector-im/riot-web/issues/new/choose
</a>;
},
},
)}
</p>
<p>
{_t(
"<b>Warning</b>: Upgrading a room will <i>not automatically migrate room " +
"members to the new version of the room.</i> We'll post a link to the new room " +
"in the old version of the room - room members will have to click this link to " +
"join the new room.",
{}, {
"b": (sub) => <b>{sub}</b>,
"i": (sub) => <i>{sub}</i>,
},
)}
</p>
<p>
{_t(
"Please confirm that you'd like to go forward with upgrading this room " +
"from <oldVersion /> to <newVersion />.",
{},
{
oldVersion: () => <code>{room ? room.getVersion() : "1"}</code>,
newVersion: () => <code>{args}</code>,
},
)}
</p>
</div>
),
button: _t("Upgrade"),
onFinished: (confirm) => {
if (!confirm) return;
MatrixClientPeg.get().upgradeRoom(roomId, args);
},
});
return success();
}
return reject(this.getUsage());
},
@ -365,7 +432,7 @@ export const CommandMap = {
if (!targetRoomId) targetRoomId = roomId;
return success(
cli.leave(targetRoomId).then(function() {
cli.leaveRoomChain(targetRoomId).then(function() {
dis.dispatch({action: 'view_next_room'});
}),
);
@ -540,6 +607,26 @@ export const CommandMap = {
},
}),
addwidget: new Command({
name: 'addwidget',
args: '<url>',
description: _td('Adds a custom widget by URL to the room'),
runFn: function(roomId, args) {
if (!args || (!args.startsWith("https://") && !args.startsWith("http://"))) {
return reject(_t("Please supply a https:// or http:// widget URL"));
}
if (WidgetUtils.canUserModifyWidgets(roomId)) {
const userId = MatrixClientPeg.get().getUserId();
const nowMs = (new Date()).getTime();
const widgetId = encodeURIComponent(`${roomId}_${userId}_${nowMs}`);
return success(WidgetUtils.setRoomWidget(
roomId, widgetId, "m.custom", args, "Custom Widget", {}));
} else {
return reject(_t("You cannot modify widgets in this room."));
}
},
}),
// Verify a user, device, and pubkey tuple
verify: new Command({
name: 'verify',

View File

@ -17,6 +17,7 @@ import MatrixClientPeg from './MatrixClientPeg';
import CallHandler from './CallHandler';
import { _t } from './languageHandler';
import * as Roles from './Roles';
import {isValid3pidInvite} from "./RoomInvite";
function textForMemberEvent(ev) {
// XXX: SYJS-16 "sender is sometimes null for join messages"
@ -366,6 +367,15 @@ function textForCallInviteEvent(event) {
function textForThreePidInviteEvent(event) {
const senderName = event.sender ? event.sender.name : event.getSender();
if (!isValid3pidInvite(event)) {
const targetDisplayName = event.getPrevContent().display_name || _t("Someone");
return _t('%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.', {
senderName,
targetDisplayName,
});
}
return _t('%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.', {
senderName,
targetDisplayName: event.getContent().display_name,

View File

@ -1,7 +1,7 @@
const React = require('react');
const ReactDom = require('react-dom');
import PropTypes from 'prop-types';
const Velocity = require('velocity-vector');
const Velocity = require('velocity-animate');
/**
* The Velociraptor contains components and animates transitions with velocity.

View File

@ -1,4 +1,4 @@
const Velocity = require('velocity-vector');
const Velocity = require('velocity-animate');
// courtesy of https://github.com/julianshapiro/velocity/issues/283
// We only use easeOutBounce (easeInBounce is just sort of nonsensical)

View File

@ -1,5 +1,6 @@
/*
Copyright 2017 New Vector Ltd
Copyright 2019 Travis Ralston
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -21,6 +22,11 @@ limitations under the License.
import FromWidgetPostMessageApi from './FromWidgetPostMessageApi';
import ToWidgetPostMessageApi from './ToWidgetPostMessageApi';
import Modal from "./Modal";
import MatrixClientPeg from "./MatrixClientPeg";
import SettingsStore from "./settings/SettingsStore";
import WidgetOpenIDPermissionsDialog from "./components/views/dialogs/WidgetOpenIDPermissionsDialog";
import WidgetUtils from "./utils/WidgetUtils";
if (!global.mxFromWidgetMessaging) {
global.mxFromWidgetMessaging = new FromWidgetPostMessageApi();
@ -34,12 +40,14 @@ if (!global.mxToWidgetMessaging) {
const OUTBOUND_API_NAME = 'toWidget';
export default class WidgetMessaging {
constructor(widgetId, widgetUrl, target) {
constructor(widgetId, widgetUrl, isUserWidget, target) {
this.widgetId = widgetId;
this.widgetUrl = widgetUrl;
this.isUserWidget = isUserWidget;
this.target = target;
this.fromWidget = global.mxFromWidgetMessaging;
this.toWidget = global.mxToWidgetMessaging;
this._onOpenIdRequest = this._onOpenIdRequest.bind(this);
this.start();
}
@ -109,9 +117,57 @@ export default class WidgetMessaging {
start() {
this.fromWidget.addEndpoint(this.widgetId, this.widgetUrl);
this.fromWidget.addListener("get_openid", this._onOpenIdRequest);
}
stop() {
this.fromWidget.removeEndpoint(this.widgetId, this.widgetUrl);
this.fromWidget.removeListener("get_openid", this._onOpenIdRequest);
}
async _onOpenIdRequest(ev, rawEv) {
if (ev.widgetId !== this.widgetId) return; // not interesting
const widgetSecurityKey = WidgetUtils.getWidgetSecurityKey(this.widgetId, this.widgetUrl, this.isUserWidget);
const settings = SettingsStore.getValue("widgetOpenIDPermissions");
if (settings.deny && settings.deny.includes(widgetSecurityKey)) {
this.fromWidget.sendResponse(rawEv, {state: "blocked"});
return;
}
if (settings.allow && settings.allow.includes(widgetSecurityKey)) {
const responseBody = {state: "allowed"};
const credentials = await MatrixClientPeg.get().getOpenIdToken();
Object.assign(responseBody, credentials);
this.fromWidget.sendResponse(rawEv, responseBody);
return;
}
// Confirm that we received the request
this.fromWidget.sendResponse(rawEv, {state: "request"});
// Actually ask for permission to send the user's data
Modal.createTrackedDialog("OpenID widget permissions", '',
WidgetOpenIDPermissionsDialog, {
widgetUrl: this.widgetUrl,
widgetId: this.widgetId,
isUserWidget: this.isUserWidget,
onFinished: async (confirm) => {
const responseBody = {success: confirm};
if (confirm) {
const credentials = await MatrixClientPeg.get().getOpenIdToken();
Object.assign(responseBody, credentials);
}
this.messageToWidget({
api: OUTBOUND_API_NAME,
action: "openid_credentials",
data: responseBody,
}).catch((error) => {
console.error("Failed to send OpenID credentials: ", error);
});
},
},
);
}
}

View File

@ -56,7 +56,7 @@ export default class RoomProvider extends AutocompleteProvider {
const {command, range} = this.getCurrentCommand(query, selection, force);
if (command) {
// the only reason we need to do this is because Fuse only matches on properties
this.matcher.setObjects(client.getRooms().filter(
let matcherObjects = client.getRooms().filter(
(room) => !!room && !!getDisplayAliasForRoom(room),
).map((room) => {
return {
@ -64,7 +64,21 @@ export default class RoomProvider extends AutocompleteProvider {
name: room.name,
displayedAlias: getDisplayAliasForRoom(room),
};
}));
});
// Filter out any matches where the user will have also autocompleted new rooms
matcherObjects = matcherObjects.filter((r) => {
const tombstone = r.room.currentState.getStateEvents("m.room.tombstone", "");
if (tombstone && tombstone.getContent() && tombstone.getContent()['replacement_room']) {
const hasReplacementRoom = matcherObjects.some(
(r2) => r2.room.roomId === tombstone.getContent()['replacement_room'],
);
return !hasReplacementRoom;
}
return true;
});
this.matcher.setObjects(matcherObjects);
const matchedString = command[0];
completions = this.matcher.match(matchedString);
completions = _sortBy(completions, [

View File

@ -121,8 +121,10 @@ export default class AutoHideScrollbar extends React.Component {
render() {
return (<div
ref={this._collectContainerRef}
style={this.props.style}
className={["mx_AutoHideScrollbar", this.props.className].join(" ")}
onScroll={this.props.onScroll}
onWheel={this.props.onWheel}
>
<div className="mx_AutoHideScrollbar_offset">
{ this.props.children }

View File

@ -1,197 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations 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 sdk from '../../index';
import dis from '../../dispatcher';
import Velocity from 'velocity-vector';
import 'velocity-vector/velocity.ui';
import SettingsStore from '../../settings/SettingsStore';
const CALLOUT_ANIM_DURATION = 1000;
module.exports = React.createClass({
displayName: 'BottomLeftMenu',
propTypes: {
collapsed: React.PropTypes.bool.isRequired,
},
getInitialState: function() {
return ({
directoryHover: false,
roomsHover: false,
homeHover: false,
peopleHover: false,
settingsHover: false,
});
},
componentWillMount: function() {
this._dispatcherRef = dis.register(this.onAction);
this._peopleButton = null;
this._directoryButton = null;
this._createRoomButton = null;
this._lastCallouts = {};
},
componentWillUnmount: function() {
dis.unregister(this._dispatcherRef);
},
// Room events
onDirectoryClick: function() {
dis.dispatch({ action: 'view_room_directory' });
},
onDirectoryMouseEnter: function() {
this.setState({ directoryHover: true });
},
onDirectoryMouseLeave: function() {
this.setState({ directoryHover: false });
},
onRoomsClick: function() {
dis.dispatch({ action: 'view_create_room' });
},
onRoomsMouseEnter: function() {
this.setState({ roomsHover: true });
},
onRoomsMouseLeave: function() {
this.setState({ roomsHover: false });
},
// Home button events
onHomeClick: function() {
dis.dispatch({ action: 'view_home_page' });
},
onHomeMouseEnter: function() {
this.setState({ homeHover: true });
},
onHomeMouseLeave: function() {
this.setState({ homeHover: false });
},
// People events
onPeopleClick: function() {
dis.dispatch({ action: 'view_create_chat' });
},
onPeopleMouseEnter: function() {
this.setState({ peopleHover: true });
},
onPeopleMouseLeave: function() {
this.setState({ peopleHover: false });
},
// Settings events
onSettingsClick: function() {
dis.dispatch({ action: 'view_user_settings' });
},
onSettingsMouseEnter: function() {
this.setState({ settingsHover: true });
},
onSettingsMouseLeave: function() {
this.setState({ settingsHover: false });
},
onAction: function(payload) {
let calloutElement;
switch (payload.action) {
// Incoming instruction: dance!
case 'callout_start_chat':
calloutElement = this._peopleButton;
break;
case 'callout_room_directory':
calloutElement = this._directoryButton;
break;
case 'callout_create_room':
calloutElement = this._createRoomButton;
break;
}
if (calloutElement) {
const lastCallout = this._lastCallouts[payload.action];
const now = Date.now();
if (lastCallout == undefined || lastCallout < now - CALLOUT_ANIM_DURATION) {
this._lastCallouts[payload.action] = now;
Velocity(ReactDOM.findDOMNode(calloutElement), "callout.bounce", CALLOUT_ANIM_DURATION);
}
}
},
// Get the label/tooltip to show
getLabel: function(label, show) {
if (show) {
const Tooltip = sdk.getComponent("elements.Tooltip");
return <Tooltip className="mx_BottomLeftMenu_tooltip" label={label} />;
}
},
_collectPeopleButton: function(e) {
this._peopleButton = e;
},
_collectDirectoryButton: function(e) {
this._directoryButton = e;
},
_collectCreateRoomButton: function(e) {
this._createRoomButton = e;
},
render: function() {
const HomeButton = sdk.getComponent('elements.HomeButton');
const StartChatButton = sdk.getComponent('elements.StartChatButton');
const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton');
const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton');
const SettingsButton = sdk.getComponent('elements.SettingsButton');
const GroupsButton = sdk.getComponent('elements.GroupsButton');
const groupsButton = !SettingsStore.getValue("TagPanel.enableTagPanel") ?
<GroupsButton tooltip={true} /> : null;
return (
<div className="mx_BottomLeftMenu">
<div className="mx_BottomLeftMenu_options">
<HomeButton tooltip={true} />
<div ref={this._collectPeopleButton}>
<StartChatButton tooltip={true} />
</div>
<div ref={this._collectDirectoryButton}>
<RoomDirectoryButton tooltip={true} />
</div>
<div ref={this._collectCreateRoomButton}>
<CreateRoomButton tooltip={true} />
</div>
{ groupsButton }
<span className="mx_BottomLeftMenu_settings">
<SettingsButton tooltip={true} />
</span>
</div>
</div>
);
},
});

View File

@ -56,6 +56,7 @@ export default class ContextualMenu extends React.Component {
menuPaddingRight: PropTypes.number,
menuPaddingBottom: PropTypes.number,
menuPaddingLeft: PropTypes.number,
zIndex: PropTypes.number,
// If true, insert an invisible screen-sized element behind the
// menu that when clicked will close it.
@ -215,16 +216,22 @@ export default class ContextualMenu extends React.Component {
menuStyle["paddingRight"] = props.menuPaddingRight;
}
const wrapperStyle = {};
if (!isNaN(Number(props.zIndex))) {
menuStyle["zIndex"] = props.zIndex + 1;
wrapperStyle["zIndex"] = props.zIndex;
}
const ElementClass = props.elementClass;
// FIXME: If a menu uses getDefaultProps it clobbers the onFinished
// property set here so you can't close the menu from a button click!
return <div className={className} style={position}>
return <div className={className} style={{...position, ...wrapperStyle}}>
<div className={menuClasses} style={menuStyle} ref={this.collectContextMenuRect}>
{ chevron }
<ElementClass {...props} onFinished={props.closeMenu} onResize={props.windowResize} />
</div>
{ props.hasBackground && <div className="mx_ContextualMenu_background"
{ props.hasBackground && <div className="mx_ContextualMenu_background" style={wrapperStyle}
onClick={props.closeMenu} onContextMenu={this.onContextMenu} /> }
<style>{ chevronCSS }</style>
</div>;

View File

@ -123,6 +123,7 @@ const FilePanel = React.createClass({
timelineSet={this.state.timelineSet}
showUrlPreview = {false}
tileShape="file_grid"
resizeNotifier={this.props.resizeNotifier}
empty={_t('There are no visible files in this room')}
/>
);

View File

@ -0,0 +1,38 @@
/*
Copyright 2019 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.
*/
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../languageHandler";
export default class GenericErrorPage extends React.PureComponent {
static propTypes = {
message: PropTypes.string.isRequired,
};
render() {
return <div className='mx_GenericErrorPage'>
<div className='mx_GenericErrorPage_box'>
<h1>{_t("Error loading Riot")}</h1>
<p>{this.props.message}</p>
<p>{_t(
"If this is unexpected, please contact your system administrator " +
"or technical support representative.",
)}</p>
</div>
</div>;
}
}

View File

@ -15,9 +15,22 @@ limitations under the License.
*/
import React from "react";
import PropTypes from "prop-types";
import AutoHideScrollbar from "./AutoHideScrollbar";
export default class IndicatorScrollbar extends React.Component {
static PropTypes = {
// If true, the scrollbar will append mx_IndicatorScrollbar_leftOverflowIndicator
// and mx_IndicatorScrollbar_rightOverflowIndicator elements to the list for positioning
// by the parent element.
trackHorizontalOverflow: PropTypes.bool,
// If true, when the user tries to use their mouse wheel in the component it will
// scroll horizontally rather than vertically. This should only be used on components
// with no vertical scroll opportunity.
verticalScrollsHorizontally: PropTypes.bool,
};
constructor(props) {
super(props);
this._collectScroller = this._collectScroller.bind(this);
@ -25,6 +38,18 @@ export default class IndicatorScrollbar extends React.Component {
this.checkOverflow = this.checkOverflow.bind(this);
this._scrollElement = null;
this._autoHideScrollbar = null;
this.state = {
leftIndicatorOffset: 0,
rightIndicatorOffset: 0,
};
}
moveToOrigin() {
if (!this._scrollElement) return;
this._scrollElement.scrollLeft = 0;
this._scrollElement.scrollTop = 0;
}
_collectScroller(scroller) {
@ -43,6 +68,10 @@ export default class IndicatorScrollbar extends React.Component {
const hasTopOverflow = this._scrollElement.scrollTop > 0;
const hasBottomOverflow = this._scrollElement.scrollHeight >
(this._scrollElement.scrollTop + this._scrollElement.clientHeight);
const hasLeftOverflow = this._scrollElement.scrollLeft > 0;
const hasRightOverflow = this._scrollElement.scrollWidth >
(this._scrollElement.scrollLeft + this._scrollElement.clientWidth);
if (hasTopOverflow) {
this._scrollElement.classList.add("mx_IndicatorScrollbar_topOverflow");
} else {
@ -53,10 +82,30 @@ export default class IndicatorScrollbar extends React.Component {
} else {
this._scrollElement.classList.remove("mx_IndicatorScrollbar_bottomOverflow");
}
if (hasLeftOverflow) {
this._scrollElement.classList.add("mx_IndicatorScrollbar_leftOverflow");
} else {
this._scrollElement.classList.remove("mx_IndicatorScrollbar_leftOverflow");
}
if (hasRightOverflow) {
this._scrollElement.classList.add("mx_IndicatorScrollbar_rightOverflow");
} else {
this._scrollElement.classList.remove("mx_IndicatorScrollbar_rightOverflow");
}
if (this._autoHideScrollbar) {
this._autoHideScrollbar.checkOverflow();
}
if (this.props.trackHorizontalOverflow) {
this.setState({
// Offset from absolute position of the container
leftIndicatorOffset: hasLeftOverflow ? `${this._scrollElement.scrollLeft}px` : '0',
// Negative because we're coming from the right
rightIndicatorOffset: hasRightOverflow ? `-${this._scrollElement.scrollLeft}px` : '0',
});
}
}
getScrollTop() {
@ -69,13 +118,41 @@ export default class IndicatorScrollbar extends React.Component {
}
}
onMouseWheel = (e) => {
if (this.props.verticalScrollsHorizontally && this._scrollElement) {
// xyThreshold is the amount of horizontal motion required for the component to
// ignore the vertical delta in a scroll. Used to stop trackpads from acting in
// strange ways. Should be positive.
const xyThreshold = 0;
// yRetention is the factor multiplied by the vertical delta to try and reduce
// the harshness of the scroll behaviour. Should be a value between 0 and 1.
const yRetention = 1.0;
if (Math.abs(e.deltaX) < xyThreshold) {
// noinspection JSSuspiciousNameCombination
this._scrollElement.scrollLeft += e.deltaY * yRetention;
}
}
};
render() {
const leftIndicatorStyle = {left: this.state.leftIndicatorOffset};
const rightIndicatorStyle = {right: this.state.rightIndicatorOffset};
const leftOverflowIndicator = this.props.trackHorizontalOverflow
? <div className="mx_IndicatorScrollbar_leftOverflowIndicator" style={leftIndicatorStyle} /> : null;
const rightOverflowIndicator = this.props.trackHorizontalOverflow
? <div className="mx_IndicatorScrollbar_rightOverflowIndicator" style={rightIndicatorStyle} /> : null;
return (<AutoHideScrollbar
ref={this._collectScrollerComponent}
wrappedRef={this._collectScroller}
onWheel={this.onMouseWheel}
{... this.props}
>
{ leftOverflowIndicator }
{ this.props.children }
{ rightOverflowIndicator }
</AutoHideScrollbar>);
}
}

View File

@ -27,6 +27,7 @@ import VectorConferenceHandler from '../../VectorConferenceHandler';
import TagPanelButtons from './TagPanelButtons';
import SettingsStore from '../../settings/SettingsStore';
import {_t} from "../../languageHandler";
import Analytics from "../../Analytics";
const LeftPanel = React.createClass({
@ -45,11 +46,23 @@ const LeftPanel = React.createClass({
getInitialState: function() {
return {
searchFilter: '',
breadcrumbs: false,
};
},
componentWillMount: function() {
this.focusedElement = null;
this._settingWatchRef = SettingsStore.watchSetting(
"feature_room_breadcrumbs", null, this._onBreadcrumbsChanged);
const useBreadcrumbs = SettingsStore.isFeatureEnabled("feature_room_breadcrumbs");
Analytics.setBreadcrumbs(useBreadcrumbs);
this.setState({breadcrumbs: useBreadcrumbs});
},
componentWillUnmount: function() {
SettingsStore.unwatchSetting(this._settingWatchRef);
},
shouldComponentUpdate: function(nextProps, nextState) {
@ -73,6 +86,22 @@ const LeftPanel = React.createClass({
return false;
},
componentDidUpdate(prevProps, prevState) {
if (prevState.breadcrumbs !== this.state.breadcrumbs) {
Analytics.setBreadcrumbs(this.state.breadcrumbs);
}
},
_onBreadcrumbsChanged: function(settingName, roomId, level, valueAtLevel, value) {
// Features are only possible at a single level, so we can get away with using valueAtLevel.
// The SettingsStore runs on the same tick as the update, so `value` will be wrong.
this.setState({breadcrumbs: valueAtLevel});
// For some reason the setState doesn't trigger a render of the component, so force one.
// Probably has to do with the change happening outside of a change detector cycle.
this.forceUpdate();
},
_onFocus: function(ev) {
this.focusedElement = ev.target;
},
@ -220,7 +249,7 @@ const LeftPanel = React.createClass({
collapsed={this.props.collapsed} />);
let breadcrumbs;
if (SettingsStore.isFeatureEnabled("feature_room_breadcrumbs")) {
if (this.state.breadcrumbs) {
breadcrumbs = (<RoomBreadcrumbs collapsed={this.props.collapsed} />);
}
@ -234,14 +263,13 @@ const LeftPanel = React.createClass({
<CallPreview ConferenceHandler={VectorConferenceHandler} />
<RoomList
ref={this.collectRoomList}
toolbarShown={this.props.toolbarShown}
resizeNotifier={this.props.resizeNotifier}
collapsed={this.props.collapsed}
searchFilter={this.state.searchFilter}
ConferenceHandler={VectorConferenceHandler} />
</aside>
</div>
);
// <BottomLeftMenu collapsed={this.props.collapsed}/>
},
});

View File

@ -22,7 +22,6 @@ import PropTypes from 'prop-types';
import { DragDropContext } from 'react-beautiful-dnd';
import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
import Notifier from '../../Notifier';
import PageTypes from '../../PageTypes';
import CallMediaHandler from '../../CallMediaHandler';
import sdk from '../../index';
@ -121,6 +120,18 @@ const LoggedInView = React.createClass({
this._matrixClient.on("RoomState.events", this.onRoomStateEvents);
},
componentDidUpdate(prevProps) {
// attempt to guess when a banner was opened or closed
if (
(prevProps.showCookieBar !== this.props.showCookieBar) ||
(prevProps.hasNewVersion !== this.props.hasNewVersion) ||
(prevProps.userHasGeneratedPassword !== this.props.userHasGeneratedPassword) ||
(prevProps.showNotifierToolbar !== this.props.showNotifierToolbar)
) {
this.props.resizeNotifier.notifyBannersChanged();
}
},
componentWillUnmount: function() {
document.removeEventListener('keydown', this._onKeyDown);
this._matrixClient.removeListener("accountData", this.onAccountData);
@ -173,6 +184,7 @@ const LoggedInView = React.createClass({
},
onResized: (size) => {
window.localStorage.setItem("mx_lhs_size", '' + size);
this.props.resizeNotifier.notifyLeftHandleResized();
},
};
const resizer = new Resizer(
@ -448,6 +460,7 @@ const LoggedInView = React.createClass({
disabled={this.props.middleDisabled}
collapsedRhs={this.props.collapsedRhs}
ConferenceHandler={this.props.ConferenceHandler}
resizeNotifier={this.props.resizeNotifier}
/>;
break;
@ -489,7 +502,6 @@ const LoggedInView = React.createClass({
});
let topBar;
const isGuest = this.props.matrixClient.isGuest();
if (this.state.syncErrorData && this.state.syncErrorData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') {
topBar = <ServerLimitBar kind='hard'
adminContact={this.state.syncErrorData.error.data.admin_contact}
@ -513,10 +525,7 @@ const LoggedInView = React.createClass({
topBar = <UpdateCheckBar {...this.props.checkingForUpdate} />;
} else if (this.state.userHasGeneratedPassword) {
topBar = <PasswordNagBar />;
} else if (
!isGuest && Notifier.supportsDesktopNotifications() &&
!Notifier.isEnabled() && !Notifier.isToolbarHidden()
) {
} else if (this.props.showNotifierToolbar) {
topBar = <MatrixToolbar />;
}
@ -534,7 +543,7 @@ const LoggedInView = React.createClass({
<DragDropContext onDragEnd={this._onDragEnd}>
<div ref={this._setResizeContainerRef} className={bodyClasses}>
<LeftPanel
toolbarShown={!!topBar}
resizeNotifier={this.props.resizeNotifier}
collapsed={this.props.collapseLhs || false}
disabled={this.props.leftDisabled}
/>

View File

@ -27,6 +27,9 @@ export default class MainSplit extends React.Component {
_onResized(size) {
window.localStorage.setItem("mx_rhs_size", size);
if (this.props.resizeNotifier) {
this.props.resizeNotifier.notifyRightHandleResized();
}
}
_createResizer() {

View File

@ -29,6 +29,7 @@ import PlatformPeg from "../../PlatformPeg";
import SdkConfig from "../../SdkConfig";
import * as RoomListSorter from "../../RoomListSorter";
import dis from "../../dispatcher";
import Notifier from '../../Notifier';
import Modal from "../../Modal";
import Tinter from "../../Tinter";
@ -48,6 +49,7 @@ import { _t, getCurrentLanguage } from '../../languageHandler';
import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
import { startAnyRegistrationFlow } from "../../Registration.js";
import { messageForSyncError } from '../../utils/ErrorUtils';
import ResizeNotifier from "../../utils/ResizeNotifier";
const AutoDiscovery = Matrix.AutoDiscovery;
@ -194,6 +196,8 @@ export default React.createClass({
hideToSRUsers: false,
syncError: null, // If the current syncing status is ERROR, the error object, otherwise null.
resizeNotifier: new ResizeNotifier(),
showNotifierToolbar: false,
};
return s;
},
@ -245,17 +249,6 @@ export default React.createClass({
return this.state.defaultIsUrl || "https://vector.im";
},
/**
* Whether to skip the server details phase of registration and start at the
* actual form.
* @return {boolean}
* If there was a configured default HS or default server name, skip the
* the server details.
*/
skipServerDetailsForRegistration() {
return !!this.state.defaultHsUrl;
},
componentWillMount: function() {
SdkConfig.put(this.props.config);
@ -316,6 +309,9 @@ export default React.createClass({
// N.B. we don't call the whole of setTheme() here as we may be
// racing with the theme CSS download finishing from index.js
Tinter.tint();
// For PersistentElement
this.state.resizeNotifier.on("middlePanelResized", this._dispatchTimelineResize);
},
componentDidMount: function() {
@ -398,6 +394,7 @@ export default React.createClass({
dis.unregister(this.dispatcherRef);
window.removeEventListener("focus", this.onFocus);
window.removeEventListener('resize', this.handleResize);
this.state.resizeNotifier.removeListener("middlePanelResized", this._dispatchTimelineResize);
},
componentWillUpdate: function(props, state) {
@ -556,19 +553,8 @@ export default React.createClass({
},
});
break;
case 'view_user':
// FIXME: ugly hack to expand the RightPanel and then re-dispatch.
if (this.state.collapsedRhs) {
setTimeout(()=>{
dis.dispatch({
action: 'show_right_panel',
});
dis.dispatch({
action: 'view_user',
member: payload.member,
});
}, 0);
}
case 'view_user_info':
this._viewUser(payload.userId, payload.subAction);
break;
case 'view_room':
// Takes either a room ID or room alias: if switching to a room the client is already
@ -588,8 +574,8 @@ export default React.createClass({
break;
case 'view_user_settings': {
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {}, 'mx_SettingsDialog',
/*isPriority=*/false, /*isStatic=*/true);
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {},
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
// View the welcome or home page if we need something to look at
this._viewSomethingBehindModal();
@ -638,8 +624,9 @@ export default React.createClass({
case 'view_invite':
showRoomInviteDialog(payload.roomId);
break;
case 'notifier_enabled':
this.forceUpdate();
case 'notifier_enabled': {
this.setState({showNotifierToolbar: Notifier.shouldShowToolbar()});
}
break;
case 'hide_left_panel':
this.setState({
@ -923,6 +910,22 @@ export default React.createClass({
this.notifyNewScreen('home');
},
_viewUser: function(userId, subAction) {
// Wait for the first sync so that `getRoom` gives us a room object if it's
// in the sync response
const waitForSync = this.firstSyncPromise ?
this.firstSyncPromise.promise : Promise.resolve();
waitForSync.then(() => {
if (subAction === 'chat') {
this._chatCreateOrReuse(userId);
return;
}
this.notifyNewScreen('user/' + userId);
this.setState({currentUserId: userId});
this._setPage(PageTypes.UserView);
});
},
_setMxId: function(payload) {
const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog');
const close = Modal.createTrackedDialog('Set MXID', '', SetMxIdDialog, {
@ -1058,34 +1061,48 @@ export default React.createClass({
button: _t("Leave"),
onFinished: (shouldLeave) => {
if (shouldLeave) {
const d = MatrixClientPeg.get().leave(roomId);
const d = MatrixClientPeg.get().leaveRoomChain(roomId);
// FIXME: controller shouldn't be loading a view :(
const Loader = sdk.getComponent("elements.Spinner");
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
d.then(() => {
d.then((errors) => {
modal.close();
for (const leftRoomId of Object.keys(errors)) {
const err = errors[leftRoomId];
if (!err) continue;
console.error("Failed to leave room " + leftRoomId + " " + err);
let title = _t("Failed to leave room");
let message = _t("Server may be unavailable, overloaded, or you hit a bug.");
if (err.errcode === 'M_CANNOT_LEAVE_SERVER_NOTICE_ROOM') {
title = _t("Can't leave Server Notices room");
message = _t(
"This room is used for important messages from the Homeserver, " +
"so you cannot leave it.",
);
} else if (err && err.message) {
message = err.message;
}
Modal.createTrackedDialog('Failed to leave room', '', ErrorDialog, {
title: title,
description: message,
});
return;
}
if (this.state.currentRoomId === roomId) {
dis.dispatch({action: 'view_next_room'});
}
}, (err) => {
// This should only happen if something went seriously wrong with leaving the chain.
modal.close();
console.error("Failed to leave room " + roomId + " " + err);
let title = _t("Failed to leave room");
let message = _t("Server may be unavailable, overloaded, or you hit a bug.");
if (err.errcode == 'M_CANNOT_LEAVE_SERVER_NOTICE_ROOM') {
title = _t("Can't leave Server Notices room");
message = _t(
"This room is used for important messages from the Homeserver, " +
"so you cannot leave it.",
);
} else if (err && err.message) {
message = err.message;
}
Modal.createTrackedDialog('Failed to leave room', '', ErrorDialog, {
title: title,
description: message,
title: _t("Failed to leave room"),
description: _t("Unknown error"),
});
});
}
@ -1173,7 +1190,7 @@ export default React.createClass({
* Called when a new logged in session has started
*/
_onLoggedIn: async function() {
this.setStateForNewView({view: VIEWS.LOGGED_IN});
this.setStateForNewView({ view: VIEWS.LOGGED_IN });
if (this._is_registered) {
this._is_registered = false;
@ -1306,7 +1323,10 @@ export default React.createClass({
self.firstSyncPromise.resolve();
dis.dispatch({action: 'focus_composer'});
self.setState({ready: true});
self.setState({
ready: true,
showNotifierToolbar: Notifier.shouldShowToolbar(),
});
});
cli.on('Call.incoming', function(call) {
// we dispatch this synchronously to make sure that the event
@ -1535,7 +1555,16 @@ export default React.createClass({
} else if (screen.indexOf('room/') == 0) {
const segments = screen.substring(5).split('/');
const roomString = segments[0];
const eventId = segments[1]; // undefined if no event id given
let eventId = segments.splice(1).join("/"); // empty string if no event id given
// Previously we pulled the eventID from the segments in such a way
// where if there was no eventId then we'd get undefined. However, we
// now do a splice and join to handle v3 event IDs which results in
// an empty string. To maintain our potential contract with the rest
// of the app, we coerce the eventId to be undefined where applicable.
if (!eventId) eventId = undefined;
// TODO: Handle encoded room/event IDs: https://github.com/vector-im/riot-web/issues/9149
// FIXME: sort_out caseConsistency
const thirdPartyInvite = {
@ -1579,19 +1608,10 @@ export default React.createClass({
dis.dispatch(payload);
} else if (screen.indexOf('user/') == 0) {
const userId = screen.substring(5);
// Wait for the first sync so that `getRoom` gives us a room object if it's
// in the sync response
const waitFor = this.firstSyncPromise ?
this.firstSyncPromise.promise : Promise.resolve();
waitFor.then(() => {
if (params.action === 'chat') {
this._chatCreateOrReuse(userId);
return;
}
this.notifyNewScreen('user/' + userId);
this.setState({currentUserId: userId});
this._setPage(PageTypes.UserView);
dis.dispatch({
action: 'view_user_info',
userId: userId,
subAction: params.action,
});
} else if (screen.indexOf('group/') == 0) {
const groupId = screen.substring(6);
@ -1661,9 +1681,14 @@ export default React.createClass({
dis.dispatch({ action: 'show_right_panel' });
}
this.state.resizeNotifier.notifyWindowResized();
this._windowWidth = window.innerWidth;
},
_dispatchTimelineResize() {
dis.dispatch({ action: 'timeline_resize' });
},
onRoomCreated: function(roomId) {
dis.dispatch({
action: "view_room",
@ -1720,7 +1745,7 @@ export default React.createClass({
hasCancelButton: false,
});
return;
return MatrixClientPeg.get();
}
}
return Lifecycle.setLoggedIn(credentials);
@ -1759,7 +1784,7 @@ export default React.createClass({
},
_setPageSubtitle: function(subtitle='') {
document.title = `Riot ${subtitle}`;
document.title = `${SdkConfig.get().brand || 'Riot'} ${subtitle}`;
},
updateStatusIndicator: function(state, prevState) {
@ -1937,7 +1962,6 @@ export default React.createClass({
defaultServerDiscoveryError={this.state.defaultServerDiscoveryError}
defaultHsUrl={this.getDefaultHsUrl()}
defaultIsUrl={this.getDefaultIsUrl()}
skipServerDetails={this.skipServerDetailsForRegistration()}
brand={this.props.config.brand}
customHsUrl={this.getCurrentHsUrl()}
customIsUrl={this.getCurrentIsUrl()}
@ -1980,7 +2004,6 @@ export default React.createClass({
fallbackHsUrl={this.getFallbackHsUrl()}
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
onForgotPasswordClick={this.onForgotPasswordClick}
enableGuest={this.props.enableGuest}
onServerConfigChange={this.onServerConfigChange}
/>
);

View File

@ -21,7 +21,6 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import shouldHideEvent from '../../shouldHideEvent';
import {wantsDateSeparator} from '../../DateUtils';
import dis from "../../dispatcher";
import sdk from '../../index';
import MatrixClientPeg from '../../MatrixClientPeg';
@ -628,16 +627,29 @@ module.exports = React.createClass({
_onHeightChanged: function() {
const scrollPanel = this.refs.scrollPanel;
if (scrollPanel) {
scrollPanel.forceUpdate();
scrollPanel.checkScroll();
}
},
_onTypingVisible: function() {
_onTypingShown: function() {
const scrollPanel = this.refs.scrollPanel;
// this will make the timeline grow, so checkScroll
scrollPanel.checkScroll();
if (scrollPanel && scrollPanel.getScrollState().stuckAtBottom) {
// scroll down if at bottom
scrollPanel.preventShrinking();
}
},
_onTypingHidden: function() {
const scrollPanel = this.refs.scrollPanel;
if (scrollPanel) {
// as hiding the typing notifications doesn't
// update the scrollPanel, we tell it to apply
// the shrinking prevention once the typing notifs are hidden
scrollPanel.updatePreventShrinking();
// order is important here as checkScroll will scroll down to
// reveal added padding to balance the notifs disappearing.
scrollPanel.checkScroll();
scrollPanel.blockShrinking();
}
},
@ -653,22 +665,18 @@ module.exports = React.createClass({
// update the min-height, so once the last
// person stops typing, no jumping occurs
if (isAtBottom && isTypingVisible) {
scrollPanel.blockShrinking();
scrollPanel.preventShrinking();
}
}
},
clearTimelineHeight: function() {
onTimelineReset: function() {
const scrollPanel = this.refs.scrollPanel;
if (scrollPanel) {
scrollPanel.clearBlockShrinking();
scrollPanel.clearPreventShrinking();
}
},
onResize: function() {
dis.dispatch({ action: 'timeline_resize' }, true);
},
render: function() {
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
const WhoIsTypingTile = sdk.getComponent("rooms.WhoIsTypingTile");
@ -693,7 +701,12 @@ module.exports = React.createClass({
let whoIsTyping;
if (this.props.room) {
whoIsTyping = (<WhoIsTypingTile room={this.props.room} onVisible={this._onTypingVisible} ref="whoIsTyping" />);
whoIsTyping = (<WhoIsTypingTile
room={this.props.room}
onShown={this._onTypingShown}
onHidden={this._onTypingHidden}
ref="whoIsTyping" />
);
}
return (
@ -703,7 +716,8 @@ module.exports = React.createClass({
onFillRequest={this.props.onFillRequest}
onUnfillRequest={this.props.onUnfillRequest}
style={style}
stickyBottom={this.props.stickyBottom}>
stickyBottom={this.props.stickyBottom}
resizeNotifier={this.props.resizeNotifier}>
{ topSpinner }
{ this._getEventTiles() }
{ whoIsTyping }

View File

@ -50,6 +50,7 @@ export default class RightPanel extends React.Component {
FilePanel: 'FilePanel',
NotificationPanel: 'NotificationPanel',
RoomMemberInfo: 'RoomMemberInfo',
Room3pidMemberInfo: 'Room3pidMemberInfo',
GroupMemberInfo: 'GroupMemberInfo',
});
@ -155,6 +156,7 @@ export default class RightPanel extends React.Component {
groupRoomId: payload.groupRoomId,
groupId: payload.groupId,
member: payload.member,
event: payload.event,
});
}
}
@ -162,6 +164,7 @@ export default class RightPanel extends React.Component {
render() {
const MemberList = sdk.getComponent('rooms.MemberList');
const MemberInfo = sdk.getComponent('rooms.MemberInfo');
const ThirdPartyMemberInfo = sdk.getComponent('rooms.ThirdPartyMemberInfo');
const NotificationPanel = sdk.getComponent('structures.NotificationPanel');
const FilePanel = sdk.getComponent('structures.FilePanel');
@ -180,6 +183,8 @@ export default class RightPanel extends React.Component {
panel = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />;
} else if (this.state.phase === RightPanel.Phase.RoomMemberInfo) {
panel = <MemberInfo member={this.state.member} key={this.props.roomId || this.state.member.userId} />;
} else if (this.state.phase === RightPanel.Phase.Room3pidMemberInfo) {
panel = <ThirdPartyMemberInfo event={this.state.event} key={this.props.roomId} />;
} else if (this.state.phase === RightPanel.Phase.GroupMemberInfo) {
panel = <GroupMemberInfo
groupMember={this.state.member}
@ -193,7 +198,7 @@ export default class RightPanel extends React.Component {
} else if (this.state.phase === RightPanel.Phase.NotificationPanel) {
panel = <NotificationPanel />;
} else if (this.state.phase === RightPanel.Phase.FilePanel) {
panel = <FilePanel roomId={this.props.roomId} />;
panel = <FilePanel roomId={this.props.roomId} resizeNotifier={this.props.resizeNotifier} />;
}
const classes = classNames("mx_RightPanel", "mx_fadable", {

View File

@ -26,14 +26,17 @@ const dis = require('../../dispatcher');
import { linkifyAndSanitizeHtml } from '../../HtmlUtils';
import Promise from 'bluebird';
import { _t } from '../../languageHandler';
import {instanceForInstanceId, protocolNameForInstanceId} from '../../utils/DirectoryUtils';
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
import Analytics from '../../Analytics';
const MAX_NAME_LENGTH = 80;
const MAX_TOPIC_LENGTH = 160;
function track(action) {
Analytics.trackEvent('RoomDirectory', action);
}
module.exports = React.createClass({
displayName: 'RoomDirectory',
@ -53,6 +56,7 @@ module.exports = React.createClass({
publicRooms: [],
loading: true,
protocolsLoading: true,
error: null,
instanceId: null,
includeAll: false,
roomServer: null,
@ -95,10 +99,12 @@ module.exports = React.createClass({
// thing you see when loading the client!
return;
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to get protocol list from homeserver', '', ErrorDialog, {
title: _t('Failed to get protocol list from homeserver'),
description: _t('The homeserver may be too old to support third party networks'),
track('Failed to get protocol list from homeserver');
this.setState({
error: _t(
'Riot failed to get the protocol list from the homeserver. ' +
'The homeserver may be too old to support third party networks.',
),
});
});
@ -187,12 +193,14 @@ module.exports = React.createClass({
return;
}
this.setState({ loading: false });
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to get public room list', '', ErrorDialog, {
title: _t('Failed to get public room list'),
description: ((err && err.message) ? err.message : _t('The server may be unavailable or overloaded')),
track('Failed to get public room list');
this.setState({
loading: false,
error:
`${_t('Riot failed to get the public room list.')} ` +
`${(err && err.message) ? err.message : _t('The homeserver may be unavailable or overloaded.')}`
,
});
});
},
@ -511,25 +519,15 @@ module.exports = React.createClass({
},
render: function() {
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
const Loader = sdk.getComponent("elements.Spinner");
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
// TODO: clean this up
if (this.state.protocolsLoading) {
return (
<div className="mx_RoomDirectory">
<Loader />
</div>
);
}
let content;
if (this.state.loading) {
content = <div className="mx_RoomDirectory">
<Loader />
</div>;
if (this.state.error) {
content = this.state.error;
} else if (this.state.protocolsLoading || this.state.loading) {
content = <Loader />;
} else {
const rows = this.getRows();
// we still show the scrollpanel, at least for now, because
@ -551,39 +549,53 @@ module.exports = React.createClass({
onFillRequest={ this.onFillRequest }
stickyBottom={false}
startAtBottom={false}
onResize={function() {}}
>
{ scrollpanel_content }
</ScrollPanel>;
}
const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId);
let instance_expected_field_type;
if (
protocolName &&
this.protocols &&
this.protocols[protocolName] &&
this.protocols[protocolName].location_fields.length > 0 &&
this.protocols[protocolName].field_types
) {
const last_field = this.protocols[protocolName].location_fields.slice(-1)[0];
instance_expected_field_type = this.protocols[protocolName].field_types[last_field];
}
let listHeader;
if (!this.state.protocolsLoading) {
const NetworkDropdown = sdk.getComponent('directory.NetworkDropdown');
const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox');
let placeholder = _t('Search for a room');
if (!this.state.instanceId) {
placeholder = _t('Search for a room like #example') + ':' + this.state.roomServer;
} else if (instance_expected_field_type) {
placeholder = instance_expected_field_type.placeholder;
}
let showJoinButton = this._stringLooksLikeId(this.state.filterString, instance_expected_field_type);
if (protocolName) {
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
if (this._getFieldsForThirdPartyLocation(this.state.filterString, this.protocols[protocolName], instance) === null) {
showJoinButton = false;
const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId);
let instance_expected_field_type;
if (
protocolName &&
this.protocols &&
this.protocols[protocolName] &&
this.protocols[protocolName].location_fields.length > 0 &&
this.protocols[protocolName].field_types
) {
const last_field = this.protocols[protocolName].location_fields.slice(-1)[0];
instance_expected_field_type = this.protocols[protocolName].field_types[last_field];
}
let placeholder = _t('Search for a room');
if (!this.state.instanceId) {
placeholder = _t('Search for a room like #example') + ':' + this.state.roomServer;
} else if (instance_expected_field_type) {
placeholder = instance_expected_field_type.placeholder;
}
let showJoinButton = this._stringLooksLikeId(this.state.filterString, instance_expected_field_type);
if (protocolName) {
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
if (this._getFieldsForThirdPartyLocation(this.state.filterString, this.protocols[protocolName], instance) === null) {
showJoinButton = false;
}
}
listHeader = <div className="mx_RoomDirectory_listheader">
<DirectorySearchBox
className="mx_RoomDirectory_searchbox"
onChange={this.onFilterChange} onClear={this.onFilterClear} onJoinClick={this.onJoinClick}
placeholder={placeholder} showJoinButton={showJoinButton}
/>
<NetworkDropdown config={this.props.config} protocols={this.protocols} onOptionChange={this.onOptionChange} />
</div>;
}
const createRoomButton = (<AccessibleButton
@ -591,8 +603,6 @@ module.exports = React.createClass({
className="mx_RoomDirectory_createRoom"
>{_t("Create new room")}</AccessibleButton>);
const NetworkDropdown = sdk.getComponent('directory.NetworkDropdown');
const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox');
return (
<BaseDialog
className={'mx_RoomDirectory_dialog'}
@ -603,14 +613,7 @@ module.exports = React.createClass({
>
<div className="mx_RoomDirectory">
<div className="mx_RoomDirectory_list">
<div className="mx_RoomDirectory_listheader">
<DirectorySearchBox
className="mx_RoomDirectory_searchbox"
onChange={this.onFilterChange} onClear={this.onFilterClear} onJoinClick={this.onJoinClick}
placeholder={placeholder} showJoinButton={showJoinButton}
/>
<NetworkDropdown config={this.props.config} protocols={this.protocols} onOptionChange={this.onOptionChange} />
</div>
{listHeader}
{content}
</div>
</div>

View File

@ -1,7 +1,7 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2018, 2019 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.
@ -146,8 +146,8 @@ const RoomSubList = React.createClass({
key={room.roomId}
collapsed={this.props.collapsed || false}
unread={Unread.doesRoomHaveUnreadMessages(room)}
highlight={room.getUnreadNotificationCount('highlight') > 0 || this.props.isInvite}
notificationCount={room.getUnreadNotificationCount()}
highlight={this.props.isInvite || RoomNotifs.getUnreadNotificationCount(room, 'highlight') > 0}
notificationCount={RoomNotifs.getUnreadNotificationCount(room)}
isInvite={this.props.isInvite}
refreshSubList={this._updateSubListCount}
incomingCall={null}

View File

@ -19,29 +19,28 @@ limitations under the License.
// TODO: This component is enormous! There's several things which could stand-alone:
// - Search results component
// - Drag and drop
// - File uploading - uploadFile()
import shouldHideEvent from "../../shouldHideEvent";
import shouldHideEvent from '../../shouldHideEvent';
const React = require("react");
const ReactDOM = require("react-dom");
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import Promise from 'bluebird';
import filesize from 'filesize';
const classNames = require("classnames");
import classNames from 'classnames';
import { _t } from '../../languageHandler';
import {RoomPermalinkCreator} from "../../matrix-to";
import {RoomPermalinkCreator} from '../../matrix-to';
const MatrixClientPeg = require("../../MatrixClientPeg");
const ContentMessages = require("../../ContentMessages");
const Modal = require("../../Modal");
const sdk = require('../../index');
const CallHandler = require('../../CallHandler');
const dis = require("../../dispatcher");
const Tinter = require("../../Tinter");
const rate_limited_func = require('../../ratelimitedfunc');
const ObjectUtils = require('../../ObjectUtils');
const Rooms = require('../../Rooms');
import MatrixClientPeg from '../../MatrixClientPeg';
import ContentMessages from '../../ContentMessages';
import Modal from '../../Modal';
import sdk from '../../index';
import CallHandler from '../../CallHandler';
import dis from '../../dispatcher';
import Tinter from '../../Tinter';
import rate_limited_func from '../../ratelimitedfunc';
import ObjectUtils from '../../ObjectUtils';
import Rooms from '../../Rooms';
import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
@ -52,6 +51,7 @@ import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
import WidgetEchoStore from '../../stores/WidgetEchoStore';
import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
import WidgetUtils from '../../utils/WidgetUtils';
import AccessibleButton from "../views/elements/AccessibleButton";
const DEBUG = false;
let debuglog = function() {};
@ -144,6 +144,7 @@ module.exports = React.createClass({
// the end of the live timeline. It has the effect of hiding the
// 'scroll to bottom' knob, among a couple of other things.
atEndOfLiveTimeline: true,
atEndOfLiveTimelineInit: false, // used by componentDidUpdate to avoid unnecessary checks
showTopUnreadMessagesBar: false,
@ -168,7 +169,6 @@ module.exports = React.createClass({
MatrixClientPeg.get().on("accountData", this.onAccountData);
MatrixClientPeg.get().on("crypto.keyBackupStatus", this.onKeyBackupStatus);
MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged);
this._fetchMediaConfig();
// Start listening for RoomViewStore updates
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
this._onRoomViewStoreUpdate(true);
@ -176,27 +176,6 @@ module.exports = React.createClass({
WidgetEchoStore.on('update', this._onWidgetEchoStoreUpdate);
},
_fetchMediaConfig: function(invalidateCache: boolean = false) {
/// NOTE: Using global here so we don't make repeated requests for the
/// config every time we swap room.
if(global.mediaConfig !== undefined && !invalidateCache) {
this.setState({mediaConfig: global.mediaConfig});
return;
}
console.log("[Media Config] Fetching");
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) => {
global.mediaConfig = config;
this.setState({mediaConfig: config});
});
},
_onRoomViewStoreUpdate: function(initial) {
if (this.unmounted) {
return;
@ -293,6 +272,28 @@ module.exports = React.createClass({
return this.state.room ? this.state.room.roomId : this.state.roomId;
},
_getPermalinkCreatorForRoom: function(room) {
if (!this._permalinkCreators) this._permalinkCreators = {};
if (this._permalinkCreators[room.roomId]) return this._permalinkCreators[room.roomId];
this._permalinkCreators[room.roomId] = new RoomPermalinkCreator(room);
if (this.state.room && room.roomId === this.state.room.roomId) {
// We want to watch for changes in the creator for the primary room in the view, but
// don't need to do so for search results.
this._permalinkCreators[room.roomId].start();
} else {
this._permalinkCreators[room.roomId].load();
}
return this._permalinkCreators[room.roomId];
},
_stopAllPermalinkCreators: function() {
if (!this._permalinkCreators) return;
for (const roomId of Object.keys(this._permalinkCreators)) {
this._permalinkCreators[roomId].stop();
}
},
_onWidgetEchoStoreUpdate: function() {
this.setState({
showApps: this._shouldShowApps(this.state.room),
@ -392,7 +393,9 @@ module.exports = React.createClass({
this._updateConfCallNotification();
window.addEventListener('beforeunload', this.onPageUnload);
window.addEventListener('resize', this.onResize);
if (this.props.resizeNotifier) {
this.props.resizeNotifier.on("middlePanelResized", this.onResize);
}
this.onResize();
document.addEventListener("keydown", this.onKeyDown);
@ -428,6 +431,18 @@ module.exports = React.createClass({
roomView.addEventListener('dragend', this.onDragLeaveOrEnd);
}
}
// Note: We check the ref here with a flag because componentDidMount, despite
// documentation, does not define our messagePanel ref. It looks like our spinner
// in render() prevents the ref from being set on first mount, so we try and
// catch the messagePanel when it does mount. Because we only want the ref once,
// we use a boolean flag to avoid duplicate work.
if (this.refs.messagePanel && !this.state.atEndOfLiveTimelineInit) {
this.setState({
atEndOfLiveTimelineInit: true,
atEndOfLiveTimeline: this.refs.messagePanel.isAtEndOfLiveTimeline(),
});
}
},
componentWillUnmount: function() {
@ -443,9 +458,7 @@ module.exports = React.createClass({
}
// stop tracking room changes to format permalinks
if (this.state.permalinkCreator) {
this.state.permalinkCreator.stop();
}
this._stopAllPermalinkCreators();
if (this.refs.roomView) {
// disconnect the D&D event listeners from the room view. This
@ -472,7 +485,9 @@ module.exports = React.createClass({
}
window.removeEventListener('beforeunload', this.onPageUnload);
window.removeEventListener('resize', this.onResize);
if (this.props.resizeNotifier) {
this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize);
}
document.removeEventListener("keydown", this.onKeyDown);
@ -492,7 +507,7 @@ module.exports = React.createClass({
},
onPageUnload(event) {
if (ContentMessages.getCurrentUploads().length > 0) {
if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) {
return event.returnValue =
_t("You seem to be uploading files, are you sure you want to quit?");
} else if (this._getCallForRoom() && this.state.callState !== 'ended') {
@ -541,16 +556,14 @@ module.exports = React.createClass({
payload.data.description || payload.data.name);
break;
case 'picture_snapshot':
this.uploadFile(payload.file);
return ContentMessages.sharedInstance().sendContentListToRoom(
[payload.file], this.state.room.roomId, MatrixClientPeg.get(),
);
break;
case 'upload_failed':
// 413: File was too big or upset the server in some way.
if (payload.error && payload.error.http_status === 413) {
this._fetchMediaConfig(true);
}
case 'notifier_enabled':
case 'upload_started':
case 'upload_finished':
case 'upload_canceled':
this.forceUpdate();
break;
case 'call_state':
@ -658,11 +671,6 @@ module.exports = React.createClass({
this._loadMembersIfJoined(room);
this._calculateRecommendedVersion(room);
this._updateE2EStatus(room);
if (!this.state.permalinkCreator) {
const permalinkCreator = new RoomPermalinkCreator(room);
permalinkCreator.start();
this.setState({permalinkCreator});
}
},
_calculateRecommendedVersion: async function(room) {
@ -738,8 +746,19 @@ module.exports = React.createClass({
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) {
return;
}
if (!MatrixClientPeg.get().isCryptoEnabled()) {
// If crypto is not currently enabled, we aren't tracking devices at all,
// so we don't know what the answer is. Let's error on the safe side and show
// a warning for this case.
this.setState({
e2eStatus: "warning",
});
return;
}
room.hasUnverifiedDevices().then((hasUnverifiedDevices) => {
this.setState({e2eStatus: hasUnverifiedDevices ? "warning" : "verified"});
this.setState({
e2eStatus: hasUnverifiedDevices ? "warning" : "verified",
});
});
},
@ -865,10 +884,6 @@ module.exports = React.createClass({
}
},
onSearchResultsResize: function() {
dis.dispatch({ action: 'timeline_resize' }, true);
},
onSearchResultsFillRequest: function(backwards) {
if (!backwards) {
return Promise.resolve(false);
@ -1001,9 +1016,11 @@ module.exports = React.createClass({
onDrop: function(ev) {
ev.stopPropagation();
ev.preventDefault();
ContentMessages.sharedInstance().sendContentListToRoom(
ev.dataTransfer.files, this.state.room.roomId, MatrixClientPeg.get(),
);
this.setState({ draggingFile: false });
const files = [...ev.dataTransfer.files];
files.forEach(this.uploadFile);
dis.dispatch({action: 'focus_composer'});
},
onDragLeaveOrEnd: function(ev) {
@ -1012,55 +1029,13 @@ module.exports = React.createClass({
this.setState({ draggingFile: false });
},
isFileUploadAllowed(file) {
if (this.state.mediaConfig !== undefined &&
this.state.mediaConfig["m.upload.size"] !== undefined &&
file.size > this.state.mediaConfig["m.upload.size"]) {
return _t("File is too big. Maximum file size is %(fileSize)s", {fileSize: filesize(this.state.mediaConfig["m.upload.size"])});
}
return true;
},
uploadFile: async function(file) {
dis.dispatch({action: 'focus_composer'});
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({action: 'require_registration'});
return;
}
try {
await ContentMessages.sendContentToRoom(file, this.state.room.roomId, MatrixClientPeg.get());
} catch (error) {
if (error.name === "UnknownDeviceError") {
// Let the status bar handle this
return;
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to upload file " + file + " " + error);
Modal.createTrackedDialog('Failed to upload file', '', ErrorDialog, {
title: _t('Failed to upload file'),
description: ((error && error.message)
? error.message : _t("Server may be unavailable, overloaded, or the file too big")),
});
// bail early to avoid calling the dispatch below
return;
}
// Send message_sent callback, for things like _checkIfAlone because after all a file is still a message.
dis.dispatch({
action: 'message_sent',
});
},
injectSticker: function(url, info, text) {
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({action: 'require_registration'});
return;
}
ContentMessages.sendStickerContentToRoom(url, this.state.room.roomId, info, text, MatrixClientPeg.get())
ContentMessages.sharedInstance().sendStickerContentToRoom(url, this.state.room.roomId, info, text, MatrixClientPeg.get())
.done(undefined, (error) => {
if (error.name === "UnknownDeviceError") {
// Let the staus bar handle this
@ -1209,6 +1184,7 @@ module.exports = React.createClass({
const mxEv = result.context.getEvent();
const roomId = mxEv.getRoomId();
const room = cli.getRoom(roomId);
if (!EventTile.haveTileForEvent(mxEv)) {
// XXX: can this ever happen? It will make the result count
@ -1218,7 +1194,6 @@ module.exports = React.createClass({
if (this.state.searchScope === 'All') {
if (roomId != lastRoomId) {
const room = cli.getRoom(roomId);
// XXX: if we've left the room, we might not know about
// it. We should tell the js sdk to go and find out about
@ -1239,7 +1214,7 @@ module.exports = React.createClass({
searchResult={result}
searchHighlights={this.state.searchHighlights}
resultLink={resultLink}
permalinkCreator={this.state.permalinkCreator}
permalinkCreator={this._getPermalinkCreatorForRoom(room)}
onHeightChanged={onHeightChanged} />);
}
return ret;
@ -1364,8 +1339,7 @@ module.exports = React.createClass({
const showBar = this.refs.messagePanel.canJumpToReadMarker();
if (this.state.showTopUnreadMessagesBar != showBar) {
this.setState({showTopUnreadMessagesBar: showBar},
this.onChildResize);
this.setState({showTopUnreadMessagesBar: showBar});
}
},
@ -1408,7 +1382,7 @@ module.exports = React.createClass({
};
},
onResize: function(e) {
onResize: function() {
// It seems flexbox doesn't give us a way to constrain the auxPanel height to have
// a minimum of the height of the video element, whilst also capping it from pushing out the page
// so we have to do it via JS instead. In this implementation we cap the height by putting
@ -1426,9 +1400,6 @@ module.exports = React.createClass({
if (auxPanelMaxHeight < 50) auxPanelMaxHeight = 50;
this.setState({auxPanelMaxHeight: auxPanelMaxHeight});
// changing the maxHeight on the auxpanel will trigger a callback go
// onChildResize, so no need to worry about that here.
},
onFullscreenClick: function() {
@ -1458,10 +1429,6 @@ module.exports = React.createClass({
this.forceUpdate(); // TODO: just update the voip buttons
},
onChildResize: function() {
// no longer anything to do here
},
onStatusBarVisible: function() {
if (this.unmounted) return;
this.setState({
@ -1515,6 +1482,25 @@ module.exports = React.createClass({
}
},
_getOldRoom: function() {
const createEvent = this.state.room.currentState.getStateEvents("m.room.create", "");
if (!createEvent || !createEvent.getContent()['predecessor']) return null;
return MatrixClientPeg.get().getRoom(createEvent.getContent()['predecessor']['room_id']);
},
_getHiddenHighlightCount: function() {
const oldRoom = this._getOldRoom();
if (!oldRoom) return 0;
return oldRoom.getUnreadNotificationCount('highlight');
},
_onHiddenHighlightsClick: function() {
const oldRoom = this._getOldRoom();
if (!oldRoom) return;
dis.dispatch({action: "view_room", room_id: oldRoom.roomId});
},
render: function() {
const RoomHeader = sdk.getComponent('rooms.RoomHeader');
const MessageComposer = sdk.getComponent('rooms.MessageComposer');
@ -1525,16 +1511,21 @@ module.exports = React.createClass({
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar");
const Loader = sdk.getComponent("elements.Spinner");
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
const RoomUpgradeWarningBar = sdk.getComponent("rooms.RoomUpgradeWarningBar");
const RoomRecoveryReminder = sdk.getComponent("rooms.RoomRecoveryReminder");
if (!this.state.room) {
if (this.state.roomLoading || this.state.peekLoading) {
const loading = this.state.roomLoading || this.state.peekLoading;
if (loading) {
return (
<div className="mx_RoomView">
<Loader />
<RoomPreviewBar
canPreview={false}
error={this.state.roomLoadError}
loading={loading}
joining={this.state.joining}
/>
</div>
);
} else {
@ -1552,28 +1543,16 @@ module.exports = React.createClass({
const roomAlias = this.state.roomAlias;
return (
<div className="mx_RoomView">
<RoomHeader ref="header"
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
onForgetClick={this.onForgetClick}
onRejectClick={this.onRejectThreepidInviteButtonClicked}
canPreview={false} error={this.state.roomLoadError}
roomAlias={roomAlias}
joining={this.state.joining}
inviterName={inviterName}
invitedEmail={invitedEmail}
room={this.state.room}
oobData={this.props.oobData}
collapsedRhs={this.props.collapsedRhs}
e2eStatus={this.state.e2eStatus}
/>
<div className="mx_RoomView_body">
<div className="mx_RoomView_auxPanel">
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
onForgetClick={this.onForgetClick}
onRejectClick={this.onRejectThreepidInviteButtonClicked}
canPreview={false} error={this.state.roomLoadError}
roomAlias={roomAlias}
spinner={this.state.joining}
spinnerState="joining"
inviterName={inviterName}
invitedEmail={invitedEmail}
room={this.state.room}
/>
</div>
</div>
<div className="mx_RoomView_messagePanel"></div>
</div>
);
}
@ -1583,9 +1562,12 @@ module.exports = React.createClass({
if (myMembership == 'invite') {
if (this.state.joining || this.state.rejecting) {
return (
<div className="mx_RoomView">
<Loader />
</div>
<RoomPreviewBar
canPreview={false}
error={this.state.roomLoadError}
joining={this.state.joining}
rejecting={this.state.rejecting}
/>
);
} else {
const myUserId = MatrixClientPeg.get().credentials.userId;
@ -1600,26 +1582,14 @@ module.exports = React.createClass({
// We have a regular invite for this room.
return (
<div className="mx_RoomView">
<RoomHeader
ref="header"
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
onForgetClick={this.onForgetClick}
onRejectClick={this.onRejectButtonClicked}
inviterName={inviterName}
canPreview={false}
joining={this.state.joining}
room={this.state.room}
collapsedRhs={this.props.collapsedRhs}
e2eStatus={this.state.e2eStatus}
/>
<div className="mx_RoomView_body">
<div className="mx_RoomView_auxPanel">
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
onForgetClick={this.onForgetClick}
onRejectClick={this.onRejectButtonClicked}
inviterName={inviterName}
canPreview={false}
spinner={this.state.joining}
spinnerState="joining"
room={this.state.room}
/>
</div>
</div>
<div className="mx_RoomView_messagePanel"></div>
</div>
);
}
@ -1641,7 +1611,7 @@ module.exports = React.createClass({
let statusBar;
let isStatusAreaExpanded = true;
if (ContentMessages.getCurrentUploads().length > 0) {
if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) {
const UploadBar = sdk.getComponent('structures.UploadBar');
statusBar = <UploadBar room={this.state.room} />;
} else if (!this.state.searchResults) {
@ -1654,7 +1624,6 @@ module.exports = React.createClass({
isPeeking={myMembership !== "join"}
onInviteClick={this.onInviteButtonClick}
onStopWarningClick={this.onStopAloneWarningClick}
onResize={this.onChildResize}
onVisible={this.onStatusBarVisible}
onHidden={this.onStatusBarHidden}
/>;
@ -1673,8 +1642,12 @@ module.exports = React.createClass({
!MatrixClientPeg.get().getKeyBackupEnabled()
);
const hiddenHighlightCount = this._getHiddenHighlightCount();
let aux = null;
let previewBar;
let hideCancel = false;
let hideRightPanel = false;
if (this.state.forwardingEvent !== null) {
aux = <ForwardMessage onCancelClick={this.onCancelClick} />;
} else if (this.state.searching) {
@ -1701,18 +1674,36 @@ module.exports = React.createClass({
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
}
hideCancel = true;
aux = (
previewBar = (
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
onForgetClick={this.onForgetClick}
onRejectClick={this.onRejectThreepidInviteButtonClicked}
spinner={this.state.joining}
spinnerState="joining"
joining={this.state.joining}
inviterName={inviterName}
invitedEmail={invitedEmail}
canPreview={this.state.canPeek}
room={this.state.room}
/>
);
if (!this.state.canPeek) {
return (
<div className="mx_RoomView">
{ previewBar }
</div>
);
} else {
hideRightPanel = true;
}
} else if (hiddenHighlightCount > 0) {
aux = (
<AccessibleButton element="div" className="mx_RoomView_auxPanel_hiddenHighlights"
onClick={this._onHiddenHighlightsClick}>
{_t(
"You have %(count)s unread notifications in a prior version of this room.",
{count: hiddenHighlightCount},
)}
</AccessibleButton>
);
}
const auxPanel = (
@ -1723,7 +1714,6 @@ module.exports = React.createClass({
draggingFile={this.state.draggingFile}
displayConfCallNotification={this.state.displayConfCallNotification}
maxHeight={this.state.auxPanelMaxHeight}
onResize={this.onChildResize}
showApps={this.state.showApps}
hideAppsDrawer={false} >
{ aux }
@ -1739,22 +1729,14 @@ module.exports = React.createClass({
messageComposer =
<MessageComposer
room={this.state.room}
onResize={this.onChildResize}
uploadFile={this.uploadFile}
callState={this.state.callState}
disabled={this.props.disabled}
showApps={this.state.showApps}
uploadAllowed={this.isFileUploadAllowed}
e2eStatus={this.state.e2eStatus}
permalinkCreator={this.state.permalinkCreator}
permalinkCreator={this._getPermalinkCreatorForRoom(this.state.room)}
/>;
}
if (MatrixClientPeg.get().isGuest()) {
const AuthButtons = sdk.getComponent('views.auth.AuthButtons');
messageComposer = <AuthButtons />;
}
// TODO: Why aren't we storing the term/scope/count in this format
// in this.state if this is what RoomHeader desires?
if (this.state.searchResults) {
@ -1814,7 +1796,7 @@ module.exports = React.createClass({
<ScrollPanel ref="searchResultsPanel"
className="mx_RoomView_messagePanel mx_RoomView_searchResultsPanel"
onFillRequest={this.onSearchResultsFillRequest}
onResize={this.onSearchResultsResize}
resizeNotifier={this.props.resizeNotifier}
>
<li className={scrollheader_classes}></li>
{ this.getSearchResultTiles() }
@ -1848,7 +1830,8 @@ module.exports = React.createClass({
showUrlPreview = {this.state.showUrlPreview}
className="mx_RoomView_messagePanel"
membersLoaded={this.state.membersLoaded}
permalinkCreator={this.state.permalinkCreator}
permalinkCreator={this._getPermalinkCreatorForRoom(this.state.room)}
resizeNotifier={this.props.resizeNotifier}
/>);
let topUnreadMessagesBar = null;
@ -1881,14 +1864,16 @@ module.exports = React.createClass({
},
);
const rightPanel = this.state.room ? <RightPanel roomId={this.state.room.roomId} /> : undefined;
const rightPanel = !hideRightPanel && this.state.room &&
<RightPanel roomId={this.state.room.roomId} resizeNotifier={this.props.resizeNotifier} />;
const collapsedRhs = hideRightPanel || this.props.collapsedRhs;
return (
<main className={"mx_RoomView" + (inCall ? " mx_RoomView_inCall" : "")} ref="roomView">
<RoomHeader ref="header" room={this.state.room} searchInfo={searchInfo}
oobData={this.props.oobData}
inRoom={myMembership === 'join'}
collapsedRhs={this.props.collapsedRhs}
collapsedRhs={collapsedRhs}
onSearchClick={this.onSearchClick}
onSettingsClick={this.onSettingsClick}
onPinnedClick={this.onPinnedClick}
@ -1897,7 +1882,11 @@ module.exports = React.createClass({
onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
e2eStatus={this.state.e2eStatus}
/>
<MainSplit panel={rightPanel} collapsedRhs={this.props.collapsedRhs}>
<MainSplit
panel={rightPanel}
collapsedRhs={collapsedRhs}
resizeNotifier={this.props.resizeNotifier}
>
<div className={fadableSectionClasses}>
{ auxPanel }
<div className="mx_RoomView_timeline">
@ -1912,6 +1901,7 @@ module.exports = React.createClass({
{ statusBar }
</div>
</div>
{ previewBar }
{ messageComposer }
</div>
</MainSplit>

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