diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aac4e2974..151888a17e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,86 @@ +Changes in [3.10.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.10.0) (2020-12-07) +===================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.10.0-rc.1...v3.10.0) + + * Upgrade to JS SDK 9.3.0 + +Changes in [3.10.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.10.0-rc.1) (2020-12-02) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.9.0...v3.10.0-rc.1) + + * Upgrade to JS SDK 9.3.0-rc.1 + * Translations update from Weblate + [\#5461](https://github.com/matrix-org/matrix-react-sdk/pull/5461) + * Fix VoIP call plinth on dark theme + [\#5460](https://github.com/matrix-org/matrix-react-sdk/pull/5460) + * Add sanity checking around widget pinning + [\#5459](https://github.com/matrix-org/matrix-react-sdk/pull/5459) + * Update i18n for Appearance User Settings + [\#5457](https://github.com/matrix-org/matrix-react-sdk/pull/5457) + * Only show 'answered elsewhere' if we tried to answer too + [\#5455](https://github.com/matrix-org/matrix-react-sdk/pull/5455) + * Fixed Avatar for 3PID invites + [\#5442](https://github.com/matrix-org/matrix-react-sdk/pull/5442) + * Slightly better error if we can't capture user media + [\#5449](https://github.com/matrix-org/matrix-react-sdk/pull/5449) + * Make it possible in-code to hide rooms from the room list + [\#5445](https://github.com/matrix-org/matrix-react-sdk/pull/5445) + * Fix the stickerpicker + [\#5447](https://github.com/matrix-org/matrix-react-sdk/pull/5447) + * Add live password validation to change password dialog + [\#5436](https://github.com/matrix-org/matrix-react-sdk/pull/5436) + * LaTeX rendering in element-web using KaTeX + [\#5244](https://github.com/matrix-org/matrix-react-sdk/pull/5244) + * Add lifecycle customisation point after logout + [\#5448](https://github.com/matrix-org/matrix-react-sdk/pull/5448) + * Simplify UserMenu for Guests as they can't use most of the options + [\#5421](https://github.com/matrix-org/matrix-react-sdk/pull/5421) + * Fix known issues with modal widgets + [\#5444](https://github.com/matrix-org/matrix-react-sdk/pull/5444) + * Fix existing widgets not having approved capabilities for their function + [\#5443](https://github.com/matrix-org/matrix-react-sdk/pull/5443) + * Use the WidgetDriver to run OIDC requests + [\#5440](https://github.com/matrix-org/matrix-react-sdk/pull/5440) + * Add a customisation point for widget permissions and fix amnesia issues + [\#5439](https://github.com/matrix-org/matrix-react-sdk/pull/5439) + * Fix Widget event notification text including spurious space + [\#5441](https://github.com/matrix-org/matrix-react-sdk/pull/5441) + * Move call listener out of MatrixChat + [\#5438](https://github.com/matrix-org/matrix-react-sdk/pull/5438) + * New Look in-Call View + [\#5432](https://github.com/matrix-org/matrix-react-sdk/pull/5432) + * Support arbitrary widgets sticking to the screen + sending stickers + [\#5435](https://github.com/matrix-org/matrix-react-sdk/pull/5435) + * Auth typescripting and validation tweaks + [\#5433](https://github.com/matrix-org/matrix-react-sdk/pull/5433) + * Add new widget API actions for changing rooms and sending/receiving events + [\#5385](https://github.com/matrix-org/matrix-react-sdk/pull/5385) + * Revert room header click behaviour to opening room settings + [\#5434](https://github.com/matrix-org/matrix-react-sdk/pull/5434) + * Add option to send/edit a message with Ctrl + Enter / Command + Enter + [\#5160](https://github.com/matrix-org/matrix-react-sdk/pull/5160) + * Add Analytics instrumentation to the Homepage + [\#5409](https://github.com/matrix-org/matrix-react-sdk/pull/5409) + * Fix encrypted video playback in Chrome-based browsers + [\#5430](https://github.com/matrix-org/matrix-react-sdk/pull/5430) + * Add border-radius for video + [\#5333](https://github.com/matrix-org/matrix-react-sdk/pull/5333) + * Push name to the end, near text, in IRC layout + [\#5166](https://github.com/matrix-org/matrix-react-sdk/pull/5166) + * Disable notifications for the room you have recently been active in + [\#5325](https://github.com/matrix-org/matrix-react-sdk/pull/5325) + * Search through the list of unfiltered rooms rather than the rooms in the + state which are already filtered by the search text + [\#5331](https://github.com/matrix-org/matrix-react-sdk/pull/5331) + * Lighten blockquote colour in dark mode + [\#5353](https://github.com/matrix-org/matrix-react-sdk/pull/5353) + * Specify community description img must be mxc urls + [\#5364](https://github.com/matrix-org/matrix-react-sdk/pull/5364) + * Add keyboard shortcut to close the current conversation + [\#5253](https://github.com/matrix-org/matrix-react-sdk/pull/5253) + * Redirect user home from auth screens if they are already logged in + [\#5423](https://github.com/matrix-org/matrix-react-sdk/pull/5423) + Changes in [3.9.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.9.0) (2020-11-23) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.9.0-rc.1...v3.9.0) diff --git a/package.json b/package.json index 1e778f9875..b328823b24 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.9.0", + "version": "3.10.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -58,6 +58,7 @@ "blueimp-canvas-to-blob": "^3.27.0", "browser-encrypt-attachment": "^0.3.0", "browser-request": "^0.3.3", + "cheerio": "^1.0.0-rc.3", "classnames": "^2.2.6", "commonmark": "^0.29.1", "counterpart": "^0.18.6", @@ -77,7 +78,6 @@ "html-entities": "^1.3.1", "is-ip": "^2.0.0", "katex": "^0.12.0", - "cheerio": "^1.0.0-rc.3", "linkifyjs": "^2.1.9", "lodash": "^4.17.19", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", @@ -159,6 +159,7 @@ "lolex": "^5.1.2", "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.2", + "olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz", "react-test-renderer": "^16.13.1", "rimraf": "^2.7.1", "stylelint": "^9.10.1", diff --git a/res/css/_common.scss b/res/css/_common.scss index 7ab88d6f02..87336a1c03 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -170,7 +170,6 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { border: 1px solid rgba($primary-fg-color, .1); // these things should probably not be defined globally margin: 9px; - flex: 0 0 auto; } .mx_textinput { diff --git a/res/css/structures/auth/_Login.scss b/res/css/structures/auth/_Login.scss index a8cb7d7eee..9c98ca3a1c 100644 --- a/res/css/structures/auth/_Login.scss +++ b/res/css/structures/auth/_Login.scss @@ -81,6 +81,7 @@ limitations under the License. } .mx_Login_underlinedServerName { + width: max-content; border-bottom: 1px dashed $accent-color; } diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss index f00907aeef..9753d3afb5 100644 --- a/res/css/views/rooms/_MemberList.scss +++ b/res/css/views/rooms/_MemberList.scss @@ -46,6 +46,11 @@ limitations under the License. } } +.mx_GroupMemberList_query, +.mx_GroupRoomList_query { + flex: 0 0 auto; +} + .mx_MemberList_chevron { position: absolute; right: 35px; @@ -59,10 +64,8 @@ limitations under the License. flex: 1 1 0px; } -.mx_MemberList_query, -.mx_GroupMemberList_query, -.mx_GroupRoomList_query { - flex: 1 1 0; +.mx_MemberList_query { + height: 16px; // stricter rule to override the one in _common.scss &[type="text"] { @@ -70,10 +73,6 @@ limitations under the License. } } -.mx_MemberList_query { - height: 16px; -} - .mx_MemberList_wrapper { padding: 10px; } diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 5447f32f5d..236880a531 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -20,6 +20,7 @@ limitations under the License. background-color: $voipcall-plinth-color; padding-left: 8px; padding-right: 8px; + margin: 5px 5px 5px 18px; // XXX: CallContainer sets pointer-events: none - should probably be set back in a better place pointer-events: initial; } @@ -135,9 +136,9 @@ limitations under the License. padding-top: 20px; padding-bottom: 15px; color: $accent-fg-color; - font-weight: bold; .mx_AccessibleButton_hasKind { padding: 0px; + font-weight: bold; } } @@ -219,6 +220,7 @@ limitations under the License. } .mx_CallView_header_callType { + font-size: 1.2rem; font-weight: bold; vertical-align: middle; } diff --git a/scripts/ci/riot-unit-tests.sh b/scripts/ci/riot-unit-tests.sh deleted file mode 120000 index 199dfb58fd..0000000000 --- a/scripts/ci/riot-unit-tests.sh +++ /dev/null @@ -1 +0,0 @@ -app-tests.sh \ No newline at end of file diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index f443e59090..41dc031b06 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -416,14 +416,14 @@ export default class CallHandler { title = _t("Unable to access microphone"); description =
{_t( - "Call failed because no microphone could not be accessed. " + + "Call failed because microphone could not be accessed. " + "Check that a microphone is plugged in and set up correctly.", )}
; } else if (call.type === CallType.Video) { title = _t("Unable to access webcam / microphone"); description =
- {_t("Call failed because no webcam or microphone could not be accessed. Check that:")} + {_t("Call failed because webcam or microphone could not be accessed. Check that:")}
; - const memberCount = useMemberCount(room); + const memberCount = useRoomMemberCount(room); return diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx index be4ecaffb3..9be3d6be18 100644 --- a/src/components/views/rooms/NewRoomIntro.tsx +++ b/src/components/views/rooms/NewRoomIntro.tsx @@ -60,8 +60,9 @@ const NewRoomIntro = () => { { caption &&

{ caption }

} ; } else { + const inRoom = room && room.getMyMembership() === "join"; const topic = room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic; - const canAddTopic = room.currentState.maySendStateEvent(EventType.RoomTopic, cli.getUserId()); + const canAddTopic = inRoom && room.currentState.maySendStateEvent(EventType.RoomTopic, cli.getUserId()); const onTopicClick = () => { dis.dispatch({ @@ -99,9 +100,25 @@ const NewRoomIntro = () => { }); } - const onInviteClick = () => { - dis.dispatch({ action: "view_invite", roomId }); - }; + let canInvite = inRoom; + const powerLevels = room.currentState.getStateEvents(EventType.RoomPowerLevels, "")?.getContent(); + const me = room.getMember(cli.getUserId()); + if (powerLevels && me && powerLevels.invite > me.powerLevel) { + canInvite = false; + } + + let buttons; + if (canInvite) { + const onInviteClick = () => { + dis.dispatch({ action: "view_invite", roomId }); + }; + + buttons =
+ + {_t("Invite to this room")} + +
+ } const avatarUrl = room.currentState.getStateEvents(EventType.RoomAvatar, "")?.getContent()?.url; body = @@ -119,11 +136,7 @@ const NewRoomIntro = () => { roomName: () => { room.name }, })}

{topicText}

-
- - {_t("Invite to this room")} - -
+ { buttons }
; } diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index e88060304a..8171da7eca 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -42,6 +42,8 @@ import {Key, isOnlyCtrlOrCmdKeyEvent} from "../../../Keyboard"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import RateLimitedFunc from '../../../ratelimitedfunc'; import {Action} from "../../../dispatcher/actions"; +import {containsEmoji} from "../../../effects/utils"; +import {CHAT_EFFECTS} from '../../../effects'; import SettingsStore from "../../../settings/SettingsStore"; import CountlyAnalytics from "../../../CountlyAnalytics"; @@ -326,6 +328,11 @@ export default class SendMessageComposer extends React.Component { }); } dis.dispatch({action: "message_sent"}); + CHAT_EFFECTS.forEach((effect) => { + if (containsEmoji(content, effect.emojis)) { + dis.dispatch({action: `effects.${effect.command}`}); + } + }); CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, !!replyToEvent, content); } diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index d6a4921f1a..4d8493401e 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -50,6 +50,7 @@ export default class PreferencesUserSettingsTab extends React.Component { 'showAvatarChanges', 'showDisplaynameChanges', 'showImages', + 'showChatEffects', 'Pill.shouldShowPillAvatar', ]; diff --git a/src/effects/ICanvasEffect.ts b/src/effects/ICanvasEffect.ts new file mode 100644 index 0000000000..9bf3e9293d --- /dev/null +++ b/src/effects/ICanvasEffect.ts @@ -0,0 +1,47 @@ +/* + Copyright 2020 Nurjin Jafar + Copyright 2020 Nordeck IT + Consulting GmbH. + + 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. + */ +/** + * Defines the constructor of a canvas based room effect + */ +export interface ICanvasEffectConstructable { + /** + * @param {{[key:string]:any}} options? Optional animation options + * @returns ICanvasEffect Returns a new instance of the canvas effect + */ + new(options?: { [key: string]: any }): ICanvasEffect; +} + +/** + * Defines the interface of a canvas based room effect + */ +export default interface ICanvasEffect { + /** + * @param {HTMLCanvasElement} canvas The canvas instance as the render target of the animation + * @param {number} timeout? A timeout that defines the runtime of the animation (defaults to false) + */ + start: (canvas: HTMLCanvasElement, timeout?: number) => Promise; + + /** + * Stops the current animation + */ + stop: () => Promise; + + /** + * Returns a value that defines if the animation is currently running + */ + isRunning: boolean; +} diff --git a/src/effects/confetti/index.ts b/src/effects/confetti/index.ts new file mode 100644 index 0000000000..8c4b2d2616 --- /dev/null +++ b/src/effects/confetti/index.ts @@ -0,0 +1,191 @@ +/* + Copyright 2020 Nurjin Jafar + Copyright 2020 Nordeck IT + Consulting GmbH. + + 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 ICanvasEffect from '../ICanvasEffect'; + +export type ConfettiOptions = { + /** + * max confetti count + */ + maxCount: number, + /** + * particle animation speed + */ + speed: number, + /** + * the confetti animation frame interval in milliseconds + */ + frameInterval: number, + /** + * the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) + */ + alpha: number, + /** + * use gradient instead of solid particle color + */ + gradient: boolean, +} + +type ConfettiParticle = { + color: string, + color2: string, + x: number, + y: number, + diameter: number, + tilt: number, + tiltAngleIncrement: number, + tiltAngle: number, +} + +export const DefaultOptions: ConfettiOptions = { + maxCount: 150, + speed: 3, + frameInterval: 15, + alpha: 1.0, + gradient: false, +}; + +export default class Confetti implements ICanvasEffect { + private readonly options: ConfettiOptions; + + constructor(options: { [key: string]: any }) { + this.options = {...DefaultOptions, ...options}; + } + + private context: CanvasRenderingContext2D | null = null; + private supportsAnimationFrame = window.requestAnimationFrame; + private colors = ['rgba(30,144,255,', 'rgba(107,142,35,', 'rgba(255,215,0,', + 'rgba(255,192,203,', 'rgba(106,90,205,', 'rgba(173,216,230,', + 'rgba(238,130,238,', 'rgba(152,251,152,', 'rgba(70,130,180,', + 'rgba(244,164,96,', 'rgba(210,105,30,', 'rgba(220,20,60,']; + + private lastFrameTime = Date.now(); + private particles: Array = []; + private waveAngle = 0; + + public isRunning: boolean; + + public start = async (canvas: HTMLCanvasElement, timeout = 3000) => { + if (!canvas) { + return; + } + this.context = canvas.getContext('2d'); + this.particles = []; + const count = this.options.maxCount; + while (this.particles.length < count) { + this.particles.push(this.resetParticle({} as ConfettiParticle, canvas.width, canvas.height)); + } + this.isRunning = true; + this.runAnimation(); + if (timeout) { + window.setTimeout(this.stop, timeout); + } + } + + public stop = async () => { + this.isRunning = false; + } + + private resetParticle = (particle: ConfettiParticle, width: number, height: number): ConfettiParticle => { + particle.color = this.colors[(Math.random() * this.colors.length) | 0] + (this.options.alpha + ')'); + if (this.options.gradient) { + particle.color2 = this.colors[(Math.random() * this.colors.length) | 0] + (this.options.alpha + ')'); + } else { + particle.color2 = particle.color; + } + particle.x = Math.random() * width; + particle.y = Math.random() * -height; + particle.diameter = Math.random() * 10 + 5; + particle.tilt = Math.random() * -10; + particle.tiltAngleIncrement = Math.random() * 0.07 + 0.05; + particle.tiltAngle = Math.random() * Math.PI; + return particle; + } + + private runAnimation = (): void => { + if (!this.context || !this.context.canvas) { + return; + } + if (this.particles.length === 0) { + this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height); + } else { + const now = Date.now(); + const delta = now - this.lastFrameTime; + if (!this.supportsAnimationFrame || delta > this.options.frameInterval) { + this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height); + this.updateParticles(); + this.drawParticles(this.context); + this.lastFrameTime = now - (delta % this.options.frameInterval); + } + requestAnimationFrame(this.runAnimation); + } + } + + + private drawParticles = (context: CanvasRenderingContext2D): void => { + if (!this.context || !this.context.canvas) { + return; + } + let x; let x2; let y2; + for (const particle of this.particles) { + this.context.beginPath(); + context.lineWidth = particle.diameter; + x2 = particle.x + particle.tilt; + x = x2 + particle.diameter / 2; + y2 = particle.y + particle.tilt + particle.diameter / 2; + if (this.options.gradient) { + const gradient = context.createLinearGradient(x, particle.y, x2, y2); + gradient.addColorStop(0, particle.color); + gradient.addColorStop(1.0, particle.color2); + context.strokeStyle = gradient; + } else { + context.strokeStyle = particle.color; + } + context.moveTo(x, particle.y); + context.lineTo(x2, y2); + context.stroke(); + } + } + + private updateParticles = () => { + if (!this.context || !this.context.canvas) { + return; + } + const width = this.context.canvas.width; + const height = this.context.canvas.height; + let particle: ConfettiParticle; + this.waveAngle += 0.01; + for (let i = 0; i < this.particles.length; i++) { + particle = this.particles[i]; + if (!this.isRunning && particle.y < -15) { + particle.y = height + 100; + } else { + particle.tiltAngle += particle.tiltAngleIncrement; + particle.x += Math.sin(this.waveAngle) - 0.5; + particle.y += (Math.cos(this.waveAngle) + particle.diameter + this.options.speed) * 0.5; + particle.tilt = Math.sin(particle.tiltAngle) * 15; + } + if (particle.x > width + 20 || particle.x < -20 || particle.y > height) { + if (this.isRunning && this.particles.length <= this.options.maxCount) { + this.resetParticle(particle, width, height); + } else { + this.particles.splice(i, 1); + i--; + } + } + } + } +} diff --git a/src/effects/index.ts b/src/effects/index.ts new file mode 100644 index 0000000000..16a0851070 --- /dev/null +++ b/src/effects/index.ts @@ -0,0 +1,89 @@ +/* + Copyright 2020 Nurjin Jafar + Copyright 2020 Nordeck IT + Consulting GmbH. + + 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 { _t, _td } from "../languageHandler"; + +export type Effect = { + /** + * one or more emojis that will trigger this effect + */ + emojis: Array; + /** + * the matrix message type that will trigger this effect + */ + msgType: string; + /** + * the room command to trigger this effect + */ + command: string; + /** + * a function that returns the translated description of the effect + */ + description: () => string; + /** + * a function that returns the translated fallback message. this message will be shown if the user did not provide a custom message + */ + fallbackMessage: () => string; + /** + * animation options + */ + options: TOptions; +} + +type ConfettiOptions = { + /** + * max confetti count + */ + maxCount: number, + /** + * particle animation speed + */ + speed: number, + /** + * the confetti animation frame interval in milliseconds + */ + frameInterval: number, + /** + * the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) + */ + alpha: number, + /** + * use gradient instead of solid particle color + */ + gradient: boolean, +} + +/** + * This configuration defines room effects that can be triggered by custom message types and emojis + */ +export const CHAT_EFFECTS: Array> = [ + { + emojis: ['🎊', '🎉'], + msgType: 'nic.custom.confetti', + command: 'confetti', + description: () => _td("Sends the given message with confetti"), + fallbackMessage: () => _t("sends confetti") + " 🎉", + options: { + maxCount: 150, + speed: 3, + frameInterval: 15, + alpha: 1.0, + gradient: false, + }, + } as Effect, +]; + + diff --git a/src/effects/utils.ts b/src/effects/utils.ts new file mode 100644 index 0000000000..c2b499b154 --- /dev/null +++ b/src/effects/utils.ts @@ -0,0 +1,24 @@ +/* + Copyright 2020 Nurjin Jafar + Copyright 2020 Nordeck IT + Consulting GmbH. + + 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. + */ +/** + * Checks a message if it contains one of the provided emojis + * @param {Object} content The message + * @param {Array} emojis The list of emojis to check for + */ +export const containsEmoji = (content: { msgtype: string, body: string }, emojis: Array): boolean => { + return emojis.some((emoji) => content.body && content.body.includes(emoji)); +} diff --git a/src/hooks/useRoomMembers.ts b/src/hooks/useRoomMembers.ts new file mode 100644 index 0000000000..e25436045e --- /dev/null +++ b/src/hooks/useRoomMembers.ts @@ -0,0 +1,40 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {useState} from "react"; +import {Room} from "matrix-js-sdk/src/models/room"; +import {RoomMember} from "matrix-js-sdk/src/models/room-member"; + +import {useEventEmitter} from "./useEventEmitter"; +import {throttle} from "lodash"; + +// Hook to simplify watching Matrix Room joined members +export const useRoomMembers = (room: Room, throttleWait = 250) => { + const [members, setMembers] = useState(room.getJoinedMembers()); + useEventEmitter(room.currentState, "RoomState.members", throttle(() => { + setMembers(room.getJoinedMembers()); + }, throttleWait, {leading: true, trailing: true})); + return members; +}; + +// Hook to simplify watching Matrix Room joined member count +export const useRoomMemberCount = (room: Room, throttleWait = 250) => { + const [count, setCount] = useState(room.getJoinedMemberCount()); + useEventEmitter(room.currentState, "RoomState.members", throttle(() => { + setCount(room.getJoinedMemberCount()); + }, throttleWait, {leading: true, trailing: true})); + return count; +}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index fd01ac63a9..96f8917d60 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -47,9 +47,9 @@ "Try using turn.matrix.org": "Try using turn.matrix.org", "OK": "OK", "Unable to access microphone": "Unable to access microphone", - "Call failed because no microphone could not be accessed. Check that a microphone is plugged in and set up correctly.": "Call failed because no microphone could not be accessed. Check that a microphone is plugged in and set up correctly.", + "Call failed because microphone could not be accessed. Check that a microphone is plugged in and set up correctly.": "Call failed because microphone could not be accessed. Check that a microphone is plugged in and set up correctly.", "Unable to access webcam / microphone": "Unable to access webcam / microphone", - "Call failed because no webcam or microphone could not be accessed. Check that:": "Call failed because no webcam or microphone could not be accessed. Check that:", + "Call failed because webcam or microphone could not be accessed. Check that:": "Call failed because webcam or microphone could not be accessed. Check that:", "A microphone and webcam are plugged in and set up correctly": "A microphone and webcam are plugged in and set up correctly", "Permission is granted to use the webcam": "Permission is granted to use the webcam", "No other application is using the webcam": "No other application is using the webcam", @@ -406,6 +406,7 @@ "Messages": "Messages", "Actions": "Actions", "Advanced": "Advanced", + "Effects": "Effects", "Other": "Other", "Command error": "Command error", "Usage": "Usage", @@ -826,6 +827,7 @@ "Manually verify all remote sessions": "Manually verify all remote sessions", "IRC display name width": "IRC display name width", "Enable experimental, compact IRC style layout": "Enable experimental, compact IRC style layout", + "Show chat effects": "Show chat effects", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", "Uploading logs": "Uploading logs", @@ -844,6 +846,8 @@ "When rooms are upgraded": "When rooms are upgraded", "My Ban List": "My Ban List", "This is your list of users/servers you have blocked - don't leave the room!": "This is your list of users/servers you have blocked - don't leave the room!", + "Sends the given message with confetti": "Sends the given message with confetti", + "sends confetti": "sends confetti", "You held the call Resume": "You held the call Resume", "%(peerName)s held the call": "%(peerName)s held the call", "Video Call": "Video Call", @@ -1965,6 +1969,7 @@ "Removing…": "Removing…", "Confirm Removal": "Confirm Removal", "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.", + "Reason (optional)": "Reason (optional)", "Clear all data in this session?": "Clear all data in this session?", "Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.", "Clear all data": "Clear all data", @@ -2158,11 +2163,12 @@ "A connection error occurred while trying to contact the server.": "A connection error occurred while trying to contact the server.", "The server is not configured to indicate what the problem is (CORS).": "The server is not configured to indicate what the problem is (CORS).", "Recent changes that have not yet been received": "Recent changes that have not yet been received", - "Unable to validate homeserver/identity server": "Unable to validate homeserver/identity server", + "Unable to validate homeserver": "Unable to validate homeserver", + "Invalid URL": "Invalid URL", "Specify a homeserver": "Specify a homeserver", "Matrix.org is the biggest public homeserver in the world, so it’s a good place for many.": "Matrix.org is the biggest public homeserver in the world, so it’s a good place for many.", "Sign into your homeserver": "Sign into your homeserver", - "We call the places you where you can host your account ‘homeservers’.": "We call the places you where you can host your account ‘homeservers’.", + "We call the places where you can host your account ‘homeservers’.": "We call the places where you can host your account ‘homeservers’.", "Other homeserver": "Other homeserver", "Use your preferred Matrix homeserver if you have one, or host your own.": "Use your preferred Matrix homeserver if you have one, or host your own.", "Learn more": "Learn more", diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 31e133be72..6bec31a1cb 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -634,6 +634,11 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td("Enable experimental, compact IRC style layout"), default: false, }, + "showChatEffects": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td("Show chat effects"), + default: true, + }, "Widgets.pinned": { supportedLevels: LEVELS_ROOM_OR_ACCOUNT, default: {}, diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 7c05e4b500..82f169f498 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -42,7 +42,7 @@ for (const key of Object.keys(SETTINGS)) { if (SETTINGS[key].invertedSettingName) { // Invert now so that the rest of the system will invert it back // to what was intended. - invertedDefaultSettings[key] = !SETTINGS[key].default; + invertedDefaultSettings[SETTINGS[key].invertedSettingName] = !SETTINGS[key].default; } } diff --git a/src/utils/AutoDiscoveryUtils.js b/src/utils/AutoDiscoveryUtils.js index 94aa0c3544..18b6451d3e 100644 --- a/src/utils/AutoDiscoveryUtils.js +++ b/src/utils/AutoDiscoveryUtils.js @@ -34,6 +34,8 @@ export class ValidatedServerConfig { isUrl: string; isDefault: boolean; + // when the server config is based on static URLs the hsName is not resolvable and things may wish to use hsUrl + isNameResolvable: boolean; warning: string; } @@ -161,7 +163,7 @@ export default class AutoDiscoveryUtils { const url = new URL(homeserverUrl); const serverName = url.hostname; - return AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, result, syntaxOnly); + return AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, result, syntaxOnly, true); } /** @@ -179,12 +181,12 @@ export default class AutoDiscoveryUtils { * input. * @param {string} serverName The domain name the AutoDiscovery result is for. * @param {*} discoveryResult The AutoDiscovery result. - * @param {boolean} syntaxOnly If true, errors relating to liveliness of the servers will - * not be raised. + * @param {boolean} syntaxOnly If true, errors relating to liveliness of the servers will not be raised. + * @param {boolean} isSynthetic If true, then the discoveryResult was synthesised locally. * @returns {Promise} Resolves to the validated configuration. */ static buildValidatedConfigFromDiscovery( - serverName: string, discoveryResult, syntaxOnly=false): ValidatedServerConfig { + serverName: string, discoveryResult, syntaxOnly=false, isSynthetic=false): ValidatedServerConfig { if (!discoveryResult || !discoveryResult["m.homeserver"]) { // This shouldn't happen without major misconfiguration, so we'll log a bit of information // in the log so we can find this bit of codee but otherwise tell teh user "it broke". @@ -252,6 +254,7 @@ export default class AutoDiscoveryUtils { isUrl: preferredIdentityUrl, isDefault: false, warning: hsResult.error, + isNameResolvable: !isSynthetic, }); } } diff --git a/src/utils/permalinks/Permalinks.js b/src/utils/permalinks/Permalinks.js index 0f54bcce05..39c5776852 100644 --- a/src/utils/permalinks/Permalinks.js +++ b/src/utils/permalinks/Permalinks.js @@ -129,6 +129,17 @@ export class RoomPermalinkCreator { return getPermalinkConstructor().forEvent(this._roomId, eventId, this._serverCandidates); } + forShareableRoom() { + if (this._room) { + // Prefer to use canonical alias for permalink if possible + const alias = this._room.getCanonicalAlias(); + if (alias) { + return getPermalinkConstructor().forRoom(alias, this._serverCandidates); + } + } + return getPermalinkConstructor().forRoom(this._roomId, this._serverCandidates); + } + forRoom() { return getPermalinkConstructor().forRoom(this._roomId, this._serverCandidates); } diff --git a/test/utils/permalinks/Permalinks-test.js b/test/utils/permalinks/Permalinks-test.js index 72cd66cb69..0bd4466d97 100644 --- a/test/utils/permalinks/Permalinks-test.js +++ b/test/utils/permalinks/Permalinks-test.js @@ -34,6 +34,7 @@ function mockRoom(roomId, members, serverACL) { return { roomId, + getCanonicalAlias: () => roomId, getJoinedMembers: () => members, getMember: (userId) => members.find(m => m.userId === userId), currentState: { diff --git a/yarn.lock b/yarn.lock index c06494d319..1a6a0b0fb3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1256,10 +1256,10 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.11.2": - version "7.11.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736" - integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw== +"@babel/runtime@^7.12.5": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" + integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== dependencies: regenerator-runtime "^0.13.4" @@ -4842,9 +4842,9 @@ has@^1.0.1, has@^1.0.3: function-bind "^1.1.1" highlight.js@^10.1.2: - version "10.1.2" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.1.2.tgz#c20db951ba1c22c055010648dfffd7b2a968e00c" - integrity sha512-Q39v/Mn5mfBlMff9r+zzA+gWxRsCRKwEMvYTiisLr/XUiFI/4puWt0Ojdko3R3JCNWGdOWaA5g/Yxqa23kC5AA== + version "10.4.1" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.4.1.tgz#d48fbcf4a9971c4361b3f95f302747afe19dbad0" + integrity sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg== hoist-non-react-statics@^3.3.0: version "3.3.2" @@ -6390,10 +6390,10 @@ log-symbols@^2.0.0, log-symbols@^2.2.0: dependencies: chalk "^2.0.1" -loglevel@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0" - integrity sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ== +loglevel@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" + integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== lolex@^5.0.0, lolex@^5.1.2: version "5.1.2" @@ -6513,15 +6513,15 @@ mathml-tag-names@^2.0.1: integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== "matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "9.2.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/6661bde6088e6e43f31198e8532432e162aef33c" + version "9.3.0" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/ff6612f9d0aa1a7c08b65a0b41c5ab997506016f" dependencies: - "@babel/runtime" "^7.11.2" + "@babel/runtime" "^7.12.5" another-json "^0.2.0" browser-request "^0.3.3" bs58 "^4.0.1" content-type "^1.0.4" - loglevel "^1.7.0" + loglevel "^1.7.1" qs "^6.9.4" request "^2.88.2" unhomoglyph "^1.0.6" @@ -6994,6 +6994,10 @@ object.values@^1.1.1: function-bind "^1.1.1" has "^1.0.3" +"olm@https://packages.matrix.org/npm/olm/olm-3.2.1.tgz": + version "3.2.1" + resolved "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz#d623d76f99c3518dde68be8c86618d68bc7b004a" + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"