From 66b69dfc264a72a1ed88993639158024964f064f Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 8 Feb 2019 18:39:52 +0000 Subject: [PATCH 001/178] Released js-sdk --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9ea3989f20..5513fee403 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "linkifyjs": "^2.1.6", "lodash": "^4.13.1", "lolex": "2.3.2", - "matrix-js-sdk": "0.14.3", + "matrix-js-sdk": "1.0.0-rc.1", "optimist": "^0.6.1", "pako": "^1.0.5", "prop-types": "^15.5.8", From f18e2dc98b57a3989b7772ee959cd8aa80630a36 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 8 Feb 2019 18:44:00 +0000 Subject: [PATCH 002/178] Prepare changelog for v1.0.0-rc.1 --- CHANGELOG.md | 453 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 453 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7293b00d1b..cb4490d058 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,456 @@ +Changes in [1.0.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.0.0-rc.1) (2019-02-08) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.8...v1.0.0-rc.1) + + * Call isGuest correctly + [\#2603](https://github.com/matrix-org/matrix-react-sdk/pull/2603) + * Update from Weblate. + [\#2602](https://github.com/matrix-org/matrix-react-sdk/pull/2602) + * Prompt to restore backup rather than verify + [\#2594](https://github.com/matrix-org/matrix-react-sdk/pull/2594) + * Remove key backup & sas from labs + [\#2599](https://github.com/matrix-org/matrix-react-sdk/pull/2599) + * Update avatar colors + [\#2600](https://github.com/matrix-org/matrix-react-sdk/pull/2600) + * Fix: typeerror when creating DM + [\#2601](https://github.com/matrix-org/matrix-react-sdk/pull/2601) + * Render disabled mxField textareas as disabled + [\#2591](https://github.com/matrix-org/matrix-react-sdk/pull/2591) + * SDK support for welcome page + [\#2597](https://github.com/matrix-org/matrix-react-sdk/pull/2597) + * Change SAS to decimal / emoji + [\#2596](https://github.com/matrix-org/matrix-react-sdk/pull/2596) + * Render join rules and guest access changes in the timeline + [\#2592](https://github.com/matrix-org/matrix-react-sdk/pull/2592) + * Ensure toggle switches listen to property changes + [\#2590](https://github.com/matrix-org/matrix-react-sdk/pull/2590) + * Local echo on room access settings + [\#2593](https://github.com/matrix-org/matrix-react-sdk/pull/2593) + * guard custom tags with feature flag + [\#2589](https://github.com/matrix-org/matrix-react-sdk/pull/2589) + * remove ll feature flag, it's time! + [\#2588](https://github.com/matrix-org/matrix-react-sdk/pull/2588) + * Trust on decrypt + [\#2583](https://github.com/matrix-org/matrix-react-sdk/pull/2583) + * Remove click-to-verify from SAS + [\#2586](https://github.com/matrix-org/matrix-react-sdk/pull/2586) + * Fix: make sure custom tag scroller doesn't overflow parent + [\#2587](https://github.com/matrix-org/matrix-react-sdk/pull/2587) + * Fix: throttle custom tags updating in LLP + [\#2585](https://github.com/matrix-org/matrix-react-sdk/pull/2585) + * Fix firefox scrolling settings tabs differently + [\#2579](https://github.com/matrix-org/matrix-react-sdk/pull/2579) + * Actually change power levels when they are changed + [\#2580](https://github.com/matrix-org/matrix-react-sdk/pull/2580) + * Fix: logging in again breaks CustomRoomTagStore + [\#2584](https://github.com/matrix-org/matrix-react-sdk/pull/2584) + * Fix: click on notif badge + [\#2582](https://github.com/matrix-org/matrix-react-sdk/pull/2582) + * Extend slash command '/topic' to display the room topic + [\#2532](https://github.com/matrix-org/matrix-react-sdk/pull/2532) + * Fix: community badges + [\#2581](https://github.com/matrix-org/matrix-react-sdk/pull/2581) + * Bring back custom tags, also badges on communities + [\#2575](https://github.com/matrix-org/matrix-react-sdk/pull/2575) + * Style reset password to match design + [\#2578](https://github.com/matrix-org/matrix-react-sdk/pull/2578) + * Key Backup: Don't fail if no keys + [\#2577](https://github.com/matrix-org/matrix-react-sdk/pull/2577) + * Remove old user and room settings + [\#2554](https://github.com/matrix-org/matrix-react-sdk/pull/2554) + * increase debouncing of filtering because its quite laggy atm + [\#2576](https://github.com/matrix-org/matrix-react-sdk/pull/2576) + * Tweak field padding to avoid overlapping with selected text + [\#2573](https://github.com/matrix-org/matrix-react-sdk/pull/2573) + * Adapt login flow for the v2 design + [\#2574](https://github.com/matrix-org/matrix-react-sdk/pull/2574) + * Remove the arrow-paren lint rule + [\#2572](https://github.com/matrix-org/matrix-react-sdk/pull/2572) + * Ensure we show registration form when custom URLs are disabled + [\#2571](https://github.com/matrix-org/matrix-react-sdk/pull/2571) + * Fix: search term disappears when collapsing and expanding left panel + [\#2568](https://github.com/matrix-org/matrix-react-sdk/pull/2568) + * Fix: 'jump to bottom' creates big amounts of whitespace at the bottom + [\#2567](https://github.com/matrix-org/matrix-react-sdk/pull/2567) + * Fix: being able to size sections in leftpanel larger than their content + while filtering + [\#2566](https://github.com/matrix-org/matrix-react-sdk/pull/2566) + * Redesign: widget makeover + [\#2565](https://github.com/matrix-org/matrix-react-sdk/pull/2565) + * Restore dropdown chevron to right + [\#2564](https://github.com/matrix-org/matrix-react-sdk/pull/2564) + * Remove warning about encryption being beta + [\#2563](https://github.com/matrix-org/matrix-react-sdk/pull/2563) + * Add e2e icon to room header/composer/member info, more ... + [\#2557](https://github.com/matrix-org/matrix-react-sdk/pull/2557) + * Remove guest warning bar + [\#2562](https://github.com/matrix-org/matrix-react-sdk/pull/2562) + * Style tweaks to support auth background + [\#2561](https://github.com/matrix-org/matrix-react-sdk/pull/2561) + * Set a minimum width on the settings tab content + [\#2560](https://github.com/matrix-org/matrix-react-sdk/pull/2560) + * Fix exception while saving room settings + [\#2555](https://github.com/matrix-org/matrix-react-sdk/pull/2555) + * Disable old settings, making tabbed settings the default + [\#2559](https://github.com/matrix-org/matrix-react-sdk/pull/2559) + * fix UnknownDeviceDialog layout + [\#2558](https://github.com/matrix-org/matrix-react-sdk/pull/2558) + * Misc fixes to settings + [\#2553](https://github.com/matrix-org/matrix-react-sdk/pull/2553) + * Add error message when registration is disabled + [\#2548](https://github.com/matrix-org/matrix-react-sdk/pull/2548) + * Hide registration fields that aren't used by any flow + [\#2551](https://github.com/matrix-org/matrix-react-sdk/pull/2551) + * Ensure correct server URLs with .well-known and server type + [\#2547](https://github.com/matrix-org/matrix-react-sdk/pull/2547) + * Spell homeserver correctly + [\#2552](https://github.com/matrix-org/matrix-react-sdk/pull/2552) + * Auto-focus username on registration + [\#2546](https://github.com/matrix-org/matrix-react-sdk/pull/2546) + * Fixed settings dialog header; Adjust padding on dialog + [\#2549](https://github.com/matrix-org/matrix-react-sdk/pull/2549) + * Fix empty lightbox when there is no avatarUrl + [\#2314](https://github.com/matrix-org/matrix-react-sdk/pull/2314) + * make overflow gradients much smaller and turn bottom into drop shadow + [\#2544](https://github.com/matrix-org/matrix-react-sdk/pull/2544) + * Make auth validation less annoying + [\#2539](https://github.com/matrix-org/matrix-react-sdk/pull/2539) + * layout composer independent of avatar being present + [\#2545](https://github.com/matrix-org/matrix-react-sdk/pull/2545) + * Matthew/cyrillic + [\#2543](https://github.com/matrix-org/matrix-react-sdk/pull/2543) + * Allow expanding the left panel manually when in narrow mode + [\#2541](https://github.com/matrix-org/matrix-react-sdk/pull/2541) + * Redesign: community page cleanup + [\#2538](https://github.com/matrix-org/matrix-react-sdk/pull/2538) + * Redesign: Disable ILAG + [\#2536](https://github.com/matrix-org/matrix-react-sdk/pull/2536) + * Use custom appearance and arrow for field selects + [\#2540](https://github.com/matrix-org/matrix-react-sdk/pull/2540) + * Fix typo + [\#2537](https://github.com/matrix-org/matrix-react-sdk/pull/2537) + * Merge redesign into develop + [\#2535](https://github.com/matrix-org/matrix-react-sdk/pull/2535) + * disable e2e tests everywhere as redesign breaks them for now + [\#2534](https://github.com/matrix-org/matrix-react-sdk/pull/2534) + * avoid horizontal scrollbar in composer when placeholder doesn't fit + [\#2533](https://github.com/matrix-org/matrix-react-sdk/pull/2533) + * fix dropdown style when input is shown + [\#2531](https://github.com/matrix-org/matrix-react-sdk/pull/2531) + * Redesign: tiny fix: stretch device label in member info if content doesn't + fill it + [\#2530](https://github.com/matrix-org/matrix-react-sdk/pull/2530) + * Style registration flow + [\#2527](https://github.com/matrix-org/matrix-react-sdk/pull/2527) + * Redesign: small member info panel makeover + [\#2522](https://github.com/matrix-org/matrix-react-sdk/pull/2522) + * Render the home page when viewing the directory + [\#2529](https://github.com/matrix-org/matrix-react-sdk/pull/2529) + * Fix indentation on all new settings CSS + [\#2528](https://github.com/matrix-org/matrix-react-sdk/pull/2528) + * Round 1 of misc fixes for settings + [\#2526](https://github.com/matrix-org/matrix-react-sdk/pull/2526) + * Implement the Security & Privacy tab of new room settings + [\#2523](https://github.com/matrix-org/matrix-react-sdk/pull/2523) + * Implement the Advanced tab of new room settings + [\#2525](https://github.com/matrix-org/matrix-react-sdk/pull/2525) + * Implement the Roles & Permissions tab of new room settings + [\#2524](https://github.com/matrix-org/matrix-react-sdk/pull/2524) + * Redesign: room directory makeover + [\#2519](https://github.com/matrix-org/matrix-react-sdk/pull/2519) + * Iterate upon the room upgrade warning bar + [\#2518](https://github.com/matrix-org/matrix-react-sdk/pull/2518) + * redesign: small fixes + [\#2520](https://github.com/matrix-org/matrix-react-sdk/pull/2520) + * Implement the "general" tab of new room settings + [\#2516](https://github.com/matrix-org/matrix-react-sdk/pull/2516) + * Tweak auth overflow on Windows and Linux + [\#2521](https://github.com/matrix-org/matrix-react-sdk/pull/2521) + * Redesign: switch layout when filtering room sublists + [\#2515](https://github.com/matrix-org/matrix-react-sdk/pull/2515) + * Make native scrollbars prettier + [\#2470](https://github.com/matrix-org/matrix-react-sdk/pull/2470) + * Add server type selector and style login flow + [\#2517](https://github.com/matrix-org/matrix-react-sdk/pull/2517) + * Implement flair tab in user settings + [\#2512](https://github.com/matrix-org/matrix-react-sdk/pull/2512) + * Override UA/OS styles for disabled Field selects + [\#2502](https://github.com/matrix-org/matrix-react-sdk/pull/2502) + * Be more positive with setting labels + [\#2504](https://github.com/matrix-org/matrix-react-sdk/pull/2504) + * Redesign: new roomlist layout fixes + [\#2514](https://github.com/matrix-org/matrix-react-sdk/pull/2514) + * Redesign: new layout algorithm for room sublists. + [\#2507](https://github.com/matrix-org/matrix-react-sdk/pull/2507) + * Short-Authentication-String Verification + [\#2461](https://github.com/matrix-org/matrix-react-sdk/pull/2461) + * Fix unmount TypeError in `DeviceVerifyButtons` + [\#2513](https://github.com/matrix-org/matrix-react-sdk/pull/2513) + * Remove support for team servers + [\#2511](https://github.com/matrix-org/matrix-react-sdk/pull/2511) + * Initial structure for new room settings + [\#2510](https://github.com/matrix-org/matrix-react-sdk/pull/2510) + * Tweak wording on logout warning + [\#2509](https://github.com/matrix-org/matrix-react-sdk/pull/2509) + * Fix NPE in RoomRecoveryReminder + [\#2508](https://github.com/matrix-org/matrix-react-sdk/pull/2508) + * New text/caption for key backup by verifying device + [\#2506](https://github.com/matrix-org/matrix-react-sdk/pull/2506) + * Implement the "Security & Privacy" tab of new user settings + [\#2499](https://github.com/matrix-org/matrix-react-sdk/pull/2499) + * Add simple animations to toggle switches + [\#2505](https://github.com/matrix-org/matrix-react-sdk/pull/2505) + * Default a Field's placeholder to the label + [\#2503](https://github.com/matrix-org/matrix-react-sdk/pull/2503) + * Have the settings dialog be fixed in size + [\#2501](https://github.com/matrix-org/matrix-react-sdk/pull/2501) + * Implement the "Help & About" tab of new user settings + [\#2500](https://github.com/matrix-org/matrix-react-sdk/pull/2500) + * Implement the "Voice & Video" tab of new user settings + [\#2498](https://github.com/matrix-org/matrix-react-sdk/pull/2498) + * Add widget screenshots to the Labs section + [\#2497](https://github.com/matrix-org/matrix-react-sdk/pull/2497) + * Implement the "Preferences" tab on new user settings + [\#2495](https://github.com/matrix-org/matrix-react-sdk/pull/2495) + * Add target="_blank" to links that don't have it + [\#2496](https://github.com/matrix-org/matrix-react-sdk/pull/2496) + * Implement the "Notifications" tab of new user settings + [\#2494](https://github.com/matrix-org/matrix-react-sdk/pull/2494) + * Implement the "Labs" tab of new user settings + [\#2492](https://github.com/matrix-org/matrix-react-sdk/pull/2492) + * Implement the "General" tab of new user settings + [\#2491](https://github.com/matrix-org/matrix-react-sdk/pull/2491) + * Appease linter in auth related files + [\#2493](https://github.com/matrix-org/matrix-react-sdk/pull/2493) + * Update text and links in authentication flows + [\#2489](https://github.com/matrix-org/matrix-react-sdk/pull/2489) + * Move LanguageSelector to views + [\#2490](https://github.com/matrix-org/matrix-react-sdk/pull/2490) + * Restyle auth page language selector + [\#2488](https://github.com/matrix-org/matrix-react-sdk/pull/2488) + * Fix desktop captcha check + [\#2487](https://github.com/matrix-org/matrix-react-sdk/pull/2487) + * Basic structure for tabbed user settings + [\#2476](https://github.com/matrix-org/matrix-react-sdk/pull/2476) + * Token encouragement if zxcvbn gives no feedback + [\#2471](https://github.com/matrix-org/matrix-react-sdk/pull/2471) + * Fix: show rooms and people section when empty while filtering + [\#2481](https://github.com/matrix-org/matrix-react-sdk/pull/2481) + * Fix AuthFooter CSS rules conflicting with anchors all over the app + [\#2486](https://github.com/matrix-org/matrix-react-sdk/pull/2486) + * Support selects on Field + [\#2484](https://github.com/matrix-org/matrix-react-sdk/pull/2484) + * Fix integrations server error popup being hidden behind right panel + [\#2482](https://github.com/matrix-org/matrix-react-sdk/pull/2482) + * Fix: apparently room can be null here + [\#2480](https://github.com/matrix-org/matrix-react-sdk/pull/2480) + * Redesign: pull jump to bottom button out of room status bar + [\#2478](https://github.com/matrix-org/matrix-react-sdk/pull/2478) + * Redesign: set default size of 350px for left panel + [\#2479](https://github.com/matrix-org/matrix-react-sdk/pull/2479) + * Avoid "jumpiness" with inline typing indicator + [\#2456](https://github.com/matrix-org/matrix-react-sdk/pull/2456) + * De-lint CompatabilityPage & LoggedInView + [\#2472](https://github.com/matrix-org/matrix-react-sdk/pull/2472) + * Remove Status theme-specific hacks + [\#2473](https://github.com/matrix-org/matrix-react-sdk/pull/2473) + * Error if no sessions decrypted + [\#2469](https://github.com/matrix-org/matrix-react-sdk/pull/2469) + * Fix settings direct chat + [\#2466](https://github.com/matrix-org/matrix-react-sdk/pull/2466) + * Show verify button when we have a device to verify + [\#2464](https://github.com/matrix-org/matrix-react-sdk/pull/2464) + * Redesign: Add a form field component + [\#2463](https://github.com/matrix-org/matrix-react-sdk/pull/2463) + * Load fonts and images via source-relative URLs and requires + [\#2460](https://github.com/matrix-org/matrix-react-sdk/pull/2460) + * Say when backup is signed by unknown device + [\#2455](https://github.com/matrix-org/matrix-react-sdk/pull/2455) + * Add an /upgraderoom command to make upgrading easier for development + [\#2458](https://github.com/matrix-org/matrix-react-sdk/pull/2458) + * Merge develop->experimental + [\#2457](https://github.com/matrix-org/matrix-react-sdk/pull/2457) + * Fix: show hand cursor in topleft menu so its clear you can click it + [\#2454](https://github.com/matrix-org/matrix-react-sdk/pull/2454) + * Fix: search makeover missing icons + [\#2453](https://github.com/matrix-org/matrix-react-sdk/pull/2453) + * Redesign: search makeover + [\#2448](https://github.com/matrix-org/matrix-react-sdk/pull/2448) + * Revert "Tiled room UI" + [\#2451](https://github.com/matrix-org/matrix-react-sdk/pull/2451) + * Update from Weblate. + [\#2452](https://github.com/matrix-org/matrix-react-sdk/pull/2452) + * Improve room sublist resizing + [\#2440](https://github.com/matrix-org/matrix-react-sdk/pull/2440) + * Different dialog for new trusted backup + [\#2435](https://github.com/matrix-org/matrix-react-sdk/pull/2435) + * De-lint a few more files + [\#2436](https://github.com/matrix-org/matrix-react-sdk/pull/2436) + * Recalculate the visible rooms when rooms are upgraded + [\#2433](https://github.com/matrix-org/matrix-react-sdk/pull/2433) + * Navigate to the upgraded room's create event where possible + [\#2432](https://github.com/matrix-org/matrix-react-sdk/pull/2432) + * Don't show rooms with tombstones in the address picker + [\#2429](https://github.com/matrix-org/matrix-react-sdk/pull/2429) + * Add separate dialog for recovery method removed + [\#2427](https://github.com/matrix-org/matrix-react-sdk/pull/2427) + * Set which servers to try and join upgraded rooms through + [\#2428](https://github.com/matrix-org/matrix-react-sdk/pull/2428) + * Render a tile for tombstone events + [\#2430](https://github.com/matrix-org/matrix-react-sdk/pull/2430) + * Regenerate en_EN.json to sort entries + [\#2431](https://github.com/matrix-org/matrix-react-sdk/pull/2431) + * Key backup: Debounce passphrase feedback + [\#2426](https://github.com/matrix-org/matrix-react-sdk/pull/2426) + * Set backup niggles: 2 + [\#2425](https://github.com/matrix-org/matrix-react-sdk/pull/2425) + * Fix lint errors in MessageComposerInput + [\#2423](https://github.com/matrix-org/matrix-react-sdk/pull/2423) + * Set backup niggles: 1 + [\#2424](https://github.com/matrix-org/matrix-react-sdk/pull/2424) + * PoC: Add simple state counters to room heading + [\#2388](https://github.com/matrix-org/matrix-react-sdk/pull/2388) + * Fix a few things with cancelling recovery reminder + [\#2420](https://github.com/matrix-org/matrix-react-sdk/pull/2420) + * Add spaces back to async arrow functions + [\#2422](https://github.com/matrix-org/matrix-react-sdk/pull/2422) + * fix grid growing wider than viewport on chrome + [\#2421](https://github.com/matrix-org/matrix-react-sdk/pull/2421) + * Tiled room UI + [\#2348](https://github.com/matrix-org/matrix-react-sdk/pull/2348) + * Fix path to New Recovery Method icon + [\#2417](https://github.com/matrix-org/matrix-react-sdk/pull/2417) + * run unit tests on riot-web like before + [\#2419](https://github.com/matrix-org/matrix-react-sdk/pull/2419) + * Refactor travis-ci to use parallel jobs + [\#2414](https://github.com/matrix-org/matrix-react-sdk/pull/2414) + * Fix black-on-black GIF icon for stickers + [\#2408](https://github.com/matrix-org/matrix-react-sdk/pull/2408) + * Don't reset cached room list values when they are falsey + [\#2413](https://github.com/matrix-org/matrix-react-sdk/pull/2413) + * Make logout warning nag about key backups + [\#2407](https://github.com/matrix-org/matrix-react-sdk/pull/2407) + * Clarify readme instructions for developers + [\#2404](https://github.com/matrix-org/matrix-react-sdk/pull/2404) + * Add slash command for changing room name + [\#2401](https://github.com/matrix-org/matrix-react-sdk/pull/2401) + * Flatten and simplify the memberlist sorting algorithm + [\#2381](https://github.com/matrix-org/matrix-react-sdk/pull/2381) + * Tiny fixes for custom status messages on experimental + [\#2403](https://github.com/matrix-org/matrix-react-sdk/pull/2403) + * Part 3 of 3: Apply today's changes to experimental again + [\#2400](https://github.com/matrix-org/matrix-react-sdk/pull/2400) + * Part 2 of 3: Merge develop->experimental minus #2336 + [\#2399](https://github.com/matrix-org/matrix-react-sdk/pull/2399) + * Part 1 of 3: Back out bad merge for develop->experimental + [\#2398](https://github.com/matrix-org/matrix-react-sdk/pull/2398) + * Fix browser navigation not working between /home, /login, /register, etc + [\#2383](https://github.com/matrix-org/matrix-react-sdk/pull/2383) + * Don't re-sort the room list if the user is hovering over it + [\#2396](https://github.com/matrix-org/matrix-react-sdk/pull/2396) + * Merge develop into experimental + [\#2395](https://github.com/matrix-org/matrix-react-sdk/pull/2395) + * Added colour var to all themes + [\#2379](https://github.com/matrix-org/matrix-react-sdk/pull/2379) + * Colour, contrast & legibility improvements + [\#2378](https://github.com/matrix-org/matrix-react-sdk/pull/2378) + * Redesign: add feedback dialog & button in tag panel + [\#2376](https://github.com/matrix-org/matrix-react-sdk/pull/2376) + * Redesign: add badge with dot to rm button, to see it catches your eye better + [\#2371](https://github.com/matrix-org/matrix-react-sdk/pull/2371) + * Fix misaligned (+) icon + [\#2374](https://github.com/matrix-org/matrix-react-sdk/pull/2374) + * Avoid 'transparent black' gradients in left panel + [\#2373](https://github.com/matrix-org/matrix-react-sdk/pull/2373) + * Normalised icons + [\#2370](https://github.com/matrix-org/matrix-react-sdk/pull/2370) + * Redesign: give right panel default width + [\#2369](https://github.com/matrix-org/matrix-react-sdk/pull/2369) + * Redesign: Fix login field looking inline + [\#2368](https://github.com/matrix-org/matrix-react-sdk/pull/2368) + * Redesign: select search query on focus + [\#2367](https://github.com/matrix-org/matrix-react-sdk/pull/2367) + * Redesign: fix remaining right panel collapse issues. + [\#2366](https://github.com/matrix-org/matrix-react-sdk/pull/2366) + * Redesign: left panel fixes + [\#2364](https://github.com/matrix-org/matrix-react-sdk/pull/2364) + * Redesign: allow to hide the right panel when clicking already active button + & persist + [\#2361](https://github.com/matrix-org/matrix-react-sdk/pull/2361) + * Redesign: make room tiles less high so more rooms fit on the screen + [\#2359](https://github.com/matrix-org/matrix-react-sdk/pull/2359) + * Redesign: ignore any unknown tags + [\#2358](https://github.com/matrix-org/matrix-react-sdk/pull/2358) + * Redesign: disable setting theme completely + [\#2357](https://github.com/matrix-org/matrix-react-sdk/pull/2357) + * Force use of dharma theme + [\#2355](https://github.com/matrix-org/matrix-react-sdk/pull/2355) + * Redesign: some small fixes + [\#2354](https://github.com/matrix-org/matrix-react-sdk/pull/2354) + * Redesign: restyle jump to first unread message & rework read marker logic + (rebased) + [\#2345](https://github.com/matrix-org/matrix-react-sdk/pull/2345) + * Redesign: fix add room button alignment when collapsed + [\#2343](https://github.com/matrix-org/matrix-react-sdk/pull/2343) + * Redesign: confirm sign out from top left menu + [\#2342](https://github.com/matrix-org/matrix-react-sdk/pull/2342) + * Redesign: fix room header avatar in edit mode + [\#2344](https://github.com/matrix-org/matrix-react-sdk/pull/2344) + * Redesign: make community UX usable + [\#2341](https://github.com/matrix-org/matrix-react-sdk/pull/2341) + * Redesign: resizer persistence + [\#2321](https://github.com/matrix-org/matrix-react-sdk/pull/2321) + * Redesign: improve room sub list sizing & persist sizes + [\#2297](https://github.com/matrix-org/matrix-react-sdk/pull/2297) + * Redesign: temp solution to make room settings usable + [\#2298](https://github.com/matrix-org/matrix-react-sdk/pull/2298) + * Redesign: typing notifications in timeline + [\#2276](https://github.com/matrix-org/matrix-react-sdk/pull/2276) + * Redesign: add scroll indicator gradients to top and bottom of room sub list + [\#2275](https://github.com/matrix-org/matrix-react-sdk/pull/2275) + * Redesign: move member query field to bottom of member list + [\#2270](https://github.com/matrix-org/matrix-react-sdk/pull/2270) + * Redesign: room list visual polish + [\#2269](https://github.com/matrix-org/matrix-react-sdk/pull/2269) + * Redesign: bring back & restyle room filter field + [\#2267](https://github.com/matrix-org/matrix-react-sdk/pull/2267) + * Redesign: increase interaction rectangle of resize handles + [\#2262](https://github.com/matrix-org/matrix-react-sdk/pull/2262) + * Redesign: move right panel below room/group header + [\#2260](https://github.com/matrix-org/matrix-react-sdk/pull/2260) + * Redesign: use native auto-hiding scrollbars in room sub lists + [\#2264](https://github.com/matrix-org/matrix-react-sdk/pull/2264) + * Redesign: basic makeover of member info panel + [\#2248](https://github.com/matrix-org/matrix-react-sdk/pull/2248) + * Redesign: memberlist basic makeover + [\#2245](https://github.com/matrix-org/matrix-react-sdk/pull/2245) + * Redesign: tweak room list font sizes + [\#2246](https://github.com/matrix-org/matrix-react-sdk/pull/2246) + * Redesign: Fix room lists sizing + [\#2234](https://github.com/matrix-org/matrix-react-sdk/pull/2234) + * Redesign: fix import path + [\#2243](https://github.com/matrix-org/matrix-react-sdk/pull/2243) + * Redesign: update (most) icons + [\#2241](https://github.com/matrix-org/matrix-react-sdk/pull/2241) + * Redesign: fix basic room header layout + [\#2240](https://github.com/matrix-org/matrix-react-sdk/pull/2240) + * Redesign: 1st go at top left menu & restyling context menus + [\#2239](https://github.com/matrix-org/matrix-react-sdk/pull/2239) + * Redesign: Initial timeline tweaks + [\#2238](https://github.com/matrix-org/matrix-react-sdk/pull/2238) + * Redesign: Align visuals of room list with design + [\#2233](https://github.com/matrix-org/matrix-react-sdk/pull/2233) + * Redesign: room section header tidbits + [\#2229](https://github.com/matrix-org/matrix-react-sdk/pull/2229) + * Redesign: Add (+) button in room section header to add rooms + [\#2228](https://github.com/matrix-org/matrix-react-sdk/pull/2228) + * Redesign: 1st go at resizing room sublists + [\#2226](https://github.com/matrix-org/matrix-react-sdk/pull/2226) + * Redesign: remove room list truncation and DND + [\#2224](https://github.com/matrix-org/matrix-react-sdk/pull/2224) + * Redesign: resizeable/collapsible sections + [\#2210](https://github.com/matrix-org/matrix-react-sdk/pull/2210) + Changes in [0.14.8](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.8) (2019-01-22) ===================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.8-rc.1...v0.14.8) From e98bcf95d071f7edc83188c181a4d89a9bac1899 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 8 Feb 2019 18:44:00 +0000 Subject: [PATCH 003/178] v1.0.0-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5513fee403..034db77b3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.14.8", + "version": "1.0.0-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 9aa3640e9308737d68290aebc0a04994e2cbfc05 Mon Sep 17 00:00:00 2001 From: Nad Chishtie Date: Wed, 13 Feb 2019 19:44:45 +0000 Subject: [PATCH 004/178] Tweaked more icons. --- res/img/feather-icons/e2e/lock-verified.svg | 2 +- res/img/feather-icons/e2e/lock-warning.svg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/res/img/feather-icons/e2e/lock-verified.svg b/res/img/feather-icons/e2e/lock-verified.svg index 4cd4684ea2..819dfacc49 100644 --- a/res/img/feather-icons/e2e/lock-verified.svg +++ b/res/img/feather-icons/e2e/lock-verified.svg @@ -1,6 +1,6 @@ - + diff --git a/res/img/feather-icons/e2e/lock-warning.svg b/res/img/feather-icons/e2e/lock-warning.svg index 507c532f9d..de2bded7f8 100644 --- a/res/img/feather-icons/e2e/lock-warning.svg +++ b/res/img/feather-icons/e2e/lock-warning.svg @@ -3,7 +3,7 @@ - + From b6c1b50fd9aeca6e6666886c76d52429716ebd71 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 13 Feb 2019 13:00:35 -0700 Subject: [PATCH 005/178] Early support for improved room algorithm This changes the approach from regenerating every time there's a change to incrementally fixing the room lists. Additionally, this forces the pin options on for people and implements the sticky room behaviour. Known bugs include newly joined rooms, invites, etc not sorting correctly. --- src/stores/RoomListStore.js | 373 ++++++++++++++++++------------------ 1 file changed, 184 insertions(+), 189 deletions(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index d98adc5cae..1eec41a20a 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -19,20 +19,26 @@ import DMRoomMap from '../utils/DMRoomMap'; import Unread from '../Unread'; import SettingsStore from "../settings/SettingsStore"; +const CATEGORY_RED = "red"; +const CATEGORY_GREY = "grey"; +const CATEGORY_BOLD = "bold"; +const CATEGORY_IDLE = "idle"; + +const CATEGORY_ORDER = [CATEGORY_RED, CATEGORY_GREY, CATEGORY_BOLD, CATEGORY_IDLE]; +const LIST_ORDERS = { + "m.favourite": "manual", + "im.vector.fake.invite": "recent", + "im.vector.fake.recent": "recent", + "im.vector.fake.direct": "recent", + "m.lowpriority": "recent", + "im.vector.fake.archived": "recent", +}; + /** * A class for storing application state for categorising rooms in * the RoomList. */ class RoomListStore extends Store { - static _listOrders = { - "m.favourite": "manual", - "im.vector.fake.invite": "recent", - "im.vector.fake.recent": "recent", - "im.vector.fake.direct": "recent", - "m.lowpriority": "recent", - "im.vector.fake.archived": "recent", - }; - constructor() { super(dis); @@ -43,44 +49,39 @@ class RoomListStore extends Store { _init() { // Initialise state + const defaultLists = { + "m.server_notice": [/* { room: js-sdk room, category: string } */], + "im.vector.fake.invite": [], + "m.favourite": [], + "im.vector.fake.recent": [], + "im.vector.fake.direct": [], + "m.lowpriority": [], + "im.vector.fake.archived": [], + }; this._state = { - lists: { - "m.server_notice": [], - "im.vector.fake.invite": [], - "m.favourite": [], - "im.vector.fake.recent": [], - "im.vector.fake.direct": [], - "m.lowpriority": [], - "im.vector.fake.archived": [], - }, + // The rooms in these arrays are ordered according to either the + // 'recents' behaviour or 'manual' behaviour. + lists: defaultLists, + presentationLists: defaultLists, // like `lists`, but with arrays of rooms instead ready: false, - - // The room cache stores a mapping of roomId to cache record. - // Each cache record is a key/value pair for various bits of - // data used to sort the room list. Currently this stores the - // following bits of informations: - // "timestamp": number, The timestamp of the last relevant - // event in the room. - // "notifications": boolean, Whether or not the user has been - // highlighted on any unread events. - // "unread": boolean, Whether or not the user has any - // unread events. - // - // All of the cached values are lazily loaded on read in the - // recents comparator. When an event is received for a particular - // room, all the cached values are invalidated - forcing the - // next read to set new values. The entries do not expire on - // their own. - roomCache: {}, + stickyRoomId: null, }; } _setState(newState) { + if (newState['lists']) { + const presentationLists = {}; + for (const key of Object.keys(newState['lists'])) { + presentationLists[key] = newState['lists'][key].map((e) => e.room); + } + newState['presentationLists'] = presentationLists; + } this._state = Object.assign(this._state, newState); this.__emitChange(); } - __onDispatch(payload) { + __onDispatch = (payload) => { + const logicallyReady = this._matrixClient && this._state.ready; switch (payload.action) { // Initialise state after initial sync case 'MatrixActions.sync': { @@ -89,30 +90,30 @@ class RoomListStore extends Store { } this._matrixClient = payload.matrixClient; - this._generateRoomLists(); + this._generateInitialRoomLists(); } break; case 'MatrixActions.Room.tags': { - if (!this._state.ready) break; - this._generateRoomLists(); + if (!logicallyReady) break; + console.log("!! Tags: ", payload); } break; case 'MatrixActions.Room.timeline': { - if (!this._state.ready || + if (!logicallyReady || !payload.isLiveEvent || !payload.isLiveUnfilteredRoomTimelineEvent || !this._eventTriggersRecentReorder(payload.event) - ) break; + ) { + break; + } - this._clearCachedRoomState(payload.event.getRoomId()); - this._generateRoomLists(); + this._roomUpdateTriggered(payload.event.getRoomId()); } break; // When an event is decrypted, it could mean we need to reorder the room // list because we now know the type of the event. case 'MatrixActions.Event.decrypted': { - // We may not have synced or done an initial generation of the lists - if (!this._matrixClient || !this._state.ready) break; + if (!logicallyReady) break; const roomId = payload.event.getRoomId(); @@ -129,50 +130,57 @@ class RoomListStore extends Store { // Either this event was not added to the live timeline (e.g. pagination) // or it doesn't affect the ordering of the room list. - if (liveTimeline !== eventTimeline || - !this._eventTriggersRecentReorder(payload.event) - ) break; + if (liveTimeline !== eventTimeline || !this._eventTriggersRecentReorder(payload.event)) { + break; + } - this._clearCachedRoomState(payload.event.getRoomId()); - this._generateRoomLists(); + this._roomUpdateTriggered(roomId); } break; case 'MatrixActions.accountData': { + if (!logicallyReady) break; if (payload.event_type !== 'm.direct') break; - this._generateRoomLists(); - } - break; - case 'MatrixActions.Room.accountData': { - if (payload.event_type === 'm.fully_read') { - this._clearCachedRoomState(payload.room.roomId); - this._generateRoomLists(); - } + // TODO: Handle direct chat changes + console.log("!! Direct Chats: ", payload); } break; + // TODO: Remove if not actually needed + // case 'MatrixActions.Room.accountData': { + // if (!logicallyReady) break; + // if (payload.event_type === 'm.fully_read') { + // console.log("!! Fully read: ", payload); + // } + // } + // break; case 'MatrixActions.Room.myMembership': { - this._generateRoomLists(); + if (!logicallyReady) break; + // TODO: Slot room into list + this._roomUpdateTriggered(payload.room.roomId); } break; // This could be a new room that we've been invited to, joined or created // we won't get a RoomMember.membership for these cases if we're not already // a member. case 'MatrixActions.Room': { - if (!this._state.ready || !this._matrixClient.credentials.userId) break; - this._generateRoomLists(); + if (!logicallyReady) break; + // TODO: Slot room into list + this._roomUpdateTriggered(payload.room.roomId); } break; case 'RoomListActions.tagRoom.pending': { + if (!logicallyReady) break; // XXX: we only show one optimistic update at any one time. // Ideally we should be making a list of in-flight requests // that are backed by transaction IDs. Until the js-sdk // supports this, we're stuck with only being able to use // the most recent optimistic update. - this._generateRoomLists(payload.request); + console.log("!! Optimistic tag: ", payload); } break; case 'RoomListActions.tagRoom.failure': { + if (!logicallyReady) break; // Reset state according to js-sdk - this._generateRoomLists(); + console.log("!! Optimistic tag failure: ", payload); } break; case 'on_logged_out': { @@ -182,10 +190,73 @@ class RoomListStore extends Store { this._matrixClient = null; } break; + case 'view_room': { + if (!logicallyReady) break; + + // Note: it is important that we set a new stickyRoomId before setting the old room + // to IDLE. If we don't, the wrong room gets counted as sticky. + const currentSticky = this._state.stickyRoomId; + this._setState({stickyRoomId: payload.room_id}); + if (currentSticky) { + this._setRoomCategory(this._matrixClient.getRoom(currentSticky), CATEGORY_IDLE); + } + } + break; + } + }; + + _roomUpdateTriggered(roomId) { + const room = this._matrixClient.getRoom(roomId); + if (!room) return; + + if (this._state.stickyRoomId !== room.roomId) { + this._setRoomCategory(room, this._calculateCategory(room)); } } - _generateRoomLists(optimisticRequest) { + _setRoomCategory(room, category) { + const listsClone = {}; + const targetCatIndex = CATEGORY_ORDER.indexOf(category); + + // We need to update all instances of a room to ensure that they are correctly organized + // in the list. We do this by shallow-cloning the entire `lists` object using a single + // iterator. Within the loop, we also rebuild the list of rooms per tag (key) so that the + // updated room gets slotted into the right spot. + + for (const key of Object.keys(this._state.lists)) { + listsClone[key] = []; + let pushedEntry = false; + const hasRoom = !!this._state.lists[key].find((e) => e.room.roomId === room.roomId); + for (const entry of this._state.lists[key]) { + // if the list is a recent list, and the room appears in this list, and we're not looking at a sticky + // room (sticky rooms have unreliable categories), try to slot the new room in + if (LIST_ORDERS[key] === 'recent' && hasRoom && entry.room.roomId !== this._state.stickyRoomId) { + if (!pushedEntry) { + // If we've hit the top of a boundary (either because there's no rooms in the target or + // we've reached the grouping of rooms), insert our room ahead of the others in the category. + // This ensures that our room is on top (more recent) than the others. + const changedBoundary = CATEGORY_ORDER.indexOf(entry.category) >= targetCatIndex; + if (changedBoundary) { + listsClone[key].push({room: room, category: category}); + pushedEntry = true; + } + } + + // We insert our own record as needed, so don't let the old one through. + if (entry.room.roomId === room.roomId) { + continue; + } + } + + // Fall through and clone the list. + listsClone[key].push(entry); + } + } + + this._setState({lists: listsClone}); + } + + _generateInitialRoomLists() { const lists = { "m.server_notice": [], "im.vector.fake.invite": [], @@ -196,36 +267,20 @@ class RoomListStore extends Store { "im.vector.fake.archived": [], }; - const dmRoomMap = DMRoomMap.shared(); - - // If somehow we dispatched a RoomListActions.tagRoom.failure before a MatrixActions.sync - if (!this._matrixClient) return; - const isCustomTagsEnabled = SettingsStore.isFeatureEnabled("feature_custom_tags"); - this._matrixClient.getRooms().forEach((room, index) => { + this._matrixClient.getRooms().forEach((room) => { const myUserId = this._matrixClient.getUserId(); const membership = room.getMyMembership(); const me = room.getMember(myUserId); - if (membership == "invite") { - lists["im.vector.fake.invite"].push(room); - } else if (membership == "join" || membership === "ban" || (me && me.isKicked())) { + if (membership === "invite") { + lists["im.vector.fake.invite"].push({room, category: CATEGORY_RED}); + } else if (membership === "join" || membership === "ban" || (me && me.isKicked())) { // Used to split rooms via tags let tagNames = Object.keys(room.tags); - if (optimisticRequest && optimisticRequest.room === room) { - // Remove old tag - tagNames = tagNames.filter((tagName) => tagName !== optimisticRequest.oldTag); - // Add new tag - if (optimisticRequest.newTag && - !tagNames.includes(optimisticRequest.newTag) - ) { - tagNames.push(optimisticRequest.newTag); - } - } - // ignore any m. tag names we don't know about tagNames = tagNames.filter((t) => { return (isCustomTagsEnabled && !t.startsWith('m.')) || lists[t] !== undefined; @@ -235,35 +290,31 @@ class RoomListStore extends Store { for (let i = 0; i < tagNames.length; i++) { const tagName = tagNames[i]; lists[tagName] = lists[tagName] || []; - lists[tagName].push(room); + + // We categorize all the tagged rooms the same because we don't actually + // care about the order (it's defined elsewhere) + lists[tagName].push({room, category: CATEGORY_RED}); } } else if (dmRoomMap.getUserIdForRoomId(room.roomId)) { // "Direct Message" rooms (that we're still in and that aren't otherwise tagged) - lists["im.vector.fake.direct"].push(room); + lists["im.vector.fake.direct"].push({room, category: this._calculateCategory(room)}); } else { - lists["im.vector.fake.recent"].push(room); + lists["im.vector.fake.recent"].push({room, category: this._calculateCategory(room)}); } } else if (membership === "leave") { - lists["im.vector.fake.archived"].push(room); + lists["im.vector.fake.archived"].push({room, category: this._calculateCategory(room)}); } }); - // Note: we check the settings up here instead of in the forEach or - // in the _recentsComparator to avoid hitting the SettingsStore a few - // thousand times. - const pinUnread = SettingsStore.getValue("pinUnreadRooms"); - const pinMentioned = SettingsStore.getValue("pinMentionedRooms"); Object.keys(lists).forEach((listKey) => { let comparator; - switch (RoomListStore._listOrders[listKey]) { + switch (LIST_ORDERS[listKey]) { case "recent": - comparator = (roomA, roomB) => { - return this._recentsComparator(roomA, roomB, pinUnread, pinMentioned); - }; + comparator = this._recentsComparator; break; case "manual": default: - comparator = this._getManualComparator(listKey, optimisticRequest); + comparator = this._getManualComparator(listKey); break; } lists[listKey].sort(comparator); @@ -271,52 +322,10 @@ class RoomListStore extends Store { this._setState({ lists, - ready: true, // Ready to receive updates via Room.tags events + ready: true, // Ready to receive updates to ordering }); } - _updateCachedRoomState(roomId, type, value) { - const roomCache = this._state.roomCache; - if (!roomCache[roomId]) roomCache[roomId] = {}; - - if (typeof value !== "undefined") roomCache[roomId][type] = value; - else delete roomCache[roomId][type]; - - this._setState({roomCache}); - } - - _clearCachedRoomState(roomId) { - const roomCache = this._state.roomCache; - delete roomCache[roomId]; - this._setState({roomCache}); - } - - _getRoomState(room, type) { - const roomId = room.roomId; - const roomCache = this._state.roomCache; - if (roomCache[roomId] && typeof roomCache[roomId][type] !== 'undefined') { - return roomCache[roomId][type]; - } - - if (type === "timestamp") { - const ts = this._tsOfNewestEvent(room); - this._updateCachedRoomState(roomId, "timestamp", ts); - return ts; - } else if (type === "unread-muted") { - const unread = Unread.doesRoomHaveUnreadMessages(room); - this._updateCachedRoomState(roomId, "unread-muted", unread); - return unread; - } else if (type === "unread") { - const unread = room.getUnreadNotificationCount() > 0; - this._updateCachedRoomState(roomId, "unread", unread); - return unread; - } else if (type === "notifications") { - const notifs = room.getUnreadNotificationCount("highlight") > 0; - this._updateCachedRoomState(roomId, "notifications", notifs); - return notifs; - } else throw new Error("Unrecognized room cache type: " + type); - } - _eventTriggersRecentReorder(ev) { return ev.getTs() && ( Unread.eventTriggersUnreadCount(ev) || @@ -342,53 +351,36 @@ class RoomListStore extends Store { } } - _recentsComparator(roomA, roomB, pinUnread, pinMentioned) { - // We try and set the ordering to be Mentioned > Unread > Recent - // assuming the user has the right settings, of course. + _calculateCategory(room) { + const mentions = room.getUnreadNotificationCount("highlight") > 0; + if (mentions) return CATEGORY_RED; - const timestampA = this._getRoomState(roomA, "timestamp"); - const timestampB = this._getRoomState(roomB, "timestamp"); - const timestampDiff = timestampB - timestampA; + let unread = room.getUnreadNotificationCount() > 0; + if (unread) return CATEGORY_GREY; - if (pinMentioned) { - const mentionsA = this._getRoomState(roomA, "notifications"); - const mentionsB = this._getRoomState(roomB, "notifications"); - if (mentionsA && !mentionsB) return -1; - if (!mentionsA && mentionsB) return 1; + unread = Unread.doesRoomHaveUnreadMessages(room); + if (unread) return CATEGORY_BOLD; - // If they both have notifications, sort by timestamp. - // If neither have notifications (the fourth check not shown - // here), then try and sort by unread messages and finally by - // timestamp. - if (mentionsA && mentionsB) return timestampDiff; + return CATEGORY_IDLE; + } + + _recentsComparator(entryA, entryB) { + const roomA = entryA.room; + const roomB = entryB.room; + const categoryA = entryA.category; + const categoryB = entryB.category; + + if (categoryA !== categoryB) { + const idxA = CATEGORY_ORDER.indexOf(categoryA); + const idxB = CATEGORY_ORDER.indexOf(categoryB); + if (idxA > idxB) return 1; + if (idxA < idxB) return -1; + return 0; } - if (pinUnread) { - let unreadA = this._getRoomState(roomA, "unread"); - let unreadB = this._getRoomState(roomB, "unread"); - if (unreadA && !unreadB) return -1; - if (!unreadA && unreadB) return 1; - - // If they both have unread messages, sort by timestamp - // If nether have unread message (the fourth check not shown - // here), then just sort by timestamp anyways. - if (unreadA && unreadB) return timestampDiff; - - // Unread can also mean "unread without badge", which is - // different from what the above checks for. We're also - // going to sort those here. - unreadA = this._getRoomState(roomA, "unread-muted"); - unreadB = this._getRoomState(roomB, "unread-muted"); - if (unreadA && !unreadB) return -1; - if (!unreadA && unreadB) return 1; - - // If they both have unread messages, sort by timestamp - // If nether have unread message (the fourth check not shown - // here), then just sort by timestamp anyways. - if (unreadA && unreadB) return timestampDiff; - } - - return timestampDiff; + const timestampA = this._tsOfNewestEvent(roomA); + const timestampB = this._tsOfNewestEvent(roomB); + return timestampB - timestampA; } _lexicographicalComparator(roomA, roomB) { @@ -396,7 +388,10 @@ class RoomListStore extends Store { } _getManualComparator(tagName, optimisticRequest) { - return (roomA, roomB) => { + return (entryA, entryB) => { + const roomA = entryA.room; + const roomB = entryB.room; + let metaA = roomA.tags[tagName]; let metaB = roomB.tags[tagName]; @@ -404,8 +399,8 @@ class RoomListStore extends Store { if (optimisticRequest && roomB === optimisticRequest.room) metaB = optimisticRequest.metaData; // Make sure the room tag has an order element, if not set it to be the bottom - const a = metaA ? metaA.order : undefined; - const b = metaB ? metaB.order : undefined; + const a = metaA ? Number(metaA.order) : undefined; + const b = metaB ? Number(metaB.order) : undefined; // Order undefined room tag orders to the bottom if (a === undefined && b !== undefined) { @@ -414,12 +409,12 @@ class RoomListStore extends Store { return -1; } - return a == b ? this._lexicographicalComparator(roomA, roomB) : ( a > b ? 1 : -1); + return a === b ? this._lexicographicalComparator(roomA, roomB) : ( a > b ? 1 : -1); }; } getRoomLists() { - return this._state.lists; + return this._state.presentationLists; } } From 9175655c16ecc11b224f9573833f32ee19c8847b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 13 Feb 2019 13:13:40 -0700 Subject: [PATCH 006/178] Remove old pin unread options They are not forced on, and do nothing. --- .../views/settings/tabs/PreferencesSettingsTab.js | 8 -------- src/i18n/strings/en_EN.json | 3 --- src/settings/Settings.js | 10 ---------- 3 files changed, 21 deletions(-) diff --git a/src/components/views/settings/tabs/PreferencesSettingsTab.js b/src/components/views/settings/tabs/PreferencesSettingsTab.js index b455938563..d76dc8f3dd 100644 --- a/src/components/views/settings/tabs/PreferencesSettingsTab.js +++ b/src/components/views/settings/tabs/PreferencesSettingsTab.js @@ -30,11 +30,6 @@ export default class PreferencesSettingsTab extends React.Component { 'sendTypingNotifications', ]; - static ROOM_LIST_SETTINGS = [ - 'pinUnreadRooms', - 'pinMentionedRooms', - ]; - static TIMELINE_SETTINGS = [ 'autoplayGifsAndVideos', 'urlPreviewsEnabled', @@ -106,9 +101,6 @@ export default class PreferencesSettingsTab extends React.Component { {_t("Composer")} {this._renderGroup(PreferencesSettingsTab.COMPOSER_SETTINGS)} - {_t("Room list")} - {this._renderGroup(PreferencesSettingsTab.ROOM_LIST_SETTINGS)} - {_t("Timeline")} {this._renderGroup(PreferencesSettingsTab.TIMELINE_SETTINGS)} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index fe41beb7ae..380b9e894f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -300,8 +300,6 @@ "Enable URL previews for this room (only affects you)": "Enable URL previews for this room (only affects you)", "Enable URL previews by default for participants in this room": "Enable URL previews by default for participants in this room", "Room Colour": "Room Colour", - "Pin rooms I'm mentioned in to the top of the room list": "Pin rooms I'm mentioned in to the top of the room list", - "Pin unread rooms to the top of the room list": "Pin unread rooms to the top of the room list", "Enable widget screenshots on supported widgets": "Enable widget screenshots on supported widgets", "Prompt before sending invites to potentially invalid matrix IDs": "Prompt before sending invites to potentially invalid matrix IDs", "Show developer tools": "Show developer tools", @@ -551,7 +549,6 @@ "Start automatically after system login": "Start automatically after system login", "Preferences": "Preferences", "Composer": "Composer", - "Room list": "Room list", "Timeline": "Timeline", "Autocomplete delay (ms)": "Autocomplete delay (ms)", "To change the room's avatar, you must be a": "To change the room's avatar, you must be a", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 4108848033..cf68fed8ba 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -321,16 +321,6 @@ export const SETTINGS = { default: true, controller: new AudioNotificationsEnabledController(), }, - "pinMentionedRooms": { - supportedLevels: LEVELS_ACCOUNT_SETTINGS, - displayName: _td("Pin rooms I'm mentioned in to the top of the room list"), - default: false, - }, - "pinUnreadRooms": { - supportedLevels: LEVELS_ACCOUNT_SETTINGS, - displayName: _td("Pin unread rooms to the top of the room list"), - default: false, - }, "enableWidgetScreenshots": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Enable widget screenshots on supported widgets'), From b741b76797a77f2f21785bbfd8652437b5a2c867 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 13 Feb 2019 14:08:19 -0700 Subject: [PATCH 007/178] Handle joins/leaves safely --- src/stores/RoomListStore.js | 66 +++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 1eec41a20a..b2ceb9cfd4 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -154,7 +154,6 @@ class RoomListStore extends Store { // break; case 'MatrixActions.Room.myMembership': { if (!logicallyReady) break; - // TODO: Slot room into list this._roomUpdateTriggered(payload.room.roomId); } break; @@ -163,25 +162,25 @@ class RoomListStore extends Store { // a member. case 'MatrixActions.Room': { if (!logicallyReady) break; - // TODO: Slot room into list this._roomUpdateTriggered(payload.room.roomId); } break; - case 'RoomListActions.tagRoom.pending': { - if (!logicallyReady) break; - // XXX: we only show one optimistic update at any one time. - // Ideally we should be making a list of in-flight requests - // that are backed by transaction IDs. Until the js-sdk - // supports this, we're stuck with only being able to use - // the most recent optimistic update. - console.log("!! Optimistic tag: ", payload); - } - break; - case 'RoomListActions.tagRoom.failure': { - if (!logicallyReady) break; - // Reset state according to js-sdk - console.log("!! Optimistic tag failure: ", payload); - } + // TODO: Re-enable optimistic updates when we support dragging again + // case 'RoomListActions.tagRoom.pending': { + // if (!logicallyReady) break; + // // XXX: we only show one optimistic update at any one time. + // // Ideally we should be making a list of in-flight requests + // // that are backed by transaction IDs. Until the js-sdk + // // supports this, we're stuck with only being able to use + // // the most recent optimistic update. + // console.log("!! Optimistic tag: ", payload); + // } + // break; + // case 'RoomListActions.tagRoom.failure': { + // if (!logicallyReady) break; + // // Reset state according to js-sdk + // console.log("!! Optimistic tag failure: ", payload); + // } break; case 'on_logged_out': { // Reset state without pushing an update to the view, which generally assumes that @@ -218,11 +217,18 @@ class RoomListStore extends Store { const listsClone = {}; const targetCatIndex = CATEGORY_ORDER.indexOf(category); + const myMembership = room.getMyMembership(); + let doInsert = true; + if (myMembership !== "join" && myMembership !== "invite") { + doInsert = false; + } + // We need to update all instances of a room to ensure that they are correctly organized // in the list. We do this by shallow-cloning the entire `lists` object using a single // iterator. Within the loop, we also rebuild the list of rooms per tag (key) so that the // updated room gets slotted into the right spot. + let inserted = false; for (const key of Object.keys(this._state.lists)) { listsClone[key] = []; let pushedEntry = false; @@ -231,7 +237,7 @@ class RoomListStore extends Store { // if the list is a recent list, and the room appears in this list, and we're not looking at a sticky // room (sticky rooms have unreliable categories), try to slot the new room in if (LIST_ORDERS[key] === 'recent' && hasRoom && entry.room.roomId !== this._state.stickyRoomId) { - if (!pushedEntry) { + if (!pushedEntry && doInsert) { // If we've hit the top of a boundary (either because there's no rooms in the target or // we've reached the grouping of rooms), insert our room ahead of the others in the category. // This ensures that our room is on top (more recent) than the others. @@ -239,6 +245,7 @@ class RoomListStore extends Store { if (changedBoundary) { listsClone[key].push({room: room, category: category}); pushedEntry = true; + inserted = true; } } @@ -253,6 +260,29 @@ class RoomListStore extends Store { } } + if (!inserted) { + // There's a good chance that we just joined the room, so we need to organize it + // We also could have left it... + let tags = []; + if (doInsert) { + tags = Object.keys(room.tags); + if (tags.length === 0) { + tags.push(myMembership === 'join' ? 'im.vector.fake.recent' : 'im.vector.fake.invite'); + } + } else { + tags = ['im.vector.fake.archived']; + } + for (const tag of tags) { + for (let i = 0; i < listsClone[tag].length; i++) { + const catIdxAtPosition = CATEGORY_ORDER.indexOf(listsClone[tag][i].category); + if (catIdxAtPosition >= targetCatIndex) { + listsClone[tag].splice(i, 0, {room: room, category: category}); + break; + } + } + } + } + this._setState({lists: listsClone}); } From 2eb80f793ceec9bf4d2fa158a9e8cc9110a6ac10 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 13 Feb 2019 14:22:00 -0700 Subject: [PATCH 008/178] Try to handle direct chats and tag changes This is a very blunt approach in that it ignores the sticky room. --- src/stores/RoomListStore.js | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index b2ceb9cfd4..58300e43ea 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -95,7 +95,9 @@ class RoomListStore extends Store { break; case 'MatrixActions.Room.tags': { if (!logicallyReady) break; - console.log("!! Tags: ", payload); + // TODO: Figure out which rooms changed in the tag and only change those. + // This is very blunt and wipes out the sticky room stuff + this._generateInitialRoomLists(); } break; case 'MatrixActions.Room.timeline': { @@ -140,8 +142,9 @@ class RoomListStore extends Store { case 'MatrixActions.accountData': { if (!logicallyReady) break; if (payload.event_type !== 'm.direct') break; - // TODO: Handle direct chat changes - console.log("!! Direct Chats: ", payload); + // TODO: Figure out which rooms changed in the direct chat and only change those. + // This is very blunt and wipes out the sticky room stuff + this._generateInitialRoomLists(); } break; // TODO: Remove if not actually needed @@ -181,7 +184,7 @@ class RoomListStore extends Store { // // Reset state according to js-sdk // console.log("!! Optimistic tag failure: ", payload); // } - break; + // break; case 'on_logged_out': { // Reset state without pushing an update to the view, which generally assumes that // the matrix client isn't `null` and so causing a re-render will cause NPEs. @@ -213,7 +216,7 @@ class RoomListStore extends Store { } } - _setRoomCategory(room, category) { + _setRoomCategory(room, category, targetTags = []) { const listsClone = {}; const targetCatIndex = CATEGORY_ORDER.indexOf(category); @@ -221,6 +224,17 @@ class RoomListStore extends Store { let doInsert = true; if (myMembership !== "join" && myMembership !== "invite") { doInsert = false; + } else { + const dmRoomMap = DMRoomMap.shared(); + if (dmRoomMap.getUserIdForRoomId(room.roomId)) { + if (!targetTags.includes('im.vector.fake.direct')) { + targetTags.push('im.vector.fake.direct'); + } + } else { + if (!targetTags.includes('im.vector.fake.recent')) { + targetTags.push('im.vector.fake.recent'); + } + } } // We need to update all instances of a room to ensure that they are correctly organized @@ -237,7 +251,8 @@ class RoomListStore extends Store { // if the list is a recent list, and the room appears in this list, and we're not looking at a sticky // room (sticky rooms have unreliable categories), try to slot the new room in if (LIST_ORDERS[key] === 'recent' && hasRoom && entry.room.roomId !== this._state.stickyRoomId) { - if (!pushedEntry && doInsert) { + const inTag = targetTags.length === 0 || targetTags.includes('im.vector.ake.recent'); + if (!pushedEntry && doInsert && inTag) { // If we've hit the top of a boundary (either because there's no rooms in the target or // we've reached the grouping of rooms), insert our room ahead of the others in the category. // This ensures that our room is on top (more recent) than the others. @@ -266,6 +281,9 @@ class RoomListStore extends Store { let tags = []; if (doInsert) { tags = Object.keys(room.tags); + if (tags.length === 0) { + tags = targetTags; + } if (tags.length === 0) { tags.push(myMembership === 'join' ? 'im.vector.fake.recent' : 'im.vector.fake.invite'); } From 821b34b487169d3813bca78567d3fba61d164064 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 13 Feb 2019 17:03:27 -0700 Subject: [PATCH 009/178] React to read receipt changes from ourselves When a room is read on another device, it should be re-sorted --- src/actions/MatrixActionCreators.js | 19 +++++++++++++++++++ src/stores/RoomListStore.js | 17 ++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/actions/MatrixActionCreators.js b/src/actions/MatrixActionCreators.js index c1d42ffd0d..c89ec44435 100644 --- a/src/actions/MatrixActionCreators.js +++ b/src/actions/MatrixActionCreators.js @@ -131,6 +131,24 @@ function createRoomTagsAction(matrixClient, roomTagsEvent, room) { return { action: 'MatrixActions.Room.tags', room }; } +/** + * Create a MatrixActions.Room.receipt action that represents a MatrixClient + * `Room.receipt` event, each parameter mapping to a key-value in the action. + * + * @param {MatrixClient} matrixClient the matrix client + * @param {MatrixEvent} event the receipt event. + * @param {Room} room the room the receipt happened in. + * @returns {Object} an action of type MatrixActions.Room.receipt. + */ +function createRoomReceiptAction(matrixClient, event, room) { + return { + action: 'MatrixActions.Room.receipt', + event, + room, + matrixClient, + }; +} + /** * @typedef RoomTimelineAction * @type {Object} @@ -233,6 +251,7 @@ export default { this._addMatrixClientListener(matrixClient, 'Room.accountData', createRoomAccountDataAction); this._addMatrixClientListener(matrixClient, 'Room', createRoomAction); this._addMatrixClientListener(matrixClient, 'Room.tags', createRoomTagsAction); + this._addMatrixClientListener(matrixClient, 'Room.receipt', createRoomReceiptAction); this._addMatrixClientListener(matrixClient, 'Room.timeline', createRoomTimelineAction); this._addMatrixClientListener(matrixClient, 'Room.myMembership', createSelfMembershipAction); this._addMatrixClientListener(matrixClient, 'Event.decrypted', createEventDecryptedAction); diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 58300e43ea..30d14351e0 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -93,6 +93,20 @@ class RoomListStore extends Store { this._generateInitialRoomLists(); } break; + case 'MatrixActions.Room.receipt': { + if (!logicallyReady) break; + + // First see if the receipt event is for our own user + const myUserId = this._matrixClient.getUserId(); + for (const eventId of Object.keys(payload.event.getContent())) { + const receiptUsers = Object.keys(payload.event.getContent()[eventId]['m.read'] || {}); + if (receiptUsers.includes(myUserId)) { + this._roomUpdateTriggered(payload.room.roomId); + return; + } + } + } + break; case 'MatrixActions.Room.tags': { if (!logicallyReady) break; // TODO: Figure out which rooms changed in the tag and only change those. @@ -212,7 +226,8 @@ class RoomListStore extends Store { if (!room) return; if (this._state.stickyRoomId !== room.roomId) { - this._setRoomCategory(room, this._calculateCategory(room)); + const category = this._calculateCategory(room); + this._setRoomCategory(room, category); } } From 52f48f742246828855c508b6021b0c820c029a68 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 13 Feb 2019 18:19:18 -0700 Subject: [PATCH 010/178] Order by timestamp within the categorized room lists When we load the page, all encrypted events arrive well after we've generated our initial grouping which can cause them to jump to the top of their categories wrongly. For direct chats, this meant that people who don't have a lot of unread messages would see ancient rooms bubbling to the top for no reason after the page has loaded. We still have to track when the last category change was (ie: when we switched from red -> grey) so that when the category doesn't exist in the list we can insert the room at the right place (the start of the last category when we switch beyond the order expected). --- src/stores/RoomListStore.js | 39 ++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 30d14351e0..84939fa129 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -231,24 +231,22 @@ class RoomListStore extends Store { } } - _setRoomCategory(room, category, targetTags = []) { + _setRoomCategory(room, category) { const listsClone = {}; const targetCatIndex = CATEGORY_ORDER.indexOf(category); + const targetTs = this._tsOfNewestEvent(room); const myMembership = room.getMyMembership(); let doInsert = true; + let targetTags = []; if (myMembership !== "join" && myMembership !== "invite") { doInsert = false; } else { const dmRoomMap = DMRoomMap.shared(); if (dmRoomMap.getUserIdForRoomId(room.roomId)) { - if (!targetTags.includes('im.vector.fake.direct')) { - targetTags.push('im.vector.fake.direct'); - } + targetTags.push('im.vector.fake.direct'); } else { - if (!targetTags.includes('im.vector.fake.recent')) { - targetTags.push('im.vector.fake.recent'); - } + targetTags.push('im.vector.fake.recent'); } } @@ -262,21 +260,40 @@ class RoomListStore extends Store { listsClone[key] = []; let pushedEntry = false; const hasRoom = !!this._state.lists[key].find((e) => e.room.roomId === room.roomId); + let lastCategoryBoundary = 0; + let lastCategoryIndex = 0; for (const entry of this._state.lists[key]) { + // if the list is a recent list, and the room appears in this list, and we're not looking at a sticky // room (sticky rooms have unreliable categories), try to slot the new room in if (LIST_ORDERS[key] === 'recent' && hasRoom && entry.room.roomId !== this._state.stickyRoomId) { - const inTag = targetTags.length === 0 || targetTags.includes('im.vector.ake.recent'); + const inTag = targetTags.length === 0 || targetTags.includes(key); if (!pushedEntry && doInsert && inTag) { + const entryTs = this._tsOfNewestEvent(entry.room); + const entryCategory = CATEGORY_ORDER.indexOf(entry.category); + // If we've hit the top of a boundary (either because there's no rooms in the target or // we've reached the grouping of rooms), insert our room ahead of the others in the category. // This ensures that our room is on top (more recent) than the others. - const changedBoundary = CATEGORY_ORDER.indexOf(entry.category) >= targetCatIndex; - if (changedBoundary) { - listsClone[key].push({room: room, category: category}); + const changedBoundary = entryCategory > targetCatIndex; + const currentCategory = entryCategory === targetCatIndex; + if (changedBoundary || (currentCategory && targetTs >= entryTs)) { + if (changedBoundary) { + // If we changed a boundary, then we've gone too far - go to the top of the last + // section instead. + listsClone[key].splice(lastCategoryBoundary, 0, {room, category}); + } else { + // If we're ordering by timestamp, just insert normally + listsClone[key].push({room, category}); + } pushedEntry = true; inserted = true; } + + if (entryCategory !== lastCategoryIndex) { + lastCategoryBoundary = listsClone[key].length - 1; + } + lastCategoryIndex = entryCategory; } // We insert our own record as needed, so don't let the old one through. From c0b63f986f8a730a422e29e421d12c42b375622b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 13 Feb 2019 19:16:11 -0700 Subject: [PATCH 011/178] Implement a cache on _tsOfNewestEvent: ~75% improvement --- src/stores/RoomListStore.js | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 84939fa129..f7e9703c73 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -96,7 +96,8 @@ class RoomListStore extends Store { case 'MatrixActions.Room.receipt': { if (!logicallyReady) break; - // First see if the receipt event is for our own user + // First see if the receipt event is for our own user. If it was, trigger + // a room update (we probably read the room on a different device). const myUserId = this._matrixClient.getUserId(); for (const eventId of Object.keys(payload.event.getContent())) { const receiptUsers = Object.keys(payload.event.getContent()[eventId]['m.read'] || {}); @@ -161,14 +162,6 @@ class RoomListStore extends Store { this._generateInitialRoomLists(); } break; - // TODO: Remove if not actually needed - // case 'MatrixActions.Room.accountData': { - // if (!logicallyReady) break; - // if (payload.event_type === 'm.fully_read') { - // console.log("!! Fully read: ", payload); - // } - // } - // break; case 'MatrixActions.Room.myMembership': { if (!logicallyReady) break; this._roomUpdateTriggered(payload.room.roomId); @@ -263,7 +256,6 @@ class RoomListStore extends Store { let lastCategoryBoundary = 0; let lastCategoryIndex = 0; for (const entry of this._state.lists[key]) { - // if the list is a recent list, and the room appears in this list, and we're not looking at a sticky // room (sticky rooms have unreliable categories), try to slot the new room in if (LIST_ORDERS[key] === 'recent' && hasRoom && entry.room.roomId !== this._state.stickyRoomId) { @@ -386,11 +378,23 @@ class RoomListStore extends Store { } }); + // We use this cache in the recents comparator because _tsOfNewestEvent can take a while + const latestEventTsCache = {}; // roomId => timestamp + Object.keys(lists).forEach((listKey) => { let comparator; switch (LIST_ORDERS[listKey]) { case "recent": - comparator = this._recentsComparator; + comparator = (entryA, entryB) => { + this._recentsComparator(entryA, entryB, (room) => { + if (latestEventTsCache[room.roomId]) { + return latestEventTsCache[room.roomId]; + } + const ts = this._tsOfNewestEvent(room); + latestEventTsCache[room.roomId] = ts; + return ts; + }); + }; break; case "manual": default: @@ -444,7 +448,7 @@ class RoomListStore extends Store { return CATEGORY_IDLE; } - _recentsComparator(entryA, entryB) { + _recentsComparator(entryA, entryB, tsOfNewestEventFn) { const roomA = entryA.room; const roomB = entryB.room; const categoryA = entryA.category; @@ -458,8 +462,8 @@ class RoomListStore extends Store { return 0; } - const timestampA = this._tsOfNewestEvent(roomA); - const timestampB = this._tsOfNewestEvent(roomB); + const timestampA = tsOfNewestEventFn(roomA); + const timestampB = tsOfNewestEventFn(roomB); return timestampB - timestampA; } From b08ab6cd12912fea0ec68a86d6c8f48c45dbd40c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 13 Feb 2019 19:35:01 -0700 Subject: [PATCH 012/178] Fix boundary math calculations --- src/stores/RoomListStore.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index f7e9703c73..caed059905 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -43,6 +43,7 @@ class RoomListStore extends Store { super(dis); this._init(); + this.__onDispatch = this.__onDispatch.bind(this); this._getManualComparator = this._getManualComparator.bind(this); this._recentsComparator = this._recentsComparator.bind(this); } @@ -80,7 +81,7 @@ class RoomListStore extends Store { this.__emitChange(); } - __onDispatch = (payload) => { + __onDispatch(payload) { const logicallyReady = this._matrixClient && this._state.ready; switch (payload.action) { // Initialise state after initial sync @@ -231,7 +232,7 @@ class RoomListStore extends Store { const myMembership = room.getMyMembership(); let doInsert = true; - let targetTags = []; + const targetTags = []; if (myMembership !== "join" && myMembership !== "invite") { doInsert = false; } else { @@ -253,8 +254,8 @@ class RoomListStore extends Store { listsClone[key] = []; let pushedEntry = false; const hasRoom = !!this._state.lists[key].find((e) => e.room.roomId === room.roomId); - let lastCategoryBoundary = 0; - let lastCategoryIndex = 0; + let desiredCategoryBoundaryIndex = 0; + let foundBoundary = false; for (const entry of this._state.lists[key]) { // if the list is a recent list, and the room appears in this list, and we're not looking at a sticky // room (sticky rooms have unreliable categories), try to slot the new room in @@ -264,16 +265,21 @@ class RoomListStore extends Store { const entryTs = this._tsOfNewestEvent(entry.room); const entryCategory = CATEGORY_ORDER.indexOf(entry.category); + if (entryCategory >= targetCatIndex && !foundBoundary) { + desiredCategoryBoundaryIndex = listsClone[key].length - 1; + foundBoundary = true; + } + // If we've hit the top of a boundary (either because there's no rooms in the target or // we've reached the grouping of rooms), insert our room ahead of the others in the category. // This ensures that our room is on top (more recent) than the others. - const changedBoundary = entryCategory > targetCatIndex; + const changedBoundary = entryCategory >= targetCatIndex; const currentCategory = entryCategory === targetCatIndex; - if (changedBoundary || (currentCategory && targetTs >= entryTs)) { - if (changedBoundary) { + if (changedBoundary || (false && currentCategory && targetTs >= entryTs)) { + if (changedBoundary && false) { // If we changed a boundary, then we've gone too far - go to the top of the last // section instead. - listsClone[key].splice(lastCategoryBoundary, 0, {room, category}); + listsClone[key].splice(desiredCategoryBoundaryIndex, 0, {room, category}); } else { // If we're ordering by timestamp, just insert normally listsClone[key].push({room, category}); @@ -281,11 +287,6 @@ class RoomListStore extends Store { pushedEntry = true; inserted = true; } - - if (entryCategory !== lastCategoryIndex) { - lastCategoryBoundary = listsClone[key].length - 1; - } - lastCategoryIndex = entryCategory; } // We insert our own record as needed, so don't let the old one through. @@ -386,7 +387,7 @@ class RoomListStore extends Store { switch (LIST_ORDERS[listKey]) { case "recent": comparator = (entryA, entryB) => { - this._recentsComparator(entryA, entryB, (room) => { + return this._recentsComparator(entryA, entryB, (room) => { if (latestEventTsCache[room.roomId]) { return latestEventTsCache[room.roomId]; } From 0c7e0a264b921dbf58c6d84063fa573c6e95ba5a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 13 Feb 2019 19:49:28 -0700 Subject: [PATCH 013/178] Inline documentation --- src/stores/RoomListStore.js | 77 ++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index caed059905..d2e94ffd05 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -1,5 +1,5 @@ /* -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. @@ -19,10 +19,22 @@ import DMRoomMap from '../utils/DMRoomMap'; import Unread from '../Unread'; import SettingsStore from "../settings/SettingsStore"; -const CATEGORY_RED = "red"; -const CATEGORY_GREY = "grey"; -const CATEGORY_BOLD = "bold"; -const CATEGORY_IDLE = "idle"; +/* +Room sorting algorithm: +* Always prefer to have red > grey > bold > idle +* The room being viewed should be sticky (not jump down to the idle list) +* When switching to a new room, sort the last sticky room to the top of the idle list. + +The approach taken by the store is to generate an initial representation of all the +tagged lists (accepting that it'll take a little bit longer to calculate) and make +small changes to that over time. This results in quick changes to the room list while +also having update operations feel more like popping/pushing to a stack. + */ + +const CATEGORY_RED = "red"; // Mentions in the room +const CATEGORY_GREY = "grey"; // Unread notified messages (not mentions) +const CATEGORY_BOLD = "bold"; // Unread messages (not notified, 'Mentions Only' rooms) +const CATEGORY_IDLE = "idle"; // Nothing of interest const CATEGORY_ORDER = [CATEGORY_RED, CATEGORY_GREY, CATEGORY_BOLD, CATEGORY_IDLE]; const LIST_ORDERS = { @@ -70,6 +82,10 @@ class RoomListStore extends Store { } _setState(newState) { + // If we're changing the lists, transparently change the presentation lists (which + // is given to requesting components). This dramatically simplifies our code elsewhere + // while also ensuring we don't need to update all the calling components to support + // categories. if (newState['lists']) { const presentationLists = {}; for (const key of Object.keys(newState['lists'])) { @@ -205,20 +221,24 @@ class RoomListStore extends Store { // Note: it is important that we set a new stickyRoomId before setting the old room // to IDLE. If we don't, the wrong room gets counted as sticky. - const currentSticky = this._state.stickyRoomId; + const currentStickyId = this._state.stickyRoomId; this._setState({stickyRoomId: payload.room_id}); - if (currentSticky) { - this._setRoomCategory(this._matrixClient.getRoom(currentSticky), CATEGORY_IDLE); + if (currentStickyId) { + this._setRoomCategory(this._matrixClient.getRoom(currentStickyId), CATEGORY_IDLE); } } break; } - }; + } _roomUpdateTriggered(roomId) { const room = this._matrixClient.getRoom(roomId); if (!room) return; + // We don't calculate categories for sticky rooms because we have a moderate + // interest in trying to maintain the category that they were last in before + // being artificially flagged as IDLE. Also, this reduces the amount of time + // we spend in _setRoomCategory ever so slightly. if (this._state.stickyRoomId !== room.roomId) { const category = this._calculateCategory(room); this._setRoomCategory(room, category); @@ -227,8 +247,8 @@ class RoomListStore extends Store { _setRoomCategory(room, category) { const listsClone = {}; - const targetCatIndex = CATEGORY_ORDER.indexOf(category); - const targetTs = this._tsOfNewestEvent(room); + const targetCategoryIndex = CATEGORY_ORDER.indexOf(category); + const targetTimestamp = this._tsOfNewestEvent(room); const myMembership = room.getMyMembership(); let doInsert = true; @@ -247,35 +267,43 @@ class RoomListStore extends Store { // We need to update all instances of a room to ensure that they are correctly organized // in the list. We do this by shallow-cloning the entire `lists` object using a single // iterator. Within the loop, we also rebuild the list of rooms per tag (key) so that the - // updated room gets slotted into the right spot. + // updated room gets slotted into the right spot. This sacrifices code clarity for not + // iterating on potentially large collections multiple times. let inserted = false; for (const key of Object.keys(this._state.lists)) { listsClone[key] = []; let pushedEntry = false; const hasRoom = !!this._state.lists[key].find((e) => e.room.roomId === room.roomId); + + // We track where the boundary within listsClone[key] is just in case our timestamp + // ordering fails. If we can't stick the room in at the correct place in the category + // grouping based on timestamp, we'll stick it at the top of the group which will be + // the index we track here. let desiredCategoryBoundaryIndex = 0; let foundBoundary = false; + for (const entry of this._state.lists[key]) { // if the list is a recent list, and the room appears in this list, and we're not looking at a sticky // room (sticky rooms have unreliable categories), try to slot the new room in if (LIST_ORDERS[key] === 'recent' && hasRoom && entry.room.roomId !== this._state.stickyRoomId) { const inTag = targetTags.length === 0 || targetTags.includes(key); if (!pushedEntry && doInsert && inTag) { - const entryTs = this._tsOfNewestEvent(entry.room); - const entryCategory = CATEGORY_ORDER.indexOf(entry.category); + const entryTimestamp = this._tsOfNewestEvent(entry.room); + const entryCategoryIndex = CATEGORY_ORDER.indexOf(entry.category); - if (entryCategory >= targetCatIndex && !foundBoundary) { + // As per above, check if we're meeting that boundary we wanted to locate. + if (entryCategoryIndex >= targetCategoryIndex && !foundBoundary) { desiredCategoryBoundaryIndex = listsClone[key].length - 1; foundBoundary = true; } - // If we've hit the top of a boundary (either because there's no rooms in the target or - // we've reached the grouping of rooms), insert our room ahead of the others in the category. - // This ensures that our room is on top (more recent) than the others. - const changedBoundary = entryCategory >= targetCatIndex; - const currentCategory = entryCategory === targetCatIndex; - if (changedBoundary || (false && currentCategory && targetTs >= entryTs)) { + // If we've hit the top of a boundary beyond our target category, insert at the top of + // the grouping to ensure the room isn't slotted incorrectly. Otherwise, try to insert + // based on most recent timestamp. + const changedBoundary = entryCategoryIndex > targetCategoryIndex; + const currentCategory = entryCategoryIndex === targetCategoryIndex; + if (changedBoundary || (false && currentCategory && targetTimestamp >= entryTimestamp)) { if (changedBoundary && false) { // If we changed a boundary, then we've gone too far - go to the top of the last // section instead. @@ -317,8 +345,9 @@ class RoomListStore extends Store { } for (const tag of tags) { for (let i = 0; i < listsClone[tag].length; i++) { + // Just find the top of our category grouping and insert it there. const catIdxAtPosition = CATEGORY_ORDER.indexOf(listsClone[tag][i].category); - if (catIdxAtPosition >= targetCatIndex) { + if (catIdxAtPosition >= targetCategoryIndex) { listsClone[tag].splice(i, 0, {room: room, category: category}); break; } @@ -379,7 +408,9 @@ class RoomListStore extends Store { } }); - // We use this cache in the recents comparator because _tsOfNewestEvent can take a while + // We use this cache in the recents comparator because _tsOfNewestEvent can take a while. This + // cache only needs to survive the sort operation below and should not be implemented outside + // of this function, otherwise the room lists will almost certainly be out of date and wrong. const latestEventTsCache = {}; // roomId => timestamp Object.keys(lists).forEach((listKey) => { From b12362dd379f8a84cdcc4be3727c17106f9d06f8 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 13 Feb 2019 19:51:34 -0700 Subject: [PATCH 014/178] Disable dragging tests for room list We don't support dragging, so don't test it. --- test/components/views/rooms/RoomList-test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index 0c970edb0b..754367cd23 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -180,7 +180,8 @@ describe('RoomList', () => { } function itDoesCorrectOptimisticUpdatesForDraggedRoomTiles() { - describe('does correct optimistic update when dragging from', () => { + // TODO: Re-enable dragging tests when we support dragging again. + xdescribe('does correct optimistic update when dragging from', () => { it('rooms to people', () => { expectCorrectMove(undefined, 'im.vector.fake.direct'); }); From a2a13636ed49e9eb1d97f63e26adc4d6dcd9570a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 13 Feb 2019 20:02:18 -0700 Subject: [PATCH 015/178] Don't blow up when rooms have no timelines --- src/stores/RoomListStore.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index d2e94ffd05..e38a592f7a 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -450,6 +450,10 @@ class RoomListStore extends Store { } _tsOfNewestEvent(room) { + // Apparently we can have rooms without timelines, at least under testing + // environments. Just return MAX_INT when this happens. + if (!room.timeline) return Number.MAX_SAFE_INTEGER; + for (let i = room.timeline.length - 1; i >= 0; --i) { const ev = room.timeline[i]; if (this._eventTriggersRecentReorder(ev)) { From 45a415f8bf12c9d7806c190b8d07c478d14e16e0 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 13 Feb 2019 20:16:47 -0700 Subject: [PATCH 016/178] Protection around lack of room for tests --- src/stores/RoomListStore.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index e38a592f7a..9a764cd493 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -246,6 +246,8 @@ class RoomListStore extends Store { } _setRoomCategory(room, category) { + if (!room) return; // This should only happen in tests + const listsClone = {}; const targetCategoryIndex = CATEGORY_ORDER.indexOf(category); const targetTimestamp = this._tsOfNewestEvent(room); @@ -419,6 +421,8 @@ class RoomListStore extends Store { case "recent": comparator = (entryA, entryB) => { return this._recentsComparator(entryA, entryB, (room) => { + if (!room) return Number.MAX_SAFE_INTEGER; // Should only happen in tests + if (latestEventTsCache[room.roomId]) { return latestEventTsCache[room.roomId]; } @@ -452,7 +456,7 @@ class RoomListStore extends Store { _tsOfNewestEvent(room) { // Apparently we can have rooms without timelines, at least under testing // environments. Just return MAX_INT when this happens. - if (!room.timeline) return Number.MAX_SAFE_INTEGER; + if (!room || !room.timeline) return Number.MAX_SAFE_INTEGER; for (let i = room.timeline.length - 1; i >= 0; --i) { const ev = room.timeline[i]; From 3cd605c443ca550d6959077285e30d22c0303fb3 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 14 Feb 2019 11:11:09 +0000 Subject: [PATCH 017/178] Update dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 034db77b3f..d760c2291f 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "linkifyjs": "^2.1.6", "lodash": "^4.13.1", "lolex": "2.3.2", - "matrix-js-sdk": "1.0.0-rc.1", + "matrix-js-sdk": "1.0.0-rc.2", "optimist": "^0.6.1", "pako": "^1.0.5", "prop-types": "^15.5.8", From c3f81e8358bc3401d0803727e8aee3cd5ff3d3b2 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 14 Feb 2019 11:15:36 +0000 Subject: [PATCH 018/178] Prepare changelog for v1.0.0-rc.2 --- CHANGELOG.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb4490d058..ad31549fe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,62 @@ +Changes in [1.0.0-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.0.0-rc.2) (2019-02-14) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.0.0-rc.1...v1.0.0-rc.2) + + * Update from Weblate. + [\#2635](https://github.com/matrix-org/matrix-react-sdk/pull/2635) + * use throttle as its more responsive + [\#2632](https://github.com/matrix-org/matrix-react-sdk/pull/2632) + * update range when items size changes + [\#2631](https://github.com/matrix-org/matrix-react-sdk/pull/2631) + * Fix registration after clicking email link + [\#2630](https://github.com/matrix-org/matrix-react-sdk/pull/2630) + * Re-check key backup status when settings opened + [\#2626](https://github.com/matrix-org/matrix-react-sdk/pull/2626) + * Improve room list rendering performance + [\#2629](https://github.com/matrix-org/matrix-react-sdk/pull/2629) + * Adjust top left menu items + [\#2628](https://github.com/matrix-org/matrix-react-sdk/pull/2628) + * Normalised icon strokes to 1px + [\#2627](https://github.com/matrix-org/matrix-react-sdk/pull/2627) + * Security: Force TURN setting was inverted + [\#2623](https://github.com/matrix-org/matrix-react-sdk/pull/2623) + * Add redesigned dark theme + [\#2619](https://github.com/matrix-org/matrix-react-sdk/pull/2619) + * Fix mx_RoomTile_name weighting + [\#2610](https://github.com/matrix-org/matrix-react-sdk/pull/2610) + * Add divider between tabs and regular buttons in room header + [\#2621](https://github.com/matrix-org/matrix-react-sdk/pull/2621) + * Update from Weblate. + [\#2622](https://github.com/matrix-org/matrix-react-sdk/pull/2622) + * Change taking a community off the left-left panel less scary + [\#2609](https://github.com/matrix-org/matrix-react-sdk/pull/2609) + * Fixes and styling related to e2e icons and dialogs + [\#2620](https://github.com/matrix-org/matrix-react-sdk/pull/2620) + * Fix: stickers layout + [\#2618](https://github.com/matrix-org/matrix-react-sdk/pull/2618) + * Fix: dont assume settings label only has one line + [\#2616](https://github.com/matrix-org/matrix-react-sdk/pull/2616) + * Labs feature: recent room breadcrumbs + [\#2615](https://github.com/matrix-org/matrix-react-sdk/pull/2615) + * Fix: roomlist reordering lags + [\#2612](https://github.com/matrix-org/matrix-react-sdk/pull/2612) + * Change text in e2e UX to new copy + [\#2617](https://github.com/matrix-org/matrix-react-sdk/pull/2617) + * Add display name / avatar to incoming sas dialog + [\#2613](https://github.com/matrix-org/matrix-react-sdk/pull/2613) + * Restore backup on new recovery method dialog + [\#2614](https://github.com/matrix-org/matrix-react-sdk/pull/2614) + * Welcome page cleanup + [\#2611](https://github.com/matrix-org/matrix-react-sdk/pull/2611) + * Scale up settings UI to be easier to read + [\#2604](https://github.com/matrix-org/matrix-react-sdk/pull/2604) + * !important shouldn't have a space + [\#2608](https://github.com/matrix-org/matrix-react-sdk/pull/2608) + * Add legacy verification button on wait + [\#2607](https://github.com/matrix-org/matrix-react-sdk/pull/2607) + * Update from Weblate. + [\#2606](https://github.com/matrix-org/matrix-react-sdk/pull/2606) + Changes in [1.0.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.0.0-rc.1) (2019-02-08) ============================================================================================================= [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.8...v1.0.0-rc.1) From 5c5593d030055c53a900899cd6f42308a247d697 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 14 Feb 2019 11:15:37 +0000 Subject: [PATCH 019/178] v1.0.0-rc.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d760c2291f..fd2865efd5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "1.0.0-rc.1", + "version": "1.0.0-rc.2", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 5dd10a4a5ca29d70f594d9cc8d1e0e26cb3f8f3d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 14 Feb 2019 16:52:59 +0000 Subject: [PATCH 020/178] Update dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fd2865efd5..a9fac6677e 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "linkifyjs": "^2.1.6", "lodash": "^4.13.1", "lolex": "2.3.2", - "matrix-js-sdk": "1.0.0-rc.2", + "matrix-js-sdk": "1.0.0", "optimist": "^0.6.1", "pako": "^1.0.5", "prop-types": "^15.5.8", From d9f4c2929ef2ff1a5537eb0a6ab0eeffe79425df Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 14 Feb 2019 16:55:19 +0000 Subject: [PATCH 021/178] Prepare changelog for v1.0.0 --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad31549fe7..d52105f9f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +Changes in [1.0.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.0.0) (2019-02-14) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.0.0-rc.2...v1.0.0) + + * Update from Weblate. + [\#2646](https://github.com/matrix-org/matrix-react-sdk/pull/2646) + * Remove 'welcome' from top-left menu + [\#2641](https://github.com/matrix-org/matrix-react-sdk/pull/2641) + * Turn on pin unread rooms for everyone + [\#2645](https://github.com/matrix-org/matrix-react-sdk/pull/2645) + * Update help buoy text and issue links + [\#2640](https://github.com/matrix-org/matrix-react-sdk/pull/2640) + * Fix icons being cut off in settings + [\#2644](https://github.com/matrix-org/matrix-react-sdk/pull/2644) + * Add credit for cover photo usage + [\#2643](https://github.com/matrix-org/matrix-react-sdk/pull/2643) + * make e2e icons on message transparent + [\#2642](https://github.com/matrix-org/matrix-react-sdk/pull/2642) + * fix close button being half off screen + [\#2639](https://github.com/matrix-org/matrix-react-sdk/pull/2639) + * Fix excessive timeline whitespace + [\#2638](https://github.com/matrix-org/matrix-react-sdk/pull/2638) + * Remove the white screen of welcome + [\#2637](https://github.com/matrix-org/matrix-react-sdk/pull/2637) + * always rerender room tiles + [\#2636](https://github.com/matrix-org/matrix-react-sdk/pull/2636) + Changes in [1.0.0-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.0.0-rc.2) (2019-02-14) ============================================================================================================= [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.0.0-rc.1...v1.0.0-rc.2) From 321dd49db4fbe360fc2ff109ac117305c955b061 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 14 Feb 2019 16:55:19 +0000 Subject: [PATCH 022/178] v1.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a9fac6677e..0b7646dda7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "1.0.0-rc.2", + "version": "1.0.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 840f3cede8afda157c0c960eaef6a9c4c9ada77f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 14 Feb 2019 18:04:15 +0100 Subject: [PATCH 023/178] highlight e2e icon on event when hovering whole event --- res/css/views/rooms/_EventTile.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 24a23a0f98..6a847e58bc 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -277,8 +277,7 @@ limitations under the License. } /* End to end encryption stuff */ - -.mx_EventTile_e2eIcon:hover { +.mx_EventTile:hover .mx_EventTile_e2eIcon { opacity: 1; } From f46df2ddd9c800ac806be9a31291a9f97d706e93 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 14 Feb 2019 11:09:37 -0700 Subject: [PATCH 024/178] Add a bit of safety around reading events for room settings Fixes https://github.com/vector-im/riot-web/issues/8530 and maybe https://github.com/vector-im/riot-web/issues/8641 --- .../settings/tabs/RolesRoomSettingsTab.js | 6 +++-- .../settings/tabs/SecurityRoomSettingsTab.js | 24 ++++++++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/components/views/settings/tabs/RolesRoomSettingsTab.js b/src/components/views/settings/tabs/RolesRoomSettingsTab.js index 2195017782..d223e8f2e9 100644 --- a/src/components/views/settings/tabs/RolesRoomSettingsTab.js +++ b/src/components/views/settings/tabs/RolesRoomSettingsTab.js @@ -116,7 +116,8 @@ export default class RolesRoomSettingsTab extends React.Component { _onPowerLevelsChanged = (value, powerLevelKey) => { const client = MatrixClientPeg.get(); const room = client.getRoom(this.props.roomId); - let plContent = room.currentState.getStateEvents('m.room.power_levels', '').getContent() || {}; + const plEvent = room.currentState.getStateEvents('m.room.power_levels', ''); + let plContent = plEvent ? (plEvent.getContent() || {}) : {}; // Clone the power levels just in case plContent = Object.assign({}, plContent); @@ -151,7 +152,8 @@ export default class RolesRoomSettingsTab extends React.Component { const client = MatrixClientPeg.get(); const room = client.getRoom(this.props.roomId); - const plContent = room.currentState.getStateEvents('m.room.power_levels', '').getContent() || {}; + const plEvent = room.currentState.getStateEvents('m.room.power_levels', ''); + const plContent = plEvent ? (plEvent.getContent() || {}) : {}; const canChangeLevels = room.currentState.mayClientSendStateEvent('m.room.power_levels', client); const powerLevelDescriptors = { diff --git a/src/components/views/settings/tabs/SecurityRoomSettingsTab.js b/src/components/views/settings/tabs/SecurityRoomSettingsTab.js index 593e8151d2..698f67dd18 100644 --- a/src/components/views/settings/tabs/SecurityRoomSettingsTab.js +++ b/src/components/views/settings/tabs/SecurityRoomSettingsTab.js @@ -43,13 +43,31 @@ export default class SecurityRoomSettingsTab extends React.Component { const room = MatrixClientPeg.get().getRoom(this.props.roomId); const state = room.currentState; - const joinRule = state.getStateEvents("m.room.join_rules", "").getContent()['join_rule']; - const guestAccess = state.getStateEvents("m.room.guest_access", "").getContent()['guest_access']; - const history = state.getStateEvents("m.room.history_visibility", "").getContent()['history_visibility']; + + const joinRule = this._pullContentPropertyFromEvent( + state.getStateEvents("m.room.join_rules", ""), + 'join_rule', + 'invite', + ); + const guestAccess = this._pullContentPropertyFromEvent( + state.getStateEvents("m.room.guest_access", ""), + 'guest_access', + 'forbidden', + ); + const history = this._pullContentPropertyFromEvent( + state.getStateEvents("m.room.history_visibility", ""), + 'history_visibility', + 'shared', + ); const encrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.roomId); this.setState({joinRule, guestAccess, history, encrypted}); } + _pullContentPropertyFromEvent(event, key, defaultValue) { + if (!event || !event.getContent()) return defaultValue; + return event.getContent()[key] || defaultValue; + } + componentWillUnmount(): void { MatrixClientPeg.get().removeListener("RoomState.events", this._onStateEvent); } From 85930fca702d2c08efcb3740d312f90b2c7008f3 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 15 Feb 2019 03:31:55 -0800 Subject: [PATCH 025/178] Bring back the lowered opacity for offline/unavailable avatars. At some point during the riot redesign, the bit of css that lowered the opacity for offline/unavailable contacts was removed. This makes it impossible to discern presence for a contact unless you hover your cursor over their avatar. It's very handy to be able to see presence at a glance without any hovering, so this PR reintroduces the lowered opacity. I've also slightly decreased the opacity level from 0.66 to 0.5 to make it slightly more noticable. --- res/css/views/rooms/_EntityTile.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/res/css/views/rooms/_EntityTile.scss b/res/css/views/rooms/_EntityTile.scss index 80b8126b54..7fb61989f9 100644 --- a/res/css/views/rooms/_EntityTile.scss +++ b/res/css/views/rooms/_EntityTile.scss @@ -81,7 +81,6 @@ limitations under the License. color: $primary-fg-color; } -/* .mx_EntityTile_unavailable .mx_EntityTile_avatar, .mx_EntityTile_unavailable .mx_EntityTile_name, .mx_EntityTile_unavailable .mx_EntityTile_name_hover, @@ -89,7 +88,7 @@ limitations under the License. .mx_EntityTile_offline_beenactive .mx_EntityTile_name, .mx_EntityTile_offline_beenactive .mx_EntityTile_name_hover { - opacity: 0.66; + opacity: 0.5; } .mx_EntityTile_offline_neveractive .mx_EntityTile_avatar, @@ -105,7 +104,6 @@ limitations under the License. { opacity: 0.25; } -*/ .mx_EntityTile_subtext { font-size: 11px; From dedaf0f5a2288705673a6cf70dd577519f37d8f3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 15 Feb 2019 12:36:54 +0100 Subject: [PATCH 026/178] disable lazy list rendering if extraTiles are provided --- src/components/structures/RoomSubList.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index f7f74da728..d72f84b47f 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -282,7 +282,7 @@ const RoomSubList = React.createClass({ this.setState({scrollTop: this.refs.scroller.getScrollTop()}); }, - _getRenderItems: function() { + _getList: function() { // try our best to not create a new array // because LazyRenderList rerender when the items prop // is not the same object as the previous value @@ -296,6 +296,12 @@ const RoomSubList = React.createClass({ return list.concat(extraTiles); }, + _canUseLazyListRendering() { + // for now disable lazy rendering as they are already rendered tiles + // not rooms like props.list we pass to LazyRenderList + return !this.props.extraTiles || !this.props.extraTiles.length; + }, + render: function() { const len = this.props.list.length + this.props.extraTiles.length; const isCollapsed = this.state.hidden && !this.props.forceExpand; @@ -310,7 +316,7 @@ const RoomSubList = React.createClass({ return
{this._getHeaderJsx(isCollapsed)}
; - } else { + } else if (this._canUseLazyListRendering()) { return
{this._getHeaderJsx(isCollapsed)} @@ -319,7 +325,16 @@ const RoomSubList = React.createClass({ height={ this.state.scrollerHeight } renderItem={ this.makeRoomTile } itemHeight={34} - items={this._getRenderItems()} /> + items={ this.props.list } /> + +
; + } else { + const roomTiles = this.props.list.map(r => this.makeRoomTile(r)); + const tiles = roomTiles.concat(this.props.extraTiles); + return
+ {this._getHeaderJsx(isCollapsed)} + + { tiles }
; } From c39c0e48344aea9b9776e2c6578c197b768c5ea0 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 15 Feb 2019 12:37:22 +0100 Subject: [PATCH 027/178] fix for the ... button not being aligned for group invite tiles --- res/css/views/rooms/_RoomTile.scss | 4 ++++ src/components/views/groups/GroupInviteTile.js | 1 + 2 files changed, 5 insertions(+) diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index 6b2e2573e5..f2557f7849 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -191,3 +191,7 @@ limitations under the License. .mx_RoomTile.mx_RoomTile_transparent:focus { background-color: $roomtile-transparent-focused-color; } + +.mx_GroupInviteTile .mx_RoomTile_name { + flex: 1; +} diff --git a/src/components/views/groups/GroupInviteTile.js b/src/components/views/groups/GroupInviteTile.js index 9e28ff5adf..44441f4754 100644 --- a/src/components/views/groups/GroupInviteTile.js +++ b/src/components/views/groups/GroupInviteTile.js @@ -150,6 +150,7 @@ export default React.createClass({ const classes = classNames('mx_RoomTile mx_RoomTile_highlight', { 'mx_RoomTile_menuDisplayed': this.state.menuDisplayed, 'mx_RoomTile_selected': this.state.selected, + 'mx_GroupInviteTile': true, }); return ( From 05ddee6a6be247096e173c4f8bcb6b682860b46c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 15 Feb 2019 12:56:11 +0100 Subject: [PATCH 028/178] remove dead code --- src/components/structures/RoomSubList.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index d72f84b47f..78cc5bd58f 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -282,20 +282,6 @@ const RoomSubList = React.createClass({ this.setState({scrollTop: this.refs.scroller.getScrollTop()}); }, - _getList: function() { - // try our best to not create a new array - // because LazyRenderList rerender when the items prop - // is not the same object as the previous value - const {list, extraTiles} = this.props; - if (!extraTiles || !extraTiles.length) { - return list; - } - if (!list || list.length) { - return extraTiles; - } - return list.concat(extraTiles); - }, - _canUseLazyListRendering() { // for now disable lazy rendering as they are already rendered tiles // not rooms like props.list we pass to LazyRenderList From 5e3c598d315ab3a4ba3a1e1d1361a9f33b70de4d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 15 Feb 2019 13:52:48 +0100 Subject: [PATCH 029/178] Prepare changelog for v1.0.1 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d52105f9f9..6e2d021254 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +Changes in [1.0.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.0.1) (2019-02-15) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.0.0...v1.0.1) + + * Fix community invites crashing the app + [\#2650](https://github.com/matrix-org/matrix-react-sdk/pull/2650) + Changes in [1.0.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.0.0) (2019-02-14) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.0.0-rc.2...v1.0.0) From c8fa30d8eee722a2b68ce0008d7e8a73e540ef35 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 15 Feb 2019 13:52:48 +0100 Subject: [PATCH 030/178] v1.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0b7646dda7..fba17bb9c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "1.0.0", + "version": "1.0.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 91c91c5ca9cec166d90b71211a4f14b61ffcdf93 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 15 Feb 2019 15:14:59 +0000 Subject: [PATCH 031/178] Restore previous redacted message look in dark theme --- res/themes/dark/css/_dark.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index c0d0f15613..f3bb483e30 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -103,6 +103,10 @@ $panel-divider-color: $header-panel-border-color; $widget-menu-bar-bg-color: $search-bg-color; +// event redaction +$event-redacted-fg-color: #606060; +$event-redacted-border-color: #000000; + // event timestamp $event-timestamp-color: $text-secondary-color; From 3f8ff77b7e9163547a56aca75f743a7743c6361b Mon Sep 17 00:00:00 2001 From: Ben Parsons Date: Fri, 15 Feb 2019 15:55:16 +0000 Subject: [PATCH 032/178] make mx_SenderProfile inline-block, stops accidental name inserting --- res/css/views/rooms/_EventTile.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 24a23a0f98..848c2aa3ff 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -45,14 +45,14 @@ limitations under the License. .mx_EventTile .mx_SenderProfile { color: $primary-fg-color; font-size: 14px; - display: block; /* anti-zalgo, with overflow hidden */ + display: inline-block; /* anti-zalgo, with overflow hidden */ overflow-y: hidden; cursor: pointer; padding-left: 65px; /* left gutter */ padding-bottom: 0px; padding-top: 0px; margin: 0px; - line-height: 22px; + line-height: 17px; } .mx_EventTile .mx_SenderProfile .mx_Flair { From 1e01f1b52a97a00bd80e24dd0b11ce573f681aad Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 15 Feb 2019 09:47:38 -0600 Subject: [PATCH 033/178] Fix typo "Scisors" -> "Scissors" Signed-off-by: Aaron Raimist --- src/components/views/verification/VerificationShowSas.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/verification/VerificationShowSas.js b/src/components/views/verification/VerificationShowSas.js index bca68e92d3..a2531800e5 100644 --- a/src/components/views/verification/VerificationShowSas.js +++ b/src/components/views/verification/VerificationShowSas.js @@ -140,7 +140,7 @@ _td("Light bulb"); _td("Book"); _td("Pencil"); _td("Paperclip"); -_td("Scisors"); +_td("Scissors"); _td("Padlock"); _td("Key"); _td("Hammer"); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 10baa72164..9afcc07147 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -382,7 +382,7 @@ "Book": "Book", "Pencil": "Pencil", "Paperclip": "Paperclip", - "Scisors": "Scisors", + "Scissors": "Scissors", "Padlock": "Padlock", "Key": "Key", "Hammer": "Hammer", From eb908dbd0d3b1ebcc5b246823a701ab1fe4a8db9 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 15 Feb 2019 21:28:26 -0600 Subject: [PATCH 034/178] Make pre use the same text color as code Signed-off-by: Aaron Raimist --- res/css/views/rooms/_EventTile.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 6a847e58bc..d2a2afbba0 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -408,7 +408,12 @@ limitations under the License. .mx_EventTile_content .markdown-body code { // deliberate constants as we're behind an invert filter background-color: #f8f8f8; - color: #333; +} + +.mx_EventTile_content .markdown-body { + pre, code { + color: #333; + } } .mx_EventTile_pre_container { From 459f9d4fbcef354dff0cc3a04829290eebf3aeb7 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 15 Feb 2019 21:56:42 -0600 Subject: [PATCH 035/178] Set h3-color in dark theme This is things like "INVITED" in memberlist Signed-off-by: Aaron Raimist --- res/themes/dark/css/_dark.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index f3bb483e30..11a6b5e728 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -67,6 +67,8 @@ $menu-selected-color: $room-highlight-color; $avatar-initial-color: #ffffff; $avatar-bg-color: $bg-color; +$h3-color: $primary-fg-color; + $dialog-background-bg-color: $header-panel-bg-color; $lightbox-background-bg-color: #000; From 53fa59f5a4fc068f9a5b79e3e57b8ccac3a8e7de Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 15 Feb 2019 23:40:23 -0700 Subject: [PATCH 036/178] Remove old debugging code The algorithm is correctly applied when these are removed. --- src/stores/RoomListStore.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 9a764cd493..b998476cc5 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -276,7 +276,7 @@ class RoomListStore extends Store { for (const key of Object.keys(this._state.lists)) { listsClone[key] = []; let pushedEntry = false; - const hasRoom = !!this._state.lists[key].find((e) => e.room.roomId === room.roomId); + const hasRoom = this._state.lists[key].some((e) => e.room.roomId === room.roomId); // We track where the boundary within listsClone[key] is just in case our timestamp // ordering fails. If we can't stick the room in at the correct place in the category @@ -305,8 +305,8 @@ class RoomListStore extends Store { // based on most recent timestamp. const changedBoundary = entryCategoryIndex > targetCategoryIndex; const currentCategory = entryCategoryIndex === targetCategoryIndex; - if (changedBoundary || (false && currentCategory && targetTimestamp >= entryTimestamp)) { - if (changedBoundary && false) { + if (changedBoundary || (currentCategory && targetTimestamp >= entryTimestamp)) { + if (changedBoundary) { // If we changed a boundary, then we've gone too far - go to the top of the last // section instead. listsClone[key].splice(desiredCategoryBoundaryIndex, 0, {room, category}); From cb15bc968ccffcc3316869824fa9e80f8a17961d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 15 Feb 2019 23:41:48 -0700 Subject: [PATCH 037/178] Remove excessive dispatch binding --- src/stores/RoomListStore.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index b998476cc5..deac557fbe 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -55,7 +55,6 @@ class RoomListStore extends Store { super(dis); this._init(); - this.__onDispatch = this.__onDispatch.bind(this); this._getManualComparator = this._getManualComparator.bind(this); this._recentsComparator = this._recentsComparator.bind(this); } From 9bc0ae7fc51b27e782d1df103a8e0030875f10d0 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Sat, 16 Feb 2019 12:28:35 -0600 Subject: [PATCH 038/178] Add comment Signed-off-by: Aaron Raimist --- res/css/views/rooms/_EventTile.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index d2a2afbba0..3880c480de 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -412,6 +412,7 @@ limitations under the License. .mx_EventTile_content .markdown-body { pre, code { + // deliberate constants as we're behind an invert filter color: #333; } } From 9318c4ec787131966ca28d4f016b42fb704b107a Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 15 Feb 2019 20:26:27 -0600 Subject: [PATCH 039/178] Update dark theme bg-color to show hover effect on messages Signed-off-by: Aaron Raimist --- res/themes/dark/css/_dark.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 11a6b5e728..5b4b791804 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -38,7 +38,7 @@ $tagpanel-bg-color: $base-color; $selected-color: $room-highlight-color; // selected for hoverover & selected event tiles -$event-selected-color: $search-bg-color; +$event-selected-color: #111316; // used for the hairline dividers in RoomView $primary-hairline-color: $header-panel-border-color; From fa3662257dbaa41118601af2a8c3cf4733ea4c99 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 15 Feb 2019 17:20:02 +0000 Subject: [PATCH 040/178] Tweak widget bar color for dark theme --- res/themes/dark/css/_dark.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 5b4b791804..1ffd337a3b 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -103,7 +103,7 @@ $roomtile-transparent-focused-color: rgba(0, 0, 0, 0.1); $panel-divider-color: $header-panel-border-color; -$widget-menu-bar-bg-color: $search-bg-color; +$widget-menu-bar-bg-color: $header-panel-bg-color; // event redaction $event-redacted-fg-color: #606060; From c490606f155aa0d613621d61985df99ee142e214 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 15 Feb 2019 17:34:39 +0000 Subject: [PATCH 041/178] Revert back to previous base color on dark theme --- res/themes/dark/css/_dark.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 1ffd337a3b..6f32157c48 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -1,7 +1,7 @@ // unified palette // try to use these colors when possible $bg-color: #181b21; -$base-color: #1b1f25; +$base-color: #15171b; $base-text-color: #edf3ff; $header-panel-bg-color: #22262e; $header-panel-border-color: #000000; From 402992cee457655c51583d63ec1f2fe5f27cfe1a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 15 Feb 2019 18:16:10 +0000 Subject: [PATCH 042/178] Adjust add room button for dark theme --- res/css/structures/_MyGroups.scss | 4 ++-- res/css/structures/_RoomSubList.scss | 21 +++++++++++++++------ res/themes/dark/css/_dark.scss | 3 ++- res/themes/light/css/_light.scss | 3 ++- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/res/css/structures/_MyGroups.scss b/res/css/structures/_MyGroups.scss index b3a5c4f473..4428eadc48 100644 --- a/res/css/structures/_MyGroups.scss +++ b/res/css/structures/_MyGroups.scss @@ -49,11 +49,11 @@ limitations under the License. height: 40px; width: 40px; border-radius: 20px; - background-color: $roomheader-addroom-color; + background-color: $roomheader-addroom-bg-color; position: relative; &:before { - background-color: $accent-fg-color; + background-color: $roomheader-addroom-fg-color; mask: url('$(res)/img/icons-create-room.svg'); mask-repeat: no-repeat; mask-position: center; diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index f76df1f683..235e636c35 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -83,15 +83,24 @@ limitations under the License. } .mx_RoomSubList_addRoom { - background-color: $roomheader-addroom-color; - color: $roomsublist-background; - background-image: url('$(res)/img/icons-room-add.svg'); - background-repeat: no-repeat; - background-position: center; + background-color: $roomheader-addroom-bg-color; border-radius: 10px; // 16/2 + 2 padding height: 16px; flex: 0 0 16px; - background-clip: content-box; + position: relative; + + &:before { + background-color: $roomheader-addroom-fg-color; + mask: url('$(res)/img/icons-room-add.svg'); + mask-repeat: no-repeat; + mask-position: center; + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + } } .mx_RoomSubList_badgeHighlight { diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 6f32157c48..535e43e93e 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -82,7 +82,8 @@ $settings-subsection-fg-color: $text-secondary-color; $topleftmenu-color: $text-primary-color; $roomheader-color: $text-primary-color; -$roomheader-addroom-color: $header-panel-text-primary-color; +$roomheader-addroom-bg-color: #3c4556; // $search-placeholder-color at 0.5 opacity +$roomheader-addroom-fg-color: $text-primary-color; $tagpanel-button-color: $header-panel-text-primary-color; $roomheader-button-color: $header-panel-text-primary-color; $groupheader-button-color: $header-panel-text-primary-color; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 7bd84d6191..0eb136e5d9 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -145,7 +145,8 @@ $rte-group-pill-color: #aaa; $topleftmenu-color: #212121; $roomheader-color: #45474a; -$roomheader-addroom-color: #91A1C0; +$roomheader-addroom-bg-color: #91A1C0; +$roomheader-addroom-fg-color: $accent-fg-color; $tagpanel-button-color: #91A1C0; $roomheader-button-color: #91A1C0; $groupheader-button-color: #91A1C0; From 52bd1edd7285d9544d97a37c20ec8e70b9a767ff Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 18 Feb 2019 11:41:23 +0000 Subject: [PATCH 043/178] Tweak room and group pill color for dark theme --- res/themes/dark/css/_dark.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 535e43e93e..31e9aa7434 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -25,6 +25,8 @@ $focus-bg-color: $room-highlight-color; $mention-user-pill-bg-color: $warning-color; $other-user-pill-bg-color: $room-highlight-color; +$rte-room-pill-color: $room-highlight-color; +$rte-group-pill-color: $room-highlight-color; // informational plinth $info-plinth-bg-color: $header-panel-bg-color; From e16f29b1e03f514860c26f30615b3c8085177391 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 18 Feb 2019 12:14:55 +0000 Subject: [PATCH 044/178] Use darker color of photo lightboxes for dialogs on dark theme --- res/themes/dark/css/_dark.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 31e9aa7434..3122951034 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -71,6 +71,11 @@ $avatar-bg-color: $bg-color; $h3-color: $primary-fg-color; +$dialog-title-fg-color: #454545; +$dialog-backdrop-color: #000; +$dialog-shadow-color: rgba(0, 0, 0, 0.48); +$dialog-close-fg-color: #9fa9ba; + $dialog-background-bg-color: $header-panel-bg-color; $lightbox-background-bg-color: #000; From 3475b6faedea2fe4bf919cf8a4cca205c1312a15 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 18 Feb 2019 12:19:46 +0000 Subject: [PATCH 045/178] Remove light styles that duplicate common --- res/themes/light/css/_light.scss | 35 -------------------------------- 1 file changed, 35 deletions(-) diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 0eb136e5d9..e13784b4fa 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -317,41 +317,6 @@ $panel-gradient: rgba(242, 245, 248, 0), rgba(242, 245, 248, 1); } } -input[type=text].mx_textinput_icon, -input[type=search].mx_textinput_icon { - padding-left: 36px; - background-repeat: no-repeat; - background-position: 10px center; -} - -// FIXME THEME - Tint by CSS rather than referencing a duplicate asset -input[type=text].mx_textinput_icon.mx_textinput_search, -input[type=search].mx_textinput_icon.mx_textinput_search { - background-image: url('$(res)/img/feather-icons/search-input.svg'); -} - -// dont search UI as not all browsers support it, -// we implement it ourselves where needed instead -input[type=search]::-webkit-search-decoration, -input[type=search]::-webkit-search-cancel-button, -input[type=search]::-webkit-search-results-button, -input[type=search]::-webkit-search-results-decoration { - display: none; -} - -.input[type=text]::-webkit-input-placeholder, -.input[type=text]::-moz-placeholder, -.input[type=search]::-webkit-input-placeholder, -.input[type=search]::-moz-placeholder { - color: #a5aab2; -} - -// Override Firefox's UA style so we get a consistent look across browsers -input::placeholder, -textarea::placeholder { - opacity: initial; -} - // ***** Mixins! ***** @define-mixin mx_DialogButton { From 96f1538420a93c3d21145c9e0941f3e2ac9540ad Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 18 Feb 2019 12:20:47 +0000 Subject: [PATCH 046/178] Remove bad syntax for input placeholder styles --- res/css/_common.scss | 7 ------- 1 file changed, 7 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index 4399fb224e..c6c924c6c4 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -82,13 +82,6 @@ input[type=search]::-webkit-search-results-decoration { display: none; } -.input[type=text]::-webkit-input-placeholder, -.input[type=text]::-moz-placeholder, -.input[type=search]::-webkit-input-placeholder, -.input[type=search]::-moz-placeholder { - color: #a5aab2; -} - // Override Firefox's UA style so we get a consistent look across browsers input::placeholder, textarea::placeholder { From 98a3ecbcb569300ce6fbf4c2563da6a0c96fd65f Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 18 Feb 2019 13:34:25 +0000 Subject: [PATCH 047/178] Tweak placeholder styles to also affect member search --- res/themes/light/css/_light.scss | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index e13784b4fa..e52f62b044 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -286,9 +286,12 @@ $panel-gradient: rgba(242, 245, 248, 0), rgba(242, 245, 248, 1); flex: 1; color: $primary-fg-color; } - input::placeholder { - color: $roomsublist-label-fg-color; - } + } + + :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text]::placeholder, + :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search]::placeholder, + .mx_textinput input::placeholder { + color: $roomsublist-label-fg-color; } } From fdd88b60a6f996951b00e7af7032d782e6645079 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 18 Feb 2019 13:43:18 +0000 Subject: [PATCH 048/178] Lift panel input styling up to common --- res/css/_common.scss | 65 ++++++++++++++++++++++++++++++ res/themes/dark/css/_dark.scss | 49 +---------------------- res/themes/light/css/_light.scss | 68 -------------------------------- 3 files changed, 66 insertions(+), 116 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index c6c924c6c4..4769df1c33 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -104,6 +104,71 @@ textarea { color: $primary-fg-color; } +// .mx_textinput is a container for a text input +// + some other controls like buttons, ... +// it has the appearance of a text box so the controls +// appear to be part of the input + +.mx_Dialog, .mx_MatrixChat { + :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text], + :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search], + .mx_textinput { + display: block; + box-sizing: border-box; + background-color: transparent; + color: $input-darker-fg-color; + border-radius: 4px; + border: 1px solid #c1c1c1; + // these things should probably not be defined + // globally + margin: 9px; + flex: 0 0 auto; + } + + .mx_textinput { + display: flex; + align-items: center; + + > input[type=text], + > input[type=search] { + border: none; + flex: 1; + color: $primary-fg-color; + } + } + + :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text]::placeholder, + :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search]::placeholder, + .mx_textinput input::placeholder { + color: $roomsublist-label-fg-color; + } +} + +/*** panels ***/ +.dark-panel { + background-color: $secondary-accent-color; +} + +.dark-panel { + :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text], + :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search], + .mx_textinput { + color: $input-darker-fg-color; + background-color: $input-darker-bg-color; + border: none; + } +} + +.light-panel { + :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text], + :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search], + .mx_textinput { + color: $input-lighter-fg-color; + background-color: $input-lighter-bg-color; + border: none; + } +} + /* Prevent ugly dotted highlight around selected elements in Firefox */ ::-moz-focus-inner { border: 0; diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 3122951034..8542e617c0 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -141,59 +141,12 @@ $room-warning-bg-color: $header-panel-bg-color; $panel-gradient: rgba(34, 38, 46, 0), rgba(34, 38, 46, 1); -/*** form elements ***/ - -// .mx_textinput is a container for a text input -// + some other controls like buttons, ... -// it has the appearance of a text box so the controls -// appear to be part of the input - -.mx_Dialog, .mx_MatrixChat { - - :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text], - :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search], - .mx_textinput { - background-color: transparent; - color: $input-darker-fg-color; - border: 1px solid #c1c1c1; - } - - .mx_textinput { - > input[type=text], - > input[type=search] { - color: $primary-fg-color; - } - input::placeholder { - color: $roomsublist-label-fg-color; - } - } -} - /*** panels ***/ + .dark-panel { background-color: $header-panel-bg-color; } -.dark-panel { - :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text], - :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search], - .mx_textinput { - color: $input-darker-fg-color; - background-color: $input-darker-bg-color; - border: none; - } -} - -.light-panel { - :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text], - :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search], - .mx_textinput { - color: $input-lighter-fg-color; - background-color: $input-lighter-bg-color; - border: none; - } -} - // ***** Mixins! ***** @define-mixin mx_DialogButton { diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index e52f62b044..2b3f3a33ca 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -252,74 +252,6 @@ $authpage-secondary-color: #61708b; $panel-gradient: rgba(242, 245, 248, 0), rgba(242, 245, 248, 1); -/*** form elements ***/ - -// .mx_textinput is a container for a text input -// + some other controls like buttons, ... -// it has the appearance of a text box so the controls -// appear to be part of the input - -.mx_Dialog, .mx_MatrixChat { - - :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text], - :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search], - .mx_textinput { - display: block; - box-sizing: border-box; - background-color: transparent; - color: $input-darker-fg-color; - border-radius: 4px; - border: 1px solid #c1c1c1; - // these things should probably not be defined - // globally - margin: 9px; - flex: 0 0 auto; - } - - .mx_textinput { - display: flex; - align-items: center; - - > input[type=text], - > input[type=search] { - border: none; - flex: 1; - color: $primary-fg-color; - } - } - - :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text]::placeholder, - :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search]::placeholder, - .mx_textinput input::placeholder { - color: $roomsublist-label-fg-color; - } -} - -/*** panels ***/ -.dark-panel { - background-color: $secondary-accent-color; -} - -.dark-panel { - :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text], - :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search], - .mx_textinput { - color: $input-darker-fg-color; - background-color: $input-darker-bg-color; - border: none; - } -} - -.light-panel { - :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text], - :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search], - .mx_textinput { - color: $input-lighter-fg-color; - background-color: $input-lighter-bg-color; - border: none; - } -} - // ***** Mixins! ***** @define-mixin mx_DialogButton { From 19977b052fc6fe568106d78d717cca1281c1d5df Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 18 Feb 2019 13:50:17 +0000 Subject: [PATCH 049/178] Add new var for dark panel bg color --- res/css/_common.scss | 2 +- res/themes/dark/css/_dark.scss | 7 +------ res/themes/light/css/_light.scss | 1 + 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index 4769df1c33..fd93c8c967 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -146,7 +146,7 @@ textarea { /*** panels ***/ .dark-panel { - background-color: $secondary-accent-color; + background-color: $dark-panel-bg-color; } .dark-panel { diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 8542e617c0..35ba1ce53c 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -139,14 +139,9 @@ $button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color $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); -/*** panels ***/ - -.dark-panel { - background-color: $header-panel-bg-color; -} - // ***** Mixins! ***** @define-mixin mx_DialogButton { diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 2b3f3a33ca..acf2340013 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -250,6 +250,7 @@ $authpage-lang-color: #4e5054; $authpage-primary-color: #454545; $authpage-secondary-color: #61708b; +$dark-panel-bg-color: $secondary-accent-color; $panel-gradient: rgba(242, 245, 248, 0), rgba(242, 245, 248, 1); // ***** Mixins! ***** From f240efb36df501f363333890a00af0664c6d3cb7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 18 Feb 2019 16:13:24 +0100 Subject: [PATCH 050/178] get cache-busted url for languages.json through file-loader requiring it --- src/languageHandler.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/languageHandler.js b/src/languageHandler.js index 8735150d20..e462f05eda 100644 --- a/src/languageHandler.js +++ b/src/languageHandler.js @@ -338,8 +338,9 @@ export function getCurrentLanguage() { function getLangsJson() { return new Promise((resolve, reject) => { + const url = require("../../riot-web/webapp/i18n/languages.json"); request( - { method: "GET", url: i18nFolder + 'languages.json' }, + { method: "GET", url }, (err, response, body) => { if (err || response.status < 200 || response.status >= 300) { reject({err: err, response: response}); From 33858c8aff26f4198fed488be3e7a6d43d80d005 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 18 Feb 2019 16:26:04 +0100 Subject: [PATCH 051/178] move this path to webpack config file otherwise react-sdk wouldn't be able to build anymore without riot-web in a specific location --- src/languageHandler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languageHandler.js b/src/languageHandler.js index e462f05eda..33f703c204 100644 --- a/src/languageHandler.js +++ b/src/languageHandler.js @@ -338,9 +338,9 @@ export function getCurrentLanguage() { function getLangsJson() { return new Promise((resolve, reject) => { - const url = require("../../riot-web/webapp/i18n/languages.json"); + // LANGUAGES_FILE is a webpack compile-time define, see webpack config request( - { method: "GET", url }, + { method: "GET", url: require(LANGUAGES_FILE) }, (err, response, body) => { if (err || response.status < 200 || response.status >= 300) { reject({err: err, response: response}); From f51d25c394b04458e9ff4b0a02dd0907d11ea74d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 18 Feb 2019 16:36:11 +0100 Subject: [PATCH 052/178] declare LANGUAGES_FILE as global in eslint config --- .eslintrc.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.eslintrc.js b/.eslintrc.js index 8474cd86d7..fdf0bb351e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -15,6 +15,9 @@ module.exports = { "flowtype", "babel" ], + globals: { + LANGUAGES_FILE: "readonly", + }, env: { es6: true, }, From 09ed795c7966718fa1f7c377d782ef6e67f32bc3 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 18 Feb 2019 15:49:36 +0000 Subject: [PATCH 053/178] Allow theming member info minimise button --- res/css/views/rooms/_MemberInfo.scss | 4 ++++ src/components/views/rooms/MemberInfo.js | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/res/css/views/rooms/_MemberInfo.scss b/res/css/views/rooms/_MemberInfo.scss index 707c186518..60faf3ef13 100644 --- a/res/css/views/rooms/_MemberInfo.scss +++ b/res/css/views/rooms/_MemberInfo.scss @@ -34,6 +34,10 @@ limitations under the License. height: 16px; padding: 10px 15px; cursor: pointer; + mask-image: url('$(res)/img/minimise.svg'); + mask-repeat: no-repeat; + mask-position: center; + background-color: $rightpanel-button-color; } .mx_MemberInfo_name h2 { diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index f4c600af8d..c7ea54a4c8 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -983,9 +983,10 @@ module.exports = withMatrixClient(React.createClass({ return (
- - {_t('Close')} - + { e2eIconElement } { memberName }
From 6bf8269bcd03f26b2cc33910939f5cf6d972601a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 18 Feb 2019 17:26:09 +0100 Subject: [PATCH 054/178] riot-web is a subdirectory when running the tests --- scripts/travis/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/travis/build.sh b/scripts/travis/build.sh index a353e38a06..df192d70b2 100755 --- a/scripts/travis/build.sh +++ b/scripts/travis/build.sh @@ -23,5 +23,5 @@ ln -s "$REACT_SDK_DIR/node_modules/matrix-js-sdk" node_modules/matrix-js-sdk rm -r node_modules/matrix-react-sdk ln -s "$REACT_SDK_DIR" node_modules/matrix-react-sdk -npm run build +RIOT_LANGUAGES_FILE="../riot-web/webapp/i18n/languages.json" npm run build popd From 5fb990eb5f7d429e06a05da8a96a97d3166cb277 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 18 Feb 2019 18:26:40 +0000 Subject: [PATCH 055/178] Convert Markdown button to SVG mask This makes it easier to theme via CSS, which helps with the dark theme. --- res/css/views/rooms/_MessageComposer.scss | 19 ++++++++++-- res/img/button-md-false.png | Bin 569 -> 0 bytes res/img/button-md-false.svg | 29 ------------------ res/img/button-md-false@2x.png | Bin 1086 -> 0 bytes res/img/button-md-false@3x.png | Bin 1548 -> 0 bytes res/img/button-md-true.png | Bin 483 -> 0 bytes res/img/button-md-true.svg | 14 --------- res/img/button-md-true@2x.png | Bin 906 -> 0 bytes res/img/button-md-true@3x.png | Bin 1201 -> 0 bytes res/img/markdown.svg | 3 ++ src/components/views/rooms/MessageComposer.js | 17 +++++----- .../views/rooms/MessageComposerInput.js | 14 ++++++--- 12 files changed, 39 insertions(+), 57 deletions(-) delete mode 100644 res/img/button-md-false.png delete mode 100644 res/img/button-md-false.svg delete mode 100644 res/img/button-md-false@2x.png delete mode 100644 res/img/button-md-false@3x.png delete mode 100644 res/img/button-md-true.png delete mode 100644 res/img/button-md-true.svg delete mode 100644 res/img/button-md-true@2x.png delete mode 100644 res/img/button-md-true@3x.png create mode 100644 res/img/markdown.svg diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index db8eb4995d..89115669d3 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -255,14 +255,29 @@ limitations under the License. } .mx_MessageComposer_formatbar_markdown { + height: 17px; + width: 30px; margin-right: 64px; } .mx_MessageComposer_input_markdownIndicator { - cursor: pointer; height: 10px; + width: 12px; padding: 4px 4px 4px 0; - opacity: 0.8; +} + +.mx_MessageComposer_formatbar_markdown, +.mx_MessageComposer_input_markdownIndicator { + cursor: pointer; + mask-image: url('$(res)/img/markdown.svg'); + mask-size: contain; + mask-position: center; + mask-repeat: no-repeat; + background-color: $composer-button-color; + + &.mx_MessageComposer_markdownDisabled { + opacity: 0.2; + } } .mx_MatrixChat_useCompactLayout { diff --git a/res/img/button-md-false.png b/res/img/button-md-false.png deleted file mode 100644 index 6debbccc937a75e1a07a2a36518e221efb696823..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 569 zcmV-90>=G`P)Px$^GQTOR5%f>lTAwkQ51&nI5U20n$SW62`sRMghb#f7w+1H`X`irKr45x3;Gkf zOl*-LxR6$YU$_&bjn+a?upCi)&)^-#VcLWT4)@G^&w1vYJ9h*I%VaWQDwVQOybr5Z ztLsv!v=W-lX4>g=mIOiA2UlY(m&>$dv)S&fRx2alv*j%?3ASf0V>k(U=bS}ROol=s zo7?R^#O5b|BO16Tog8|d>sS??{-)E_a0d?M3&o1h=bMbj2|wM09|qjtvDJdyjQE$+*~gd3esw|dY{kdVXplNr-VQtFu;G1gY$g- zdK8I7nw3h0HZ9p~_7z8RVYl1q5~z7Q7aZLr+wHc9v67sE2`IDcCGCQ&us*)}2Ja=8+8&U!HINf2k- zBz2KxnUNcffHbn18KIN|?o`6%&*P7LM6uVb@IRUgmZaP-lh~_%7EU=y00000NkvXX Hu0mjflU)I? diff --git a/res/img/button-md-false.svg b/res/img/button-md-false.svg deleted file mode 100644 index 6414933d96..0000000000 --- a/res/img/button-md-false.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - D335F9E8-C813-47D7-B1BE-C8DEF2C8214F - Created with sketchtool. - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/img/button-md-false@2x.png b/res/img/button-md-false@2x.png deleted file mode 100644 index 497f5385d140cfda7cd24c5b79cf3871a700b017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1086 zcmV-E1i|}>P)Px&_(?=TR9FesS6@g|VHDqgbz`!J%|BC;>|v;E;Db;?U>5DAmmq?C>q&%q2#O$l zis-?hw+bJkmx_X(5=TBn3`R}d5SdbIMk+e7hat{suDSI)mwlJ-UU% zc&`=!mcwv(c=)iduWwvGH#awAx7$B42fbdeGZ+j^tVxSw$?@$h+}8=iDB945X5jSZipS%F0Fq^qk)eUMwzdqDc?tk}0*y()s}=cdHftdJa#`05oKE!n z{l8MF)Hw={EMM#F?EJ2XPuj z#7|94SzB9MXNj+50l*mmdIwvjk`uzr&(Dvb`5$L8867?H_dr#^5!r&d9iEw)v2}NM ze}L?w(<;5Fp;Ruft*ucxlms7*Mu(f4nqE;%TDTS*xw%#goZ_A8?d?q=`)5g@j}IIs z{Xc0#VZnZEWo2a?+Y<2MaM*|Y_DAN1_`;Q&$&CIBaC-Sz=;MPx6NSv;Rz%Sk_W}Mo zGY*A9Cmaq(l*#msK)_2aO;e>*00N6lYX;3?0sQmj<>gI0vreZo2EBj@dZ9#^ot!EH zM-yGdD0)jxf#wjtGLYHx^6sX9T&CURLpF3-Djp`8Kym;iqRURVM*uyV5bof>-0i`!ZgI77X}nzNr}0@w6yI01q7Z{+`rz{BLDyZ07*qoM6N<$ Ef*aWjwEzGB diff --git a/res/img/button-md-false@3x.png b/res/img/button-md-false@3x.png deleted file mode 100644 index 1184e6b351ec17fb2e1f8cdaa866ea3d81d5cb07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1548 zcmV+n2J`ueP)Px)#z{m$RA>e5T3t+IMHIfbEzt58HUUzK6=jjlVvQRkAgq56ZcKRa!A%JA6I zqdpj4HmQjTlI)8QMq;eClwXk{!G`xHTO4Om186mfcb`X&JBE;JzI{sNeR zmX?+taddRFv8JZxZ@GXdiUNR=lTZBn>gwvFrqa^VCs|JkjFOTP!DKSAb(a4ets*c%VO_?7AMj->9?;+8@#Hk_78{sKjT3UJ&1ltMzvUJtpYM z3KU(YPknv;T{!s~&p4I_nJAXLRwY1K*?iV@0fig@FgLfix4*_TO6SiDb#-;&bOP(^ z>xP}39f5wB0_jB4^48YYjM?hZ4HQ@ycez||!;o>FwTO;u0B(%OV`%`^h?A>8zy1Av zmZhn_v9V#uR*#;b2!n%z`?IsNFYfN{rsN&~@*M#AM3Yx}6xJ0K@}sM(>mCkO&q$*T zKq#>~^XQ}Iu5|~6ENE+M^C34}L4G+FiB*nTPC%hf91h1fi;IhoWlF4agO0*`&Om{4 z@s5s;e~yBaT5UKqD8s|UWkW+lm0CI;A0NM$FGB3;Ir=!;B3nfN%P*%8^?%_ZAbH&94vadGjDxw*M>YzGH* z(d+eogmceJ9G)r?qU9X`^MNX1j!+;FI0GHiI3$`-3et}ws{i*CeDwDCe7=;=2on<% zmjKv@JOi}Q;L!E?`S~}rv>{nq3^q46eUV6{9NT|E`q9~l_vjia)B#4IysE0|HgZft z_d#O)k=J=PG{(U@?`Z)c*<6A`BT>nY2#pY&VQtL1j%YMgR#y5v z9#1t#iHMc(bM$|iNe$BWYx$QYmCvqtw;`OexM*caXQ(#4aMyyKu?( zE~|(7#imi!RdCKhA;Ty_$RIrdksJmv*5>AB4<%HhVd=ku{_guIP7y)_qvlb)qs50K zTPPF?l2U|hQaeJ}uBIayAQtFKjBYp_7O;m%>Vw?FYV@%(0Onk0XXmeokk0^!o8?iR z0HJ|V`=|y53hx4#gx;p6CINc}wSDm9&O+BZF)Au51e~3CGFoXDF+dl$wzg7i%jVAl z$q~|O!BLc_Xk-&9C3RZuv_4uQC8=9!ZYt4}4LKEZM5&z;ouej6qBfMj=q4cBl`bn# zXf*VC2o!AQ^ir45L%?JU2q`#G(?>644anSygr}Fn9LhL*b~a2- yPPSB6S3eF$MP4m@fSpqgfxljyq5K30b^RY({^*N^6Hc%I0000Px$ok>JNR5%f}Q@=~YP!R4*qDv@tQgBgpu#<=o2mb*jZDSmCaQDZigR9Wpx;VJ_ zCj?Yd(o(nn8C}Iq$0}-)#_!TxcuzVg#0SZD_uakk-Q7z>k|d(hXv`aiF;yyP#c@1r zx7)9xR;%6NfZc+0a~Vz3&NO&CIa8o98jTzcRnr0gv2;aP*Y!ipzAA*+#}o63uy2UZ zVqX&fB=PUeGa76V;v$|qoz4K}0C%wfyu{PDEbFw{Y(}t4wrwYbw9s|v`#$9>Jo&Vg z%E#3B9RxuO60PP>s zCJ3*vCoN}Q|5@tzVAi3$Kk--c6To{Ir=xh^QIf|k#A;$?7aa3#9>-9x!T$7W) zSHPbThw5~WA#VvC^x%`|Ec1*){Uei&w|7h8Fc$WW4VBFgcg+%DVcwCp!bF? Zt#3CFoq7f*3)uhw002ovPDHLkV1lx_+k^lB diff --git a/res/img/button-md-true.svg b/res/img/button-md-true.svg deleted file mode 100644 index 2acc4f675c..0000000000 --- a/res/img/button-md-true.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - 2A63B135-4281-4FBB-A88C-012AE22E9594 - Created with sketchtool. - - - - - - - - - \ No newline at end of file diff --git a/res/img/button-md-true@2x.png b/res/img/button-md-true@2x.png deleted file mode 100644 index ad9067f385b2fbb975c6df7a006adab817800751..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 906 zcmV;519kj~P)Px&L`g(JR9Fesmpf=vQ5eU2la^>xMT0}ZL8l5na4B}Ex+o}plA(jtpjaq~RK%g9 zwnM>+2t`Fid_ZxK^g(Jae~KP!xe-|ufpCX)}~TWS@w z;4%y&13wlHhtCG<&wHjDc)A8ghHV= zOm>W9Sv?`TsMqT~QVd%6`PaNH;A-;u`T1Fw%QYrI0R5dZ@&h_9y49ezfh$n0t*z$) z99BH^#N6E6E5)zos0ui5n+)~b59rgJ!2Q((EOS`~t{!4TLqi@3q%^*Av95@$XkTAn zlP>>H-ZJpT#l@y*Goa%}$CI02wq4*Ri z6AjR=Ua-^G3rd*5!NG=DEEa_QqU(!!Kty~#-+{@=$waYDWO8p6p3T?-|BP?5=!9C{ zaR~tYL{CpoyU2L)z#IN zOUMZfko=@dW@X@en1$Yk=KjQ(#RLKYFE2pVwYHhrm984#bxhjKH_cJ*hnB4SQj-#M zlm%Zhv#ZnKRHpISV`dj!Rlrp+iQA~zr?7=iBS|gNvz*$n?n_RC)3^gYr0dQ2s)4Iy zOyD+4YE?%$jB{$o#h-;-CTG9M<*2Q$QZCV7*luzWms!LPK%cRi$6&=P9!wHQ87#xc zM25Oayi=3S*l)zWFEZ>RuF}b>7op{ntoWwIlV}8S9DLZ6_e_YZ4 zS98>nz_|~(+FPo*?W_QZd$dHIyRKI7t0^;}KLThr_p?i9383(!*jQ62H85QI3>aT~ gJf4d)GczB41OD$WoQHsRivR!s07*qoM6N<$g6lN16951J diff --git a/res/img/button-md-true@3x.png b/res/img/button-md-true@3x.png deleted file mode 100644 index d615867dc4604e2f898fb0f15e4e2b19e5bd0bf5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1201 zcmV;i1Wx;jP)Px(Ye_^wRA>e5noCGjQ5eUaM^>7c^rSWg8AJ<%#%L2E5rMCqXre`i77;`S6>XwG z6c;UeLPgNV$_NApQHK&*v=RhcL_sC40$WHyj~SEH>34>%-VW#FnVCB|dFR66p7XuV z`TqAi-~H|*FE4lG5HLZ&V+n;qp{3*FiAJc0j> zhp)Phj*hQ7nQWmu0*Y{k?KR}}dQWN`BhusbJv}{dHTz9$LSXM19muae`ShzYF33mA3u zzsQqj``ENjN!kyQc#~Gj5{0xQg<|ViQ&ZDoQ-63Dyo|!C-c)`*8JZG8apWG+C2u4ABJYZ0xJ#jh8J?FY0{7SlmXc|LS+l8X-6?IzP_ z+up;*eiNOP7pXGNq-$th_QWd^E3`0exk|^BhQ60=_Yi>hnCe(^L~tAW$F1>PXGcL1 zlQH>B;-#NRtelfgtjcv`C!ZF^$?Zx31(E1FgU&?-OG|Pbl;1NovhY(})kI>|&Yl7T z2a9Nt+S=MpbE&h2!^qs2OHSrz3KS7BH<_$Op3tH3YCiM!69sE^_5f`!yms2m_i~-A zOwSl7OymccjM>NbMsmzka#peI6o3Q(tqYlanP)Q2*rH7)Pge$~%c9X}x2QiXTfty( z^W@}Y1fJ0=O-s*ycB*)@+x*x4-P9kCiI~vD#Kan!_*p=_Gfw$10);SL!tWY8RkKi7 zCH?yb_SWg_5%!|qN$Mafg47}RIg7i}hNP4g!XA>9@~yf;))gckDJuT!oRX=llmkV9 zYz~whC<+7=B=L|HQ_x*Y0*|Qplj)Uf5s(}QkSR!SL{7OQ@!ULY=PPE+%WJ!bpm2@m z>UxDJd0tRZa85+T%oC(|h@v{(ODjcShAxB_@ + + diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 10142b2b4b..7eef868ef7 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -499,15 +499,16 @@ export default class MessageComposer extends React.Component {
{ formatButtons } -
- +
+ + onClick={this.onToggleFormattingClicked} + className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor" + src={require("../../../../res/img/icon-text-cancel.svg")} + />
; } diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index ab89e402ae..1954ca0d84 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -60,6 +60,7 @@ import ReplyPreview from "./ReplyPreview"; import RoomViewStore from '../../../stores/RoomViewStore'; import ReplyThread from "../elements/ReplyThread"; import {ContentHelpers} from 'matrix-js-sdk'; +import AccessibleButton from '../elements/AccessibleButton'; const EMOJI_UNICODE_TO_SHORTNAME = mapUnicodeToShort(); const REGEX_EMOJI_WHITESPACE = new RegExp('(?:^|\\s)(' + asciiRegexp + ')\\s$'); @@ -1582,6 +1583,11 @@ export default class MessageComposerInput extends React.Component { placeholder = undefined; } + const markdownClasses = classNames({ + mx_MessageComposer_input_markdownIndicator: true, + mx_MessageComposer_markdownDisabled: this.state.isRichTextEnabled, + }); + return (
@@ -1596,10 +1602,10 @@ export default class MessageComposerInput extends React.Component { />
- + Date: Tue, 19 Feb 2019 11:49:49 +0000 Subject: [PATCH 056/178] Tweak light theme color values --- res/themes/light/css/_light.scss | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 7bd84d6191..45bfc715a3 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -14,7 +14,7 @@ $notice-secondary-color: #61708b; $header-panel-bg-color: #f2f5f8; // typical text (dark-on-white in light skin) -$primary-fg-color: #454545; +$primary-fg-color: #2e2f32; $primary-bg-color: #ffffff; // used for dialog box text @@ -54,7 +54,7 @@ $preview-bar-bg-color: #f7f7f7; $secondary-accent-color: #f2f5f8; $tertiary-accent-color: #d3efe1; -$tagpanel-bg-color: #2e3649; +$tagpanel-bg-color: #27303a; // used by RoomDirectory permissions $plinth-bg-color: $secondary-accent-color; @@ -105,7 +105,7 @@ $avatar-bg-color: #ffffff; $h3-color: #3d3b39; -$dialog-title-fg-color: #454545; +$dialog-title-fg-color: #2e2f32; $dialog-backdrop-color: rgba(46, 48, 51, 0.38); $dialog-shadow-color: rgba(0, 0, 0, 0.48); $dialog-close-fg-color: #9fa9ba; @@ -132,7 +132,7 @@ $settings-profile-placeholder-bg-color: #e7e7e7; $settings-profile-overlay-bg-color: #000; $settings-profile-overlay-placeholder-bg-color: transparent; $settings-profile-overlay-fg-color: #fff; -$settings-profile-overlay-placeholder-fg-color: #454545; +$settings-profile-overlay-placeholder-fg-color: #2e2f32; $settings-subsection-fg-color: #61708b; $voip-decline-color: #f48080; @@ -246,7 +246,7 @@ $authpage-bg-color: #2e3649; $authpage-modal-bg-color: rgba(255, 255, 255, 0.59); $authpage-body-bg-color: #ffffff; $authpage-lang-color: #4e5054; -$authpage-primary-color: #454545; +$authpage-primary-color: #232f32; $authpage-secondary-color: #61708b; $panel-gradient: rgba(242, 245, 248, 0), rgba(242, 245, 248, 1); From 4c83d898bd558ad16cf54325958db21cdb0cf9eb Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 19 Feb 2019 13:10:10 +0000 Subject: [PATCH 057/178] Show link to login even during UI auth This gives users an escape hatch in case something goes wrong with the UI auth step, and they'd like to go somewhere else in the auth process. --- src/components/structures/auth/Registration.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 6b578f0f68..03c7645721 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -505,14 +505,9 @@ module.exports = React.createClass({ errorText =
{ err }
; } - let signIn; - if (!this.state.doingUIAuth) { - signIn = ( - - { _t('Sign in instead') } - - ); - } + const signIn = + { _t('Sign in instead') } + ; return ( From 198dee9abdb15d12fe29bfb3431eb4b931579c1c Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 19 Feb 2019 13:27:35 +0000 Subject: [PATCH 058/178] Rename default theme to light theme --- src/components/views/settings/tabs/GeneralUserSettingsTab.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/tabs/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/GeneralUserSettingsTab.js index 2364475239..fd3274c9e0 100644 --- a/src/components/views/settings/tabs/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/GeneralUserSettingsTab.js @@ -145,7 +145,7 @@ export default class GeneralUserSettingsTab extends React.Component { {_t("Theme")} - + diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9afcc07147..0f5a875f0d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -522,7 +522,7 @@ "Phone numbers": "Phone numbers", "Language and region": "Language and region", "Theme": "Theme", - "Default theme": "Default theme", + "Light theme": "Light theme", "Dark theme": "Dark theme", "Account management": "Account management", "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", From 3c156218da9b8978b5e656182c5610d05f8577f6 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 19 Feb 2019 13:57:31 +0000 Subject: [PATCH 059/178] Allow captchas on Riot desktop builds --- src/components/views/auth/CaptchaForm.js | 29 ++++++------------------ src/i18n/strings/en_EN.json | 1 - 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/src/components/views/auth/CaptchaForm.js b/src/components/views/auth/CaptchaForm.js index ca450c5df4..d3b7d0bfe5 100644 --- a/src/components/views/auth/CaptchaForm.js +++ b/src/components/views/auth/CaptchaForm.js @@ -17,7 +17,6 @@ limitations under the License. 'use strict'; import React from 'react'; -import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; @@ -61,29 +60,15 @@ module.exports = React.createClass({ } else { console.log("Loading recaptcha script..."); window.mx_on_recaptcha_loaded = () => {this._onCaptchaLoaded();}; - const protocol = global.location.protocol; + let protocol = global.location.protocol; if (protocol === "vector:") { - const warning = document.createElement('div'); - // XXX: fix hardcoded app URL. Better solutions include: - // * jumping straight to a hosted captcha page (but we don't support that yet) - // * embedding the captcha in an iframe (if that works) - // * using a better captcha lib - ReactDOM.render(_t( - "Robot check is currently unavailable on desktop - please use a web browser", - {}, - { - 'a': (sub) => { - return { sub }; - }, - }), warning); - this.refs.recaptchaContainer.appendChild(warning); - } else { - const scriptTag = document.createElement('script'); - scriptTag.setAttribute( - 'src', protocol+"//www.google.com/recaptcha/api.js?onload=mx_on_recaptcha_loaded&render=explicit", - ); - this.refs.recaptchaContainer.appendChild(scriptTag); + protocol = "https:"; } + const scriptTag = document.createElement('script'); + scriptTag.setAttribute( + 'src', `${protocol}//www.google.com/recaptcha/api.js?onload=mx_on_recaptcha_loaded&render=explicit`, + ); + this.refs.recaptchaContainer.appendChild(scriptTag); } }, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9afcc07147..57a9501c5e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1224,7 +1224,6 @@ "Sign in": "Sign in", "Login": "Login", "powered by Matrix": "powered by Matrix", - "Robot check is currently unavailable on desktop - please use a web browser": "Robot check is currently unavailable on desktop - please use a web browser", "This homeserver would like to make sure you are not a robot.": "This homeserver would like to make sure you are not a robot.", "Custom Server Options": "Custom Server Options", "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.": "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.", From 1cde4abe6a703f813e010a561ede24378b750ec9 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 19 Feb 2019 13:59:15 +0000 Subject: [PATCH 060/178] Improve text layout above captcha --- src/components/views/auth/CaptchaForm.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/auth/CaptchaForm.js b/src/components/views/auth/CaptchaForm.js index d3b7d0bfe5..bb4785f299 100644 --- a/src/components/views/auth/CaptchaForm.js +++ b/src/components/views/auth/CaptchaForm.js @@ -126,8 +126,9 @@ module.exports = React.createClass({ return (
- { _t("This homeserver would like to make sure you are not a robot.") } -
+

{_t( + "This homeserver would like to make sure you are not a robot.", + )}

{ error }
From b90e33b81b6fae54a37efafd5fdc391af11cfe08 Mon Sep 17 00:00:00 2001 From: Tomas Batalla Date: Tue, 19 Feb 2019 10:52:59 -0800 Subject: [PATCH 061/178] Fix off by one error for username colors The hash result would only have a range of 0..7, but the css of color variants is 1..8 --- src/components/views/messages/SenderProfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/messages/SenderProfile.js b/src/components/views/messages/SenderProfile.js index a8c52e06b1..0d47de4266 100644 --- a/src/components/views/messages/SenderProfile.js +++ b/src/components/views/messages/SenderProfile.js @@ -97,7 +97,7 @@ export default React.createClass({ render() { const EmojiText = sdk.getComponent('elements.EmojiText'); const {mxEvent} = this.props; - const colorNumber = hashCode(mxEvent.getSender()) % 8; + const colorNumber = (hashCode(mxEvent.getSender()) % 8) + 1; const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); const {msgtype} = mxEvent.getContent(); From 561d1f37ec076b49ed8f1094a506ad2a89c0098c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 19 Feb 2019 14:56:56 -0700 Subject: [PATCH 062/178] Stick a couple micro optimizations into the setRoomCategory hot path --- src/stores/RoomListStore.js | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index deac557fbe..81b1bb621f 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -231,14 +231,15 @@ class RoomListStore extends Store { } _roomUpdateTriggered(roomId) { - const room = this._matrixClient.getRoom(roomId); - if (!room) return; - // We don't calculate categories for sticky rooms because we have a moderate // interest in trying to maintain the category that they were last in before // being artificially flagged as IDLE. Also, this reduces the amount of time // we spend in _setRoomCategory ever so slightly. - if (this._state.stickyRoomId !== room.roomId) { + if (this._state.stickyRoomId !== roomId) { + // Micro optimization: Only look up the room if we're confident we'll need it. + const room = this._matrixClient.getRoom(roomId); + if (!room) return; + const category = this._calculateCategory(room); this._setRoomCategory(room, category); } @@ -249,7 +250,15 @@ class RoomListStore extends Store { const listsClone = {}; const targetCategoryIndex = CATEGORY_ORDER.indexOf(category); - const targetTimestamp = this._tsOfNewestEvent(room); + + // Micro optimization: Support lazily loading the last timestamp in a room + let _targetTimestamp = null; + const targetTimestamp = () => { + if (_targetTimestamp === null) { + _targetTimestamp = this._tsOfNewestEvent(room); + } + return _targetTimestamp; + }; const myMembership = room.getMyMembership(); let doInsert = true; @@ -290,7 +299,15 @@ class RoomListStore extends Store { if (LIST_ORDERS[key] === 'recent' && hasRoom && entry.room.roomId !== this._state.stickyRoomId) { const inTag = targetTags.length === 0 || targetTags.includes(key); if (!pushedEntry && doInsert && inTag) { - const entryTimestamp = this._tsOfNewestEvent(entry.room); + // Micro optimization: Support lazily loading the last timestamp in a room + let _entryTimestamp = null; + const entryTimestamp = () => { + if (_entryTimestamp === null) { + _entryTimestamp = this._tsOfNewestEvent(entry.room); + } + return _entryTimestamp; + }; + const entryCategoryIndex = CATEGORY_ORDER.indexOf(entry.category); // As per above, check if we're meeting that boundary we wanted to locate. @@ -304,7 +321,7 @@ class RoomListStore extends Store { // based on most recent timestamp. const changedBoundary = entryCategoryIndex > targetCategoryIndex; const currentCategory = entryCategoryIndex === targetCategoryIndex; - if (changedBoundary || (currentCategory && targetTimestamp >= entryTimestamp)) { + if (changedBoundary || (currentCategory && targetTimestamp() >= entryTimestamp())) { if (changedBoundary) { // If we changed a boundary, then we've gone too far - go to the top of the last // section instead. From 64103b7af4986138bf68d0a697b2117422f09173 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 19 Feb 2019 15:15:39 -0700 Subject: [PATCH 063/178] More micro optimizations to make the hot paths faster --- src/stores/RoomListStore.js | 46 ++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 81b1bb621f..47480aef85 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -282,23 +282,29 @@ class RoomListStore extends Store { let inserted = false; for (const key of Object.keys(this._state.lists)) { - listsClone[key] = []; - let pushedEntry = false; const hasRoom = this._state.lists[key].some((e) => e.room.roomId === room.roomId); + // Speed optimization: Skip the loop below if we're not going to do anything productive + if (!hasRoom || LIST_ORDERS[key] !== 'recent') { + listsClone[key] = this._state.lists[key]; + continue; + } else { + listsClone[key] = []; + } + // We track where the boundary within listsClone[key] is just in case our timestamp // ordering fails. If we can't stick the room in at the correct place in the category // grouping based on timestamp, we'll stick it at the top of the group which will be // the index we track here. let desiredCategoryBoundaryIndex = 0; let foundBoundary = false; + let pushedEntry = false; for (const entry of this._state.lists[key]) { // if the list is a recent list, and the room appears in this list, and we're not looking at a sticky // room (sticky rooms have unreliable categories), try to slot the new room in - if (LIST_ORDERS[key] === 'recent' && hasRoom && entry.room.roomId !== this._state.stickyRoomId) { - const inTag = targetTags.length === 0 || targetTags.includes(key); - if (!pushedEntry && doInsert && inTag) { + if (entry.room.roomId !== this._state.stickyRoomId) { + if (!pushedEntry && doInsert && (targetTags.length === 0 || targetTags.includes(key))) { // Micro optimization: Support lazily loading the last timestamp in a room let _entryTimestamp = null; const entryTimestamp = () => { @@ -356,7 +362,7 @@ class RoomListStore extends Store { tags = targetTags; } if (tags.length === 0) { - tags.push(myMembership === 'join' ? 'im.vector.fake.recent' : 'im.vector.fake.invite'); + tags = [myMembership === 'join' ? 'im.vector.fake.recent' : 'im.vector.fake.invite']; } } else { tags = ['im.vector.fake.archived']; @@ -388,7 +394,15 @@ class RoomListStore extends Store { }; const dmRoomMap = DMRoomMap.shared(); - const isCustomTagsEnabled = SettingsStore.isFeatureEnabled("feature_custom_tags"); + + // Speed optimization: Hitting the SettingsStore is expensive, so avoid that at all costs. + let _isCustomTagsEnabled = null; + const isCustomTagsEnabled = () => { + if (_isCustomTagsEnabled === null) { + _isCustomTagsEnabled = SettingsStore.isFeatureEnabled("feature_custom_tags"); + } + return _isCustomTagsEnabled; + }; this._matrixClient.getRooms().forEach((room) => { const myUserId = this._matrixClient.getUserId(); @@ -403,7 +417,9 @@ class RoomListStore extends Store { // ignore any m. tag names we don't know about tagNames = tagNames.filter((t) => { - return (isCustomTagsEnabled && !t.startsWith('m.')) || lists[t] !== undefined; + // Speed optimization: Avoid hitting the SettingsStore at all costs by making it the + // last condition possible. + return lists[t] !== undefined || (!t.startsWith('m.') && isCustomTagsEnabled()); }); if (tagNames.length) { @@ -411,9 +427,10 @@ class RoomListStore extends Store { const tagName = tagNames[i]; lists[tagName] = lists[tagName] || []; - // We categorize all the tagged rooms the same because we don't actually - // care about the order (it's defined elsewhere) - lists[tagName].push({room, category: CATEGORY_RED}); + // Default to an arbitrary category for tags which aren't ordered by recents + let category = CATEGORY_IDLE; + if (LIST_ORDERS[tagName] === 'recent') category = this._calculateCategory(room); + lists[tagName].push({room, category: category}); } } else if (dmRoomMap.getUserIdForRoomId(room.roomId)) { // "Direct Message" rooms (that we're still in and that aren't otherwise tagged) @@ -422,7 +439,9 @@ class RoomListStore extends Store { lists["im.vector.fake.recent"].push({room, category: this._calculateCategory(room)}); } } else if (membership === "leave") { - lists["im.vector.fake.archived"].push({room, category: this._calculateCategory(room)}); + // The category of these rooms is not super important, so deprioritize it to the lowest + // possible value. + lists["im.vector.fake.archived"].push({room, category: CATEGORY_IDLE}); } }); @@ -442,6 +461,7 @@ class RoomListStore extends Store { if (latestEventTsCache[room.roomId]) { return latestEventTsCache[room.roomId]; } + const ts = this._tsOfNewestEvent(room); latestEventTsCache[room.roomId] = ts; return ts; @@ -515,7 +535,7 @@ class RoomListStore extends Store { const idxB = CATEGORY_ORDER.indexOf(categoryB); if (idxA > idxB) return 1; if (idxA < idxB) return -1; - return 0; + return 0; // Technically not possible } const timestampA = tsOfNewestEventFn(roomA); From c16791814eeb157f5b2f51b4ee1cdf64c3ce8d6c Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Tue, 19 Feb 2019 19:58:03 -0600 Subject: [PATCH 064/178] Set event-sending-color in dark theme Signed-off-by: Aaron Raimist --- res/themes/dark/css/_dark.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 35ba1ce53c..deed7235cb 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -113,6 +113,9 @@ $panel-divider-color: $header-panel-border-color; $widget-menu-bar-bg-color: $header-panel-bg-color; +// event tile lifecycle +$event-sending-color: $text-secondary-color; + // event redaction $event-redacted-fg-color: #606060; $event-redacted-border-color: #000000; From 44bedb208426b643244e2cc6e0a3f2c93b41695d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 20 Feb 2019 09:36:18 +0100 Subject: [PATCH 065/178] fix unit tests without breaking the build --- src/languageHandler.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/languageHandler.js b/src/languageHandler.js index 33f703c204..7c1a47fed9 100644 --- a/src/languageHandler.js +++ b/src/languageHandler.js @@ -339,8 +339,9 @@ export function getCurrentLanguage() { function getLangsJson() { return new Promise((resolve, reject) => { // LANGUAGES_FILE is a webpack compile-time define, see webpack config + const url = (typeof LANGUAGES_FILE === "string") ? require(LANGUAGES_FILE) : (i18nFolder + 'languages.json'); request( - { method: "GET", url: require(LANGUAGES_FILE) }, + { method: "GET", url }, (err, response, body) => { if (err || response.status < 200 || response.status >= 300) { reject({err: err, response: response}); From 9292a46db05d28bdb36e5fbe5ceb7fe3934b098c Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 20 Feb 2019 10:24:01 +0000 Subject: [PATCH 066/178] Update comment about Modular server type selection Modular now sets `disable_custom_urls`, so the server type selector is not shown for Modular-hosted Riot. --- src/components/views/auth/ServerTypeSelector.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/auth/ServerTypeSelector.js b/src/components/views/auth/ServerTypeSelector.js index 7a28eec0ed..25f5dcee66 100644 --- a/src/components/views/auth/ServerTypeSelector.js +++ b/src/components/views/auth/ServerTypeSelector.js @@ -62,8 +62,8 @@ function getDefaultType(defaultHsUrl) { } else if (defaultHsUrl === TYPES.FREE.hsUrl) { return FREE; } else if (new URL(defaultHsUrl).hostname.endsWith('.modular.im')) { - // TODO: Use a Riot config parameter to detect Modular-ness. - // https://github.com/vector-im/riot-web/issues/8253 + // This is an unlikely case to reach, as Modular defaults to hiding the + // server type selector. return PREMIUM; } else { return ADVANCED; From b846ac580054ba0b0acf2baa9f09a959141df3b4 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 20 Feb 2019 11:23:36 +0000 Subject: [PATCH 067/178] Rework `ServerTypeSelector` to only emit changes after initial setup `ServerTypeSelector` would call its `onChange` prop both at construction (because it computed the default selected type and consumers might want to know) as well as on actual user change. This ended up complicating consumer code, as they want to differentiate between initial state and changes made by the user. To simplify things, `ServerTypeSelector` now exports a function to compute the server type from HS URL, which can be useful for setting its initially selected type. The consumer now provides that type via a prop, and `onChange` is now only called for actual user changes, simplifying the logic in `Registration` which uses `ServerTypeSelector`. In addition, some usages of `customHsUrl` vs. `defaultHsUrl` in `Registration` are simplified to be `customHsUrl` only (since it already includes a fallback to the default URL in `MatrixChat`). --- .../structures/auth/Registration.js | 24 ++++++----------- .../views/auth/ServerTypeSelector.js | 26 +++++++------------ 2 files changed, 17 insertions(+), 33 deletions(-) diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 6b578f0f68..04570df868 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -94,7 +94,7 @@ module.exports = React.createClass({ // If we've been given a session ID, we're resuming // straight back into UI auth doingUIAuth: Boolean(this.props.sessionId), - serverType: null, + serverType: ServerType.getTypeFromHsUrl(this.props.customHsUrl), hsUrl: this.props.customHsUrl, isUrl: this.props.customIsUrl, // Phase of the overall registration dialog. @@ -122,7 +122,7 @@ module.exports = React.createClass({ }); }, - onServerTypeChange(type, initial) { + onServerTypeChange(type) { this.setState({ serverType: type, }); @@ -148,15 +148,10 @@ module.exports = React.createClass({ hsUrl: this.props.defaultHsUrl, isUrl: this.props.defaultIsUrl, }); - // if this is the initial value from the control and we're - // already in the registration phase, don't go back to the - // server details phase (but do if it's actually a change resulting - // from user interaction). - if (!initial || !this.state.phase === PHASE_REGISTRATION) { - this.setState({ - phase: PHASE_SERVER_DETAILS, - }); - } + // Reset back to server details on type change. + this.setState({ + phase: PHASE_SERVER_DETAILS, + }); break; } }, @@ -389,12 +384,9 @@ module.exports = React.createClass({ // If we're on a different phase, we only show the server type selector, // which is always shown if we allow custom URLs at all. if (PHASES_ENABLED && this.state.phase !== PHASE_SERVER_DETAILS) { - // if we've been given a custom HS URL we should actually pass that, so - // that the appropriate section is selected at the start to match the - // homeserver URL we're using return
; @@ -436,7 +428,7 @@ module.exports = React.createClass({ return
{serverDetails} diff --git a/src/components/views/auth/ServerTypeSelector.js b/src/components/views/auth/ServerTypeSelector.js index 25f5dcee66..a9311acd7b 100644 --- a/src/components/views/auth/ServerTypeSelector.js +++ b/src/components/views/auth/ServerTypeSelector.js @@ -56,12 +56,12 @@ export const TYPES = { }, }; -function getDefaultType(defaultHsUrl) { - if (!defaultHsUrl) { +export function getTypeFromHsUrl(hsUrl) { + if (!hsUrl) { return null; - } else if (defaultHsUrl === TYPES.FREE.hsUrl) { + } else if (hsUrl === TYPES.FREE.hsUrl) { return FREE; - } else if (new URL(defaultHsUrl).hostname.endsWith('.modular.im')) { + } else if (new URL(hsUrl).hostname.endsWith('.modular.im')) { // This is an unlikely case to reach, as Modular defaults to hiding the // server type selector. return PREMIUM; @@ -72,8 +72,8 @@ function getDefaultType(defaultHsUrl) { export default class ServerTypeSelector extends React.PureComponent { static propTypes = { - // The default HS URL as another way to set the initially selected type. - defaultHsUrl: PropTypes.string, + // The default selected type. + selected: PropTypes.string, // Handler called when the selected type changes. onChange: PropTypes.func.isRequired, } @@ -82,20 +82,12 @@ export default class ServerTypeSelector extends React.PureComponent { super(props); const { - defaultHsUrl, - onChange, + selected, } = props; - const type = getDefaultType(defaultHsUrl); + this.state = { - selected: type, + selected, }; - if (onChange) { - // FIXME: Supply a second 'initial' param here to flag that this is - // initialising the value rather than from user interaction - // (which sometimes we'll want to ignore). Must be a better way - // to do this. - onChange(type, true); - } } updateSelectedType(type) { From f11505a9de646ca17ff31fe1b8d80f1fbec25411 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 20 Feb 2019 12:45:55 +0100 Subject: [PATCH 068/178] bring back user page Adds a UserView that contains a MainSplit with an empty div and a RightPanel, preset to the given member. UserView fetches the profile and creates a fake member, which it passed on to the RightPanel. this doesn't use the view_user action on purpose, to avoid any interference of the UserView when trying to view a room member. --- res/css/_components.scss | 1 + res/css/structures/_MainSplit.scss | 21 +++++++ res/css/views/rooms/_MemberInfo.scss | 7 ++- src/components/structures/LoggedInView.js | 5 +- src/components/structures/MatrixChat.js | 12 ++-- src/components/structures/RightPanel.js | 16 ++++- src/components/structures/UserView.js | 77 +++++++++++++++++++++++ src/components/views/rooms/MemberInfo.js | 13 ++-- 8 files changed, 134 insertions(+), 18 deletions(-) create mode 100644 res/css/structures/_MainSplit.scss create mode 100644 src/components/structures/UserView.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 18c3597db0..6aed78a627 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -10,6 +10,7 @@ @import "./structures/_HeaderButtons.scss"; @import "./structures/_HomePage.scss"; @import "./structures/_LeftPanel.scss"; +@import "./structures/_MainSplit.scss"; @import "./structures/_MatrixChat.scss"; @import "./structures/_MyGroups.scss"; @import "./structures/_NotificationPanel.scss"; diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss new file mode 100644 index 0000000000..d822247f56 --- /dev/null +++ b/res/css/structures/_MainSplit.scss @@ -0,0 +1,21 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_MainSplit { + display: flex; + flex-direction: row; + min-width: 0; +} diff --git a/res/css/views/rooms/_MemberInfo.scss b/res/css/views/rooms/_MemberInfo.scss index 60faf3ef13..8f89b83003 100644 --- a/res/css/views/rooms/_MemberInfo.scss +++ b/res/css/views/rooms/_MemberInfo.scss @@ -32,11 +32,12 @@ limitations under the License. .mx_MemberInfo_cancel { height: 16px; - padding: 10px 15px; + width: 16px; + padding: 10px 0 10px 10px; cursor: pointer; mask-image: url('$(res)/img/minimise.svg'); mask-repeat: no-repeat; - mask-position: center; + mask-position: 16px center; background-color: $rightpanel-button-color; } @@ -47,7 +48,7 @@ limitations under the License. .mx_MemberInfo h2 { font-size: 18px; font-weight: 600; - margin: 16px 0; + margin: 16px 0 16px 15px; } .mx_MemberInfo_container { diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 2e1b91fe0e..c6c1be67ec 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -421,6 +421,7 @@ const LoggedInView = React.createClass({ render: function() { const LeftPanel = sdk.getComponent('structures.LeftPanel'); const RoomView = sdk.getComponent('structures.RoomView'); + const UserView = sdk.getComponent('structures.UserView'); const EmbeddedPage = sdk.getComponent('structures.EmbeddedPage'); const GroupView = sdk.getComponent('structures.GroupView'); const MyGroups = sdk.getComponent('structures.MyGroups'); @@ -469,9 +470,7 @@ const LoggedInView = React.createClass({ break; case PageTypes.UserView: - pageElement = null; // deliberately null for now - // TODO: fix/remove UserView - // right_panel = ; + pageElement = ; break; case PageTypes.GroupView: pageElement = ; + } else if (this.state.member) { + const RightPanel = sdk.getComponent('structures.RightPanel'); + const MainSplit = sdk.getComponent('structures.MainSplit'); + const panel = ; + return (
); + } else { + return (
); + } + } +} diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index c7ea54a4c8..b4af5398f6 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -980,13 +980,18 @@ module.exports = withMatrixClient(React.createClass({ const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); const EmojiText = sdk.getComponent('elements.EmojiText'); + let backButton; + if (this.props.member.roomId) { + backButton = (); + } + return (
- + { backButton } { e2eIconElement } { memberName }
From 23bcbc50e31c29b9dfff0529a9a45b3d290592b8 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 20 Feb 2019 12:52:29 +0100 Subject: [PATCH 069/178] show dialog when failing to load profile info --- src/components/structures/UserView.js | 9 +++++++-- src/i18n/strings/en_EN.json | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/structures/UserView.js b/src/components/structures/UserView.js index 4321aaccb9..2fe9c0937c 100644 --- a/src/components/structures/UserView.js +++ b/src/components/structures/UserView.js @@ -18,7 +18,8 @@ import React from "react"; import Matrix from "matrix-js-sdk"; import MatrixClientPeg from "../../MatrixClientPeg"; import sdk from "../../index"; - +import Modal from '../../Modal'; +import { _t } from '../../languageHandler'; export default class UserView extends React.Component { static get propTypes() { @@ -51,7 +52,11 @@ export default class UserView extends React.Component { try { profileInfo = await cli.getProfileInfo(this.props.userId); } catch (err) { - // show dialog or error or something + const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); + Modal.createTrackedDialog(_t('Could not load user profile'), '', ErrorDialog, { + title: _t('Could not load user profile'), + description: ((err && err.message) ? err.message : _t("Operation failed")), + }); this.setState({loading: false}); return; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5c813b1ae6..d5fb42160b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1402,6 +1402,7 @@ "Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others", "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other", + "Could not load user profile": "Could not load user profile", "Failed to send email": "Failed to send email", "The email address linked to your account must be entered.": "The email address linked to your account must be entered.", "A new password must be entered.": "A new password must be entered.", From 91f56a4447492d428a757beca8e76ac883473600 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 20 Feb 2019 12:14:03 +0000 Subject: [PATCH 070/178] Display default server name in registration If a default server name is set and the current HS URL is the default HS URL, we'll display that name in the "your account" text on the registration form. This can be a bit more user friendly, especially when the HS is delegated to somewhere such as Modular, since you'll then see "example.com" instead of "example.modular.im", which you have no direct relationship with as a user. This is the key bit of https://github.com/vector-im/riot-web/issues/8763 for registration. --- src/components/structures/MatrixChat.js | 1 + .../structures/auth/Registration.js | 20 ++++++++++++++++--- src/components/views/auth/RegistrationForm.js | 20 ++++++++++++++----- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 8bc1fbdd07..b8d78fc447 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1887,6 +1887,7 @@ export default React.createClass({ sessionId={this.state.register_session_id} idSid={this.state.register_id_sid} email={this.props.startingFragmentQueryParams.email} + defaultServerName={this.getDefaultServerName()} defaultServerDiscoveryError={this.state.defaultServerDiscoveryError} defaultHsUrl={this.getDefaultHsUrl()} defaultIsUrl={this.getDefaultIsUrl()} diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 04570df868..f3c6306f79 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -48,15 +48,20 @@ module.exports = React.createClass({ sessionId: PropTypes.string, makeRegistrationUrl: PropTypes.func.isRequired, idSid: PropTypes.string, + // The default server name to use when the user hasn't specified + // one. If set, `defaultHsUrl` and `defaultHsUrl` were derived for this + // via `.well-known` discovery. The server name is used instead of the + // HS URL when talking about "your account". + defaultServerName: PropTypes.string, + // An error passed along from higher up explaining that something + // went wrong when finding the defaultHsUrl. + defaultServerDiscoveryError: PropTypes.string, customHsUrl: PropTypes.string, customIsUrl: PropTypes.string, defaultHsUrl: PropTypes.string, defaultIsUrl: PropTypes.string, brand: PropTypes.string, email: PropTypes.string, - // An error passed along from higher up explaining that something - // went wrong when finding the defaultHsUrl. - defaultServerDiscoveryError: PropTypes.string, // registration shouldn't know or care how login is done. onLoginClick: PropTypes.func.isRequired, onServerConfigChange: PropTypes.func.isRequired, @@ -470,6 +475,14 @@ module.exports = React.createClass({ ) { onEditServerDetailsClick = this.onEditServerDetailsClick; } + + // If the current HS URL is the default HS URL, then we can label it + // with the default HS name (if it exists). + let hsName; + if (this.state.hsUrl === this.props.defaultHsUrl) { + hsName = this.props.defaultServerName; + } + return ; } diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index acde4d03fe..910c72ef47 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -50,6 +50,10 @@ module.exports = React.createClass({ onRegisterClick: PropTypes.func.isRequired, // onRegisterClick(Object) => ?Promise onEditServerDetailsClick: PropTypes.func, flows: PropTypes.arrayOf(PropTypes.object).isRequired, + // This is optional and only set if we used a server name to determine + // the HS URL via `.well-known` discovery. The server name is used + // instead of the HS URL when talking about "your account". + hsName: PropTypes.string, hsUrl: PropTypes.string, }, @@ -296,13 +300,19 @@ module.exports = React.createClass({ render: function() { let yourMatrixAccountText = _t('Create your account'); - try { - const parsedHsUrl = new URL(this.props.hsUrl); + if (this.props.hsName) { yourMatrixAccountText = _t('Create your %(serverName)s account', { - serverName: parsedHsUrl.hostname, + serverName: this.props.hsName, }); - } catch (e) { - // ignore + } else { + try { + const parsedHsUrl = new URL(this.props.hsUrl); + yourMatrixAccountText = _t('Create your %(serverName)s account', { + serverName: parsedHsUrl.hostname, + }); + } catch (e) { + // ignore + } } let editLink = null; From d68b7c29e212a8df680d171806a4adcb08a20b3c Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 20 Feb 2019 12:20:39 +0000 Subject: [PATCH 071/178] Only show the first line of each commit in changelog dialog Multi-line commits aren't actually formatted correctly, and most likely the first line is enough of a summary anyway, so this change trims to the first line. The commits are linked, so you can click through if you want more detail. Fixes https://github.com/vector-im/riot-web/issues/8285 --- src/components/views/dialogs/ChangelogDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/ChangelogDialog.js b/src/components/views/dialogs/ChangelogDialog.js index 965029c069..dac25123a6 100644 --- a/src/components/views/dialogs/ChangelogDialog.js +++ b/src/components/views/dialogs/ChangelogDialog.js @@ -51,7 +51,7 @@ export default class ChangelogDialog extends React.Component { return (
  • - {commit.commit.message} + {commit.commit.message.split('\n')[0]}
  • ); From f4b718008765a0a0a72ca60f15fcc79f2227319b Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 20 Feb 2019 13:40:30 +0000 Subject: [PATCH 072/178] Display default server name in login If a default server name is set and the current HS URL is the default HS URL, we'll display that name in the "sign in to" text on the login form. This can be a bit more user friendly, especially when the HS is delegated to somewhere such as Modular, since you'll then see "example.com" instead of "example.modular.im", which you have no direct relationship with as a user. This is the key bit of https://github.com/vector-im/riot-web/issues/8763 for login. --- src/components/structures/MatrixChat.js | 1 + src/components/structures/auth/Login.js | 23 ++++++++++++++++++---- src/components/views/auth/PasswordLogin.js | 22 ++++++++++++++++----- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index b8d78fc447..7de4141192 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1924,6 +1924,7 @@ export default React.createClass({ diff --git a/src/components/views/auth/PasswordLogin.js b/src/components/views/auth/PasswordLogin.js index 8db30c6d43..87fbc9c9fb 100644 --- a/src/components/views/auth/PasswordLogin.js +++ b/src/components/views/auth/PasswordLogin.js @@ -40,6 +40,10 @@ class PasswordLogin extends React.Component { initialPhoneNumber: "", initialPassword: "", loginIncorrect: false, + // This is optional and only set if we used a server name to determine + // the HS URL via `.well-known` discovery. The server name is used + // instead of the HS URL when talking about where to "sign in to". + hsName: null, hsUrl: "", disableSubmit: false, } @@ -250,13 +254,19 @@ class PasswordLogin extends React.Component { } let signInToText = _t('Sign in'); - try { - const parsedHsUrl = new URL(this.props.hsUrl); + if (this.props.hsName) { signInToText = _t('Sign in to %(serverName)s', { - serverName: parsedHsUrl.hostname, + serverName: this.props.hsName, }); - } catch (e) { - // ignore + } else { + try { + const parsedHsUrl = new URL(this.props.hsUrl); + signInToText = _t('Sign in to %(serverName)s', { + serverName: parsedHsUrl.hostname, + }); + } catch (e) { + // ignore + } } let editLink = null; @@ -338,6 +348,8 @@ PasswordLogin.propTypes = { onPhoneNumberChanged: PropTypes.func, onPasswordChanged: PropTypes.func, loginIncorrect: PropTypes.bool, + hsName: PropTypes.string, + hsUrl: PropTypes.string, disableSubmit: PropTypes.bool, }; From 5433feb4d4883c3693cc6118196724cf3bb9e42d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 20 Feb 2019 13:57:21 +0000 Subject: [PATCH 073/178] Display default server name in forgot password If a default server name is set and the current HS URL is the default HS URL, we'll display that name in the "your account" text on the forgot password form. This can be a bit more user friendly, especially when the HS is delegated to somewhere such as Modular, since you'll then see "example.com" instead of "example.modular.im", which you have no direct relationship with as a user. This is the key bit of https://github.com/vector-im/riot-web/issues/8763 for forgot password. --- .../structures/auth/ForgotPassword.js | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/components/structures/auth/ForgotPassword.js b/src/components/structures/auth/ForgotPassword.js index 8eb1e9ce70..dad56b798c 100644 --- a/src/components/structures/auth/ForgotPassword.js +++ b/src/components/structures/auth/ForgotPassword.js @@ -41,20 +41,22 @@ module.exports = React.createClass({ displayName: 'ForgotPassword', propTypes: { + // The default server name to use when the user hasn't specified + // one. If set, `defaultHsUrl` and `defaultHsUrl` were derived for this + // via `.well-known` discovery. The server name is used instead of the + // HS URL when talking about "your account". + defaultServerName: PropTypes.string, + // An error passed along from higher up explaining that something + // went wrong when finding the defaultHsUrl. + defaultServerDiscoveryError: PropTypes.string, + defaultHsUrl: PropTypes.string, defaultIsUrl: PropTypes.string, customHsUrl: PropTypes.string, customIsUrl: PropTypes.string, + onLoginClick: PropTypes.func, onComplete: PropTypes.func.isRequired, - - // The default server name to use when the user hasn't specified - // one. This is used when displaying the defaultHsUrl in the UI. - defaultServerName: PropTypes.string, - - // An error passed along from higher up explaining that something - // went wrong when finding the defaultHsUrl. - defaultServerDiscoveryError: PropTypes.string, }, getInitialState: function() { @@ -235,18 +237,24 @@ module.exports = React.createClass({ } let yourMatrixAccountText = _t('Your account'); - try { - const parsedHsUrl = new URL(this.state.enteredHsUrl); + if (this.state.enteredHsUrl === this.props.defaultHsUrl) { yourMatrixAccountText = _t('Your account on %(serverName)s', { - serverName: parsedHsUrl.hostname, + serverName: this.props.defaultServerName, }); - } catch (e) { - errorText =
    {_t( - "The homeserver URL %(hsUrl)s doesn't seem to be valid URL. Please " + - "enter a valid URL including the protocol prefix.", - { - hsUrl: this.state.enteredHsUrl, - })}
    ; + } else { + try { + const parsedHsUrl = new URL(this.state.enteredHsUrl); + yourMatrixAccountText = _t('Your account on %(serverName)s', { + serverName: parsedHsUrl.hostname, + }); + } catch (e) { + errorText =
    {_t( + "The homeserver URL %(hsUrl)s doesn't seem to be valid URL. Please " + + "enter a valid URL including the protocol prefix.", + { + hsUrl: this.state.enteredHsUrl, + })}
    ; + } } // If custom URLs are allowed, wire up the server details edit link. From d220dd49ef73ce7032c4b756d43d360d1a28fd57 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 20 Feb 2019 14:29:57 +0000 Subject: [PATCH 074/178] Clarify that the account is a Matrix account Now that auth flows can show a server name like `example.com` which might delegate the HS to some other server, it could be confusing to see text like "Sign in to example.com", especially if `example.com` runs an identity service, uses SSO, has its own account system, or other things like this. To clarify that we mean Matrix accounts, all auth flows are updated to talk in terms of " your Matrix account on ". Fixes part of https://github.com/vector-im/riot-web/issues/8763#issuecomment-464823909. --- src/components/structures/auth/ForgotPassword.js | 6 +++--- src/components/views/auth/PasswordLogin.js | 6 +++--- src/components/views/auth/RegistrationForm.js | 6 +++--- src/i18n/strings/en_EN.json | 12 +++++++----- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/components/structures/auth/ForgotPassword.js b/src/components/structures/auth/ForgotPassword.js index dad56b798c..58deb380e3 100644 --- a/src/components/structures/auth/ForgotPassword.js +++ b/src/components/structures/auth/ForgotPassword.js @@ -236,15 +236,15 @@ module.exports = React.createClass({ errorText =
    { err }
    ; } - let yourMatrixAccountText = _t('Your account'); + let yourMatrixAccountText = _t('Your Matrix account'); if (this.state.enteredHsUrl === this.props.defaultHsUrl) { - yourMatrixAccountText = _t('Your account on %(serverName)s', { + yourMatrixAccountText = _t('Your Matrix account on %(serverName)s', { serverName: this.props.defaultServerName, }); } else { try { const parsedHsUrl = new URL(this.state.enteredHsUrl); - yourMatrixAccountText = _t('Your account on %(serverName)s', { + yourMatrixAccountText = _t('Your Matrix account on %(serverName)s', { serverName: parsedHsUrl.hostname, }); } catch (e) { diff --git a/src/components/views/auth/PasswordLogin.js b/src/components/views/auth/PasswordLogin.js index 87fbc9c9fb..fb14629214 100644 --- a/src/components/views/auth/PasswordLogin.js +++ b/src/components/views/auth/PasswordLogin.js @@ -253,15 +253,15 @@ class PasswordLogin extends React.Component { ; } - let signInToText = _t('Sign in'); + let signInToText = _t('Sign in to your Matrix account'); if (this.props.hsName) { - signInToText = _t('Sign in to %(serverName)s', { + signInToText = _t('Sign in to your Matrix account on %(serverName)s', { serverName: this.props.hsName, }); } else { try { const parsedHsUrl = new URL(this.props.hsUrl); - signInToText = _t('Sign in to %(serverName)s', { + signInToText = _t('Sign in to your Matrix account on %(serverName)s', { serverName: parsedHsUrl.hostname, }); } catch (e) { diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 910c72ef47..056411033b 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -299,15 +299,15 @@ module.exports = React.createClass({ }, render: function() { - let yourMatrixAccountText = _t('Create your account'); + let yourMatrixAccountText = _t('Create your Matrix account'); if (this.props.hsName) { - yourMatrixAccountText = _t('Create your %(serverName)s account', { + yourMatrixAccountText = _t('Create your Matrix account on %(serverName)s', { serverName: this.props.hsName, }); } else { try { const parsedHsUrl = new URL(this.props.hsUrl); - yourMatrixAccountText = _t('Create your %(serverName)s account', { + yourMatrixAccountText = _t('Create your Matrix account on %(serverName)s', { serverName: parsedHsUrl.hostname, }); } catch (e) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0f5a875f0d..1b4dd35da8 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1252,13 +1252,14 @@ "Username": "Username", "Mobile phone number": "Mobile phone number", "Not sure of your password? Set a new one": "Not sure of your password? Set a new one", - "Sign in to %(serverName)s": "Sign in to %(serverName)s", + "Sign in to your Matrix account": "Sign in to your Matrix account", + "Sign in to your Matrix account on %(serverName)s": "Sign in to your Matrix account on %(serverName)s", "Change": "Change", "Sign in with": "Sign in with", "Phone": "Phone", "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?", - "Create your account": "Create your account", - "Create your %(serverName)s account": "Create your %(serverName)s account", + "Create your Matrix account": "Create your Matrix account", + "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", "Email": "Email", "Email (optional)": "Email (optional)", "Phone (optional)": "Phone (optional)", @@ -1408,8 +1409,8 @@ "A new password must be entered.": "A new password must be entered.", "New passwords must match each other.": "New passwords must match each other.", "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.", - "Your account": "Your account", - "Your account on %(serverName)s": "Your account on %(serverName)s", + "Your Matrix account": "Your Matrix account", + "Your Matrix account on %(serverName)s": "Your Matrix account on %(serverName)s", "The homeserver URL %(hsUrl)s doesn't seem to be valid URL. Please enter a valid URL including the protocol prefix.": "The homeserver URL %(hsUrl)s doesn't seem to be valid URL. Please enter a valid URL including the protocol prefix.", "A verification email will be sent to your inbox to confirm setting your new password.": "A verification email will be sent to your inbox to confirm setting your new password.", "Send Reset Email": "Send Reset Email", @@ -1453,6 +1454,7 @@ "A phone number is required to register on this homeserver.": "A phone number is required to register on this homeserver.", "You need to enter a username.": "You need to enter a username.", "An unknown error occurred.": "An unknown error occurred.", + "Create your account": "Create your account", "Commands": "Commands", "Results from DuckDuckGo": "Results from DuckDuckGo", "Emoji": "Emoji", From 42bb3c4f408767816be4700aded3db4d9658beee Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 20 Feb 2019 16:00:13 +0000 Subject: [PATCH 075/178] Prevent default for forgot password link The forgot password link should prevent default to avoid changing the URL's hash state. --- src/components/views/auth/PasswordLogin.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/views/auth/PasswordLogin.js b/src/components/views/auth/PasswordLogin.js index fb14629214..1ad93f6075 100644 --- a/src/components/views/auth/PasswordLogin.js +++ b/src/components/views/auth/PasswordLogin.js @@ -58,6 +58,7 @@ class PasswordLogin extends React.Component { loginType: PasswordLogin.LOGIN_FIELD_MXID, }; + this.onForgotPasswordClick = this.onForgotPasswordClick.bind(this); this.onSubmitForm = this.onSubmitForm.bind(this); this.onUsernameChanged = this.onUsernameChanged.bind(this); this.onUsernameBlur = this.onUsernameBlur.bind(this); @@ -74,6 +75,12 @@ class PasswordLogin extends React.Component { this._loginField = null; } + onForgotPasswordClick(ev) { + ev.preventDefault(); + ev.stopPropagation(); + this.props.onForgotPasswordClick(); + } + onSubmitForm(ev) { ev.preventDefault(); @@ -244,7 +251,7 @@ class PasswordLogin extends React.Component { forgotPasswordJsx = {_t('Not sure of your password? Set a new one', {}, { a: sub => {sub} From 567280ae3d9b8a75a7fb80ef84537a4b80b462c8 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 20 Feb 2019 18:17:19 +0000 Subject: [PATCH 076/178] Remove unreferenced images Now that images flow through a build step, it's easy to spot which ones aren't actually used. To determine this, I built Riot with the hash in file names disabled, and then used a directory compare tool to look for all images in the SDK that did not make it into the Riot build. Fixes https://github.com/vector-im/riot-web/issues/8158 --- res/img/avatar-error.svg | 15 ----- res/img/button-new-window.svg | 12 ---- res/img/button-refresh.svg | 12 ---- res/img/call.svg | 17 ----- res/img/camera_green.svg | 15 ----- res/img/cancel_green.svg | 10 --- res/img/directory-big.svg | 22 ------- res/img/e2e-blocked.svg | 12 ---- res/img/e2e-encrypting.svg | 12 ---- res/img/e2e-not_sent.svg | 12 ---- res/img/e2e-unencrypted.svg | 23 ------- res/img/e2e-verified.svg | 12 ---- res/img/e2e-warning.svg | 12 ---- res/img/edit.svg | 11 ---- res/img/edit_green.svg | 11 ---- res/img/eol.svg | 16 ----- res/img/icon-call.svg | 3 - res/img/icon-context-delete.svg | 10 --- res/img/icon-context-fave-on.svg | 15 ----- res/img/icon-context-fave.svg | 15 ----- res/img/icon-context-low-on.svg | 15 ----- res/img/icon-context-low.svg | 15 ----- res/img/icon-delete-pink.svg | 19 ------ res/img/icon-mx-user.svg | 15 ----- res/img/icon_context_copy.svg | 3 - res/img/icon_context_edit.svg | 3 - res/img/icon_context_leave.svg | 4 -- res/img/icon_context_message_dark.svg | 15 ----- res/img/icon_context_reply.svg | 3 - res/img/icon_copy_message_dark.svg | 77 ----------------------- res/img/icons-apps-active.svg | 24 ------- res/img/icons-apps.svg | 12 ---- res/img/icons-close-button.svg | 15 ----- res/img/icons-files.svg | 5 -- res/img/icons-groups-nobg.svg | 60 ------------------ res/img/icons-hide-apps.svg | 34 ---------- res/img/icons-hide-stickers.svg | 16 ----- res/img/icons-notifications.svg | 3 - res/img/icons-room.svg | 12 ---- res/img/icons-search.svg | 15 ----- res/img/icons-show-apps.svg | 33 ---------- res/img/icons-stickers.svg | 6 -- res/img/icons-upload.svg | 3 - res/img/icons-video.svg | 3 - res/img/icons_ellipsis.svg | 1 - res/img/icons_global.svg | 19 ------ res/img/list-close.svg | 15 ----- res/img/list-open.svg | 15 ----- res/img/maximise.svg | 19 ------ res/img/maximize.svg | 9 --- res/img/minimize.svg | 8 --- res/img/newmessages.svg | 15 ----- res/img/right_search.svg | 17 ----- res/img/scrolldown.svg | 15 ----- res/img/scrollto.svg | 21 ------- res/img/scrollup.svg | 91 --------------------------- res/img/search-button.svg | 15 ----- res/img/settings-big.svg | 18 ------ res/img/settings.svg | 12 ---- res/img/upload.svg | 19 ------ res/img/voice.svg | 13 ---- res/img/warning_yellow.svg | 34 ---------- 62 files changed, 1033 deletions(-) delete mode 100644 res/img/avatar-error.svg delete mode 100644 res/img/button-new-window.svg delete mode 100644 res/img/button-refresh.svg delete mode 100644 res/img/call.svg delete mode 100644 res/img/camera_green.svg delete mode 100644 res/img/cancel_green.svg delete mode 100644 res/img/directory-big.svg delete mode 100644 res/img/e2e-blocked.svg delete mode 100644 res/img/e2e-encrypting.svg delete mode 100644 res/img/e2e-not_sent.svg delete mode 100644 res/img/e2e-unencrypted.svg delete mode 100644 res/img/e2e-verified.svg delete mode 100644 res/img/e2e-warning.svg delete mode 100644 res/img/edit.svg delete mode 100644 res/img/edit_green.svg delete mode 100644 res/img/eol.svg delete mode 100644 res/img/icon-call.svg delete mode 100644 res/img/icon-context-delete.svg delete mode 100644 res/img/icon-context-fave-on.svg delete mode 100644 res/img/icon-context-fave.svg delete mode 100644 res/img/icon-context-low-on.svg delete mode 100644 res/img/icon-context-low.svg delete mode 100644 res/img/icon-delete-pink.svg delete mode 100644 res/img/icon-mx-user.svg delete mode 100644 res/img/icon_context_copy.svg delete mode 100644 res/img/icon_context_edit.svg delete mode 100644 res/img/icon_context_leave.svg delete mode 100644 res/img/icon_context_message_dark.svg delete mode 100644 res/img/icon_context_reply.svg delete mode 100644 res/img/icon_copy_message_dark.svg delete mode 100644 res/img/icons-apps-active.svg delete mode 100644 res/img/icons-apps.svg delete mode 100644 res/img/icons-close-button.svg delete mode 100644 res/img/icons-files.svg delete mode 100644 res/img/icons-groups-nobg.svg delete mode 100644 res/img/icons-hide-apps.svg delete mode 100644 res/img/icons-hide-stickers.svg delete mode 100644 res/img/icons-notifications.svg delete mode 100644 res/img/icons-room.svg delete mode 100644 res/img/icons-search.svg delete mode 100644 res/img/icons-show-apps.svg delete mode 100644 res/img/icons-stickers.svg delete mode 100644 res/img/icons-upload.svg delete mode 100644 res/img/icons-video.svg delete mode 100644 res/img/icons_ellipsis.svg delete mode 100644 res/img/icons_global.svg delete mode 100644 res/img/list-close.svg delete mode 100644 res/img/list-open.svg delete mode 100644 res/img/maximise.svg delete mode 100644 res/img/maximize.svg delete mode 100644 res/img/minimize.svg delete mode 100644 res/img/newmessages.svg delete mode 100644 res/img/right_search.svg delete mode 100644 res/img/scrolldown.svg delete mode 100644 res/img/scrollto.svg delete mode 100644 res/img/scrollup.svg delete mode 100644 res/img/search-button.svg delete mode 100644 res/img/settings-big.svg delete mode 100644 res/img/settings.svg delete mode 100644 res/img/upload.svg delete mode 100644 res/img/voice.svg delete mode 100644 res/img/warning_yellow.svg diff --git a/res/img/avatar-error.svg b/res/img/avatar-error.svg deleted file mode 100644 index c5e168944c..0000000000 --- a/res/img/avatar-error.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - 5EF602F6-A36C-41EE-BAEC-50801DFD5492 - Created with sketchtool. - - - - - - - - - - diff --git a/res/img/button-new-window.svg b/res/img/button-new-window.svg deleted file mode 100644 index dd1225e798..0000000000 --- a/res/img/button-new-window.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/res/img/button-refresh.svg b/res/img/button-refresh.svg deleted file mode 100644 index b4990a2147..0000000000 --- a/res/img/button-refresh.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/res/img/call.svg b/res/img/call.svg deleted file mode 100644 index f528f9a24e..0000000000 --- a/res/img/call.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - icons_video - Created with bin/sketchtool. - - - - - - - - - - - - \ No newline at end of file diff --git a/res/img/camera_green.svg b/res/img/camera_green.svg deleted file mode 100644 index 5aae5502cd..0000000000 --- a/res/img/camera_green.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - diff --git a/res/img/cancel_green.svg b/res/img/cancel_green.svg deleted file mode 100644 index 2e3d759be2..0000000000 --- a/res/img/cancel_green.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - Slice 1 - Created with Sketch. - - - - - \ No newline at end of file diff --git a/res/img/directory-big.svg b/res/img/directory-big.svg deleted file mode 100644 index 5631a2ae3e..0000000000 --- a/res/img/directory-big.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - icons_directory - Created with sketchtool. - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/img/e2e-blocked.svg b/res/img/e2e-blocked.svg deleted file mode 100644 index 0ab2c6efbe..0000000000 --- a/res/img/e2e-blocked.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - 2805649B-D39D-43EA-A357-659EF9B97BA4 - Created with sketchtool. - - - - - - - \ No newline at end of file diff --git a/res/img/e2e-encrypting.svg b/res/img/e2e-encrypting.svg deleted file mode 100644 index 469611cc8d..0000000000 --- a/res/img/e2e-encrypting.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - -48BF5D32-306C-4B20-88EB-24B1F743CAC9 -Created with sketchtool. - - - - - - - diff --git a/res/img/e2e-not_sent.svg b/res/img/e2e-not_sent.svg deleted file mode 100644 index fca79ae547..0000000000 --- a/res/img/e2e-not_sent.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - -48BF5D32-306C-4B20-88EB-24B1F743CAC9 -Created with sketchtool. - - - - - - - diff --git a/res/img/e2e-unencrypted.svg b/res/img/e2e-unencrypted.svg deleted file mode 100644 index 1467223638..0000000000 --- a/res/img/e2e-unencrypted.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - 16F5F38E-A6A3-472A-BC13-13F0F12876CF - Created with sketchtool. - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/img/e2e-verified.svg b/res/img/e2e-verified.svg deleted file mode 100644 index b65f50b2b6..0000000000 --- a/res/img/e2e-verified.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - 48BF5D32-306C-4B20-88EB-24B1F743CAC9 - Created with sketchtool. - - - - - - - \ No newline at end of file diff --git a/res/img/e2e-warning.svg b/res/img/e2e-warning.svg deleted file mode 100644 index 8a55f199ba..0000000000 --- a/res/img/e2e-warning.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - CCDDE6F6-B552-48FD-AD54-6939841CA2DD - Created with sketchtool. - - - - - - - \ No newline at end of file diff --git a/res/img/edit.svg b/res/img/edit.svg deleted file mode 100644 index 9ba0060774..0000000000 --- a/res/img/edit.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/res/img/edit_green.svg b/res/img/edit_green.svg deleted file mode 100644 index f7f4c7adcb..0000000000 --- a/res/img/edit_green.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/res/img/eol.svg b/res/img/eol.svg deleted file mode 100644 index 02d1946cf4..0000000000 --- a/res/img/eol.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - icon_eol - Created with sketchtool. - - - - - - - - - - - diff --git a/res/img/icon-call.svg b/res/img/icon-call.svg deleted file mode 100644 index 98677e3c70..0000000000 --- a/res/img/icon-call.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/img/icon-context-delete.svg b/res/img/icon-context-delete.svg deleted file mode 100644 index fba9fa117b..0000000000 --- a/res/img/icon-context-delete.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/res/img/icon-context-fave-on.svg b/res/img/icon-context-fave-on.svg deleted file mode 100644 index 2ae172d8eb..0000000000 --- a/res/img/icon-context-fave-on.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - DAE17B64-40B5-478A-8E8D-97AD1A6E25C8 - Created with sketchtool. - - - - - - - - - - diff --git a/res/img/icon-context-fave.svg b/res/img/icon-context-fave.svg deleted file mode 100644 index 451e1849c8..0000000000 --- a/res/img/icon-context-fave.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - 8A6E1837-F0F1-432E-A0DA-6F3741F71EBF - Created with sketchtool. - - - - - - - - - - diff --git a/res/img/icon-context-low-on.svg b/res/img/icon-context-low-on.svg deleted file mode 100644 index 7578c6335c..0000000000 --- a/res/img/icon-context-low-on.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - CD51482C-F2D4-4F63-AF9E-86513F9AF87F - Created with sketchtool. - - - - - - - - - - diff --git a/res/img/icon-context-low.svg b/res/img/icon-context-low.svg deleted file mode 100644 index 663f3ca9eb..0000000000 --- a/res/img/icon-context-low.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - B160345F-40D3-4BE6-A860-6D04BF223EF7 - Created with sketchtool. - - - - - - - - - - diff --git a/res/img/icon-delete-pink.svg b/res/img/icon-delete-pink.svg deleted file mode 100644 index aafa87f1b2..0000000000 --- a/res/img/icon-delete-pink.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - diff --git a/res/img/icon-mx-user.svg b/res/img/icon-mx-user.svg deleted file mode 100644 index 5780277f38..0000000000 --- a/res/img/icon-mx-user.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - diff --git a/res/img/icon_context_copy.svg b/res/img/icon_context_copy.svg deleted file mode 100644 index 1f9c0b01e8..0000000000 --- a/res/img/icon_context_copy.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/img/icon_context_edit.svg b/res/img/icon_context_edit.svg deleted file mode 100644 index 6f7f1fd385..0000000000 --- a/res/img/icon_context_edit.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/img/icon_context_leave.svg b/res/img/icon_context_leave.svg deleted file mode 100644 index 3fdd452a59..0000000000 --- a/res/img/icon_context_leave.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/res/img/icon_context_message_dark.svg b/res/img/icon_context_message_dark.svg deleted file mode 100644 index b4336cc377..0000000000 --- a/res/img/icon_context_message_dark.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - ED5D3E59-2561-4AC1-9B43-82FBC51767FC - Created with sketchtool. - - - - - - - - - - diff --git a/res/img/icon_context_reply.svg b/res/img/icon_context_reply.svg deleted file mode 100644 index 0a85f8e606..0000000000 --- a/res/img/icon_context_reply.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/img/icon_copy_message_dark.svg b/res/img/icon_copy_message_dark.svg deleted file mode 100644 index b81e617d8c..0000000000 --- a/res/img/icon_copy_message_dark.svg +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - image/svg+xml - - ED5D3E59-2561-4AC1-9B43-82FBC51767FC - - - - - - ED5D3E59-2561-4AC1-9B43-82FBC51767FC - Created with sketchtool. - - - - - - - diff --git a/res/img/icons-apps-active.svg b/res/img/icons-apps-active.svg deleted file mode 100644 index ea222d0511..0000000000 --- a/res/img/icons-apps-active.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/res/img/icons-apps.svg b/res/img/icons-apps.svg deleted file mode 100644 index e2fe49b005..0000000000 --- a/res/img/icons-apps.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/res/img/icons-close-button.svg b/res/img/icons-close-button.svg deleted file mode 100644 index f960d73a3c..0000000000 --- a/res/img/icons-close-button.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - 206C270A-EB00-48E4-8CC3-5D403C59177C - Created with sketchtool. - - - - - - - - - - diff --git a/res/img/icons-files.svg b/res/img/icons-files.svg deleted file mode 100644 index ea270fbc73..0000000000 --- a/res/img/icons-files.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/res/img/icons-groups-nobg.svg b/res/img/icons-groups-nobg.svg deleted file mode 100644 index a3d223b76d..0000000000 --- a/res/img/icons-groups-nobg.svg +++ /dev/null @@ -1,60 +0,0 @@ - -image/svg+xml - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/img/icons-hide-apps.svg b/res/img/icons-hide-apps.svg deleted file mode 100644 index b622e97f71..0000000000 --- a/res/img/icons-hide-apps.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/img/icons-hide-stickers.svg b/res/img/icons-hide-stickers.svg deleted file mode 100644 index f28e8646e6..0000000000 --- a/res/img/icons-hide-stickers.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - diff --git a/res/img/icons-notifications.svg b/res/img/icons-notifications.svg deleted file mode 100644 index cde30713e1..0000000000 --- a/res/img/icons-notifications.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/img/icons-room.svg b/res/img/icons-room.svg deleted file mode 100644 index d2abb21301..0000000000 --- a/res/img/icons-room.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/res/img/icons-search.svg b/res/img/icons-search.svg deleted file mode 100644 index 9d3e98106b..0000000000 --- a/res/img/icons-search.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - Shape - Created with Sketch. - - - - - - - - - - \ No newline at end of file diff --git a/res/img/icons-show-apps.svg b/res/img/icons-show-apps.svg deleted file mode 100644 index 3438157301..0000000000 --- a/res/img/icons-show-apps.svg +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/img/icons-stickers.svg b/res/img/icons-stickers.svg deleted file mode 100644 index 564ebdac97..0000000000 --- a/res/img/icons-stickers.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/res/img/icons-upload.svg b/res/img/icons-upload.svg deleted file mode 100644 index 3aea924478..0000000000 --- a/res/img/icons-upload.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/img/icons-video.svg b/res/img/icons-video.svg deleted file mode 100644 index c61a782cc4..0000000000 --- a/res/img/icons-video.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/img/icons_ellipsis.svg b/res/img/icons_ellipsis.svg deleted file mode 100644 index ba600ccacc..0000000000 --- a/res/img/icons_ellipsis.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/res/img/icons_global.svg b/res/img/icons_global.svg deleted file mode 100644 index 6c07d3c48e..0000000000 --- a/res/img/icons_global.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - icons_global copy 4 - Created with Sketch. - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/img/list-close.svg b/res/img/list-close.svg deleted file mode 100644 index cd88b2a88f..0000000000 --- a/res/img/list-close.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - -Slice 1 -Created with Sketch. - - - - diff --git a/res/img/list-open.svg b/res/img/list-open.svg deleted file mode 100644 index e180be8870..0000000000 --- a/res/img/list-open.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - -Slice 1 -Created with Sketch. - - - - diff --git a/res/img/maximise.svg b/res/img/maximise.svg deleted file mode 100644 index 981e3796de..0000000000 --- a/res/img/maximise.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - -minimise -Created with sketchtool. - - - - - - - - - - - - diff --git a/res/img/maximize.svg b/res/img/maximize.svg deleted file mode 100644 index 4f9e10191f..0000000000 --- a/res/img/maximize.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/res/img/minimize.svg b/res/img/minimize.svg deleted file mode 100644 index 410b0bc08e..0000000000 --- a/res/img/minimize.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/res/img/newmessages.svg b/res/img/newmessages.svg deleted file mode 100644 index a2ffca9020..0000000000 --- a/res/img/newmessages.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - icon_newmessages - Created with Sketch. - - - - - - - - - - \ No newline at end of file diff --git a/res/img/right_search.svg b/res/img/right_search.svg deleted file mode 100644 index b430a6be19..0000000000 --- a/res/img/right_search.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - right_search - Created with Sketch. - - - - - - - - - - - - \ No newline at end of file diff --git a/res/img/scrolldown.svg b/res/img/scrolldown.svg deleted file mode 100644 index d6599c5fc7..0000000000 --- a/res/img/scrolldown.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - icon_newmessages - Created with Sketch. - - - - - - - - - - \ No newline at end of file diff --git a/res/img/scrollto.svg b/res/img/scrollto.svg deleted file mode 100644 index 75df053a68..0000000000 --- a/res/img/scrollto.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - Slice 1 - Created with Sketch. - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/img/scrollup.svg b/res/img/scrollup.svg deleted file mode 100644 index 1692f2a6c0..0000000000 --- a/res/img/scrollup.svg +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - image/svg+xml - - - - - - - icon_newmessages - Created with Sketch. - - - - - - - - - - diff --git a/res/img/search-button.svg b/res/img/search-button.svg deleted file mode 100644 index f4808842ff..0000000000 --- a/res/img/search-button.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - icon_search - Created with Sketch. - - - - - - - - - - \ No newline at end of file diff --git a/res/img/settings-big.svg b/res/img/settings-big.svg deleted file mode 100644 index c9587d58c2..0000000000 --- a/res/img/settings-big.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - icons_settings - Created with sketchtool. - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/img/settings.svg b/res/img/settings.svg deleted file mode 100644 index 4190c7b8de..0000000000 --- a/res/img/settings.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - icon_settings_small - Created with bin/sketchtool. - - - - - - - \ No newline at end of file diff --git a/res/img/upload.svg b/res/img/upload.svg deleted file mode 100644 index 039014a2f3..0000000000 --- a/res/img/upload.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - icons_upload - Created with bin/sketchtool. - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/img/voice.svg b/res/img/voice.svg deleted file mode 100644 index ff87270ba5..0000000000 --- a/res/img/voice.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - icon_voice - Created with Sketch. - - - - - - - - \ No newline at end of file diff --git a/res/img/warning_yellow.svg b/res/img/warning_yellow.svg deleted file mode 100644 index 4d227517d2..0000000000 --- a/res/img/warning_yellow.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From b364c9cb8eab3ffaa1e99215e8c5c206fbbfad46 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 21 Feb 2019 10:50:30 +0100 Subject: [PATCH 077/178] update copyright header --- res/css/structures/_MainSplit.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index d822247f56..28c89fe7ca 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -1,5 +1,5 @@ /* -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. From ef704085f9cf278b1e1b4bcd9f7faccf624e6c5a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 21 Feb 2019 10:50:41 +0100 Subject: [PATCH 078/178] remove display: flex declarations now present on mx_MainSplit itself --- res/css/structures/_GroupView.scss | 1 - res/css/structures/_RoomView.scss | 1 - 2 files changed, 2 deletions(-) diff --git a/res/css/structures/_GroupView.scss b/res/css/structures/_GroupView.scss index 1c477a959a..bfbc92ca05 100644 --- a/res/css/structures/_GroupView.scss +++ b/res/css/structures/_GroupView.scss @@ -170,7 +170,6 @@ limitations under the License. .mx_GroupView > .mx_MainSplit { flex: 1; - display: flex; } .mx_GroupView_body { diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 77b868e391..8e3eb75608 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -77,7 +77,6 @@ limitations under the License. .mx_RoomView .mx_MainSplit { flex: 1 1 0; - display: flex; } .mx_RoomView_body { From d9b8b0f988a111bebe2c9e212d52e41edc9bb0de Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 21 Feb 2019 10:51:09 +0100 Subject: [PATCH 079/178] fix indentation --- src/components/structures/MatrixChat.js | 9 +++------ src/components/views/rooms/MemberInfo.js | 6 +++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index ef0cc33fdc..d6b074ef47 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1573,12 +1573,9 @@ export default React.createClass({ this._chatCreateOrReuse(userId); return; } - - // get profile info here somehow - - this.notifyNewScreen('user/' + userId); - this.setState({currentUserId: userId}); - this._setPage(PageTypes.UserView); + this.notifyNewScreen('user/' + userId); + this.setState({currentUserId: userId}); + this._setPage(PageTypes.UserView); }); } else if (screen.indexOf('group/') == 0) { const groupId = screen.substring(6); diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index b4af5398f6..d7726c8fe8 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -983,9 +983,9 @@ module.exports = withMatrixClient(React.createClass({ let backButton; if (this.props.member.roomId) { backButton = (); + onClick={this.onCancel} + title={_t('Close')} + />); } return ( From 1671466c469502c715416d224a1bdd949f8f3ee2 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 21 Feb 2019 11:02:58 +0000 Subject: [PATCH 080/178] Regenerate strings --- src/i18n/strings/en_EN.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 92391ee19c..abedde2a10 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -714,15 +714,13 @@ "This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.", "The conversation continues here.": "The conversation continues here.", "You do not have permission to post to this room": "You do not have permission to post to this room", - "Turn Markdown on": "Turn Markdown on", - "Turn Markdown off": "Turn Markdown off", + "Markdown is disabled": "Markdown is disabled", "Hide Text Formatting Toolbar": "Hide Text Formatting Toolbar", "Server error": "Server error", "Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.", "Command error": "Command error", "Unable to reply": "Unable to reply", "At this time it is not possible to reply with an emote.": "At this time it is not possible to reply with an emote.", - "Markdown is disabled": "Markdown is disabled", "Markdown is enabled": "Markdown is enabled", "No pinned messages.": "No pinned messages.", "Loading...": "Loading...", From 8b66b6bdb3d1e83ad32fc2ff44622043a50bc9a8 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 21 Feb 2019 11:03:48 +0000 Subject: [PATCH 081/178] Relabel custom HS link on registration from 'Edit' to 'Change' Fixes https://github.com/vector-im/riot-web/issues/8853 --- src/components/views/auth/RegistrationForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 056411033b..eabdcd0dd2 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -320,7 +320,7 @@ module.exports = React.createClass({ editLink = - {_t('Edit')} + {_t('Change')} ; } From 27abd7d507a8e6a878546d15b9d72dc4e955bff8 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 21 Feb 2019 11:35:50 +0000 Subject: [PATCH 082/178] Update validation order to match field order Validation is meant to run in reverse order of the fields (so that the last message emitted is for the first invalid field). --- src/components/views/auth/RegistrationForm.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index eabdcd0dd2..c4fe31f4c6 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -82,11 +82,11 @@ module.exports = React.createClass({ // is the one from the first invalid field. // It's not super ideal that this just calls // onError once for each invalid field. + this.validateField(FIELD_PHONE_NUMBER, ev.type); + this.validateField(FIELD_EMAIL, ev.type); this.validateField(FIELD_PASSWORD_CONFIRM, ev.type); this.validateField(FIELD_PASSWORD, ev.type); this.validateField(FIELD_USERNAME, ev.type); - this.validateField(FIELD_PHONE_NUMBER, ev.type); - this.validateField(FIELD_EMAIL, ev.type); const self = this; if (this.allFieldsValid()) { From acae2e9976c2d5267903cfcd353c977e4c9fa488 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 21 Feb 2019 12:36:31 +0000 Subject: [PATCH 083/178] Wait until password confirm is non-empty If password confirm is empty on blur, there's no reason to try validating it. The user may just be tabbing through fields. --- src/components/views/auth/RegistrationForm.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index c4fe31f4c6..bd597de66a 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -206,10 +206,14 @@ module.exports = React.createClass({ } break; case FIELD_PASSWORD_CONFIRM: - this.markFieldValid( - fieldID, pwd1 == pwd2, - "RegistrationForm.ERR_PASSWORD_MISMATCH", - ); + if (allowEmpty && pwd2 === "") { + this.markFieldValid(fieldID, true); + } else { + this.markFieldValid( + fieldID, pwd1 == pwd2, + "RegistrationForm.ERR_PASSWORD_MISMATCH", + ); + } break; } }, From 86a375c7dacd1d02e7a363e49e4054cb23e04627 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 21 Feb 2019 14:31:11 +0000 Subject: [PATCH 084/178] Report validity state of all registration fields on any change This passes the validity state of all fields to the consumer of `RegistrationForm` via the `onValdiationChange` callback, instead of just the most recent error. In addition, we notify the consumer for any validation change, whether success or failure. This allows old validation messages to be properly cleared. It also allows the consumer to be aware of multiple validation errors and display the next one after you have fixed the first. Fixes https://github.com/vector-im/riot-web/issues/8769 --- .../structures/auth/Registration.js | 13 ++++++-- src/components/views/auth/RegistrationForm.js | 33 ++++++++++--------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 30b553a5b1..b00c0c193f 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -288,7 +288,16 @@ module.exports = React.createClass({ }); }, - onFormValidationFailed: function(errCode) { + onFormValidationChange: function(fieldErrors) { + // Find the first error and show that. + const errCode = Object.values(fieldErrors).find(value => value); + if (!errCode) { + this.setState({ + errorText: null, + }); + return; + } + let errMsg; switch (errCode) { case "RegistrationForm.ERR_PASSWORD_MISSING": @@ -490,7 +499,7 @@ module.exports = React.createClass({ defaultPhoneNumber={this.state.formVals.phoneNumber} defaultPassword={this.state.formVals.password} minPasswordLength={MIN_PASSWORD_LENGTH} - onError={this.onFormValidationFailed} + onValidationChange={this.onFormValidationChange} onRegisterClick={this.onFormSubmit} onEditServerDetailsClick={onEditServerDetailsClick} flows={this.state.flows} diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index bd597de66a..d031dc7bdd 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -46,7 +46,7 @@ module.exports = React.createClass({ defaultUsername: PropTypes.string, defaultPassword: PropTypes.string, minPasswordLength: PropTypes.number, - onError: PropTypes.func, + onValidationChange: PropTypes.func, onRegisterClick: PropTypes.func.isRequired, // onRegisterClick(Object) => ?Promise onEditServerDetailsClick: PropTypes.func, flows: PropTypes.arrayOf(PropTypes.object).isRequired, @@ -60,15 +60,14 @@ module.exports = React.createClass({ getDefaultProps: function() { return { minPasswordLength: 6, - onError: function(e) { - console.error(e); - }, + onValidationChange: console.error, }; }, getInitialState: function() { return { - fieldValid: {}, + // Field error codes by field ID + fieldErrors: {}, // The ISO2 country code selected in the phone number entry phoneCountry: this.props.defaultPhoneCountry, }; @@ -81,7 +80,7 @@ module.exports = React.createClass({ // the error that ends up being displayed // is the one from the first invalid field. // It's not super ideal that this just calls - // onError once for each invalid field. + // onValidationChange once for each invalid field. this.validateField(FIELD_PHONE_NUMBER, ev.type); this.validateField(FIELD_EMAIL, ev.type); this.validateField(FIELD_PASSWORD_CONFIRM, ev.type); @@ -134,9 +133,9 @@ module.exports = React.createClass({ * @returns {boolean} true if all fields were valid last time they were validated. */ allFieldsValid: function() { - const keys = Object.keys(this.state.fieldValid); + const keys = Object.keys(this.state.fieldErrors); for (let i = 0; i < keys.length; ++i) { - if (this.state.fieldValid[keys[i]] == false) { + if (this.state.fieldErrors[keys[i]]) { return false; } } @@ -218,13 +217,17 @@ module.exports = React.createClass({ } }, - markFieldValid: function(fieldID, val, errorCode) { - const fieldValid = this.state.fieldValid; - fieldValid[fieldID] = val; - this.setState({fieldValid: fieldValid}); - if (!val) { - this.props.onError(errorCode); + markFieldValid: function(fieldID, valid, errorCode) { + const { fieldErrors } = this.state; + if (valid) { + fieldErrors[fieldID] = null; + } else { + fieldErrors[fieldID] = errorCode; } + this.setState({ + fieldErrors, + }); + this.props.onValidationChange(fieldErrors); }, fieldElementById(fieldID) { @@ -244,7 +247,7 @@ module.exports = React.createClass({ _classForField: function(fieldID, ...baseClasses) { let cls = baseClasses.join(' '); - if (this.state.fieldValid[fieldID] === false) { + if (this.state.fieldErrors[fieldID]) { if (cls) cls += ' '; cls += 'error'; } From 8e32798f45ad47804b4d5a3ebe7bbc05d5a8ffd1 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 21 Feb 2019 14:41:42 +0000 Subject: [PATCH 085/178] Ensure fields with errors are clearly visible Until we have better validation, let's at least ensure fields with errors are properly marked via color. --- res/css/views/auth/_AuthBody.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index 6216bdd4b8..778f5f6a4d 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -58,6 +58,10 @@ limitations under the License. background-color: $authpage-body-bg-color; } +.mx_AuthBody input.error { + color: $warning-color; +} + .mx_AuthBody_editServerDetails { padding-left: 1em; font-size: 12px; From b74107116b4c4a14a5edfddc14e23b38a199cd1d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 21 Feb 2019 15:00:56 +0000 Subject: [PATCH 086/178] Clarify what the username error refers to Fixes https://github.com/vector-im/riot-web/issues/8839 --- src/components/structures/auth/Registration.js | 2 +- src/components/views/dialogs/SetMxIdDialog.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 30b553a5b1..5d05e4139b 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -313,7 +313,7 @@ module.exports = React.createClass({ errMsg = _t('A phone number is required to register on this homeserver.'); break; case "RegistrationForm.ERR_USERNAME_INVALID": - errMsg = _t("Only use lower case letters, numbers and '=_-./'"); + errMsg = _t("A username can only contain lower case letters, numbers and '=_-./'"); break; case "RegistrationForm.ERR_USERNAME_BLANK": errMsg = _t('You need to enter a username.'); diff --git a/src/components/views/dialogs/SetMxIdDialog.js b/src/components/views/dialogs/SetMxIdDialog.js index 6f11a28eae..dfaff52278 100644 --- a/src/components/views/dialogs/SetMxIdDialog.js +++ b/src/components/views/dialogs/SetMxIdDialog.js @@ -115,7 +115,7 @@ export default React.createClass({ // user ID roughly looks okay from a Matrix perspective. if (!SAFE_LOCALPART_REGEX.test(this.state.username)) { this.setState({ - usernameError: _t("Only use lower case letters, numbers and '=_-./'"), + usernameError: _t("A username can only contain lower case letters, numbers and '=_-./'"), }); return Promise.reject(); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index abedde2a10..0212903641 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1132,7 +1132,7 @@ "Email address": "Email address", "This will allow you to reset your password and receive notifications.": "This will allow you to reset your password and receive notifications.", "Skip": "Skip", - "Only use lower case letters, numbers and '=_-./'": "Only use lower case letters, numbers and '=_-./'", + "A username can only contain lower case letters, numbers and '=_-./'": "A username can only contain lower case letters, numbers and '=_-./'", "Username not available": "Username not available", "Username invalid: %(errMessage)s": "Username invalid: %(errMessage)s", "An error occurred: %(error_string)s": "An error occurred: %(error_string)s", From d662dccfba12cabc29c09b908907c1fd3f95ccc4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 09:17:35 -0700 Subject: [PATCH 087/178] Fix favourites losing rooms and sorting weirdly By not flagging the room as inserted, we end up sorting it. Fixes https://github.com/vector-im/riot-web/issues/8857 --- src/stores/RoomListStore.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 47480aef85..9bf1f90da5 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -287,6 +287,7 @@ class RoomListStore extends Store { // Speed optimization: Skip the loop below if we're not going to do anything productive if (!hasRoom || LIST_ORDERS[key] !== 'recent') { listsClone[key] = this._state.lists[key]; + inserted = true; // Ensure that we don't try and sort the room into the tag continue; } else { listsClone[key] = []; From fe141412b64d7d16fd68812372363b052d67a85f Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 21 Feb 2019 18:02:00 +0000 Subject: [PATCH 088/178] Skip server details on registration with a default HS If Riot has been configured with a `default_hs_url` (or `default_server_name`, which then sets a default HS URL), then skip the server details on registration by default. Fixes https://github.com/vector-im/riot-web/issues/8840 --- src/components/structures/MatrixChat.js | 12 ++++++++++++ src/components/structures/auth/Registration.js | 7 +++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 7de4141192..6fc2cb073d 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -245,6 +245,17 @@ 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); @@ -1891,6 +1902,7 @@ 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()} diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 30b553a5b1..6431431640 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -60,6 +60,7 @@ module.exports = React.createClass({ customIsUrl: PropTypes.string, defaultHsUrl: PropTypes.string, defaultIsUrl: PropTypes.string, + skipServerDetails: PropTypes.bool, brand: PropTypes.string, email: PropTypes.string, // registration shouldn't know or care how login is done. @@ -75,8 +76,10 @@ module.exports = React.createClass({ // (they could come in from the URL params in a // registration email link) (this.props.clientSecret && this.props.sessionId) || - // or if custom URLs aren't allowed, skip them - !customURLsAllowed + // if custom URLs aren't allowed, skip to form + !customURLsAllowed || + // if other logic says to, skip to form + this.props.skipServerDetails ) { initialPhase = PHASE_REGISTRATION; } From ee8a027b4c78fe3239d1bce30d49a1519b0ab81f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 15:18:11 -0700 Subject: [PATCH 089/178] Fix toggle for email notifications The function doesn't receive an event, it receives a boolean. Fixes https://github.com/vector-im/riot-web/issues/8837 --- src/components/views/settings/Notifications.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js index 03bb4765ed..3b6321dc6f 100644 --- a/src/components/views/settings/Notifications.js +++ b/src/components/views/settings/Notifications.js @@ -132,9 +132,9 @@ module.exports = React.createClass({ }); }, - onEnableEmailNotificationsChange: function(address, event) { + onEnableEmailNotificationsChange: function(address, checked) { let emailPusherPromise; - if (event.target.checked) { + if (checked) { const data = {}; data['brand'] = SdkConfig.get().brand || 'Riot'; emailPusherPromise = UserSettingsStore.addEmailPusher(address, data); From 23dd3573f813ac95552e4be052771962527705b3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 15:18:54 -0700 Subject: [PATCH 090/178] Fix SdkConfig import in Notifications Fixes an issue discovered after fixing the toggle - The SdkConfig in onEnableEmailNotificationsChange ends up being undefined if the import is left as-is. --- src/components/views/settings/Notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js index 3b6321dc6f..42c981fa03 100644 --- a/src/components/views/settings/Notifications.js +++ b/src/components/views/settings/Notifications.js @@ -28,7 +28,7 @@ import { PushRuleVectorState, ContentRules, } from '../../../notifications'; -import * as SdkConfig from "../../../SdkConfig"; +import SdkConfig from "../../../SdkConfig"; import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; // TODO: this "view" component still has far too much application logic in it, From 2903a0e71218830c8b1c9ef072f1e8d98f589a67 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 8 Feb 2019 09:11:30 -0700 Subject: [PATCH 091/178] Rework EditableItemList to support mxField Also improves upon the general UX to be a bit friendlier for direct manipulation things. --- res/css/views/elements/_EditableItemList.scss | 59 +++-- .../views/elements/EditableItemList.js | 212 +++++++++--------- .../views/room_settings/AliasSettings.js | 16 +- 3 files changed, 131 insertions(+), 156 deletions(-) diff --git a/res/css/views/elements/_EditableItemList.scss b/res/css/views/elements/_EditableItemList.scss index 9fbb39aa17..be96d811d3 100644 --- a/res/css/views/elements/_EditableItemList.scss +++ b/res/css/views/elements/_EditableItemList.scss @@ -1,5 +1,5 @@ /* -Copyright 2017 New Vector Ltd. +Copyright 2017, 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. @@ -16,47 +16,38 @@ limitations under the License. .mx_EditableItemList { margin-top: 12px; - margin-bottom: 0px; + margin-bottom: 10px; } .mx_EditableItem { - display: flex; - margin-left: 56px; + margin-bottom: 5px; + margin-left: 15px; } -.mx_EditableItem .mx_EditableItem_editable { - border: 0px; - border-bottom: 1px solid $strong-input-border-color; - padding: 0px; - min-width: 240px; - max-width: 400px; - margin-bottom: 16px; -} - -.mx_EditableItem .mx_EditableItem_editable:focus { - border-bottom: 1px solid $accent-color; - outline: none; - box-shadow: none; -} - -.mx_EditableItem .mx_EditableItem_editablePlaceholder { - color: $settings-grey-fg-color; -} - -.mx_EditableItem .mx_EditableItem_addButton, -.mx_EditableItem .mx_EditableItem_removeButton { - padding-left: 0.5em; - position: relative; +.mx_EditableItem_delete { + margin-right: 5px; cursor: pointer; - - visibility: hidden; + vertical-align: middle; } -.mx_EditableItem:hover .mx_EditableItem_addButton, -.mx_EditableItem:hover .mx_EditableItem_removeButton { - visibility: visible; +.mx_EditableItem_email { + vertical-align: middle; +} + +.mx_EditableItem_promptText { + margin-right: 10px; +} + +.mx_EditableItem_confirmBtn { + margin-right: 5px; +} + +.mx_EditableItemList_newItem .mx_Field input { + // Use 100% of the space available for the input, but don't let the 10px + // padding on either side of the input to push it out of alignment. + width: calc(100% - 20px); } .mx_EditableItemList_label { - margin-bottom: 8px; -} + margin-bottom: 5px; +} \ No newline at end of file diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js index 7d96b1fd20..b87b2e78a5 100644 --- a/src/components/views/elements/EditableItemList.js +++ b/src/components/views/elements/EditableItemList.js @@ -1,5 +1,5 @@ /* -Copyright 2017 New Vector Ltd. +Copyright 2017, 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. @@ -18,140 +18,132 @@ import React from 'react'; import PropTypes from 'prop-types'; import sdk from '../../../index'; import {_t} from '../../../languageHandler.js'; +import Field from "./Field"; +import AccessibleButton from "./AccessibleButton"; -const EditableItem = React.createClass({ - displayName: 'EditableItem', - - propTypes: { - initialValue: PropTypes.string, +export class EditableItem extends React.Component { + static propTypes = { index: PropTypes.number, + value: PropTypes.string, + onRemove: PropTypes.func, + }; + + constructor() { + super(); + + this.state = { + verifyRemove: false, + }; + } + + _onRemove = (e) => { + e.stopPropagation(); + e.preventDefault(); + + this.setState({verifyRemove: true}); + }; + + _onDontRemove = (e) => { + e.stopPropagation(); + e.preventDefault(); + + this.setState({verifyRemove: false}); + }; + + _onActuallyRemove = (e) => { + e.stopPropagation(); + e.preventDefault(); + + if (this.props.onRemove) this.props.onRemove(this.props.index); + this.setState({verifyRemove: false}); + }; + + render() {if (this.state.verifyRemove) { + return ( +
    + + {_t("Are you sure?")} + + + {_t("Yes")} + + + {_t("No")} + +
    + ); + } + + return ( +
    + {_t("Remove")} + {this.props.value} +
    + ); + } +} + +export default class EditableItemList extends React.Component{ + static propTypes = { + items: PropTypes.arrayOf(PropTypes.string).isRequired, + itemsLabel: PropTypes.string, + noItemsLabel: PropTypes.string, placeholder: PropTypes.string, - onChange: PropTypes.func, - onRemove: PropTypes.func, - onAdd: PropTypes.func, - - addOnChange: PropTypes.bool, - }, - - onChange: function(value) { - this.setState({ value }); - if (this.props.onChange) this.props.onChange(value, this.props.index); - if (this.props.addOnChange && this.props.onAdd) this.props.onAdd(value); - }, - - onRemove: function() { - if (this.props.onRemove) this.props.onRemove(this.props.index); - }, - - onAdd: function() { - if (this.props.onAdd) this.props.onAdd(this.state.value); - }, - - render: function() { - const EditableText = sdk.getComponent('elements.EditableText'); - return
    - - { this.props.onAdd ? -
    - {_t("Add")} -
    - : -
    - {_t("Delete")} -
    - } -
    ; - }, -}); - -// TODO: Make this use the new Field element -module.exports = React.createClass({ - displayName: 'EditableItemList', - - propTypes: { - items: PropTypes.arrayOf(PropTypes.string).isRequired, - onNewItemChanged: PropTypes.func, onItemAdded: PropTypes.func, - onItemEdited: PropTypes.func, onItemRemoved: PropTypes.func, canEdit: PropTypes.bool, - }, + }; - getDefaultProps: function() { - return { - onItemAdded: () => {}, - onItemEdited: () => {}, - onItemRemoved: () => {}, - onNewItemChanged: () => {}, - }; - }, + _onItemAdded = (e) => { + e.stopPropagation(); + e.preventDefault(); - onItemAdded: function(value) { - this.props.onItemAdded(value); - }, + if (!this.refs.newItem) return; - onItemEdited: function(value, index) { - if (value.length === 0) { - this.onItemRemoved(index); - } else { - this.props.onItemEdited(value, index); - } - }, + const value = this.refs.newItem.value; + if (this.props.onItemAdded) this.props.onItemAdded(value); + }; - onItemRemoved: function(index) { - this.props.onItemRemoved(index); - }, + _onItemRemoved = (index) => { + if (this.props.onItemRemoved) this.props.onItemRemoved(index); + }; - onNewItemChanged: function(value) { - this.props.onNewItemChanged(value); - }, + _renderNewItemField() { + return ( +
    + + + {_t("Add")} + + + ) + } - render: function() { + render() { const editableItems = this.props.items.map((item, index) => { return ; }); - const label = this.props.items.length > 0 ? - this.props.itemsLabel : this.props.noItemsLabel; + const label = this.props.items.length > 0 ? this.props.itemsLabel : this.props.noItemsLabel; return (
    { label }
    { editableItems } - { this.props.canEdit ? - // This is slightly evil; we want a new instance of - // EditableItem when the list grows. To make sure it's - // reset to its initial state. - :
    - } + { this.props.canEdit ? this._renderNewItemField() :
    }
    ); - }, -}); + } +} diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index 827656e770..a56b4903bc 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -259,21 +259,13 @@ module.exports = React.createClass({
    { _t("Remote addresses for this room:") }
    -
    +
      { this.state.remoteDomains.map((domain, i) => { - return this.state.domainToAliases[domain].map(function(alias, j) { - return ( -
      - -
      - ); + return this.state.domainToAliases[domain].map((alias, j) => { + return
    • {alias}
    • ; }); }) } -
    +
    ); } From 5d2f17c49a7493acc5f1cd52b74bccd80c050282 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 20 Feb 2019 16:13:35 -0700 Subject: [PATCH 092/178] Make adding aliases direct manipulation --- .../views/elements/EditableItemList.js | 19 ++- .../views/room_settings/AliasSettings.js | 136 +++++++++--------- src/i18n/strings/en_EN.json | 5 +- 3 files changed, 78 insertions(+), 82 deletions(-) diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js index b87b2e78a5..1fdae69edd 100644 --- a/src/components/views/elements/EditableItemList.js +++ b/src/components/views/elements/EditableItemList.js @@ -92,9 +92,11 @@ export default class EditableItemList extends React.Component{ itemsLabel: PropTypes.string, noItemsLabel: PropTypes.string, placeholder: PropTypes.string, + newItem: PropTypes.string, onItemAdded: PropTypes.func, onItemRemoved: PropTypes.func, + onNewItemChanged: PropTypes.func, canEdit: PropTypes.bool, }; @@ -103,22 +105,25 @@ export default class EditableItemList extends React.Component{ e.stopPropagation(); e.preventDefault(); - if (!this.refs.newItem) return; - - const value = this.refs.newItem.value; - if (this.props.onItemAdded) this.props.onItemAdded(value); + if (this.props.onItemAdded) this.props.onItemAdded(this.props.newItem); }; _onItemRemoved = (index) => { if (this.props.onItemRemoved) this.props.onItemRemoved(index); }; + _onNewItemChanged = (e) => { + if (this.props.onNewItemChanged) this.props.onNewItemChanged(e.target.value); + }; + _renderNewItemField() { return ( -
    - + {_t("Add")} diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index a56b4903bc..2757c15515 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -1,6 +1,6 @@ /* Copyright 2016 OpenMarket 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. @@ -23,32 +23,32 @@ const MatrixClientPeg = require('../../../MatrixClientPeg'); const sdk = require("../../../index"); import { _t } from '../../../languageHandler'; import Field from "../elements/Field"; +import ErrorDialog from "../dialogs/ErrorDialog"; const Modal = require("../../../Modal"); -module.exports = React.createClass({ - displayName: 'AliasSettings', - - propTypes: { +export default class AliasSettings extends React.Component { + static propTypes = { roomId: PropTypes.string.isRequired, canSetCanonicalAlias: PropTypes.bool.isRequired, canSetAliases: PropTypes.bool.isRequired, aliasEvents: PropTypes.array, // [MatrixEvent] canonicalAliasEvent: PropTypes.object, // MatrixEvent - }, + }; - getDefaultProps: function() { - return { - canSetAliases: false, - canSetCanonicalAlias: false, - aliasEvents: [], - }; - }, + static defaultProps = { + canSetAliases: false, + canSetCanonicalAlias: false, + aliasEvents: [], + }; - getInitialState: function() { - return this.recalculateState(this.props.aliasEvents, this.props.canonicalAliasEvent); - }, + constructor(props) { + super(props); - recalculateState: function(aliasEvents, canonicalAliasEvent) { + const aliasState = this.recalculateState(props.aliasEvents, props.canonicalAliasEvent); + this.state = Object.assign({newItem: ""}, aliasState); + } + + recalculateState(aliasEvents, canonicalAliasEvent) { aliasEvents = aliasEvents || []; const state = { @@ -69,9 +69,9 @@ module.exports = React.createClass({ } return state; - }, + } - saveSettings: function() { + saveSettings() { let promises = []; // save new aliases for m.room.aliases @@ -118,9 +118,9 @@ module.exports = React.createClass({ } return promises; - }, + } - aliasEventsToDictionary: function(aliasEvents) { // m.room.alias events + aliasEventsToDictionary(aliasEvents) { // m.room.alias events const dict = {}; aliasEvents.forEach((event) => { dict[event.getStateKey()] = ( @@ -128,35 +128,53 @@ module.exports = React.createClass({ ); }); return dict; - }, + } - isAliasValid: function(alias) { + isAliasValid(alias) { // XXX: FIXME https://github.com/matrix-org/matrix-doc/issues/668 return (alias.match(/^#([^\/:,]+?):(.+)$/) && encodeURI(alias) === alias); - }, + } - getAliasOperations: function() { + getAliasOperations() { const oldAliases = this.aliasEventsToDictionary(this.props.aliasEvents); return ObjectUtils.getKeyValueArrayDiffs(oldAliases, this.state.domainToAliases); - }, + } - onNewAliasChanged: function(value) { + onNewAliasChanged = (value) => { this.setState({newAlias: value}); - }, + }; - onLocalAliasAdded: function(alias) { + onLocalAliasAdded = (alias) => { if (!alias || alias.length === 0) return; // ignore attempts to create blank aliases const localDomain = MatrixClientPeg.get().getDomain(); if (!alias.includes(':')) alias += ':' + localDomain; if (this.isAliasValid(alias) && alias.endsWith(localDomain)) { - this.state.domainToAliases[localDomain] = this.state.domainToAliases[localDomain] || []; - this.state.domainToAliases[localDomain].push(alias); + MatrixClientPeg.get().createAlias(alias, this.props.roomId).then(() => { + const localAliases = this.state.domainToAliases[localDomain] || []; + const domainAliases = Object.assign({}, this.state.domainToAliases); + domainAliases[localDomain] = [...localAliases, alias]; - this.setState({ - domainToAliases: this.state.domainToAliases, - // Reset the add field - newAlias: "", + this.setState({ + domainToAliases: domainAliases, + // Reset the add field + newAlias: "", + }); + + if (!this.props.canonicalAlias) { + this.setState({ + canonicalAlias: alias, + }); + } + }).catch((err) => { + console.error(err); + Modal.createTrackedDialog('Error creating alias', '', ErrorDialog, { + title: _t("Error creating alias"), + description: _t( + "There was an error creating that alias. It may not be allowed by the server " + + "or a temporary failure occurred." + ), + }); }); } else { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); @@ -165,30 +183,9 @@ module.exports = React.createClass({ description: _t('\'%(alias)s\' is not a valid format for an alias', { alias: alias }), }); } + }; - if (!this.props.canonicalAlias) { - this.setState({ - canonicalAlias: alias, - }); - } - }, - - onLocalAliasChanged: function(alias, index) { - if (alias === "") return; // hit the delete button to delete please - const localDomain = MatrixClientPeg.get().getDomain(); - if (!alias.includes(':')) alias += ':' + localDomain; - if (this.isAliasValid(alias) && alias.endsWith(localDomain)) { - this.state.domainToAliases[localDomain][index] = alias; - } else { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Invalid address format', '', ErrorDialog, { - title: _t('Invalid address format'), - description: _t('\'%(alias)s\' is not a valid format for an address', { alias: alias }), - }); - } - }, - - onLocalAliasDeleted: function(index) { + onLocalAliasDeleted = (index) => { const localDomain = MatrixClientPeg.get().getDomain(); // It's a bit naughty to directly manipulate this.state, and React would // normally whine at you, but it can't see us doing the splice. Given we @@ -204,17 +201,15 @@ module.exports = React.createClass({ canonicalAlias: null, }); } - }, + }; - onCanonicalAliasChange: function(event) { + onCanonicalAliasChange = (event) => { this.setState({ canonicalAlias: event.target.value, }); - }, + }; - render: function() { - const self = this; - const EditableText = sdk.getComponent("elements.EditableText"); + render() { const EditableItemList = sdk.getComponent("elements.EditableItemList"); const localDomain = MatrixClientPeg.get().getDomain(); @@ -227,8 +222,8 @@ module.exports = React.createClass({ element='select' id='canonicalAlias' label={_t('Main address')}> { - Object.keys(self.state.domainToAliases).map((domain, i) => { - return self.state.domainToAliases[domain].map((alias, j) => { + Object.keys(this.state.domainToAliases).map((domain, i) => { + return this.state.domainToAliases[domain].map((alias, j) => { if (alias === this.state.canonicalAlias) found = true; return ( @@ -280,7 +275,6 @@ module.exports = React.createClass({ onNewItemChanged={this.onNewAliasChanged} canEdit={this.props.canSetAliases} onItemAdded={this.onLocalAliasAdded} - onItemEdited={this.onLocalAliasChanged} onItemRemoved={this.onLocalAliasDeleted} itemsLabel={_t('Local addresses for this room:')} noItemsLabel={_t('This room has no local addresses')} @@ -288,10 +282,8 @@ module.exports = React.createClass({ 'New address (e.g. #foo:%(localDomain)s)', {localDomain: localDomain}, )} /> - { remote_aliases_section } -
    ); - }, -}); + } +}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0048636411..f322c56bd9 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -804,10 +804,10 @@ "Hide Stickers": "Hide Stickers", "Show Stickers": "Show Stickers", "Jump to first unread message.": "Jump to first unread message.", + "Error creating alias": "Error creating alias", + "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.": "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.", "Invalid alias format": "Invalid alias format", "'%(alias)s' is not a valid format for an alias": "'%(alias)s' is not a valid format for an alias", - "Invalid address format": "Invalid address format", - "'%(alias)s' is not a valid format for an address": "'%(alias)s' is not a valid format for an address", "Main address": "Main address", "not specified": "not specified", "not set": "not set", @@ -925,7 +925,6 @@ "Verify...": "Verify...", "Join": "Join", "No results": "No results", - "Delete": "Delete", "Communities": "Communities", "Home": "Home", "You cannot delete this image. (%(code)s)": "You cannot delete this image. (%(code)s)", From 278e48cfc98113e92d529b74f2a44a1750ef00cc Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 20 Feb 2019 20:44:08 -0700 Subject: [PATCH 093/178] Make removing aliases direct manipulation --- .../views/room_settings/AliasSettings.js | 62 +++++++++++-------- src/i18n/strings/en_EN.json | 2 + 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index 2757c15515..97f08a8cf2 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -155,17 +155,18 @@ export default class AliasSettings extends React.Component { const domainAliases = Object.assign({}, this.state.domainToAliases); domainAliases[localDomain] = [...localAliases, alias]; - this.setState({ + const newState = { domainToAliases: domainAliases, // Reset the add field newAlias: "", - }); + }; + // TODO: How should we do direct manipulation for this? if (!this.props.canonicalAlias) { - this.setState({ - canonicalAlias: alias, - }); + newState.canonicalAlias = alias; } + + this.setState(newState); }).catch((err) => { console.error(err); Modal.createTrackedDialog('Error creating alias', '', ErrorDialog, { @@ -187,20 +188,31 @@ export default class AliasSettings extends React.Component { onLocalAliasDeleted = (index) => { const localDomain = MatrixClientPeg.get().getDomain(); - // It's a bit naughty to directly manipulate this.state, and React would - // normally whine at you, but it can't see us doing the splice. Given we - // promptly setState anyway, it's just about acceptable. The alternative - // would be to arbitrarily deepcopy to a temp variable and then setState - // that, but why bother when we can cut this corner. - const alias = this.state.domainToAliases[localDomain].splice(index, 1); - this.setState({ - domainToAliases: this.state.domainToAliases, - }); - if (this.props.canonicalAlias === alias) { - this.setState({ - canonicalAlias: null, + + const alias = this.state.domainToAliases[localDomain][index]; + + // TODO: In future, we should probably be making sure that the alias actually belongs + // to this room. See https://github.com/vector-im/riot-web/issues/7353 + MatrixClientPeg.get().deleteAlias(alias).then(() => { + const localAliases = this.state.domainToAliases[localDomain].filter((a) => a !== alias); + const domainAliases = Object.assign({}, this.state.domainToAliases); + domainAliases[localDomain] = localAliases; + + const newState = {domainToAliases: domainAliases}; + if (this.props.canonicalAlias === alias) { + newState.canonicalAlias = null; + } + this.setState(newState); + }).catch((err) => { + console.error(err); + Modal.createTrackedDialog('Error removing alias', '', ErrorDialog, { + title: _t("Error removing alias"), + description: _t( + "There was an error removing that alias. It may no longer exist or a temporary " + + "error occurred." + ), }); - } + }); }; onCanonicalAliasChange = (event) => { @@ -213,11 +225,11 @@ export default class AliasSettings extends React.Component { const EditableItemList = sdk.getComponent("elements.EditableItemList"); const localDomain = MatrixClientPeg.get().getDomain(); - let canonical_alias_section; + let canonicalAliasSection; if (this.props.canSetCanonicalAlias) { let found = false; const canonicalValue = this.state.canonicalAlias || ""; - canonical_alias_section = ( + canonicalAliasSection = ( @@ -242,14 +254,14 @@ export default class AliasSettings extends React.Component { ); } else { - canonical_alias_section = ( + canonicalAliasSection = ( { this.state.canonicalAlias || _t('not set') } ); } - let remote_aliases_section; + let remoteAliasesSection; if (this.state.remoteDomains.length) { - remote_aliases_section = ( + remoteAliasesSection = (
    { _t("Remote addresses for this room:") } @@ -267,7 +279,7 @@ export default class AliasSettings extends React.Component { return (
    - {canonical_alias_section} + {canonicalAliasSection} - { remote_aliases_section } + {remoteAliasesSection}
    ); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f322c56bd9..d373076eb7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -808,6 +808,8 @@ "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.": "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.", "Invalid alias format": "Invalid alias format", "'%(alias)s' is not a valid format for an alias": "'%(alias)s' is not a valid format for an alias", + "Error removing alias": "Error removing alias", + "There was an error removing that alias. It may no longer exist or a temporary error occurred.": "There was an error removing that alias. It may no longer exist or a temporary error occurred.", "Main address": "Main address", "not specified": "not specified", "not set": "not set", From 750c9202ccc5d63059d360e43f80cd24ed97d842 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 16:11:09 -0700 Subject: [PATCH 094/178] Make the canonical alias direct manipulation --- .../views/room_settings/AliasSettings.js | 53 +++++++++++++------ src/i18n/strings/en_EN.json | 2 + 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index 97f08a8cf2..ee4fc73593 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -55,6 +55,7 @@ export default class AliasSettings extends React.Component { domainToAliases: {}, // { domain.com => [#alias1:domain.com, #alias2:domain.com] } remoteDomains: [], // [ domain.com, foobar.com ] canonicalAlias: null, // #canonical:domain.com + updatingCanonicalAlias: false, }; const localDomain = MatrixClientPeg.get().getDomain(); @@ -140,6 +141,32 @@ export default class AliasSettings extends React.Component { return ObjectUtils.getKeyValueArrayDiffs(oldAliases, this.state.domainToAliases); } + changeCanonicalAlias(alias) { + if (!this.props.canSetCanonicalAlias) return; + + this.setState({ + canonicalAlias: alias, + updatingCanonicalAlias: true, + }); + + const eventContent = {}; + if (alias) eventContent["alias"] = alias; + + MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.canonical_alias", + eventContent, "").catch((err) => { + console.error(err); + Modal.createTrackedDialog('Error updating main address', '', ErrorDialog, { + title: _t("Error updating main address"), + description: _t( + "There was an error updating the room's main address. It may not be allowed by the server " + + "or a temporary failure occurred." + ), + }); + }).finally(() => { + this.setState({updatingCanonicalAlias: false}); + }); + } + onNewAliasChanged = (value) => { this.setState({newAlias: value}); }; @@ -155,18 +182,15 @@ export default class AliasSettings extends React.Component { const domainAliases = Object.assign({}, this.state.domainToAliases); domainAliases[localDomain] = [...localAliases, alias]; - const newState = { + this.setState({ domainToAliases: domainAliases, // Reset the add field newAlias: "", - }; + }); - // TODO: How should we do direct manipulation for this? - if (!this.props.canonicalAlias) { - newState.canonicalAlias = alias; + if (!this.state.canonicalAlias) { + this.changeCanonicalAlias(alias); } - - this.setState(newState); }).catch((err) => { console.error(err); Modal.createTrackedDialog('Error creating alias', '', ErrorDialog, { @@ -198,11 +222,11 @@ export default class AliasSettings extends React.Component { const domainAliases = Object.assign({}, this.state.domainToAliases); domainAliases[localDomain] = localAliases; - const newState = {domainToAliases: domainAliases}; - if (this.props.canonicalAlias === alias) { - newState.canonicalAlias = null; + this.setState({domainToAliases: domainAliases}); + + if (this.state.canonicalAlias === alias) { + this.changeCanonicalAlias(null); } - this.setState(newState); }).catch((err) => { console.error(err); Modal.createTrackedDialog('Error removing alias', '', ErrorDialog, { @@ -216,9 +240,7 @@ export default class AliasSettings extends React.Component { }; onCanonicalAliasChange = (event) => { - this.setState({ - canonicalAlias: event.target.value, - }); + this.changeCanonicalAlias(event.target.value); }; render() { @@ -231,7 +253,8 @@ export default class AliasSettings extends React.Component { const canonicalValue = this.state.canonicalAlias || ""; canonicalAliasSection = ( + disabled={this.state.updatingCanonicalAlias} element='select' + id='canonicalAlias' label={_t('Main address')}> { Object.keys(this.state.domainToAliases).map((domain, i) => { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d373076eb7..b74f14a99e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -804,6 +804,8 @@ "Hide Stickers": "Hide Stickers", "Show Stickers": "Show Stickers", "Jump to first unread message.": "Jump to first unread message.", + "Error updating main address": "Error updating main address", + "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.", "Error creating alias": "Error creating alias", "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.": "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.", "Invalid alias format": "Invalid alias format", From 2990cf41e7d6b3886e22f206273ec20b97c3c9f5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 16:27:49 -0700 Subject: [PATCH 095/178] Remove old "click to save" functionality from AliasSettings --- .../views/room_settings/AliasSettings.js | 73 ++----------------- .../settings/tabs/GeneralRoomSettingsTab.js | 11 +-- 2 files changed, 7 insertions(+), 77 deletions(-) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index ee4fc73593..d6a6b608e2 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -44,81 +44,25 @@ export default class AliasSettings extends React.Component { constructor(props) { super(props); - const aliasState = this.recalculateState(props.aliasEvents, props.canonicalAliasEvent); - this.state = Object.assign({newItem: ""}, aliasState); - } - - recalculateState(aliasEvents, canonicalAliasEvent) { - aliasEvents = aliasEvents || []; - const state = { domainToAliases: {}, // { domain.com => [#alias1:domain.com, #alias2:domain.com] } remoteDomains: [], // [ domain.com, foobar.com ] canonicalAlias: null, // #canonical:domain.com updatingCanonicalAlias: false, + newItem: "", }; + const localDomain = MatrixClientPeg.get().getDomain(); - - state.domainToAliases = this.aliasEventsToDictionary(aliasEvents); - + state.domainToAliases = this.aliasEventsToDictionary(props.aliasEvents || []); state.remoteDomains = Object.keys(state.domainToAliases).filter((domain) => { return domain !== localDomain && state.domainToAliases[domain].length > 0; }); - if (canonicalAliasEvent) { - state.canonicalAlias = canonicalAliasEvent.getContent().alias; + if (props.canonicalAliasEvent) { + state.canonicalAlias = props.canonicalAliasEvent.getContent().alias; } - return state; - } - - saveSettings() { - let promises = []; - - // save new aliases for m.room.aliases - const aliasOperations = this.getAliasOperations(); - for (let i = 0; i < aliasOperations.length; i++) { - const alias_operation = aliasOperations[i]; - console.log("alias %s %s", alias_operation.place, alias_operation.val); - switch (alias_operation.place) { - case 'add': - promises.push( - MatrixClientPeg.get().createAlias( - alias_operation.val, this.props.roomId, - ), - ); - break; - case 'del': - promises.push( - MatrixClientPeg.get().deleteAlias( - alias_operation.val, - ), - ); - break; - default: - console.log("Unknown alias operation, ignoring: " + alias_operation.place); - } - } - - let oldCanonicalAlias = null; - if (this.props.canonicalAliasEvent) { - oldCanonicalAlias = this.props.canonicalAliasEvent.getContent().alias; - } - - const newCanonicalAlias = this.state.canonicalAlias; - - if (this.props.canSetCanonicalAlias && oldCanonicalAlias !== newCanonicalAlias) { - console.log("AliasSettings: Updating canonical alias"); - promises = [Promise.all(promises).then( - MatrixClientPeg.get().sendStateEvent( - this.props.roomId, "m.room.canonical_alias", { - alias: newCanonicalAlias, - }, "", - ), - )]; - } - - return promises; + this.state = state; } aliasEventsToDictionary(aliasEvents) { // m.room.alias events @@ -136,11 +80,6 @@ export default class AliasSettings extends React.Component { return (alias.match(/^#([^\/:,]+?):(.+)$/) && encodeURI(alias) === alias); } - getAliasOperations() { - const oldAliases = this.aliasEventsToDictionary(this.props.aliasEvents); - return ObjectUtils.getKeyValueArrayDiffs(oldAliases, this.state.domainToAliases); - } - changeCanonicalAlias(alias) { if (!this.props.canSetCanonicalAlias) return; diff --git a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/GeneralRoomSettingsTab.js index 75373a69bc..0ce2c174e4 100644 --- a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js +++ b/src/components/views/settings/tabs/GeneralRoomSettingsTab.js @@ -68,12 +68,6 @@ export default class GeneralRoomSettingsTab extends React.Component { }); }; - _saveAliases = (e) => { - // TODO: Live modification? - if (!this.refs.aliasSettings) return; - this.refs.aliasSettings.saveSettings(); - }; - _saveGroups = (e) => { // TODO: Live modification? if (!this.refs.flairSettings) return; @@ -113,12 +107,9 @@ export default class GeneralRoomSettingsTab extends React.Component { {_t("Room Addresses")}
    - - - {_t("Save")} -
    Date: Thu, 21 Feb 2019 16:33:02 -0700 Subject: [PATCH 096/178] Disable main address dropdown when lacking permissions Instead of rendering it as a span with no label. --- .../views/room_settings/AliasSettings.js | 61 ++++++++----------- src/i18n/strings/en_EN.json | 1 - 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index d6a6b608e2..4859174b28 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -186,40 +186,33 @@ export default class AliasSettings extends React.Component { const EditableItemList = sdk.getComponent("elements.EditableItemList"); const localDomain = MatrixClientPeg.get().getDomain(); - let canonicalAliasSection; - if (this.props.canSetCanonicalAlias) { - let found = false; - const canonicalValue = this.state.canonicalAlias || ""; - canonicalAliasSection = ( - - - { - Object.keys(this.state.domainToAliases).map((domain, i) => { - return this.state.domainToAliases[domain].map((alias, j) => { - if (alias === this.state.canonicalAlias) found = true; - return ( - - ); - }); - }) - } - { - found || !this.state.canonicalAlias ? '' : - - } - - ); - } else { - canonicalAliasSection = ( - { this.state.canonicalAlias || _t('not set') } - ); - } + let found = false; + const canonicalValue = this.state.canonicalAlias || ""; + const canonicalAliasSection = ( + + + { + Object.keys(this.state.domainToAliases).map((domain, i) => { + return this.state.domainToAliases[domain].map((alias, j) => { + if (alias === this.state.canonicalAlias) found = true; + return ( + + ); + }); + }) + } + { + found || !this.state.canonicalAlias ? '' : + + } + + ); let remoteAliasesSection; if (this.state.remoteDomains.length) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b74f14a99e..c32b604f7e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -814,7 +814,6 @@ "There was an error removing that alias. It may no longer exist or a temporary error occurred.": "There was an error removing that alias. It may no longer exist or a temporary error occurred.", "Main address": "Main address", "not specified": "not specified", - "not set": "not set", "Remote addresses for this room:": "Remote addresses for this room:", "Local addresses for this room:": "Local addresses for this room:", "This room has no local addresses": "This room has no local addresses", From 10daa35263eb88b539cb41aec4cdd73e1a44dc3e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 16:40:12 -0700 Subject: [PATCH 097/178] Make related groups direct manipulation --- .../room_settings/RelatedGroupSettings.js | 48 +++++++------------ .../settings/tabs/GeneralRoomSettingsTab.js | 11 +---- 2 files changed, 19 insertions(+), 40 deletions(-) diff --git a/src/components/views/room_settings/RelatedGroupSettings.js b/src/components/views/room_settings/RelatedGroupSettings.js index 91a538ca93..51268426d8 100644 --- a/src/components/views/room_settings/RelatedGroupSettings.js +++ b/src/components/views/room_settings/RelatedGroupSettings.js @@ -1,5 +1,5 @@ /* -Copyright 2017 New Vector Ltd. +Copyright 2017, 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. @@ -21,6 +21,7 @@ import sdk from '../../../index'; import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; import isEqual from 'lodash/isEqual'; +import ErrorDialog from "../dialogs/ErrorDialog"; const GROUP_ID_REGEX = /\+\S+:\S+/; @@ -46,7 +47,7 @@ module.exports = React.createClass({ getInitialState: function() { return { newGroupsList: this.getInitialGroupList(), - newGroupId: null, + newGroupId: "", }; }, @@ -54,24 +55,19 @@ module.exports = React.createClass({ return this.props.relatedGroupsEvent ? (this.props.relatedGroupsEvent.getContent().groups || []) : []; }, - needsSaving: function() { - const cli = this.context.matrixClient; - const room = cli.getRoom(this.props.roomId); - if (!room.currentState.maySendStateEvent('m.room.related_groups', cli.getUserId())) return false; - return !isEqual(this.getInitialGroupList(), this.state.newGroupsList); - }, - - saveSettings: function() { - if (!this.needsSaving()) return Promise.resolve(); - - return this.context.matrixClient.sendStateEvent( - this.props.roomId, - 'm.room.related_groups', - { - groups: this.state.newGroupsList, - }, - '', - ); + updateGroups: function() { + this.context.matrixClient.sendStateEvent(this.props.roomId, 'm.room.related_groups', { + groups: this.state.newGroupsList, + }, '').catch((err) => { + console.error(err); + Modal.createTrackedDialog('Error updating flair', '', ErrorDialog, { + title: _t("Error updating flair"), + description: _t( + "There was an error updating the flair for this room. The server may not allow it or " + + "a temporary error occurred." + ), + }); + }) }, validateGroupId: function(groupId) { @@ -98,21 +94,14 @@ module.exports = React.createClass({ newGroupsList: this.state.newGroupsList.concat([groupId]), newGroupId: '', }); - }, - - onGroupEdited: function(groupId, index) { - if (groupId.length === 0 || !this.validateGroupId(groupId)) { - return; - } - this.setState({ - newGroupsList: Object.assign(this.state.newGroupsList, {[index]: groupId}), - }); + this.updateGroups(); }, onGroupDeleted: function(index) { const newGroupsList = this.state.newGroupsList.slice(); newGroupsList.splice(index, 1); this.setState({ newGroupsList }); + this.updateGroups(); }, render: function() { @@ -126,7 +115,6 @@ module.exports = React.createClass({ canEdit={this.props.canSetRelatedGroups} onNewItemChanged={this.onNewGroupChanged} onItemAdded={this.onGroupAdded} - onItemEdited={this.onGroupEdited} onItemRemoved={this.onGroupDeleted} itemsLabel={_t('Showing flair for these communities:')} noItemsLabel={_t('This room is not showing flair for any communities')} diff --git a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/GeneralRoomSettingsTab.js index 0ce2c174e4..f43fc8a682 100644 --- a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js +++ b/src/components/views/settings/tabs/GeneralRoomSettingsTab.js @@ -68,12 +68,6 @@ export default class GeneralRoomSettingsTab extends React.Component { }); }; - _saveGroups = (e) => { - // TODO: Live modification? - if (!this.refs.flairSettings) return; - this.refs.flairSettings.saveSettings(); - }; - _onLeaveClick = () => { dis.dispatch({ action: 'leave_room', @@ -122,12 +116,9 @@ export default class GeneralRoomSettingsTab extends React.Component { {_t("Flair")}
    - - - {_t("Save")} -
    {_t("URL Previews")} From 96eeab23af4321102c9db9a6c09053ab4df95521 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 16:45:57 -0700 Subject: [PATCH 098/178] Convert RelatedGroupSettings to a class component --- .../room_settings/RelatedGroupSettings.js | 73 +++++++++---------- src/i18n/strings/en_EN.json | 2 + 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/src/components/views/room_settings/RelatedGroupSettings.js b/src/components/views/room_settings/RelatedGroupSettings.js index 51268426d8..3381bdd0ff 100644 --- a/src/components/views/room_settings/RelatedGroupSettings.js +++ b/src/components/views/room_settings/RelatedGroupSettings.js @@ -25,39 +25,33 @@ import ErrorDialog from "../dialogs/ErrorDialog"; const GROUP_ID_REGEX = /\+\S+:\S+/; -module.exports = React.createClass({ - displayName: 'RelatedGroupSettings', - - propTypes: { +export default class RelatedGroupSettings extends React.Component { + static propTypes = { roomId: PropTypes.string.isRequired, canSetRelatedGroups: PropTypes.bool.isRequired, relatedGroupsEvent: PropTypes.instanceOf(MatrixEvent), - }, + }; - contextTypes: { + static contextTypes = { matrixClient: PropTypes.instanceOf(MatrixClient), - }, + }; - getDefaultProps: function() { - return { - canSetRelatedGroups: false, - }; - }, + static defaultProps = { + canSetRelatedGroups: false, + }; - getInitialState: function() { - return { - newGroupsList: this.getInitialGroupList(), + constructor(props) { + super(props); + + this.state = { newGroupId: "", + newGroupsList: props.relatedGroupsEvent ? (props.relatedGroupsEvent.getContent().groups || []) : [], }; - }, + } - getInitialGroupList: function() { - return this.props.relatedGroupsEvent ? (this.props.relatedGroupsEvent.getContent().groups || []) : []; - }, - - updateGroups: function() { + updateGroups(newGroupsList) { this.context.matrixClient.sendStateEvent(this.props.roomId, 'm.room.related_groups', { - groups: this.state.newGroupsList, + groups: newGroupsList, }, '').catch((err) => { console.error(err); Modal.createTrackedDialog('Error updating flair', '', ErrorDialog, { @@ -68,9 +62,9 @@ module.exports = React.createClass({ ), }); }) - }, + } - validateGroupId: function(groupId) { + validateGroupId(groupId) { if (!GROUP_ID_REGEX.test(groupId)) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Invalid related community ID', '', ErrorDialog, { @@ -80,31 +74,32 @@ module.exports = React.createClass({ return false; } return true; - }, + } - onNewGroupChanged: function(newGroupId) { + onNewGroupChanged = (newGroupId) => { this.setState({ newGroupId }); - }, + }; - onGroupAdded: function(groupId) { + onGroupAdded = (groupId) => { if (groupId.length === 0 || !this.validateGroupId(groupId)) { return; } + const newGroupsList = [...this.state.newGroupsList, groupId]; this.setState({ - newGroupsList: this.state.newGroupsList.concat([groupId]), + newGroupsList: newGroupsList, newGroupId: '', }); - this.updateGroups(); - }, + this.updateGroups(newGroupsList); + }; - onGroupDeleted: function(index) { - const newGroupsList = this.state.newGroupsList.slice(); - newGroupsList.splice(index, 1); + onGroupDeleted = (index) => { + const group = this.state.newGroupsList[index]; + const newGroupsList = this.state.newGroupsList.filter((g) => g !== group); this.setState({ newGroupsList }); - this.updateGroups(); - }, + this.updateGroups(newGroupsList); + }; - render: function() { + render() { const localDomain = this.context.matrixClient.getDomain(); const EditableItemList = sdk.getComponent('elements.EditableItemList'); return
    @@ -123,5 +118,5 @@ module.exports = React.createClass({ )} />
    ; - }, -}); + } +}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c32b604f7e..c292e6ee85 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -818,6 +818,8 @@ "Local addresses for this room:": "Local addresses for this room:", "This room has no local addresses": "This room has no local addresses", "New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)", + "Error updating flair": "Error updating flair", + "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.", "Invalid community ID": "Invalid community ID", "'%(groupId)s' is not a valid community ID": "'%(groupId)s' is not a valid community ID", "Showing flair for these communities:": "Showing flair for these communities:", From 003d0eb0bf396e54956cf4795807aad243318541 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 17:03:15 -0700 Subject: [PATCH 099/178] Show changes to related groups (flair) in the timeline --- src/TextForEvent.js | 31 +++++++++++++++++++++++++ src/components/views/rooms/EventTile.js | 1 + src/i18n/strings/en_EN.json | 3 +++ 3 files changed, 35 insertions(+) diff --git a/src/TextForEvent.js b/src/TextForEvent.js index ac0af82ff1..030c346ccc 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -166,6 +166,36 @@ function textForGuestAccessEvent(ev) { } } +function textForRelatedGroupsEvent(ev) { + const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); + const groups = ev.getContent().groups || []; + const prevGroups = ev.getPrevContent().groups || []; + const added = groups.filter((g) => !prevGroups.includes(g)); + const removed = prevGroups.filter((g) => !groups.includes(g)); + + if (added.length && !removed.length) { + return _t('%(senderDisplayName)s enabled flair for %(groups)s in this room.', { + senderDisplayName, + groups: added.join(', '), + }); + } else if (!added.length && removed.length) { + return _t('%(senderDisplayName)s disabled flair for %(groups)s in this room.', { + senderDisplayName, + groups: removed.join(', '), + }); + } else if (added.length && removed.length) { + return _t('%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for ' + + '%(oldGroups)s in this room.', { + senderDisplayName, + newGroups: added.join(', '), + oldGroups: removed.join(', '), + }); + } else { + // Don't bother rendering this change (because there were no changes) + return ''; + } +} + function textForServerACLEvent(ev) { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const prevContent = ev.getPrevContent(); @@ -473,6 +503,7 @@ const stateHandlers = { 'm.room.tombstone': textForTombstoneEvent, 'm.room.join_rules': textForJoinRulesEvent, 'm.room.guest_access': textForGuestAccessEvent, + 'm.room.related_groups': textForRelatedGroupsEvent, 'im.vector.modular.widgets': textForWidgetEvent, }; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 8e1fb5af9f..15b8357b24 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -65,6 +65,7 @@ const stateEventTileTypes = { 'm.room.tombstone': 'messages.TextualEvent', 'm.room.join_rules': 'messages.TextualEvent', 'm.room.guest_access': 'messages.TextualEvent', + 'm.room.related_groups': 'messages.TextualEvent', }; function getHandlerTile(ev) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c292e6ee85..9b456f6721 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -192,6 +192,9 @@ "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s has allowed guests to join the room.", "%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s has prevented guests from joining the room.", "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s changed guest access to %(rule)s", + "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s enabled flair for %(groups)s in this room.", + "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s disabled flair for %(groups)s in this room.", + "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.", "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.", "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s added %(addedAddresses)s as addresses for this room.", "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s added %(addedAddresses)s as an address for this room.", From 1e6594ceba06664d7484519ce33d76206cda0a7e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 17:15:25 -0700 Subject: [PATCH 100/178] Disable removal of items if the user doesn't have permission --- .../views/elements/EditableItemList.js | 39 +++++++++++-------- .../views/room_settings/AliasSettings.js | 1 + .../room_settings/RelatedGroupSettings.js | 1 + 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js index 1fdae69edd..66b5962426 100644 --- a/src/components/views/elements/EditableItemList.js +++ b/src/components/views/elements/EditableItemList.js @@ -58,28 +58,29 @@ export class EditableItem extends React.Component { this.setState({verifyRemove: false}); }; - render() {if (this.state.verifyRemove) { - return ( -
    + render() { + if (this.state.verifyRemove) { + return ( +
    {_t("Are you sure?")} - - {_t("Yes")} - - - {_t("No")} - -
    - ); - } + + {_t("Yes")} + + + {_t("No")} + +
    + ); + } return (
    {_t("Remove")} + onClick={this._onRemove} className="mx_EditableItem_delete" alt={_t("Remove")}/> {this.props.value}
    ); @@ -99,6 +100,7 @@ export default class EditableItemList extends React.Component{ onNewItemChanged: PropTypes.func, canEdit: PropTypes.bool, + canRemove: PropTypes.bool, }; _onItemAdded = (e) => { @@ -133,6 +135,10 @@ export default class EditableItemList extends React.Component{ render() { const editableItems = this.props.items.map((item, index) => { + if (!this.props.canRemove) { + return
  • {item}
  • + } + return ; }); + const editableItemsSection = this.props.canRemove ? editableItems :
      {editableItems}
    ; const label = this.props.items.length > 0 ? this.props.itemsLabel : this.props.noItemsLabel; return (
    { label }
    - { editableItems } + { editableItemsSection } { this.props.canEdit ? this._renderNewItemField() :
    }
    ); } diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index 4859174b28..1e399ec01b 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -240,6 +240,7 @@ export default class AliasSettings extends React.Component { items={this.state.domainToAliases[localDomain] || []} newItem={this.state.newAlias} onNewItemChanged={this.onNewAliasChanged} + canRemove={this.props.canSetAliases} canEdit={this.props.canSetAliases} onItemAdded={this.onLocalAliasAdded} onItemRemoved={this.onLocalAliasDeleted} diff --git a/src/components/views/room_settings/RelatedGroupSettings.js b/src/components/views/room_settings/RelatedGroupSettings.js index 3381bdd0ff..64025b261e 100644 --- a/src/components/views/room_settings/RelatedGroupSettings.js +++ b/src/components/views/room_settings/RelatedGroupSettings.js @@ -107,6 +107,7 @@ export default class RelatedGroupSettings extends React.Component { items={this.state.newGroupsList} className={"mx_RelatedGroupSettings"} newItem={this.state.newGroupId} + canRemove={this.props.canSetRelatedGroups} canEdit={this.props.canSetRelatedGroups} onNewItemChanged={this.onNewGroupChanged} onItemAdded={this.onGroupAdded} From 9795161f40d17cacc4ac89b6115262e561d9dc10 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 17:53:29 -0700 Subject: [PATCH 101/178] Misc linter cleanup --- .eslintignore.errorfiles | 1 - src/components/views/elements/EditableItemList.js | 9 ++++----- src/components/views/room_settings/AliasSettings.js | 8 +++----- .../views/room_settings/RelatedGroupSettings.js | 7 +++---- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 4830f8235a..d5eeebf1f2 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -31,7 +31,6 @@ src/components/views/globals/UpdateCheckBar.js src/components/views/messages/MFileBody.js src/components/views/messages/RoomAvatarEvent.js src/components/views/messages/TextualBody.js -src/components/views/room_settings/AliasSettings.js src/components/views/room_settings/ColorSettings.js src/components/views/rooms/Autocomplete.js src/components/views/rooms/AuxPanel.js diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js index 66b5962426..28c6e10612 100644 --- a/src/components/views/elements/EditableItemList.js +++ b/src/components/views/elements/EditableItemList.js @@ -16,7 +16,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import sdk from '../../../index'; import {_t} from '../../../languageHandler.js'; import Field from "./Field"; import AccessibleButton from "./AccessibleButton"; @@ -80,14 +79,14 @@ export class EditableItem extends React.Component { return (
    {_t("Remove")}/ + onClick={this._onRemove} className="mx_EditableItem_delete" alt={_t("Remove")} /> {this.props.value}
    ); } } -export default class EditableItemList extends React.Component{ +export default class EditableItemList extends React.Component { static propTypes = { items: PropTypes.arrayOf(PropTypes.string).isRequired, itemsLabel: PropTypes.string, @@ -130,13 +129,13 @@ export default class EditableItemList extends React.Component{ {_t("Add")} - ) + ); } render() { const editableItems = this.props.items.map((item, index) => { if (!this.props.canRemove) { - return
  • {item}
  • + return
  • {item}
  • ; } return { @@ -136,7 +134,7 @@ export default class AliasSettings extends React.Component { title: _t("Error creating alias"), description: _t( "There was an error creating that alias. It may not be allowed by the server " + - "or a temporary failure occurred." + "or a temporary failure occurred.", ), }); }); @@ -172,7 +170,7 @@ export default class AliasSettings extends React.Component { title: _t("Error removing alias"), description: _t( "There was an error removing that alias. It may no longer exist or a temporary " + - "error occurred." + "error occurred.", ), }); }); diff --git a/src/components/views/room_settings/RelatedGroupSettings.js b/src/components/views/room_settings/RelatedGroupSettings.js index 64025b261e..b42ae1a8d7 100644 --- a/src/components/views/room_settings/RelatedGroupSettings.js +++ b/src/components/views/room_settings/RelatedGroupSettings.js @@ -20,7 +20,6 @@ import {MatrixEvent, MatrixClient} from 'matrix-js-sdk'; import sdk from '../../../index'; import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; -import isEqual from 'lodash/isEqual'; import ErrorDialog from "../dialogs/ErrorDialog"; const GROUP_ID_REGEX = /\+\S+:\S+/; @@ -58,10 +57,10 @@ export default class RelatedGroupSettings extends React.Component { title: _t("Error updating flair"), description: _t( "There was an error updating the flair for this room. The server may not allow it or " + - "a temporary error occurred." + "a temporary error occurred.", ), }); - }) + }); } validateGroupId(groupId) { @@ -120,4 +119,4 @@ export default class RelatedGroupSettings extends React.Component { />
    ; } -}; +} From 75a2593523dc4aa97ad9b72a851b51d0fe23a142 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 18:18:12 -0700 Subject: [PATCH 102/178] More misc linter cleanup --- src/components/views/room_settings/AliasSettings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index a2b125eb4f..df427171f1 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -75,7 +75,7 @@ export default class AliasSettings extends React.Component { isAliasValid(alias) { // XXX: FIXME https://github.com/matrix-org/matrix-doc/issues/668 - return (alias.match(/^#([^\/:,]+?):(.+)$/) && encodeURI(alias) === alias); + return (alias.match(/^#([^/:,]+?):(.+)$/) && encodeURI(alias) === alias); } changeCanonicalAlias(alias) { @@ -252,4 +252,4 @@ export default class AliasSettings extends React.Component {
    ); } -}; +} From 892802e9ccc04d6cb663bdb15e43d842b8e8d870 Mon Sep 17 00:00:00 2001 From: Alexander Terczka Date: Fri, 22 Feb 2019 02:40:01 +0100 Subject: [PATCH 103/178] RoomDirectory Dropdown should use roomDirectory.servers The sample config.json in riot-web has "roomDirectory.servers" and not "servers". Are other systems than riot-web use "servers" without "roomDirectory" ? Then this would break these. --- src/components/views/directory/NetworkDropdown.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/directory/NetworkDropdown.js b/src/components/views/directory/NetworkDropdown.js index 864a1be233..c70cdeb0de 100644 --- a/src/components/views/directory/NetworkDropdown.js +++ b/src/components/views/directory/NetworkDropdown.js @@ -131,8 +131,8 @@ export default class NetworkDropdown extends React.Component { const options = []; let servers = []; - if (this.props.config.servers) { - servers = servers.concat(this.props.config.servers); + if (this.props.config.roomDirectory.servers) { + servers = servers.concat(this.props.config.roomDirectory.servers); } if (servers.indexOf(MatrixClientPeg.getHomeServerName()) == -1) { From e7b3cbfdf40065bec5eddd85c8147e1a107ff22d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 19:21:32 -0700 Subject: [PATCH 104/178] Fix categorization of favourites and new rooms New rooms (joined, invited, created, etc) were being ignored because they matched the check as soon as the iterator hit a non-recents section. This fixes the check to ensure there's a positive ID on the room being in the tag (or not, in the case of new rooms) before lying to the rest of the function. Additionally, a fix for favourites has been included to stop the list expanding to fill the void - turns out it was inserting the room twice into the list, and this was breaking the tile rendering. The room sublist would allocate space for the tile, but React would prevent the tile from showing up because of duplicate keys. Fixes https://github.com/vector-im/riot-web/issues/8868 Fixes https://github.com/vector-im/riot-web/issues/8857 correctly --- src/stores/RoomListStore.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 9bf1f90da5..0a11c2774a 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -287,7 +287,11 @@ class RoomListStore extends Store { // Speed optimization: Skip the loop below if we're not going to do anything productive if (!hasRoom || LIST_ORDERS[key] !== 'recent') { listsClone[key] = this._state.lists[key]; - inserted = true; // Ensure that we don't try and sort the room into the tag + if (LIST_ORDERS[key] !== 'recent' && (hasRoom || targetTags.includes(key))) { + // Ensure that we don't try and sort the room into the tag + inserted = true; + doInsert = false; + } continue; } else { listsClone[key] = []; From 3e6199c0f79a7a7f13390a9089b35773b46b67c0 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 22 Feb 2019 14:41:27 +0000 Subject: [PATCH 105/178] Change Markdown buttons to `onClick` `AccessibleButton` expects click handlers, which we can use here, as long we make some additional changes to avoid race processing the new state (see subsequent patch). Fixes https://github.com/vector-im/riot-web/issues/8866 --- src/components/views/rooms/MessageComposer.js | 2 +- src/components/views/rooms/MessageComposerInput.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 7eef868ef7..651974a408 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -501,7 +501,7 @@ export default class MessageComposer extends React.Component { { formatButtons }
    Date: Fri, 22 Feb 2019 15:12:28 +0000 Subject: [PATCH 106/178] Isolate rich text notifications from other input changes The step that would notify parent components of rich text state changes was stirred into the input's change handler, which leads to race which the parent is sometimes notified of the old rich text state instead of the new. Here we avoid this complication by using a separate path for sending the rich text state when we know we have updated it correctly. --- src/components/views/rooms/MessageComposer.js | 2 ++ src/components/views/rooms/MessageComposerInput.js | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 651974a408..be6fbee4f6 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -257,6 +257,8 @@ export default class MessageComposer extends React.Component { } onInputStateChanged(inputState) { + // Merge the new input state with old to support partial updates + inputState = Object.assign({}, this.state.inputState, inputState); this.setState({inputState}); } diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 1514b58e27..d55544293b 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -628,7 +628,6 @@ export default class MessageComposerInput extends React.Component { } const inputState = { marks: editorState.activeMarks, - isRichTextEnabled: this.state.isRichTextEnabled, blockType, }; this.props.onInputStateChanged(inputState); @@ -698,8 +697,13 @@ export default class MessageComposerInput extends React.Component { this.setState({ editorState: this.createEditorState(enabled, editorState), isRichTextEnabled: enabled, - }, ()=>{ + }, () => { this._editor.focus(); + if (this.props.onInputStateChanged) { + this.props.onInputStateChanged({ + isRichTextEnabled: enabled, + }); + } }); SettingsStore.setValue("MessageComposerInput.isRichTextEnabled", null, SettingLevel.ACCOUNT, enabled); From f5c477bbc8b702729ba9938928e12eb9ac95d92a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 22 Feb 2019 15:48:36 +0000 Subject: [PATCH 107/178] Remove duplicated conversion from `enableRichText` `enableRichText` would convert the editor state between rich and md versions explicitly, but then it would also call `createEditorState`, which can do the same thing. This removes the duplication, and also supplies the right arguments to `createEditorState` so it can do its work correctly. --- src/components/views/rooms/MessageComposerInput.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 1954ca0d84..08e5de3477 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -686,17 +686,14 @@ export default class MessageComposerInput extends React.Component { enableRichtext(enabled: boolean) { if (enabled === this.state.isRichTextEnabled) return; - let editorState = null; - if (enabled) { - editorState = this.mdToRichEditorState(this.state.editorState); - } else { - editorState = this.richToMdEditorState(this.state.editorState); - } - Analytics.setRichtextMode(enabled); this.setState({ - editorState: this.createEditorState(enabled, editorState), + editorState: this.createEditorState( + enabled, + this.state.editorState, + this.state.isRichTextEnabled, + ), isRichTextEnabled: enabled, }, ()=>{ this._editor.focus(); From ec35423a0d51793a7ed5f6ff920527b44c7bd74a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 22 Feb 2019 16:16:05 +0000 Subject: [PATCH 108/178] Use correct initial phase for server type The initial phase of registration can differ by the default server type. In particular, the Matrix.org HS type wants to skip to the registration form. Fixes https://github.com/vector-im/riot-web/issues/8862 --- .../structures/auth/Registration.js | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index b5040777db..711ec3c1d7 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -69,8 +69,10 @@ module.exports = React.createClass({ }, getInitialState: function() { + const serverType = ServerType.getTypeFromHsUrl(this.props.customHsUrl); + const customURLsAllowed = !SdkConfig.get()['disable_custom_urls']; - let initialPhase = PHASE_SERVER_DETAILS; + let initialPhase = this.getDefaultPhaseForServerType(serverType); if ( // if we have these two, skip to the good bit // (they could come in from the URL params in a @@ -81,6 +83,11 @@ module.exports = React.createClass({ // if other logic says to, skip to form this.props.skipServerDetails ) { + // TODO: It would seem we've now added enough conditions here that the initial + // phase will _always_ be the form. It's tempting to remove the complexity and + // just do that, but we keep tweaking and changing auth, so let's wait until + // things settle a bit. + // Filed https://github.com/vector-im/riot-web/issues/8886 to track this. initialPhase = PHASE_REGISTRATION; } @@ -102,7 +109,7 @@ module.exports = React.createClass({ // If we've been given a session ID, we're resuming // straight back into UI auth doingUIAuth: Boolean(this.props.sessionId), - serverType: ServerType.getTypeFromHsUrl(this.props.customHsUrl), + serverType, hsUrl: this.props.customHsUrl, isUrl: this.props.customIsUrl, // Phase of the overall registration dialog. @@ -130,6 +137,19 @@ module.exports = React.createClass({ }); }, + getDefaultPhaseForServerType(type) { + switch (type) { + case ServerType.FREE: { + // Move directly to the registration phase since the server + // details are fixed. + return PHASE_REGISTRATION; + } + case ServerType.PREMIUM: + case ServerType.ADVANCED: + return PHASE_SERVER_DETAILS; + } + }, + onServerTypeChange(type) { this.setState({ serverType: type, @@ -144,10 +164,6 @@ module.exports = React.createClass({ hsUrl, isUrl, }); - // Move directly to the registration phase since the server details are fixed. - this.setState({ - phase: PHASE_REGISTRATION, - }); break; } case ServerType.PREMIUM: @@ -156,12 +172,13 @@ module.exports = React.createClass({ hsUrl: this.props.defaultHsUrl, isUrl: this.props.defaultIsUrl, }); - // Reset back to server details on type change. - this.setState({ - phase: PHASE_SERVER_DETAILS, - }); break; } + + // Reset the phase to default phase for the server type. + this.setState({ + phase: this.getDefaultPhaseForServerType(type), + }); }, _replaceClient: async function() { From 20cd1987846485206db27c754a8ce4a3deabbc7c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 22 Feb 2019 10:31:14 -0700 Subject: [PATCH 109/178] Only set e2e info callback if the event is encrypted Fixes https://github.com/vector-im/riot-web/issues/8551 --- src/components/views/rooms/EventTile.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 15b8357b24..a537c4ca5d 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -321,6 +321,9 @@ module.exports = withMatrixClient(React.createClass({ const {tile, replyThread} = this.refs; + let e2eInfoCallback = null; + if (this.props.mxEvent.isEncrypted()) e2eInfoCallback = () => this.onCryptoClicked(); + ContextualMenu.createMenu(MessageContextMenu, { chevronOffset: 10, mxEvent: this.props.mxEvent, @@ -328,7 +331,7 @@ module.exports = withMatrixClient(React.createClass({ top: y, eventTileOps: tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined, collapseReplyThread: replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined, - e2eInfoCallback: () => this.onCryptoClicked(), + e2eInfoCallback: e2eInfoCallback, onFinished: function() { self.setState({menu: false}); }, From bd54a401bc823c62dd8e9b74199161ff55a636a9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 22 Feb 2019 10:47:18 -0700 Subject: [PATCH 110/178] Sort settings tabs into a logical structure Fixes https://github.com/vector-im/riot-web/issues/8864 --- res/css/_components.scss | 18 ++--- .../{ => room}/_GeneralRoomSettingsTab.scss | 0 .../{ => room}/_RolesRoomSettingsTab.scss | 0 .../{ => room}/_SecurityRoomSettingsTab.scss | 0 .../{ => user}/_GeneralUserSettingsTab.scss | 0 .../_HelpUserSettingsTab.scss} | 4 +- .../_NotificationUserSettingsTab.scss} | 2 +- .../_PreferencesUserSettingsTab.scss} | 4 +- .../_SecurityUserSettingsTab.scss} | 18 ++--- .../_VoiceUserSettingsTab.scss} | 6 +- .../views/dialogs/RoomSettingsDialog.js | 8 +- .../views/dialogs/UserSettingsDialog.js | 30 +++---- .../{ => room}/AdvancedRoomSettingsTab.js | 10 +-- .../tabs/{ => room}/GeneralRoomSettingsTab.js | 14 ++-- .../tabs/{ => room}/RolesRoomSettingsTab.js | 10 +-- .../{ => room}/SecurityRoomSettingsTab.js | 14 ++-- .../FlairUserSettingsTab.js} | 8 +- .../tabs/{ => user}/GeneralUserSettingsTab.js | 30 +++---- .../HelpUserSettingsTab.js} | 32 ++++---- .../LabsUserSettingsTab.js} | 10 +-- .../NotificationUserSettingsTab.js} | 8 +- .../PreferencesUserSettingsTab.js} | 24 +++--- .../SecurityUserSettingsTab.js} | 30 +++---- .../VoiceUserSettingsTab.js} | 22 +++--- src/i18n/strings/en_EN.json | 78 +++++++++---------- 25 files changed, 190 insertions(+), 190 deletions(-) rename res/css/views/settings/tabs/{ => room}/_GeneralRoomSettingsTab.scss (100%) rename res/css/views/settings/tabs/{ => room}/_RolesRoomSettingsTab.scss (100%) rename res/css/views/settings/tabs/{ => room}/_SecurityRoomSettingsTab.scss (100%) rename res/css/views/settings/tabs/{ => user}/_GeneralUserSettingsTab.scss (100%) rename res/css/views/settings/tabs/{_HelpSettingsTab.scss => user/_HelpUserSettingsTab.scss} (87%) rename res/css/views/settings/tabs/{_NotificationSettingsTab.scss => user/_NotificationUserSettingsTab.scss} (91%) rename res/css/views/settings/tabs/{_PreferencesSettingsTab.scss => user/_PreferencesUserSettingsTab.scss} (89%) rename res/css/views/settings/tabs/{_SecuritySettingsTab.scss => user/_SecurityUserSettingsTab.scss} (67%) rename res/css/views/settings/tabs/{_VoiceSettingsTab.scss => user/_VoiceUserSettingsTab.scss} (84%) rename src/components/views/settings/tabs/{ => room}/AdvancedRoomSettingsTab.js (93%) rename src/components/views/settings/tabs/{ => room}/GeneralRoomSettingsTab.js (92%) rename src/components/views/settings/tabs/{ => room}/RolesRoomSettingsTab.js (98%) rename src/components/views/settings/tabs/{ => room}/SecurityRoomSettingsTab.js (96%) rename src/components/views/settings/tabs/{FlairSettingsTab.js => user/FlairUserSettingsTab.js} (84%) rename src/components/views/settings/tabs/{ => user}/GeneralUserSettingsTab.js (88%) rename src/components/views/settings/tabs/{HelpSettingsTab.js => user/HelpUserSettingsTab.js} (90%) rename src/components/views/settings/tabs/{LabsSettingsTab.js => user/LabsUserSettingsTab.js} (85%) rename src/components/views/settings/tabs/{NotificationSettingsTab.js => user/NotificationUserSettingsTab.js} (80%) rename src/components/views/settings/tabs/{PreferencesSettingsTab.js => user/PreferencesUserSettingsTab.js} (81%) rename src/components/views/settings/tabs/{SecuritySettingsTab.js => user/SecurityUserSettingsTab.js} (89%) rename src/components/views/settings/tabs/{VoiceSettingsTab.js => user/VoiceUserSettingsTab.js} (90%) diff --git a/res/css/_components.scss b/res/css/_components.scss index 6aed78a627..f3b07255ae 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -150,16 +150,16 @@ @import "./views/settings/_Notifications.scss"; @import "./views/settings/_PhoneNumbers.scss"; @import "./views/settings/_ProfileSettings.scss"; -@import "./views/settings/tabs/_GeneralRoomSettingsTab.scss"; -@import "./views/settings/tabs/_GeneralUserSettingsTab.scss"; -@import "./views/settings/tabs/_HelpSettingsTab.scss"; -@import "./views/settings/tabs/_NotificationSettingsTab.scss"; -@import "./views/settings/tabs/_PreferencesSettingsTab.scss"; -@import "./views/settings/tabs/_RolesRoomSettingsTab.scss"; -@import "./views/settings/tabs/_SecurityRoomSettingsTab.scss"; -@import "./views/settings/tabs/_SecuritySettingsTab.scss"; @import "./views/settings/tabs/_SettingsTab.scss"; -@import "./views/settings/tabs/_VoiceSettingsTab.scss"; +@import "./views/settings/tabs/room/_GeneralRoomSettingsTab.scss"; +@import "./views/settings/tabs/room/_RolesRoomSettingsTab.scss"; +@import "./views/settings/tabs/room/_SecurityRoomSettingsTab.scss"; +@import "./views/settings/tabs/user/_GeneralUserSettingsTab.scss"; +@import "./views/settings/tabs/user/_HelpUserSettingsTab.scss"; +@import "./views/settings/tabs/user/_NotificationUserSettingsTab.scss"; +@import "./views/settings/tabs/user/_PreferencesUserSettingsTab.scss"; +@import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss"; +@import "./views/settings/tabs/user/_VoiceUserSettingsTab.scss"; @import "./views/verification/_VerificationShowSas.scss"; @import "./views/voip/_CallView.scss"; @import "./views/voip/_IncomingCallbox.scss"; diff --git a/res/css/views/settings/tabs/_GeneralRoomSettingsTab.scss b/res/css/views/settings/tabs/room/_GeneralRoomSettingsTab.scss similarity index 100% rename from res/css/views/settings/tabs/_GeneralRoomSettingsTab.scss rename to res/css/views/settings/tabs/room/_GeneralRoomSettingsTab.scss diff --git a/res/css/views/settings/tabs/_RolesRoomSettingsTab.scss b/res/css/views/settings/tabs/room/_RolesRoomSettingsTab.scss similarity index 100% rename from res/css/views/settings/tabs/_RolesRoomSettingsTab.scss rename to res/css/views/settings/tabs/room/_RolesRoomSettingsTab.scss diff --git a/res/css/views/settings/tabs/_SecurityRoomSettingsTab.scss b/res/css/views/settings/tabs/room/_SecurityRoomSettingsTab.scss similarity index 100% rename from res/css/views/settings/tabs/_SecurityRoomSettingsTab.scss rename to res/css/views/settings/tabs/room/_SecurityRoomSettingsTab.scss diff --git a/res/css/views/settings/tabs/_GeneralUserSettingsTab.scss b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss similarity index 100% rename from res/css/views/settings/tabs/_GeneralUserSettingsTab.scss rename to res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss diff --git a/res/css/views/settings/tabs/_HelpSettingsTab.scss b/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss similarity index 87% rename from res/css/views/settings/tabs/_HelpSettingsTab.scss rename to res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss index 249f06ca95..fa0d0edeb7 100644 --- a/res/css/views/settings/tabs/_HelpSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss @@ -14,11 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_HelpSettingsTab_debugButton { +.mx_HelpUserSettingsTab_debugButton { margin-bottom: 5px; margin-top: 5px; } -.mx_HelpSettingsTab span.mx_AccessibleButton { +.mx_HelpUserSettingsTab span.mx_AccessibleButton { word-break: break-word; } \ No newline at end of file diff --git a/res/css/views/settings/tabs/_NotificationSettingsTab.scss b/res/css/views/settings/tabs/user/_NotificationUserSettingsTab.scss similarity index 91% rename from res/css/views/settings/tabs/_NotificationSettingsTab.scss rename to res/css/views/settings/tabs/user/_NotificationUserSettingsTab.scss index 8fdb688496..3cebd2958e 100644 --- a/res/css/views/settings/tabs/_NotificationSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_NotificationUserSettingsTab.scss @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_NotificationSettingsTab .mx_SettingsTab_heading { +.mx_NotificationUserSettingsTab .mx_SettingsTab_heading { margin-bottom: 10px; // Give some spacing between the title and the first elements } \ No newline at end of file diff --git a/res/css/views/settings/tabs/_PreferencesSettingsTab.scss b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss similarity index 89% rename from res/css/views/settings/tabs/_PreferencesSettingsTab.scss rename to res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss index b59b69e63b..f447221b7a 100644 --- a/res/css/views/settings/tabs/_PreferencesSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss @@ -14,11 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_PreferencesSettingsTab .mx_Field { +.mx_PreferencesUserSettingsTab .mx_Field { margin-right: 100px; // Align with the rest of the controls } -.mx_PreferencesSettingsTab .mx_Field input { +.mx_PreferencesUserSettingsTab .mx_Field input { display: block; // Subtract 10px padding on left and right diff --git a/res/css/views/settings/tabs/_SecuritySettingsTab.scss b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss similarity index 67% rename from res/css/views/settings/tabs/_SecuritySettingsTab.scss rename to res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss index ba357f16c3..4835640904 100644 --- a/res/css/views/settings/tabs/_SecuritySettingsTab.scss +++ b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss @@ -14,40 +14,40 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_SecuritySettingsTab .mx_DevicesPanel { +.mx_SecurityUserSettingsTab .mx_DevicesPanel { // Normally the panel is 880px, however this can easily overflow the container. // TODO: Fix the table to not be squishy width: auto; max-width: 880px; } -.mx_SecuritySettingsTab_deviceInfo { +.mx_SecurityUserSettingsTab_deviceInfo { display: table; padding-left: 0; } -.mx_SecuritySettingsTab_deviceInfo > li { +.mx_SecurityUserSettingsTab_deviceInfo > li { display: table-row; } -.mx_SecuritySettingsTab_deviceInfo > li > label, -.mx_SecuritySettingsTab_deviceInfo > li > span { +.mx_SecurityUserSettingsTab_deviceInfo > li > label, +.mx_SecurityUserSettingsTab_deviceInfo > li > span { display: table-cell; padding-right: 1em; } -.mx_SecuritySettingsTab_importExportButtons .mx_AccessibleButton { +.mx_SecurityUserSettingsTab_importExportButtons .mx_AccessibleButton { margin-right: 10px; } -.mx_SecuritySettingsTab_importExportButtons { +.mx_SecurityUserSettingsTab_importExportButtons { margin-bottom: 15px; } -.mx_SecuritySettingsTab_ignoredUser { +.mx_SecurityUserSettingsTab_ignoredUser { margin-bottom: 5px; } -.mx_SecuritySettingsTab_ignoredUser .mx_AccessibleButton { +.mx_SecurityUserSettingsTab_ignoredUser .mx_AccessibleButton { margin-right: 10px; } \ No newline at end of file diff --git a/res/css/views/settings/tabs/_VoiceSettingsTab.scss b/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss similarity index 84% rename from res/css/views/settings/tabs/_VoiceSettingsTab.scss rename to res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss index 5ddd57b0e2..f5dba9831e 100644 --- a/res/css/views/settings/tabs/_VoiceSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss @@ -14,15 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_VoiceSettingsTab .mx_Field select { +.mx_VoiceUserSettingsTab .mx_Field select { width: 100%; max-width: 100%; } -.mx_VoiceSettingsTab .mx_Field { +.mx_VoiceUserSettingsTab .mx_Field { margin-right: 100px; // align with the rest of the fields } -.mx_VoiceSettingsTab_missingMediaPermissions { +.mx_VoiceUserSettingsTab_missingMediaPermissions { margin-bottom: 15px; } diff --git a/src/components/views/dialogs/RoomSettingsDialog.js b/src/components/views/dialogs/RoomSettingsDialog.js index c73edb179c..1f319176c2 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.js +++ b/src/components/views/dialogs/RoomSettingsDialog.js @@ -18,10 +18,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import {Tab, TabbedView} from "../../structures/TabbedView"; import {_t, _td} from "../../../languageHandler"; -import AdvancedRoomSettingsTab from "../settings/tabs/AdvancedRoomSettingsTab"; -import RolesRoomSettingsTab from "../settings/tabs/RolesRoomSettingsTab"; -import GeneralRoomSettingsTab from "../settings/tabs/GeneralRoomSettingsTab"; -import SecurityRoomSettingsTab from "../settings/tabs/SecurityRoomSettingsTab"; +import AdvancedRoomSettingsTab from "../settings/tabs/room/AdvancedRoomSettingsTab"; +import RolesRoomSettingsTab from "../settings/tabs/room/RolesRoomSettingsTab"; +import GeneralRoomSettingsTab from "../settings/tabs/room/GeneralRoomSettingsTab"; +import SecurityRoomSettingsTab from "../settings/tabs/room/SecurityRoomSettingsTab"; import sdk from "../../../index"; export default class RoomSettingsDialog extends React.Component { diff --git a/src/components/views/dialogs/UserSettingsDialog.js b/src/components/views/dialogs/UserSettingsDialog.js index dc72acda12..1c4509eee5 100644 --- a/src/components/views/dialogs/UserSettingsDialog.js +++ b/src/components/views/dialogs/UserSettingsDialog.js @@ -18,15 +18,15 @@ import React from 'react'; import PropTypes from 'prop-types'; import {Tab, TabbedView} from "../../structures/TabbedView"; import {_t, _td} from "../../../languageHandler"; -import GeneralUserSettingsTab from "../settings/tabs/GeneralUserSettingsTab"; +import GeneralUserSettingsTab from "../settings/tabs/user/GeneralUserSettingsTab"; import SettingsStore from "../../../settings/SettingsStore"; -import LabsSettingsTab from "../settings/tabs/LabsSettingsTab"; -import SecuritySettingsTab from "../settings/tabs/SecuritySettingsTab"; -import NotificationSettingsTab from "../settings/tabs/NotificationSettingsTab"; -import PreferencesSettingsTab from "../settings/tabs/PreferencesSettingsTab"; -import VoiceSettingsTab from "../settings/tabs/VoiceSettingsTab"; -import HelpSettingsTab from "../settings/tabs/HelpSettingsTab"; -import FlairSettingsTab from "../settings/tabs/FlairSettingsTab"; +import LabsUserSettingsTab from "../settings/tabs/user/LabsUserSettingsTab"; +import SecurityUserSettingsTab from "../settings/tabs/user/SecurityUserSettingsTab"; +import NotificationUserSettingsTab from "../settings/tabs/user/NotificationUserSettingsTab"; +import PreferencesUserSettingsTab from "../settings/tabs/user/PreferencesUserSettingsTab"; +import VoiceUserSettingsTab from "../settings/tabs/user/VoiceUserSettingsTab"; +import HelpUserSettingsTab from "../settings/tabs/user/HelpUserSettingsTab"; +import FlairUserSettingsTab from "../settings/tabs/user/FlairUserSettingsTab"; import sdk from "../../../index"; export default class UserSettingsDialog extends React.Component { @@ -45,39 +45,39 @@ export default class UserSettingsDialog extends React.Component { tabs.push(new Tab( _td("Flair"), "mx_UserSettingsDialog_flairIcon", - , + , )); tabs.push(new Tab( _td("Notifications"), "mx_UserSettingsDialog_bellIcon", - , + , )); tabs.push(new Tab( _td("Preferences"), "mx_UserSettingsDialog_preferencesIcon", - , + , )); tabs.push(new Tab( _td("Voice & Video"), "mx_UserSettingsDialog_voiceIcon", - , + , )); tabs.push(new Tab( _td("Security & Privacy"), "mx_UserSettingsDialog_securityIcon", - , + , )); if (SettingsStore.getLabsFeatures().length > 0) { tabs.push(new Tab( _td("Labs"), "mx_UserSettingsDialog_labsIcon", - , + , )); } tabs.push(new Tab( _td("Help & About"), "mx_UserSettingsDialog_helpIcon", - , + , )); return tabs; diff --git a/src/components/views/settings/tabs/AdvancedRoomSettingsTab.js b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.js similarity index 93% rename from src/components/views/settings/tabs/AdvancedRoomSettingsTab.js rename to src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.js index 9b99622516..3c6a7addc3 100644 --- a/src/components/views/settings/tabs/AdvancedRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.js @@ -16,11 +16,11 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {_t} from "../../../../languageHandler"; -import MatrixClientPeg from "../../../../MatrixClientPeg"; -import sdk from "../../../../index"; -import AccessibleButton from "../../elements/AccessibleButton"; -import Modal from "../../../../Modal"; +import {_t} from "../../../../../languageHandler"; +import MatrixClientPeg from "../../../../../MatrixClientPeg"; +import sdk from "../../../../.."; +import AccessibleButton from "../../../elements/AccessibleButton"; +import Modal from "../../../../../Modal"; export default class AdvancedRoomSettingsTab extends React.Component { static propTypes = { diff --git a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js similarity index 92% rename from src/components/views/settings/tabs/GeneralRoomSettingsTab.js rename to src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js index f43fc8a682..5d707fcf16 100644 --- a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js @@ -16,14 +16,14 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {_t} from "../../../../languageHandler"; -import RoomProfileSettings from "../../room_settings/RoomProfileSettings"; -import MatrixClientPeg from "../../../../MatrixClientPeg"; -import sdk from "../../../../index"; -import AccessibleButton from "../../elements/AccessibleButton"; +import {_t} from "../../../../../languageHandler"; +import RoomProfileSettings from "../../../room_settings/RoomProfileSettings"; +import MatrixClientPeg from "../../../../../MatrixClientPeg"; +import sdk from "../../../../.."; +import AccessibleButton from "../../../elements/AccessibleButton"; import {MatrixClient} from "matrix-js-sdk"; -import dis from "../../../../dispatcher"; -import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch"; +import dis from "../../../../../dispatcher"; +import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; export default class GeneralRoomSettingsTab extends React.Component { static childContextTypes = { diff --git a/src/components/views/settings/tabs/RolesRoomSettingsTab.js b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.js similarity index 98% rename from src/components/views/settings/tabs/RolesRoomSettingsTab.js rename to src/components/views/settings/tabs/room/RolesRoomSettingsTab.js index d223e8f2e9..a6dac5a147 100644 --- a/src/components/views/settings/tabs/RolesRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.js @@ -16,11 +16,11 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {_t, _td} from "../../../../languageHandler"; -import MatrixClientPeg from "../../../../MatrixClientPeg"; -import sdk from "../../../../index"; -import AccessibleButton from "../../elements/AccessibleButton"; -import Modal from "../../../../Modal"; +import {_t, _td} from "../../../../../languageHandler"; +import MatrixClientPeg from "../../../../../MatrixClientPeg"; +import sdk from "../../../../.."; +import AccessibleButton from "../../../elements/AccessibleButton"; +import Modal from "../../../../../Modal"; const plEventsToLabels = { // These will be translated for us later. diff --git a/src/components/views/settings/tabs/SecurityRoomSettingsTab.js b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js similarity index 96% rename from src/components/views/settings/tabs/SecurityRoomSettingsTab.js rename to src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js index 698f67dd18..a6eca3bf19 100644 --- a/src/components/views/settings/tabs/SecurityRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js @@ -16,11 +16,11 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {_t} from "../../../../languageHandler"; -import MatrixClientPeg from "../../../../MatrixClientPeg"; -import sdk from "../../../../index"; -import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch"; -import {SettingLevel} from "../../../../settings/SettingsStore"; +import {_t} from "../../../../../languageHandler"; +import MatrixClientPeg from "../../../../../MatrixClientPeg"; +import sdk from "../../../../.."; +import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; +import {SettingLevel} from "../../../../../settings/SettingsStore"; export default class SecurityRoomSettingsTab extends React.Component { static propTypes = { @@ -188,7 +188,7 @@ export default class SecurityRoomSettingsTab extends React.Component { if (joinRule !== 'public' && guestAccess === 'forbidden') { guestWarning = (
    - + {_t("Guests cannot join this room even if explicitly invited.")}  {_t("Click here to fix")} @@ -201,7 +201,7 @@ export default class SecurityRoomSettingsTab extends React.Component { if (joinRule === 'public' && !hasAliases) { aliasWarning = (
    - + {_t("To link to this room, please add an alias.")} diff --git a/src/components/views/settings/tabs/FlairSettingsTab.js b/src/components/views/settings/tabs/user/FlairUserSettingsTab.js similarity index 84% rename from src/components/views/settings/tabs/FlairSettingsTab.js rename to src/components/views/settings/tabs/user/FlairUserSettingsTab.js index db513a161a..0daa20b8b3 100644 --- a/src/components/views/settings/tabs/FlairSettingsTab.js +++ b/src/components/views/settings/tabs/user/FlairUserSettingsTab.js @@ -15,14 +15,14 @@ limitations under the License. */ import React from 'react'; -import {_t} from "../../../../languageHandler"; +import {_t} from "../../../../../languageHandler"; import {DragDropContext} from "react-beautiful-dnd"; -import GroupUserSettings from "../../groups/GroupUserSettings"; -import MatrixClientPeg from "../../../../MatrixClientPeg"; +import GroupUserSettings from "../../../groups/GroupUserSettings"; +import MatrixClientPeg from "../../../../../MatrixClientPeg"; import PropTypes from "prop-types"; import {MatrixClient} from "matrix-js-sdk"; -export default class FlairSettingsTab extends React.Component { +export default class FlairUserSettingsTab extends React.Component { static childContextTypes = { matrixClient: PropTypes.instanceOf(MatrixClient), }; diff --git a/src/components/views/settings/tabs/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js similarity index 88% rename from src/components/views/settings/tabs/GeneralUserSettingsTab.js rename to src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index fd3274c9e0..093160e330 100644 --- a/src/components/views/settings/tabs/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -15,21 +15,21 @@ limitations under the License. */ import React from 'react'; -import {_t} from "../../../../languageHandler"; -import ProfileSettings from "../ProfileSettings"; -import EmailAddresses from "../EmailAddresses"; -import PhoneNumbers from "../PhoneNumbers"; -import Field from "../../elements/Field"; -import * as languageHandler from "../../../../languageHandler"; -import {SettingLevel} from "../../../../settings/SettingsStore"; -import SettingsStore from "../../../../settings/SettingsStore"; -import LanguageDropdown from "../../elements/LanguageDropdown"; -import AccessibleButton from "../../elements/AccessibleButton"; -import DeactivateAccountDialog from "../../dialogs/DeactivateAccountDialog"; -const PlatformPeg = require("../../../../PlatformPeg"); -const sdk = require('../../../../index'); -const Modal = require("../../../../Modal"); -const dis = require("../../../../dispatcher"); +import {_t} from "../../../../../languageHandler"; +import ProfileSettings from "../../ProfileSettings"; +import EmailAddresses from "../../EmailAddresses"; +import PhoneNumbers from "../../PhoneNumbers"; +import Field from "../../../elements/Field"; +import * as languageHandler from "../../../../../languageHandler"; +import {SettingLevel} from "../../../../../settings/SettingsStore"; +import SettingsStore from "../../../../../settings/SettingsStore"; +import LanguageDropdown from "../../../elements/LanguageDropdown"; +import AccessibleButton from "../../../elements/AccessibleButton"; +import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog"; +const PlatformPeg = require("../../../../../PlatformPeg"); +const sdk = require('../../../../..'); +const Modal = require("../../../../../Modal"); +const dis = require("../../../../../dispatcher"); export default class GeneralUserSettingsTab extends React.Component { constructor() { diff --git a/src/components/views/settings/tabs/HelpSettingsTab.js b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js similarity index 90% rename from src/components/views/settings/tabs/HelpSettingsTab.js rename to src/components/views/settings/tabs/user/HelpUserSettingsTab.js index 4ad62451cb..d001a3f2e6 100644 --- a/src/components/views/settings/tabs/HelpSettingsTab.js +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js @@ -16,15 +16,15 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {_t, getCurrentLanguage} from "../../../../languageHandler"; -import MatrixClientPeg from "../../../../MatrixClientPeg"; -import AccessibleButton from "../../elements/AccessibleButton"; -import SdkConfig from "../../../../SdkConfig"; -import createRoom from "../../../../createRoom"; -const packageJson = require('../../../../../package.json'); -const Modal = require("../../../../Modal"); -const sdk = require("../../../../index"); -const PlatformPeg = require("../../../../PlatformPeg"); +import {_t, getCurrentLanguage} from "../../../../../languageHandler"; +import MatrixClientPeg from "../../../../../MatrixClientPeg"; +import AccessibleButton from "../../../elements/AccessibleButton"; +import SdkConfig from "../../../../../SdkConfig"; +import createRoom from "../../../../../createRoom"; +const packageJson = require('../../../../../../package.json'); +const Modal = require("../../../../../Modal"); +const sdk = require("../../../../.."); +const PlatformPeg = require("../../../../../PlatformPeg"); // if this looks like a release, use the 'version' from package.json; else use // the git sha. Prepend version with v, to look like riot-web version @@ -45,7 +45,7 @@ const ghVersionLabel = function(repo, token='') { return { token }; }; -export default class HelpSettingsTab extends React.Component { +export default class HelpUserSettingsTab extends React.Component { static propTypes = { closeSettingsFn: PropTypes.func.isRequired, }; @@ -117,7 +117,7 @@ export default class HelpSettingsTab extends React.Component { } return ( -
    +
    {_t("Legal")}
    {legalLinks} @@ -190,7 +190,7 @@ export default class HelpSettingsTab extends React.Component { } return ( -
    +
    {_t("Help & About")}
    {_t('Bug reporting')} @@ -203,12 +203,12 @@ export default class HelpSettingsTab extends React.Component { "other users. They do not contain messages.", ) } -
    +
    {_t("Submit debug logs")}
    -
    +
    {_t("Clear Cache and Reload")} @@ -221,7 +221,7 @@ export default class HelpSettingsTab extends React.Component { {faqText}
    -
    +
    {_t("Versions")}
    {_t("matrix-react-sdk version:")} {reactSdkVersion}
    @@ -232,7 +232,7 @@ export default class HelpSettingsTab extends React.Component {
    {this._renderLegal()} {this._renderCredits()} -
    +
    {_t("Advanced")}
    {_t("Homeserver is")} {MatrixClientPeg.get().getHomeserverUrl()}
    diff --git a/src/components/views/settings/tabs/LabsSettingsTab.js b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js similarity index 85% rename from src/components/views/settings/tabs/LabsSettingsTab.js rename to src/components/views/settings/tabs/user/LabsUserSettingsTab.js index e06f87460b..c2e62044a3 100644 --- a/src/components/views/settings/tabs/LabsSettingsTab.js +++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js @@ -15,11 +15,11 @@ limitations under the License. */ import React from 'react'; -import {_t} from "../../../../languageHandler"; +import {_t} from "../../../../../languageHandler"; import PropTypes from "prop-types"; -import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore"; -import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch"; -const sdk = require("../../../../index"); +import SettingsStore, {SettingLevel} from "../../../../../settings/SettingsStore"; +import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; +const sdk = require("../../../../.."); export class LabsSettingToggle extends React.Component { static propTypes = { @@ -38,7 +38,7 @@ export class LabsSettingToggle extends React.Component { } } -export default class LabsSettingsTab extends React.Component { +export default class LabsUserSettingsTab extends React.Component { constructor() { super(); } diff --git a/src/components/views/settings/tabs/NotificationSettingsTab.js b/src/components/views/settings/tabs/user/NotificationUserSettingsTab.js similarity index 80% rename from src/components/views/settings/tabs/NotificationSettingsTab.js rename to src/components/views/settings/tabs/user/NotificationUserSettingsTab.js index 42d495f6ec..970659af6e 100644 --- a/src/components/views/settings/tabs/NotificationSettingsTab.js +++ b/src/components/views/settings/tabs/user/NotificationUserSettingsTab.js @@ -15,10 +15,10 @@ limitations under the License. */ import React from 'react'; -import {_t} from "../../../../languageHandler"; -const sdk = require("../../../../index"); +import {_t} from "../../../../../languageHandler"; +const sdk = require("../../../../.."); -export default class NotificationSettingsTab extends React.Component { +export default class NotificationUserSettingsTab extends React.Component { constructor() { super(); } @@ -26,7 +26,7 @@ export default class NotificationSettingsTab extends React.Component { render() { const Notifications = sdk.getComponent("views.settings.Notifications"); return ( -
    +
    {_t("Notifications")}
    diff --git a/src/components/views/settings/tabs/PreferencesSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js similarity index 81% rename from src/components/views/settings/tabs/PreferencesSettingsTab.js rename to src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index d76dc8f3dd..0b472cc366 100644 --- a/src/components/views/settings/tabs/PreferencesSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -15,15 +15,15 @@ limitations under the License. */ import React from 'react'; -import {_t} from "../../../../languageHandler"; -import {SettingLevel} from "../../../../settings/SettingsStore"; -import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch"; -import SettingsStore from "../../../../settings/SettingsStore"; -import Field from "../../elements/Field"; -const sdk = require("../../../../index"); -const PlatformPeg = require("../../../../PlatformPeg"); +import {_t} from "../../../../../languageHandler"; +import {SettingLevel} from "../../../../../settings/SettingsStore"; +import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; +import SettingsStore from "../../../../../settings/SettingsStore"; +import Field from "../../../elements/Field"; +const sdk = require("../../../../.."); +const PlatformPeg = require("../../../../../PlatformPeg"); -export default class PreferencesSettingsTab extends React.Component { +export default class PreferencesUserSettingsTab extends React.Component { static COMPOSER_SETTINGS = [ 'MessageComposerInput.autoReplaceEmoji', 'MessageComposerInput.suggestEmoji', @@ -95,17 +95,17 @@ export default class PreferencesSettingsTab extends React.Component { } return ( -
    +
    {_t("Preferences")}
    {_t("Composer")} - {this._renderGroup(PreferencesSettingsTab.COMPOSER_SETTINGS)} + {this._renderGroup(PreferencesUserSettingsTab.COMPOSER_SETTINGS)} {_t("Timeline")} - {this._renderGroup(PreferencesSettingsTab.TIMELINE_SETTINGS)} + {this._renderGroup(PreferencesUserSettingsTab.TIMELINE_SETTINGS)} {_t("Advanced")} - {this._renderGroup(PreferencesSettingsTab.ADVANCED_SETTINGS)} + {this._renderGroup(PreferencesUserSettingsTab.ADVANCED_SETTINGS)} {autoLaunchOption} +
    {_t('Unignore')} @@ -48,7 +48,7 @@ export class IgnoredUser extends React.Component { } } -export default class SecuritySettingsTab extends React.Component { +export default class SecurityUserSettingsTab extends React.Component { constructor() { super(); @@ -68,14 +68,14 @@ export default class SecuritySettingsTab extends React.Component { _onExportE2eKeysClicked = () => { Modal.createTrackedDialogAsync('Export E2E Keys', '', - import('../../../../async-components/views/dialogs/ExportE2eKeysDialog'), + import('../../../../../async-components/views/dialogs/ExportE2eKeysDialog'), {matrixClient: MatrixClientPeg.get()}, ); }; _onImportE2eKeysClicked = () => { Modal.createTrackedDialogAsync('Import E2E Keys', '', - import('../../../../async-components/views/dialogs/ImportE2eKeysDialog'), + import('../../../../../async-components/views/dialogs/ImportE2eKeysDialog'), {matrixClient: MatrixClientPeg.get()}, ); }; @@ -126,7 +126,7 @@ export default class SecuritySettingsTab extends React.Component { let importExportButtons = null; if (client.isCryptoEnabled()) { importExportButtons = ( -
    +
    {_t("Export E2E room keys")} @@ -140,7 +140,7 @@ export default class SecuritySettingsTab extends React.Component { return (
    {_t("Cryptography")} -
      +
      • {deviceId} @@ -207,7 +207,7 @@ export default class SecuritySettingsTab extends React.Component { ); return ( -
        +
        {_t("Security & Privacy")}
        {_t("Devices")} diff --git a/src/components/views/settings/tabs/VoiceSettingsTab.js b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js similarity index 90% rename from src/components/views/settings/tabs/VoiceSettingsTab.js rename to src/components/views/settings/tabs/user/VoiceUserSettingsTab.js index aefb114dd3..31791708e0 100644 --- a/src/components/views/settings/tabs/VoiceSettingsTab.js +++ b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js @@ -15,16 +15,16 @@ limitations under the License. */ import React from 'react'; -import {_t} from "../../../../languageHandler"; -import CallMediaHandler from "../../../../CallMediaHandler"; -import Field from "../../elements/Field"; -import AccessibleButton from "../../elements/AccessibleButton"; -import {SettingLevel} from "../../../../settings/SettingsStore"; -const Modal = require("../../../../Modal"); -const sdk = require("../../../../index"); -const MatrixClientPeg = require("../../../../MatrixClientPeg"); +import {_t} from "../../../../../languageHandler"; +import CallMediaHandler from "../../../../../CallMediaHandler"; +import Field from "../../../elements/Field"; +import AccessibleButton from "../../../elements/AccessibleButton"; +import {SettingLevel} from "../../../../../settings/SettingsStore"; +const Modal = require("../../../../../Modal"); +const sdk = require("../../../../.."); +const MatrixClientPeg = require("../../../../../MatrixClientPeg"); -export default class VoiceSettingsTab extends React.Component { +export default class VoiceUserSettingsTab extends React.Component { constructor() { super(); @@ -103,7 +103,7 @@ export default class VoiceSettingsTab extends React.Component { let webcamDropdown = null; if (this.state.mediaDevices === false) { requestButton = ( -
        +

        {_t("Missing media permissions, click the button below to request.")}

        {_t("Request media permissions")} @@ -166,7 +166,7 @@ export default class VoiceSettingsTab extends React.Component { } return ( -
        +
        {_t("Voice & Video")}
        {requestButton} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 84c9dacd07..5e4765b3af 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -500,19 +500,7 @@ "Upload profile picture": "Upload profile picture", "Display Name": "Display Name", "Save": "Save", - "This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers", - "Upgrade room to version %(ver)s": "Upgrade room to version %(ver)s", - "Room information": "Room information", - "Internal room ID:": "Internal room ID:", - "Room version": "Room version", - "Room version:": "Room version:", - "Developer options": "Developer options", - "Open Devtools": "Open Devtools", "Flair": "Flair", - "General": "General", - "Room Addresses": "Room Addresses", - "Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?", - "URL Previews": "URL Previews", "Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?", "Success": "Success", "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them", @@ -528,6 +516,7 @@ "Account management": "Account management", "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", "Deactivate Account": "Deactivate Account", + "General": "General", "Legal": "Legal", "Credits": "Credits", "For help with using Riot, click here.": "For help with using Riot, click here.", @@ -555,6 +544,44 @@ "Composer": "Composer", "Timeline": "Timeline", "Autocomplete delay (ms)": "Autocomplete delay (ms)", + "Unignore": "Unignore", + "": "", + "Import E2E room keys": "Import E2E room keys", + "Cryptography": "Cryptography", + "Device ID:": "Device ID:", + "Device key:": "Device key:", + "Ignored users": "Ignored users", + "Bulk options": "Bulk options", + "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites", + "Key backup": "Key backup", + "Security & Privacy": "Security & Privacy", + "Devices": "Devices", + "Riot collects anonymous analytics to allow us to improve the application.": "Riot collects anonymous analytics to allow us to improve the application.", + "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.", + "Learn more about how we use analytics.": "Learn more about how we use analytics.", + "No media permissions": "No media permissions", + "You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam", + "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.", + "Request media permissions": "Request media permissions", + "No Audio Outputs detected": "No Audio Outputs detected", + "No Microphones detected": "No Microphones detected", + "No Webcams detected": "No Webcams detected", + "Default Device": "Default Device", + "Audio Output": "Audio Output", + "Microphone": "Microphone", + "Camera": "Camera", + "Voice & Video": "Voice & Video", + "This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers", + "Upgrade room to version %(ver)s": "Upgrade room to version %(ver)s", + "Room information": "Room information", + "Internal room ID:": "Internal room ID:", + "Room version": "Room version", + "Room version:": "Room version:", + "Developer options": "Developer options", + "Open Devtools": "Open Devtools", + "Room Addresses": "Room Addresses", + "Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?", + "URL Previews": "URL Previews", "To change the room's avatar, you must be a": "To change the room's avatar, you must be a", "To change the room's name, you must be a": "To change the room's name, you must be a", "To change the room's main address, you must be a": "To change the room's main address, you must be a", @@ -592,38 +619,11 @@ "Members only (since the point in time of selecting this option)": "Members only (since the point in time of selecting this option)", "Members only (since they were invited)": "Members only (since they were invited)", "Members only (since they joined)": "Members only (since they joined)", - "Security & Privacy": "Security & Privacy", "Encryption": "Encryption", "Once enabled, encryption cannot be disabled.": "Once enabled, encryption cannot be disabled.", "Encrypted": "Encrypted", "Who can access this room?": "Who can access this room?", "Who can read history?": "Who can read history?", - "Unignore": "Unignore", - "": "", - "Import E2E room keys": "Import E2E room keys", - "Cryptography": "Cryptography", - "Device ID:": "Device ID:", - "Device key:": "Device key:", - "Ignored users": "Ignored users", - "Bulk options": "Bulk options", - "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites", - "Key backup": "Key backup", - "Devices": "Devices", - "Riot collects anonymous analytics to allow us to improve the application.": "Riot collects anonymous analytics to allow us to improve the application.", - "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.", - "Learn more about how we use analytics.": "Learn more about how we use analytics.", - "No media permissions": "No media permissions", - "You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam", - "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.", - "Request media permissions": "Request media permissions", - "No Audio Outputs detected": "No Audio Outputs detected", - "No Microphones detected": "No Microphones detected", - "No Webcams detected": "No Webcams detected", - "Default Device": "Default Device", - "Audio Output": "Audio Output", - "Microphone": "Microphone", - "Camera": "Camera", - "Voice & Video": "Voice & Video", "Cannot add any more widgets": "Cannot add any more widgets", "The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.", "Add a widget": "Add a widget", From 014e4a2ccf2bf1010e694020563f323613600c6f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 22 Feb 2019 11:31:17 -0700 Subject: [PATCH 111/178] Remove DragDropContext from FlairSettings This also fixes a technical bug where one could drag a community from the settings to the LLP --- .../views/groups/GroupPublicityToggle.js | 4 +- src/components/views/groups/GroupTile.js | 92 ++++++++++--------- .../tabs/user/FlairUserSettingsTab.js | 5 +- 3 files changed, 51 insertions(+), 50 deletions(-) diff --git a/src/components/views/groups/GroupPublicityToggle.js b/src/components/views/groups/GroupPublicityToggle.js index e27bf9a9d5..98fa598e20 100644 --- a/src/components/views/groups/GroupPublicityToggle.js +++ b/src/components/views/groups/GroupPublicityToggle.js @@ -68,7 +68,9 @@ export default React.createClass({ render() { const GroupTile = sdk.getComponent('groups.GroupTile'); return
        - + diff --git a/src/components/views/groups/GroupTile.js b/src/components/views/groups/GroupTile.js index 509c209baa..18ef5a5637 100644 --- a/src/components/views/groups/GroupTile.js +++ b/src/components/views/groups/GroupTile.js @@ -33,6 +33,7 @@ const GroupTile = React.createClass({ showDescription: PropTypes.bool, // Height of the group avatar in pixels avatarHeight: PropTypes.number, + draggable: PropTypes.bool, }, contextTypes: { @@ -49,6 +50,7 @@ const GroupTile = React.createClass({ return { showDescription: true, avatarHeight: 50, + draggable: true, }; }, @@ -78,54 +80,54 @@ const GroupTile = React.createClass({
        { profile.shortDescription }
        :
        ; const httpUrl = profile.avatarUrl ? this.context.matrixClient.mxcUrlToHttp( - profile.avatarUrl, avatarHeight, avatarHeight, "crop", - ) : null; + profile.avatarUrl, avatarHeight, avatarHeight, "crop") : null; + + let avatarElement = ( +
        + +
        + ); + if (this.props.draggable) { + const avatarClone = avatarElement; + avatarElement = ( + + { (droppableProvided, droppableSnapshot) => ( +
        + + { (provided, snapshot) => ( +
        +
        + {avatarClone} +
        + { /* Instead of a blank placeholder, use a copy of the avatar itself. */ } + { provided.placeholder ? avatarClone :
        } +
        + ) } + +
        + ) } + + ); + } + // XXX: Use onMouseDown as a workaround for https://github.com/atlassian/react-beautiful-dnd/issues/273 // instead of onClick. Otherwise we experience https://github.com/vector-im/riot-web/issues/6156 return - - { (droppableProvided, droppableSnapshot) => ( -
        - - { (provided, snapshot) => ( -
        -
        -
        - -
        -
        - { /* Instead of a blank placeholder, use a copy of the avatar itself. */ } - { provided.placeholder ? -
        - -
        : -
        - } -
        - ) } - -
        - ) } - + { avatarElement }
        { name }
        { descElement } diff --git a/src/components/views/settings/tabs/user/FlairUserSettingsTab.js b/src/components/views/settings/tabs/user/FlairUserSettingsTab.js index 0daa20b8b3..0063a9a981 100644 --- a/src/components/views/settings/tabs/user/FlairUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/FlairUserSettingsTab.js @@ -16,7 +16,6 @@ limitations under the License. import React from 'react'; import {_t} from "../../../../../languageHandler"; -import {DragDropContext} from "react-beautiful-dnd"; import GroupUserSettings from "../../../groups/GroupUserSettings"; import MatrixClientPeg from "../../../../../MatrixClientPeg"; import PropTypes from "prop-types"; @@ -42,9 +41,7 @@ export default class FlairUserSettingsTab extends React.Component {
        {_t("Flair")}
        - - - +
        ); From 7ea4008daa733b45e39a5aec3dad2e2aa9ee857c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 22 Feb 2019 16:33:20 -0700 Subject: [PATCH 112/178] Implement support for watching for changes in settings This implements a dream of one day being able to listen for changes in a settings to react to them, regardless of which device actually changed the setting. The use case for this kind of thing is extremely limited, but when it is needed it should be more than powerful enough. --- docs/settings.md | 33 +++++ src/MatrixClientPeg.js | 4 +- src/settings/SettingsStore.js | 117 ++++++++++++++++++ src/settings/WatchManager.js | 57 +++++++++ .../handlers/AccountSettingsHandler.js | 48 ++++++- .../handlers/ConfigSettingsHandler.js | 8 ++ .../handlers/DefaultSettingsHandler.js | 9 ++ .../handlers/DeviceSettingsHandler.js | 16 +++ src/settings/handlers/LocalEchoWrapper.js | 9 ++ .../MatrixClientBackedSettingsHandler.js | 48 +++++++ .../handlers/RoomAccountSettingsHandler.js | 51 +++++++- .../handlers/RoomDeviceSettingsHandler.js | 18 +++ src/settings/handlers/RoomSettingsHandler.js | 49 +++++++- src/settings/handlers/SettingsHandler.js | 24 ++++ 14 files changed, 484 insertions(+), 7 deletions(-) create mode 100644 src/settings/WatchManager.js create mode 100644 src/settings/handlers/MatrixClientBackedSettingsHandler.js diff --git a/docs/settings.md b/docs/settings.md index cdba01e04a..9762e7a73e 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -131,6 +131,32 @@ SettingsStore.getValue(...); // this will return the value set in `setValue` abo ``` +## Watching for changes + +Most use cases do not need to set up a watcher because they are able to react to changes as they are made, or the changes which are made are not significant enough for it to matter. Watchers are intended to be used in scenarios where it is important to react to changes made by other logged in devices. Typically, this would be done within the component itself, however the component should not be aware of the intricacies of setting inversion or remapping to particular data structures. Instead, a generic watcher interface is provided on `SettingsStore` to watch (and subsequently unwatch) for changes in a setting. + +An example of a watcher in action would be: + +```javascript +class MyComponent extends React.Component { + + settingWatcherRef = null; + + componentWillMount() { + this.settingWatcherRef = SettingsStore.watchSetting("roomColor", "!example:matrix.org", (settingName, roomId, level, newVal) => { + // Always re-read the setting value from the store to avoid reacting to changes which do not have a consequence. For example, the + // room color could have been changed at the device level, but an account override prevents that change from making a difference. + const actualVal = SettingsStore.getValue(settingName, "!example:matrix.org"); + if (actualVal !== this.state.color) this.setState({color: actualVal}); + }); + } + + componentWillUnmount() { + SettingsStore.unwatchSetting(this.settingWatcherRef); + } +} +``` + # Maintainers Reference @@ -159,3 +185,10 @@ Features automatically get considered as `disabled` if they are not listed in th ``` If `enableLabs` is true in the configuration, the default for features becomes `"labs"`. + +### Watchers + +Watchers can appear complicated under the hood: the request to watch a setting is actually forked off to individual handlers for watching. This means that the handlers need to track their changes and listen for remote changes where possible, but also makes it much easier for the `SettingsStore` to react to changes. The handler is going to know the best things to listen for (specific events, account data, etc) and thus it is left as a responsibility for the handler to track changes. + +In practice, handlers which rely on remote changes (account data, room events, etc) will always attach a listener to the `MatrixClient`. They then watch for changes to events they care about and send off appropriate updates to the 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'. + \ No newline at end of file diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index e36034c69d..1cf29c3e82 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -30,6 +30,7 @@ import MatrixActionCreators from './actions/MatrixActionCreators'; import {phasedRollOutExpiredForUser} from "./PhasedRollOut"; import Modal from './Modal'; import {verificationMethods} from 'matrix-js-sdk/lib/crypto'; +import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientBackedSettingsHandler"; interface MatrixClientCreds { homeserverUrl: string, @@ -137,8 +138,9 @@ class MatrixClientPeg { opts.pendingEventOrdering = "detached"; opts.lazyLoadMembers = true; - // Connect the matrix client to the dispatcher + // Connect the matrix client to the dispatcher and setting handlers MatrixActionCreators.start(this.matrixClient); + MatrixClientBackedSettingsHandler.matrixClient = this.matrixClient; console.log(`MatrixClientPeg: really starting MatrixClient`); await this.get().startClient(opts); diff --git a/src/settings/SettingsStore.js b/src/settings/SettingsStore.js index 1bdd72dc5f..1704ad9db2 100644 --- a/src/settings/SettingsStore.js +++ b/src/settings/SettingsStore.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -23,6 +24,7 @@ import RoomSettingsHandler from "./handlers/RoomSettingsHandler"; import ConfigSettingsHandler from "./handlers/ConfigSettingsHandler"; import {_t} from '../languageHandler'; import SdkConfig from "../SdkConfig"; +import dis from '../dispatcher'; import {SETTINGS} from "./Settings"; import LocalEchoWrapper from "./handlers/LocalEchoWrapper"; @@ -98,6 +100,121 @@ const LEVEL_ORDER = [ * be enabled). */ export default class SettingsStore { + // We support watching settings for changes, and do so only at the levels which are + // relevant to the setting. We pass the watcher on to the handlers and aggregate it + // before sending it off to the caller. We need to track which callback functions we + // provide to the handlers though so we can unwatch it on demand. In practice, we + // return a "callback reference" to the caller which correlates to an entry in this + // dictionary for each handler's callback function. + // + // We also maintain a list of monitors which are special watchers: they cause dispatches + // when the setting changes. We track which rooms we're monitoring though to ensure we + // don't duplicate updates on the bus. + static _watchers = {}; // { callbackRef => { level => callbackFn } } + static _monitors = {}; // { settingName => { roomId => callbackRef } } + + /** + * Watches for changes in a particular setting. This is done without any local echo + * wrapping and fires whenever a change is detected in a setting's value. Watching + * is intended to be used in scenarios where the app needs to react to changes made + * by other devices. It is otherwise expected that callers will be able to use the + * Controller system or track their own changes to settings. Callers should retain + * @param {string} settingName The setting name to watch + * @param {String} roomId The room ID to watch for changes in. May be null for 'all'. + * @param {function} callbackFn A function to be called when a setting change is + * detected. Four arguments can be expected: the setting name, the room ID (may be null), + * the level the change happened at, and finally the new value for those arguments. The + * callback may need to do a call to #getValue() to see if a consequential change has + * occurred. + * @returns {string} A reference to the watcher that was employed. + */ + static watchSetting(settingName, roomId, callbackFn) { + const setting = SETTINGS[settingName]; + const originalSettingName = settingName; + if (!setting) throw new Error(`${settingName} is not a setting`); + + if (setting.invertedSettingName) { + settingName = setting.invertedSettingName; + } + + const watcherId = `${new Date().getTime()}_${settingName}_${roomId}`; + SettingsStore._watchers[watcherId] = {}; + + const levels = Object.keys(LEVEL_HANDLERS); + for (const level of levels) { + const handler = SettingsStore._getHandler(originalSettingName, level); + if (!handler) continue; + + const localizedCallback = (changedInRoomId, newVal) => { + callbackFn(originalSettingName, changedInRoomId, level, newVal); + }; + + console.log(`Starting watcher for ${settingName}@${roomId || ''} at level ${level}`); + SettingsStore._watchers[watcherId][level] = localizedCallback; + handler.watchSetting(settingName, roomId, localizedCallback); + } + + return watcherId; + } + + /** + * Stops the SettingsStore from watching a setting. This is a no-op if the watcher + * provided is not found. + * @param {string} watcherReference The watcher reference (received from #watchSetting) + * to cancel. + */ + static unwatchSetting(watcherReference) { + if (!SettingsStore._watchers[watcherReference]) return; + + for (const handlerName of Object.keys(SettingsStore._watchers[watcherReference])) { + const handler = LEVEL_HANDLERS[handlerName]; + if (!handler) continue; + handler.unwatchSetting(SettingsStore._watchers[watcherReference][handlerName]); + } + + delete SettingsStore._watchers[watcherReference]; + } + + /** + * Sets up a monitor for a setting. This behaves similar to #watchSetting except instead + * of making a call to a callback, it forwards all changes to the dispatcher. Callers can + * expect to listen for the 'setting_updated' action with an object containing settingName, + * roomId, level, and newValue. + * @param {string} settingName The setting name to monitor. + * @param {String} roomId The room ID to monitor for changes in. Use null for all rooms. + */ + static monitorSetting(settingName, roomId) { + if (!this._monitors[settingName]) this._monitors[settingName] = {}; + + const registerWatcher = () => { + this._monitors[settingName][roomId] = SettingsStore.watchSetting( + settingName, roomId, (settingName, inRoomId, level, newValue) => { + dis.dispatch({ + action: 'setting_updated', + settingName, + roomId: inRoomId, + level, + newValue, + }); + }, + ); + }; + + const hasRoom = Object.keys(this._monitors[settingName]).find((r) => r === roomId || r === null); + if (!hasRoom) { + registerWatcher(); + } else { + if (roomId === null) { + // Unregister all existing watchers and register the new one + for (const roomId of Object.keys(this._monitors[settingName])) { + SettingsStore.unwatchSetting(this._monitors[settingName][roomId]); + } + this._monitors[settingName] = {}; + registerWatcher(); + } // else a watcher is already registered for the room, so don't bother registering it again + } + } + /** * Gets the translated display name for a given setting * @param {string} settingName The setting to look up. diff --git a/src/settings/WatchManager.js b/src/settings/WatchManager.js new file mode 100644 index 0000000000..0561529392 --- /dev/null +++ b/src/settings/WatchManager.js @@ -0,0 +1,57 @@ +/* +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. +*/ + +/** + * Generalized management class for dealing with watchers on a per-handler (per-level) + * basis without duplicating code. Handlers are expected to push updates through this + * class, which are then proxied outwards to any applicable watchers. + */ +export class WatchManager { + _watchers = {}; // { settingName: { roomId: callbackFns[] } } + + // Proxy for handlers to delegate changes to this manager + watchSetting(settingName, roomId, cb) { + if (!this._watchers[settingName]) this._watchers[settingName] = {}; + if (!this._watchers[settingName][roomId]) this._watchers[settingName][roomId] = []; + this._watchers[settingName][roomId].push(cb); + } + + // Proxy for handlers to delegate changes to this manager + unwatchSetting(cb) { + for (const settingName of Object.keys(this._watchers)) { + for (const roomId of Object.keys(this._watchers[settingName])) { + let idx; + while ((idx = this._watchers[settingName][roomId].indexOf(cb)) !== -1) { + this._watchers[settingName][roomId].splice(idx, 1); + } + } + } + } + + notifyUpdate(settingName, inRoomId, newValue) { + if (!this._watchers[settingName]) return; + + const roomWatchers = this._watchers[settingName]; + const callbacks = []; + + if (inRoomId !== null && roomWatchers[inRoomId]) callbacks.push(...roomWatchers[inRoomId]); + if (roomWatchers[null]) callbacks.push(...roomWatchers[null]); + + for (const callback of callbacks) { + callback(inRoomId, newValue); + } + } +} diff --git a/src/settings/handlers/AccountSettingsHandler.js b/src/settings/handlers/AccountSettingsHandler.js index b822709573..4bef585e6b 100644 --- a/src/settings/handlers/AccountSettingsHandler.js +++ b/src/settings/handlers/AccountSettingsHandler.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -14,14 +15,49 @@ See the License for the specific language governing permissions and limitations under the License. */ -import SettingsHandler from "./SettingsHandler"; import MatrixClientPeg from '../../MatrixClientPeg'; +import {WatchManager} from "../WatchManager"; +import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler"; /** * Gets and sets settings at the "account" level for the current user. * This handler does not make use of the roomId parameter. */ -export default class AccountSettingHandler extends SettingsHandler { +export default class AccountSettingsHandler extends MatrixClientBackedSettingsHandler { + constructor() { + super(); + + this._watchers = new WatchManager(); + this._onAccountData = this._onAccountData.bind(this); + } + + initMatrixClient(oldClient, newClient) { + if (oldClient) { + oldClient.removeListener("accountData", this._onAccountData); + } + + newClient.on("accountData", this._onAccountData); + } + + _onAccountData(event) { + if (event.getType() === "org.matrix.preview_urls") { + let val = event.getContent()['disable']; + if (typeof(val) !== "boolean") { + val = null; + } else { + val = !val; + } + + this._watchers.notifyUpdate("urlPreviewsEnabled", null, val); + } else if (event.getType() === "im.vector.web.settings") { + // We can't really discern what changed, so trigger updates for everything + for (const settingName of Object.keys(event.getContent())) { + console.log(settingName); + this._watchers.notifyUpdate(settingName, null, event.getContent()[settingName]); + } + } + } + getValue(settingName, roomId) { // Special case URL previews if (settingName === "urlPreviewsEnabled") { @@ -67,6 +103,14 @@ export default class AccountSettingHandler extends SettingsHandler { return cli !== undefined && cli !== null; } + watchSetting(settingName, roomId, cb) { + this._watchers.watchSetting(settingName, roomId, cb); + } + + unwatchSetting(cb) { + this._watchers.unwatchSetting(cb); + } + _getSettings(eventType = "im.vector.web.settings") { const cli = MatrixClientPeg.get(); if (!cli) return null; diff --git a/src/settings/handlers/ConfigSettingsHandler.js b/src/settings/handlers/ConfigSettingsHandler.js index a54ad1cef6..095347a542 100644 --- a/src/settings/handlers/ConfigSettingsHandler.js +++ b/src/settings/handlers/ConfigSettingsHandler.js @@ -47,4 +47,12 @@ export default class ConfigSettingsHandler extends SettingsHandler { isSupported() { return true; // SdkConfig is always there } + + watchSetting(settingName, roomId, cb) { + // no-op: no changes possible + } + + unwatchSetting(cb) { + // no-op: no changes possible + } } diff --git a/src/settings/handlers/DefaultSettingsHandler.js b/src/settings/handlers/DefaultSettingsHandler.js index 11e8b729bc..83824850b3 100644 --- a/src/settings/handlers/DefaultSettingsHandler.js +++ b/src/settings/handlers/DefaultSettingsHandler.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -51,4 +52,12 @@ export default class DefaultSettingsHandler extends SettingsHandler { isSupported() { return true; } + + watchSetting(settingName, roomId, cb) { + // no-op: no changes possible + } + + unwatchSetting(cb) { + // no-op: no changes possible + } } diff --git a/src/settings/handlers/DeviceSettingsHandler.js b/src/settings/handlers/DeviceSettingsHandler.js index b2a225e190..457fb888e9 100644 --- a/src/settings/handlers/DeviceSettingsHandler.js +++ b/src/settings/handlers/DeviceSettingsHandler.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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,6 +18,7 @@ limitations under the License. import Promise from 'bluebird'; import SettingsHandler from "./SettingsHandler"; import MatrixClientPeg from "../../MatrixClientPeg"; +import {WatchManager} from "../WatchManager"; /** * Gets and sets settings at the "device" level for the current device. @@ -31,6 +33,7 @@ export default class DeviceSettingsHandler extends SettingsHandler { constructor(featureNames) { super(); this._featureNames = featureNames; + this._watchers = new WatchManager(); } getValue(settingName, roomId) { @@ -66,18 +69,22 @@ export default class DeviceSettingsHandler extends SettingsHandler { // Special case notifications if (settingName === "notificationsEnabled") { localStorage.setItem("notifications_enabled", newValue); + this._watchers.notifyUpdate(settingName, null, newValue); return Promise.resolve(); } else if (settingName === "notificationBodyEnabled") { localStorage.setItem("notifications_body_enabled", newValue); + this._watchers.notifyUpdate(settingName, null, newValue); return Promise.resolve(); } else if (settingName === "audioNotificationsEnabled") { localStorage.setItem("audio_notifications_enabled", newValue); + this._watchers.notifyUpdate(settingName, null, newValue); return Promise.resolve(); } const settings = this._getSettings() || {}; settings[settingName] = newValue; localStorage.setItem("mx_local_settings", JSON.stringify(settings)); + this._watchers.notifyUpdate(settingName, null, newValue); return Promise.resolve(); } @@ -90,6 +97,14 @@ export default class DeviceSettingsHandler extends SettingsHandler { return localStorage !== undefined && localStorage !== null; } + watchSetting(settingName, roomId, cb) { + this._watchers.watchSetting(settingName, roomId, cb); + } + + unwatchSetting(cb) { + this._watchers.unwatchSetting(cb); + } + _getSettings() { const value = localStorage.getItem("mx_local_settings"); if (!value) return null; @@ -111,5 +126,6 @@ export default class DeviceSettingsHandler extends SettingsHandler { _writeFeature(featureName, enabled) { localStorage.setItem("mx_labs_feature_" + featureName, enabled); + this._watchers.notifyUpdate(featureName, null, enabled); } } diff --git a/src/settings/handlers/LocalEchoWrapper.js b/src/settings/handlers/LocalEchoWrapper.js index d616edd9fb..3b1200f0b7 100644 --- a/src/settings/handlers/LocalEchoWrapper.js +++ b/src/settings/handlers/LocalEchoWrapper.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -66,4 +67,12 @@ export default class LocalEchoWrapper extends SettingsHandler { isSupported() { return this._handler.isSupported(); } + + watchSetting(settingName, roomId, cb) { + this._handler.watchSetting(settingName, roomId, cb); + } + + unwatchSetting(cb) { + this._handler.unwatchSetting(cb); + } } diff --git a/src/settings/handlers/MatrixClientBackedSettingsHandler.js b/src/settings/handlers/MatrixClientBackedSettingsHandler.js new file mode 100644 index 0000000000..effe7ae9a7 --- /dev/null +++ b/src/settings/handlers/MatrixClientBackedSettingsHandler.js @@ -0,0 +1,48 @@ +/* +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 SettingsHandler from "./SettingsHandler"; + +// Dev note: This whole class exists in the event someone logs out and back in - we want +// to make sure the right MatrixClient is listening for changes. + +/** + * Represents the base class for settings handlers which need access to a MatrixClient. + * This class performs no logic and should be overridden. + */ +export default class MatrixClientBackedSettingsHandler extends SettingsHandler { + static _matrixClient; + static _instances = []; + + static set matrixClient(client) { + const oldClient = MatrixClientBackedSettingsHandler._matrixClient; + MatrixClientBackedSettingsHandler._matrixClient = client; + + for (const instance of MatrixClientBackedSettingsHandler._instances) { + instance.initMatrixClient(oldClient, client); + } + } + + constructor() { + super(); + + MatrixClientBackedSettingsHandler._instances.push(this); + } + + initMatrixClient() { + console.warn("initMatrixClient not overridden"); + } +} diff --git a/src/settings/handlers/RoomAccountSettingsHandler.js b/src/settings/handlers/RoomAccountSettingsHandler.js index d0dadc2de7..448b42f61e 100644 --- a/src/settings/handlers/RoomAccountSettingsHandler.js +++ b/src/settings/handlers/RoomAccountSettingsHandler.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -14,13 +15,51 @@ See the License for the specific language governing permissions and limitations under the License. */ -import SettingsHandler from "./SettingsHandler"; import MatrixClientPeg from '../../MatrixClientPeg'; +import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler"; +import {WatchManager} from "../WatchManager"; /** * Gets and sets settings at the "room-account" level for the current user. */ -export default class RoomAccountSettingsHandler extends SettingsHandler { +export default class RoomAccountSettingsHandler extends MatrixClientBackedSettingsHandler { + constructor() { + super(); + + this._watchers = new WatchManager(); + this._onAccountData = this._onAccountData.bind(this); + } + + initMatrixClient(oldClient, newClient) { + if (oldClient) { + oldClient.removeListener("Room.accountData", this._onAccountData); + } + + newClient.on("Room.accountData", this._onAccountData); + } + + _onAccountData(event, room) { + const roomId = room.roomId; + + if (event.getType() === "org.matrix.room.preview_urls") { + let val = event.getContent()['disable']; + if (typeof (val) !== "boolean") { + val = null; + } else { + val = !val; + } + + this._watchers.notifyUpdate("urlPreviewsEnabled", roomId, val); + } else if (event.getType() === "org.matrix.room.color_scheme") { + this._watchers.notifyUpdate("roomColor", roomId, event.getContent()); + } else if (event.getType() === "im.vector.web.settings") { + // We can't really discern what changed, so trigger updates for everything + for (const settingName of Object.keys(event.getContent())) { + this._watchers.notifyUpdate(settingName, roomId, event.getContent()[settingName]); + } + } + } + getValue(settingName, roomId) { // Special case URL previews if (settingName === "urlPreviewsEnabled") { @@ -74,6 +113,14 @@ export default class RoomAccountSettingsHandler extends SettingsHandler { return cli !== undefined && cli !== null; } + watchSetting(settingName, roomId, cb) { + this._watchers.watchSetting(settingName, roomId, cb); + } + + unwatchSetting(cb) { + this._watchers.unwatchSetting(cb); + } + _getSettings(roomId, eventType = "im.vector.web.settings") { const room = MatrixClientPeg.get().getRoom(roomId); if (!room) return null; diff --git a/src/settings/handlers/RoomDeviceSettingsHandler.js b/src/settings/handlers/RoomDeviceSettingsHandler.js index 186be3041f..710c5e6255 100644 --- a/src/settings/handlers/RoomDeviceSettingsHandler.js +++ b/src/settings/handlers/RoomDeviceSettingsHandler.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -16,12 +17,19 @@ limitations under the License. import Promise from 'bluebird'; import SettingsHandler from "./SettingsHandler"; +import {WatchManager} from "../WatchManager"; /** * Gets and sets settings at the "room-device" level for the current device in a particular * room. */ export default class RoomDeviceSettingsHandler extends SettingsHandler { + constructor() { + super(); + + this._watchers = new WatchManager(); + } + getValue(settingName, roomId) { // Special case blacklist setting to use legacy values if (settingName === "blacklistUnverifiedDevices") { @@ -44,6 +52,7 @@ export default class RoomDeviceSettingsHandler extends SettingsHandler { if (!value["blacklistUnverifiedDevicesPerRoom"]) value["blacklistUnverifiedDevicesPerRoom"] = {}; value["blacklistUnverifiedDevicesPerRoom"][roomId] = newValue; localStorage.setItem("mx_local_settings", JSON.stringify(value)); + this._watchers.notifyUpdate(settingName, roomId, newValue); return Promise.resolve(); } @@ -54,6 +63,7 @@ export default class RoomDeviceSettingsHandler extends SettingsHandler { localStorage.setItem(this._getKey(settingName, roomId), newValue); } + this._watchers.notifyUpdate(settingName, roomId, newValue); return Promise.resolve(); } @@ -65,6 +75,14 @@ export default class RoomDeviceSettingsHandler extends SettingsHandler { return localStorage !== undefined && localStorage !== null; } + watchSetting(settingName, roomId, cb) { + this._watchers.watchSetting(settingName, roomId, cb); + } + + unwatchSetting(cb) { + this._watchers.unwatchSetting(cb); + } + _read(key) { const rawValue = localStorage.getItem(key); if (!rawValue) return null; diff --git a/src/settings/handlers/RoomSettingsHandler.js b/src/settings/handlers/RoomSettingsHandler.js index 71abff94f6..1622b44dd0 100644 --- a/src/settings/handlers/RoomSettingsHandler.js +++ b/src/settings/handlers/RoomSettingsHandler.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -14,13 +15,49 @@ See the License for the specific language governing permissions and limitations under the License. */ -import SettingsHandler from "./SettingsHandler"; import MatrixClientPeg from '../../MatrixClientPeg'; +import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler"; +import {WatchManager} from "../WatchManager"; /** * Gets and sets settings at the "room" level. */ -export default class RoomSettingsHandler extends SettingsHandler { +export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandler { + constructor() { + super(); + + this._watchers = new WatchManager(); + this._onEvent = this._onEvent.bind(this); + } + + initMatrixClient(oldClient, newClient) { + if (oldClient) { + oldClient.removeListener("RoomState.events", this._onEvent); + } + + newClient.on("RoomState.events", this._onEvent); + } + + _onEvent(event) { + const roomId = event.getRoomId(); + + if (event.getType() === "org.matrix.room.preview_urls") { + let val = event.getContent()['disable']; + if (typeof (val) !== "boolean") { + val = null; + } else { + val = !val; + } + + this._watchers.notifyUpdate("urlPreviewsEnabled", roomId, val); + } else if (event.getType() === "im.vector.web.settings") { + // We can't really discern what changed, so trigger updates for everything + for (const settingName of Object.keys(event.getContent())) { + this._watchers.notifyUpdate(settingName, roomId, event.getContent()[settingName]); + } + } + } + getValue(settingName, roomId) { // Special case URL previews if (settingName === "urlPreviewsEnabled") { @@ -64,6 +101,14 @@ export default class RoomSettingsHandler extends SettingsHandler { return cli !== undefined && cli !== null; } + watchSetting(settingName, roomId, cb) { + this._watchers.watchSetting(settingName, roomId, cb); + } + + unwatchSetting(cb) { + this._watchers.unwatchSetting(cb); + } + _getSettings(roomId, eventType = "im.vector.web.settings") { const room = MatrixClientPeg.get().getRoom(roomId); if (!room) return null; diff --git a/src/settings/handlers/SettingsHandler.js b/src/settings/handlers/SettingsHandler.js index 69f633c650..0a704d5be7 100644 --- a/src/settings/handlers/SettingsHandler.js +++ b/src/settings/handlers/SettingsHandler.js @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +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. @@ -68,4 +69,27 @@ export default class SettingsHandler { isSupported() { return false; } + + /** + * Watches for a setting change within this handler. The caller should preserve + * a reference to the callback so that it may be unwatched. The caller should + * additionally provide a unique callback for multiple watchers on the same setting. + * @param {string} settingName The setting name to watch for changes in. + * @param {String} roomId The room ID to watch for changes in. + * @param {function} cb A function taking two arguments: the room ID the setting changed + * in and the new value for the setting at this level in the given room. + */ + watchSetting(settingName, roomId, cb) { + throw new Error("Invalid operation: watchSetting was not overridden"); + } + + /** + * Unwatches a previously watched setting. If the callback is not associated with + * a watcher, this is a no-op. + * @param {function} cb A callback function previously supplied to watchSetting + * which should no longer be used. + */ + unwatchSetting(cb) { + throw new Error("Invalid operation: unwatchSetting was not overridden"); + } } From b0cc69bca934865e93d759d33bba3850e9f53fda Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 22 Feb 2019 16:57:41 -0700 Subject: [PATCH 113/178] Add an option to sort the room list by recents first Fixes https://github.com/vector-im/riot-web/issues/8892 --- .../settings/tabs/PreferencesSettingsTab.js | 7 ++++ src/i18n/strings/en_EN.json | 2 ++ src/settings/Settings.js | 7 +++- src/stores/RoomListStore.js | 34 ++++++++++++++++++- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/tabs/PreferencesSettingsTab.js b/src/components/views/settings/tabs/PreferencesSettingsTab.js index d76dc8f3dd..d40e532789 100644 --- a/src/components/views/settings/tabs/PreferencesSettingsTab.js +++ b/src/components/views/settings/tabs/PreferencesSettingsTab.js @@ -44,6 +44,10 @@ export default class PreferencesSettingsTab extends React.Component { 'showDisplaynameChanges', ]; + static ROOM_LIST_SETTINGS = [ + 'RoomList.orderByImportance', + ]; + static ADVANCED_SETTINGS = [ 'alwaysShowEncryptionIcons', 'Pill.shouldShowPillAvatar', @@ -104,6 +108,9 @@ export default class PreferencesSettingsTab extends React.Component { {_t("Timeline")} {this._renderGroup(PreferencesSettingsTab.TIMELINE_SETTINGS)} + {_t("Room list")} + {this._renderGroup(PreferencesSettingsTab.ROOM_LIST_SETTINGS)} + {_t("Advanced")} {this._renderGroup(PreferencesSettingsTab.ADVANCED_SETTINGS)} {autoLaunchOption} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 84c9dacd07..d7c82e23e6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -306,6 +306,7 @@ "Enable widget screenshots on supported widgets": "Enable widget screenshots on supported widgets", "Prompt before sending invites to potentially invalid matrix IDs": "Prompt before sending invites to potentially invalid matrix IDs", "Show developer tools": "Show developer tools", + "Order rooms in the room list by most important first instead of most recent": "Order rooms in the room list by most important first instead of most recent", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", "Uploading report": "Uploading report", @@ -554,6 +555,7 @@ "Preferences": "Preferences", "Composer": "Composer", "Timeline": "Timeline", + "Room list": "Room list", "Autocomplete delay (ms)": "Autocomplete delay (ms)", "To change the room's avatar, you must be a": "To change the room's avatar, you must be a", "To change the room's name, you must be a": "To change the room's name, you must be a", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index cf68fed8ba..e4db12f5ba 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -1,6 +1,6 @@ /* Copyright 2017 Travis Ralston -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. @@ -340,4 +340,9 @@ export const SETTINGS = { displayName: _td('Show developer tools'), default: false, }, + "RoomList.orderByImportance": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td('Order rooms in the room list by most important first instead of most recent'), + default: true, + }, }; diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 0a11c2774a..aec57dedeb 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -59,6 +59,22 @@ class RoomListStore extends Store { this._recentsComparator = this._recentsComparator.bind(this); } + /** + * Alerts the RoomListStore to a potential change in how room list sorting should + * behave. + * @param {boolean} forceRegeneration True to force a change in the algorithm + */ + updateSortingAlgorithm(forceRegeneration=false) { + const byImportance = SettingsStore.getValue("RoomList.orderByImportance"); + if (byImportance !== this._state.orderRoomsByImportance || forceRegeneration) { + console.log("Updating room sorting algorithm: sortByImportance=" + byImportance); + this._setState({orderRoomsByImportance: byImportance}); + + // Trigger a resort of the entire list to reflect the change in algorithm + this._generateInitialRoomLists(); + } + } + _init() { // Initialise state const defaultLists = { @@ -77,7 +93,10 @@ class RoomListStore extends Store { presentationLists: defaultLists, // like `lists`, but with arrays of rooms instead ready: false, stickyRoomId: null, + orderRoomsByImportance: true, }; + + SettingsStore.monitorSetting('RoomList.orderByImportance', null); } _setState(newState) { @@ -99,6 +118,11 @@ class RoomListStore extends Store { __onDispatch(payload) { const logicallyReady = this._matrixClient && this._state.ready; switch (payload.action) { + case 'setting_updated': { + if (payload.settingName !== 'RoomList.orderByImportance') break; + this.updateSortingAlgorithm(); + } + break; // Initialise state after initial sync case 'MatrixActions.sync': { if (!(payload.prevState !== 'PREPARED' && payload.state === 'PREPARED')) { @@ -106,7 +130,7 @@ class RoomListStore extends Store { } this._matrixClient = payload.matrixClient; - this._generateInitialRoomLists(); + this.updateSortingAlgorithm(/*force=*/true); } break; case 'MatrixActions.Room.receipt': { @@ -517,6 +541,14 @@ class RoomListStore extends Store { } _calculateCategory(room) { + if (!this._state.orderRoomsByImportance) { + // Effectively disable the categorization of rooms if we're supposed to + // be sorting by more recent messages first. This triggers the timestamp + // comparison bit of _setRoomCategory and _recentsComparator instead of + // the category ordering. + return CATEGORY_IDLE; + } + const mentions = room.getUnreadNotificationCount("highlight") > 0; if (mentions) return CATEGORY_RED; From e31179224ed4d407e3f186af7c2259eea4fec7af Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 22 Feb 2019 20:47:06 -0700 Subject: [PATCH 114/178] Export the defaults for SdkConfig --- src/SdkConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SdkConfig.js b/src/SdkConfig.js index 65982bd6f2..78dd050a1e 100644 --- a/src/SdkConfig.js +++ b/src/SdkConfig.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -const DEFAULTS = { +export const DEFAULTS = { // URL to a page we show in an iframe to configure integrations integrations_ui_url: "https://scalar.vector.im/", // Base URL to the REST interface of the integrations server From b02b37125014bc160767c1ac811538b922d46ee5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 01:06:53 +0000 Subject: [PATCH 115/178] Allow configuration of whether closing window closes or minimizes to tray Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/BasePlatform.js | 25 +++++++++++++++++ .../settings/tabs/PreferencesSettingsTab.js | 28 +++++++++++++++++-- src/i18n/strings/en_EN.json | 1 + 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/BasePlatform.js b/src/BasePlatform.js index 79f0d69e2c..cac8c36267 100644 --- a/src/BasePlatform.js +++ b/src/BasePlatform.js @@ -113,4 +113,29 @@ export default class BasePlatform { reload() { throw new Error("reload not implemented!"); } + + supportsAutoLaunch() { + return false; + } + + // XXX: Surely this should be a setting like any other? + async getAutoLaunchEnabled() { + return false; + } + + async setAutoLaunchEnabled(enabled) { + throw new Error("Unimplemented"); + } + + supportsMinimizeToTray() { + return false; + } + + async getMinimizeToTrayEnabled() { + return false; + } + + async setMinimizeToTrayEnabled() { + throw new Error("Unimplemented"); + } } diff --git a/src/components/views/settings/tabs/PreferencesSettingsTab.js b/src/components/views/settings/tabs/PreferencesSettingsTab.js index d76dc8f3dd..b6273c5d47 100644 --- a/src/components/views/settings/tabs/PreferencesSettingsTab.js +++ b/src/components/views/settings/tabs/PreferencesSettingsTab.js @@ -59,24 +59,39 @@ export default class PreferencesSettingsTab extends React.Component { this.state = { autoLaunch: false, autoLaunchSupported: false, + minimizeToTray: true, + minimizeToTraySupported: false, }; } async componentWillMount(): void { - const autoLaunchSupported = await PlatformPeg.get().supportsAutoLaunch(); + const platform = PlatformPeg.get(); + + const autoLaunchSupported = await platform.supportsAutoLaunch(); let autoLaunch = false; if (autoLaunchSupported) { - autoLaunch = await PlatformPeg.get().getAutoLaunchEnabled(); + autoLaunch = await platform.getAutoLaunchEnabled(); } - this.setState({autoLaunch, autoLaunchSupported}); + const minimizeToTraySupported = await platform.supportsMinimizeToTray(); + let minimizeToTray = true; + + if (minimizeToTraySupported) { + minimizeToTray = await platform.getMinimizeToTrayEnabled(); + } + + this.setState({autoLaunch, autoLaunchSupported, minimizeToTraySupported, minimizeToTray}); } _onAutoLaunchChange = (checked) => { PlatformPeg.get().setAutoLaunchEnabled(checked).then(() => this.setState({autoLaunch: checked})); }; + _onMinimizeToTrayChange = (checked) => { + PlatformPeg.get().setMinimizeToTrayEnabled(checked).then(() => this.setState({minimizeToTray: checked})); + }; + _onAutocompleteDelayChange = (e) => { SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value); }; @@ -93,6 +108,12 @@ export default class PreferencesSettingsTab extends React.Component { onChange={this._onAutoLaunchChange} label={_t('Start automatically after system login')} />; } + let minimizeToTrayOption = null; + if (this.state.minimizeToTraySupported) { + minimizeToTrayOption = ; + } return (
        @@ -106,6 +127,7 @@ export default class PreferencesSettingsTab extends React.Component { {_t("Advanced")} {this._renderGroup(PreferencesSettingsTab.ADVANCED_SETTINGS)} + {minimizeToTrayOption} {autoLaunchOption} Date: Sun, 24 Feb 2019 01:20:49 +0000 Subject: [PATCH 116/178] delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/BasePlatform.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/BasePlatform.js b/src/BasePlatform.js index cac8c36267..c553c9fcd2 100644 --- a/src/BasePlatform.js +++ b/src/BasePlatform.js @@ -114,28 +114,28 @@ export default class BasePlatform { throw new Error("reload not implemented!"); } - supportsAutoLaunch() { + supportsAutoLaunch(): boolean { return false; } // XXX: Surely this should be a setting like any other? - async getAutoLaunchEnabled() { + async getAutoLaunchEnabled(): boolean { return false; } - async setAutoLaunchEnabled(enabled) { + async setAutoLaunchEnabled(enabled: boolean) { throw new Error("Unimplemented"); } - supportsMinimizeToTray() { + supportsMinimizeToTray(): boolean { return false; } - async getMinimizeToTrayEnabled() { + async getMinimizeToTrayEnabled(): boolean { return false; } - async setMinimizeToTrayEnabled() { + async setMinimizeToTrayEnabled(enabled: boolean) { throw new Error("Unimplemented"); } } From f16b9d76f4aa1d31e657125bda6feef48f7beb8e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 01:36:47 +0000 Subject: [PATCH 117/178] add roomnick SlashCommand Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/SlashCommands.js | 18 ++++++++++++++++++ src/i18n/strings/en_EN.json | 1 + 2 files changed, 19 insertions(+) diff --git a/src/SlashCommands.js b/src/SlashCommands.js index 115cf0e018..c558efffe9 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -110,6 +110,24 @@ export const CommandMap = { }, }), + roomnick: new Command({ + name: 'roomnick', + args: '', + description: _td('Changes your display nickname in the current room only'), + runFn: function(roomId, args) { + if (args) { + const cli = MatrixClientPeg.get(); + const ev = cli.getRoom(roomId).currentState.getStateEvents('m.room.member', cli.getUserId()); + const content = { + ...ev ? ev.getContent() : null, + displayname: args, + }; + return success(cli.sendStateEvent(roomId, 'm.room.member', content, cli.getUserId())); + } + return reject(this.getUsage()); + }, + }), + tint: new Command({ name: 'tint', args: ' []', diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 84c9dacd07..9a4b1ebed8 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -132,6 +132,7 @@ "To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.", "Upgrades a room to a new version": "Upgrades a room to a new version", "Changes your display nickname": "Changes your display nickname", + "Changes your display nickname in the current room only": "Changes your display nickname in the current room only", "Changes colour scheme of current room": "Changes colour scheme of current room", "Gets or sets the room topic": "Gets or sets the room topic", "This room has no topic.": "This room has no topic.", From 4472c66b968ca7a1c75db974799d4ce701e3c836 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 01:38:31 +0000 Subject: [PATCH 118/178] delint s'more Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/BasePlatform.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BasePlatform.js b/src/BasePlatform.js index c553c9fcd2..54310d1849 100644 --- a/src/BasePlatform.js +++ b/src/BasePlatform.js @@ -123,7 +123,7 @@ export default class BasePlatform { return false; } - async setAutoLaunchEnabled(enabled: boolean) { + async setAutoLaunchEnabled(enabled: boolean): void { throw new Error("Unimplemented"); } @@ -135,7 +135,7 @@ export default class BasePlatform { return false; } - async setMinimizeToTrayEnabled(enabled: boolean) { + async setMinimizeToTrayEnabled(enabled: boolean): void { throw new Error("Unimplemented"); } } From f2624beca4ff09ae91acfa46da9083d4ca501757 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 02:03:20 +0000 Subject: [PATCH 119/178] Change Share Message to Share Permalink if !m.room.message||redacted Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/context_menus/MessageContextMenu.js | 22 +++++++++++-------- src/i18n/strings/en_EN.json | 1 + 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index ffa2a0bf5c..17de6f462a 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -211,7 +211,8 @@ module.exports = React.createClass({ }, render: function() { - const eventStatus = this.props.mxEvent.status; + const mxEvent = this.props.mxEvent; + const eventStatus = mxEvent.status; let resendButton; let redactButton; let cancelButton; @@ -251,8 +252,8 @@ module.exports = React.createClass({ ); } - if (isSent && this.props.mxEvent.getType() === 'm.room.message') { - const content = this.props.mxEvent.getContent(); + if (isSent && mxEvent.getType() === 'm.room.message') { + const content = mxEvent.getContent(); if (content.msgtype && content.msgtype !== 'm.bad.encrypted' && content.hasOwnProperty('body')) { forwardButton = (
        @@ -282,7 +283,7 @@ module.exports = React.createClass({
        ); - if (this.props.mxEvent.getType() !== this.props.mxEvent.getWireType()) { + if (mxEvent.getType() !== mxEvent.getWireType()) { viewClearSourceButton = (
        { _t('View Decrypted Source') } @@ -303,8 +304,11 @@ module.exports = React.createClass({ // XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID) const permalinkButton = ( ); @@ -318,12 +322,12 @@ module.exports = React.createClass({ // Bridges can provide a 'external_url' to link back to the source. if ( - typeof(this.props.mxEvent.event.content.external_url) === "string" && - isUrlPermitted(this.props.mxEvent.event.content.external_url) + typeof(mxEvent.event.content.external_url) === "string" && + isUrlPermitted(mxEvent.event.content.external_url) ) { externalURLButton = ( ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 84c9dacd07..4c4679022d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1202,6 +1202,7 @@ "View Decrypted Source": "View Decrypted Source", "Unhide Preview": "Unhide Preview", "Share Message": "Share Message", + "Share Permalink": "Share Permalink", "Quote": "Quote", "Source URL": "Source URL", "Collapse Reply Thread": "Collapse Reply Thread", From c99b4bda325b0d46336ab07d3010da8e3e35d21f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 02:30:45 +0000 Subject: [PATCH 120/178] view user on click typing tile Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/WhoIsTypingTile.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/WhoIsTypingTile.js b/src/components/views/rooms/WhoIsTypingTile.js index dba40f033a..9dd690f6e5 100644 --- a/src/components/views/rooms/WhoIsTypingTile.js +++ b/src/components/views/rooms/WhoIsTypingTile.js @@ -170,6 +170,7 @@ module.exports = React.createClass({ width={24} height={24} resizeMethod="crop" + viewUserOnClick={true} /> ); }); From 393fd26a42ac885443ae2485cfd0cdb3f6559813 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 02:42:41 +0000 Subject: [PATCH 121/178] Settings button in Room Context Menu Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../context_menus/RoomTileContextMenu.js | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/components/views/context_menus/RoomTileContextMenu.js b/src/components/views/context_menus/RoomTileContextMenu.js index 521282488e..23c5411e2d 100644 --- a/src/components/views/context_menus/RoomTileContextMenu.js +++ b/src/components/views/context_menus/RoomTileContextMenu.js @@ -271,6 +271,28 @@ module.exports = React.createClass({ ); }, + _onClickSettings: function() { + dis.dispatch({ + action: 'view_room', + room_id: this.props.room.roomId, + }, true); + dis.dispatch({ action: 'open_room_settings' }); + if (this.props.onFinished) { + this.props.onFinished(); + } + }, + + _renderSettingsMenu: function() { + return ( +
        +
        + + { _t('Settings') } +
        +
        + ); + }, + _renderLeaveMenu: function(membership) { if (!membership) { return null; @@ -350,13 +372,17 @@ module.exports = React.createClass({ // Can't set notif level or tags on non-join rooms if (myMembership !== 'join') { - return this._renderLeaveMenu(myMembership); + return
        + { this._renderSettingsMenu() } + { this._renderLeaveMenu(myMembership) } +
        ; } return (
        { this._renderNotifMenu() }
        + { this._renderSettingsMenu() } { this._renderLeaveMenu(myMembership) }
        { this._renderRoomTagMenu() } From ac17e225567d8c122defa2f88365998dd9c2a2d7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 02:56:27 +0000 Subject: [PATCH 122/178] Toggle Search using Room Header button Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 8e32802d0a..46d634ba34 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1305,7 +1305,7 @@ module.exports = React.createClass({ }, onSearchClick: function() { - this.setState({ searching: true, showingPinned: false }); + this.setState({ searching: !this.state.searching, showingPinned: false }); }, onCancelSearchClick: function() { From e12e2c4a3d063785614fd5c351b7cfc227a42a3f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 02:57:13 +0000 Subject: [PATCH 123/178] tidy Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomView.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 46d634ba34..c93337eb6e 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1305,7 +1305,10 @@ module.exports = React.createClass({ }, onSearchClick: function() { - this.setState({ searching: !this.state.searching, showingPinned: false }); + this.setState({ + searching: !this.state.searching, + showingPinned: false, + }); }, onCancelSearchClick: function() { From fa5f1df194b8563d458bd1e9ebeb598b85316249 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 03:18:21 +0000 Subject: [PATCH 124/178] Fix z ordering of the overflow tile Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/rooms/_WhoIsTypingTile.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/rooms/_WhoIsTypingTile.scss b/res/css/views/rooms/_WhoIsTypingTile.scss index eb51595858..ef20c24c84 100644 --- a/res/css/views/rooms/_WhoIsTypingTile.scss +++ b/res/css/views/rooms/_WhoIsTypingTile.scss @@ -40,6 +40,7 @@ limitations under the License. } .mx_WhoIsTypingTile_remainingAvatarPlaceholder { + position: relative; display: inline-block; color: #acacac; background-color: #ddd; From e6bf9fe9bf917e3224aa5e3eb0f41646b8ff4b4e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 03:25:02 +0000 Subject: [PATCH 125/178] Fix share community for guests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/GroupView.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 89fce9c718..b80f49d051 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -34,6 +34,7 @@ import GroupStore from '../../stores/GroupStore'; import FlairStore from '../../stores/FlairStore'; import { showGroupAddRoomDialog } from '../../GroupAddressPicker'; import {makeGroupPermalink, makeUserPermalink} from "../../matrix-to"; +import {Group} from "matrix-js-sdk"; const LONG_DESC_PLACEHOLDER = _td( `

        HTML for your community's page

        @@ -569,7 +570,7 @@ export default React.createClass({ _onShareClick: function() { const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); Modal.createTrackedDialog('share community dialog', '', ShareDialog, { - target: this._matrixClient.getGroup(this.props.groupId), + target: this._matrixClient.getGroup(this.props.groupId) || new Group(this.props.groupId), }); }, From 7b88d5d21c82a339249362733a21115fac5d98bb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 03:43:44 +0000 Subject: [PATCH 126/178] make ViewSource less awkward Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/_common.scss | 6 --- res/css/structures/_ViewSource.scss | 13 ++++++ src/components/structures/ViewSource.js | 40 ++++++++----------- .../views/context_menus/MessageContextMenu.js | 4 ++ 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index fd93c8c967..4e327ab28d 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -249,12 +249,6 @@ textarea { box-shadow: none; } -/* View Source Dialog overide */ -.mx_Dialog_wrapper.mx_Dialog_viewsource .mx_Dialog { - padding-left: 10px; - padding-right: 10px; -} - .mx_Dialog { background-color: $primary-bg-color; color: $light-fg-color; diff --git a/res/css/structures/_ViewSource.scss b/res/css/structures/_ViewSource.scss index a4c7dcf58a..b908861c6f 100644 --- a/res/css/structures/_ViewSource.scss +++ b/res/css/structures/_ViewSource.scss @@ -14,6 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_ViewSource_label_left { + float: left; +} + +.mx_ViewSource_label_right { + float: right; +} + +.mx_ViewSource_label_bottom { + clear: both; + border-bottom: 1px solid #e5e5e5; +} + .mx_ViewSource pre { text-align: left; font-size: 12px; diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js index 4844149f59..fd35fdbeef 100644 --- a/src/components/structures/ViewSource.js +++ b/src/components/structures/ViewSource.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,11 +15,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; - import React from 'react'; import PropTypes from 'prop-types'; import SyntaxHighlight from '../views/elements/SyntaxHighlight'; +import {_t} from "../../languageHandler"; +import sdk from "../../index"; module.exports = React.createClass({ @@ -27,31 +28,24 @@ module.exports = React.createClass({ propTypes: { content: PropTypes.object.isRequired, onFinished: PropTypes.func.isRequired, - }, - - componentDidMount: function() { - document.addEventListener("keydown", this.onKeyDown); - }, - - componentWillUnmount: function() { - document.removeEventListener("keydown", this.onKeyDown); - }, - - onKeyDown: function(ev) { - if (ev.keyCode == 27) { // escape - ev.stopPropagation(); - ev.preventDefault(); - this.props.onFinished(); - } + roomId: PropTypes.string.isRequired, + eventId: PropTypes.string.isRequired, }, render: function() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return ( -
        - - { JSON.stringify(this.props.content, null, 2) } - -
        + +
        Room ID: { this.props.roomId }
        +
        Event ID: { this.props.eventId }
        +
        + +
        + + { JSON.stringify(this.props.content, null, 2) } + +
        + ); }, }); diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index ffa2a0bf5c..e948c1ad93 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -98,6 +98,8 @@ module.exports = React.createClass({ onViewSourceClick: function() { const ViewSource = sdk.getComponent('structures.ViewSource'); Modal.createTrackedDialog('View Event Source', '', ViewSource, { + roomId: this.props.mxEvent.getRoomId(), + eventId: this.props.mxEvent.getId(), content: this.props.mxEvent.event, }, 'mx_Dialog_viewsource'); this.closeMenu(); @@ -106,6 +108,8 @@ module.exports = React.createClass({ onViewClearSourceClick: function() { const ViewSource = sdk.getComponent('structures.ViewSource'); Modal.createTrackedDialog('View Clear Event Source', '', ViewSource, { + roomId: this.props.mxEvent.getRoomId(), + eventId: this.props.mxEvent.getId(), // FIXME: _clearEvent is private content: this.props.mxEvent._clearEvent, }, 'mx_Dialog_viewsource'); From dbf540074d61008607c26ccc40ca30d4c3253f2b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 24 Feb 2019 04:28:42 +0000 Subject: [PATCH 127/178] replace text Inputs in Devtools with Field bcuz prettier Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/dialogs/DevtoolsDialog.js | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.js index ea198461c5..ff118792fb 100644 --- a/src/components/views/dialogs/DevtoolsDialog.js +++ b/src/components/views/dialogs/DevtoolsDialog.js @@ -20,6 +20,7 @@ import sdk from '../../../index'; import SyntaxHighlight from '../elements/SyntaxHighlight'; import { _t } from '../../../languageHandler'; import MatrixClientPeg from '../../../MatrixClientPeg'; +import Field from "../elements/Field"; class DevtoolsComponent extends React.Component { static contextTypes = { @@ -56,14 +57,8 @@ class GenericEditor extends DevtoolsComponent { } textInput(id, label) { - return
        -
        - -
        -
        - -
        -
        ; + return ; } } @@ -138,12 +133,9 @@ class SendCustomEvent extends GenericEditor {
        -
        - -
        -
        -