From 4394a20f87922e768852dd693b7d3eb8ef3f90f4 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Tue, 18 Aug 2020 09:56:38 +0200 Subject: [PATCH 0001/1984] setting added to User Settings -> Preferences -> Timeline as an opt out for users with german translation --- .../views/settings/tabs/user/PreferencesUserSettingsTab.js | 1 + src/i18n/strings/de_DE.json | 3 ++- src/i18n/strings/en_EN.json | 1 + src/settings/Settings.ts | 5 +++++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index a77815a68c..6ed2fc2e39 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -49,6 +49,7 @@ export default class PreferencesUserSettingsTab extends React.Component { 'showAvatarChanges', 'showDisplaynameChanges', 'showImages', + 'dontShowChatEffects', ]; static ADVANCED_SETTINGS = [ diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 09dbcb2e18..edfe21d9d6 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2361,5 +2361,6 @@ "%(brand)s encountered an error during upload of:": "%(brand)s hat einen Fehler festgestellt beim hochladen von:", "Use your account to sign in to the latest version of the app at ": "Verwende dein Konto um dich an der neusten Version der App anzumelden", "We’re excited to announce Riot is now Element!": "Wir freuen uns bekanntzugeben: Riot ist jetzt Element!", - "Learn more at element.io/previously-riot": "Erfahre mehr unter element.io/previously-riot" + "Learn more at element.io/previously-riot": "Erfahre mehr unter element.io/previously-riot", + "Don't show chat effects": "Chat Effekte nicht zeigen" } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 974a96406f..98aee655fe 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -503,6 +503,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", + "Don't show chat effects": "Don't show chat effects", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", "Uploading report": "Uploading report", diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 714d80f983..59a3a4799b 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -586,4 +586,9 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td("Enable experimental, compact IRC style layout"), default: false, }, + "dontShowChatEffects": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td("Don't show chat effects"), + default: false, + }, }; From ecd4d6e19ef58f6c0b99a94890a5cd82a53e7c2a Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Tue, 18 Aug 2020 17:57:51 +0200 Subject: [PATCH 0002/1984] test commit for confetti changes --- src/SlashCommands.tsx | 13 ++ src/components/structures/RoomView.js | 7 +- src/components/views/elements/Confetti.js | 209 ++++++++++++++++++++++ src/i18n/strings/de_DE.json | 3 +- src/i18n/strings/en_EN.json | 1 + 5 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 src/components/views/elements/Confetti.js diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 2063ad3149..2d4d484899 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -44,6 +44,7 @@ import { ensureDMExists } from "./createRoom"; import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload"; import { Action } from "./dispatcher/actions"; import { EffectiveMembership, getEffectiveMembership } from "./utils/membership"; +import {func} from "prop-types"; // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 interface HTMLInputEvent extends Event { @@ -1026,6 +1027,18 @@ export const Commands = [ }, category: CommandCategories.actions, }), + new Command({ + command: "confetti", + description: _td("Throws confetti animation in the chat room"), + args: '/confetti + ', + runFn: function(roomId, args, command) { + return success((async () => { + const cli = MatrixClientPeg.get(); + await cli.sendHtmlMessage(roomId, args); + })()); + }, + category: CommandCategories.messages, + }), // Command definitions for autocompletion ONLY: // /me is special because its not handled by SlashCommands.js and is instead done inside the Composer classes diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 9a61523941..85cb1df848 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -57,6 +57,7 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import { shieldStatusForRoom } from '../../utils/ShieldUtils'; import {Action} from "../../dispatcher/actions"; import {SettingLevel} from "../../settings/SettingLevel"; +import Confetti from "../views/elements/Confetti"; const DEBUG = false; let debuglog = function() {}; @@ -67,7 +68,7 @@ if (DEBUG) { // using bind means that we get to keep useful line numbers in the console debuglog = console.log.bind(console); } - +let confetti; export default createReactClass({ displayName: 'RoomView', propTypes: { @@ -624,12 +625,14 @@ export default createReactClass({ ev.preventDefault(); } }, - onAction: function(payload) { switch (payload.action) { case 'message_send_failed': case 'message_sent': this._checkIfAlone(this.state.room); + confetti = new Confetti('100', '100'); + console.log('confetti sent'); + confetti.animateConfetti('test', 'message'); break; case 'post_sticker_message': this.injectSticker( diff --git a/src/components/views/elements/Confetti.js b/src/components/views/elements/Confetti.js new file mode 100644 index 0000000000..e9dc2c34c0 --- /dev/null +++ b/src/components/views/elements/Confetti.js @@ -0,0 +1,209 @@ +import React from "react"; +import SettingsStore from "../../../../lib/settings/SettingsStore"; +import PropTypes from "prop-types"; + +export default class Confetti extends React.Component { + displayName: 'confetti'; + constructor(props) { + super(props); + this.animateConfetti = this.animateConfetti.bind(this); + this.confetti.start = this.startConfetti; + this.startConfetti = this.startConfetti.bind(this); + this.confetti.stop = this.stopConfetti; + this.confetti.remove = this.removeConfetti; + this.confetti.isRunning = this.isConfettiRunning; + } + static propTypes = { + width: PropTypes.string.isRequired, + height: PropTypes.string.isRequired, + } + confetti = { + //set max confetti count + maxCount: 150, + //set the particle animation speed + speed: 3, + //the confetti animation frame interval in milliseconds + frameInterval: 15, + //the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) + alpha: 1.0, + start: null, + }; + 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,"]; + streamingConfetti = false; + animationTimer = null; + lastFrameTime = Date.now(); + particles = []; + waveAngle = 0; + context = null; + supportsAnimationFrame = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame; + + resetParticle(particle, width, height) { + particle.color = this.colors[(Math.random() * this.colors.length) | 0] + (this.confetti.alpha + ")"); + particle.color2 = this.colors[(Math.random() * this.colors.length) | 0] + (this.confetti.alpha + ")"); + particle.x = Math.random() * width; + particle.y = Math.random() * height - height; + particle.diameter = Math.random() * 10 + 5; + particle.tilt = Math.random() * 10 - 10; + particle.tiltAngleIncrement = Math.random() * 0.07 + 0.05; + particle.tiltAngle = Math.random() * Math.PI; + return particle; + } + + startConfetti(timeout) { + const width = window.innerWidth; + const height = window.innerHeight; + window.requestAnimationFrame = () => { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + return window.setTimeout(callback, this.confetti.frameInterval); + }; + }; + let canvas = document.getElementById("confetti-canvas"); + if (canvas === null) { + canvas = document.createElement("canvas"); + canvas.setAttribute("id", "confetti-canvas"); + canvas.setAttribute("style", "display:block;z-index:999999;pointer-events:none;position:fixed;top:0"); + document.body.prepend(canvas); + canvas.width = width; + canvas.height = height; + window.addEventListener("resize", function () { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + }, true); + this.context = canvas.getContext("2d"); + } else if (this.context === null) { + this.context = canvas.getContext("2d"); + } + const count = this.confetti.maxCount; + while (this.particles.length < count) { + this.particles.push(this.resetParticle({}, width, height)); + } + this.streamingConfetti = true; + this.runAnimation(); + if (timeout) { + window.setTimeout(this.stopConfetti, timeout); + } + } + + stopConfetti() { + this.streamingConfetti = false; + } + + runAnimation() { + if (this.particles.length === 0) { + this.context.clearRect(0, 0, window.innerWidth, window.innerHeight); + this.animationTimer = null; + } else { + const now = Date.now(); + const delta = now - this.lastFrameTime; + if (!this.supportsAnimationFrame || delta > this.confetti.frameInterval) { + this.context.clearRect(0, 0, window.innerWidth, window.innerHeight); + this.updateParticles(); + this.drawParticles(this.context); + this.lastFrameTime = now - (delta % this.confetti.frameInterval); + } + this.animationTimer = requestAnimationFrame(this.runAnimation); + } + } + + removeConfetti() { + stop(); + this.particles = []; + } + + isConfettiRunning() { + return this.streamingConfetti; + } + + drawParticles(context) { + let particle; + let x; + let x2; + let y2; + for (let i = 0; i < this.particles.length; i++) { + particle = this.particles[i]; + context.beginPath(); + context.lineWidth = particle.diameter; + x2 = particle.x + particle.tilt; + x = x2 + particle.diameter / 2; + y2 = particle.y + particle.tilt + particle.diameter / 2; + context.strokeStyle = particle.color; + context.moveTo(x, particle.y); + context.lineTo(x2, y2); + context.stroke(); + } + } + + updateParticles() { + const width = window.innerWidth; + const height = window.innerHeight; + let particle; + this.waveAngle += 0.01; + for (let i = 0; i < this.particles.length; i++) { + particle = this.particles[i]; + if (!this.streamingConfetti && 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.confetti.speed) * 0.5; + particle.tilt = Math.sin(particle.tiltAngle) * 15; + } + if (particle.x > width + 20 || particle.x < -20 || particle.y > height) { + if (this.streamingConfetti && this.particles.length <= this.confetti.maxCount) { + this.resetParticle(particle, width, height); + } else { + this.particles.splice(i, 1); + i--; + } + } + } + } + + convertToHex(content) { + const contentBodyToHexArray = []; + let hex; + for (let i = 0; i < content.body.length; i++) { + hex = content.body.codePointAt(i).toString(16); + contentBodyToHexArray.push(hex); + } + return contentBodyToHexArray; + } + + isChatEffectsDisabled() { + console.log('return value', SettingsStore.getValue('dontShowChatEffects')); + return SettingsStore.getValue('dontShowChatEffects'); + } + + isConfettiEmoji(content) { + const hexArray = this.convertToHex(content); + return !!(hexArray.includes('1f389') || hexArray.includes('1f38a')); + } + + animateConfetti(userId, message) { + // const shortendUserId = userId.slice(1).split(":").slice(0, 1); + console.log('in animate confetti method'); + if (!this.isChatEffectsDisabled()) { + this.confetti.start(3000); + } + if (!message) { + return ('*' + userId + ' throws confetti '); + } + } + + render() { + return ( ); + } +} diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index edfe21d9d6..e4311c2111 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2362,5 +2362,6 @@ "Use your account to sign in to the latest version of the app at ": "Verwende dein Konto um dich an der neusten Version der App anzumelden", "We’re excited to announce Riot is now Element!": "Wir freuen uns bekanntzugeben: Riot ist jetzt Element!", "Learn more at element.io/previously-riot": "Erfahre mehr unter element.io/previously-riot", - "Don't show chat effects": "Chat Effekte nicht zeigen" + "Don't show chat effects": "Chat Effekte nicht zeigen", + "Throws confetti animation in the chat room": "Throws confetti animation in the chat room" } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 98aee655fe..f09ec685ee 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -213,6 +213,7 @@ "Thank you!": "Thank you!", "Opens chat with the given user": "Opens chat with the given user", "Sends a message to the given user": "Sends a message to the given user", + "Throws confetti animation in the chat room": "Throws confetti animation in the chat room", "Displays action": "Displays action", "Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", From 69227dd456bb9d7d78e16157dcabda2603345ae3 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 10:26:20 +0200 Subject: [PATCH 0003/1984] translations added --- src/i18n/strings/de_DE.json | 3 ++- src/i18n/strings/en_EN.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index e4311c2111..5e5639942b 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2363,5 +2363,6 @@ "We’re excited to announce Riot is now Element!": "Wir freuen uns bekanntzugeben: Riot ist jetzt Element!", "Learn more at element.io/previously-riot": "Erfahre mehr unter element.io/previously-riot", "Don't show chat effects": "Chat Effekte nicht zeigen", - "Throws confetti animation in the chat room": "Throws confetti animation in the chat room" + "Sends the given message with confetti": "Sendet die Nachricht mit Konfetti", + " sends confetti": " sendet Konfetti" } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f09ec685ee..efd68d06a6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -213,7 +213,8 @@ "Thank you!": "Thank you!", "Opens chat with the given user": "Opens chat with the given user", "Sends a message to the given user": "Sends a message to the given user", - "Throws confetti animation in the chat room": "Throws confetti animation in the chat room", + "Sends the given message with confetti": "Sends the given message with confetti", + " sends confetti": " sends confetti", "Displays action": "Displays action", "Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", From 34cee20140d4da373fc0be630da4e11709409ed9 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 10:43:41 +0200 Subject: [PATCH 0004/1984] added confetti on command /confetti --- src/SlashCommands.tsx | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 2d4d484899..8322512b73 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -45,6 +45,7 @@ import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload"; import { Action } from "./dispatcher/actions"; import { EffectiveMembership, getEffectiveMembership } from "./utils/membership"; import {func} from "prop-types"; +import SettingsStore from "./settings/SettingsStore"; // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 interface HTMLInputEvent extends Event { @@ -1029,15 +1030,24 @@ export const Commands = [ }), new Command({ command: "confetti", - description: _td("Throws confetti animation in the chat room"), - args: '/confetti + ', - runFn: function(roomId, args, command) { + description: _td("Sends the given message with confetti"), + args: '', + runFn: function(roomId, args) { return success((async () => { - const cli = MatrixClientPeg.get(); - await cli.sendHtmlMessage(roomId, args); + const cli = MatrixClientPeg.get(); + const userId = cli.getUserId(); + const userName = userId.slice(1).split(":").slice(0, 1); + const isChatEffectsDisabled = SettingsStore.getValue('dontShowChatEffects'); + if (!args || isChatEffectsDisabled) { + args = '*' + userName + _td(' sends confetti'); + } + if (!isChatEffectsDisabled) { + dis.dispatch({action: 'confetti'}); + } + cli.sendHtmlMessage(roomId, args); })()); }, - category: CommandCategories.messages, + category: CommandCategories.actions, }), // Command definitions for autocompletion ONLY: From a7567b2e31bb403a05490a299e7ca17fd595760c Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 10:44:32 +0200 Subject: [PATCH 0005/1984] confetti animationsd handeled on roomViewTimeline --- src/components/structures/RoomView.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 85cb1df848..e48063530d 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -57,7 +57,7 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import { shieldStatusForRoom } from '../../utils/ShieldUtils'; import {Action} from "../../dispatcher/actions"; import {SettingLevel} from "../../settings/SettingLevel"; -import Confetti from "../views/elements/Confetti"; +import {animateConfetti, forceStopConfetti} from "../views/elements/Confetti"; const DEBUG = false; let debuglog = function() {}; @@ -68,7 +68,6 @@ if (DEBUG) { // using bind means that we get to keep useful line numbers in the console debuglog = console.log.bind(console); } -let confetti; export default createReactClass({ displayName: 'RoomView', propTypes: { @@ -511,6 +510,7 @@ export default createReactClass({ this.context.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); this.context.removeListener("crossSigning.keysChanged", this.onCrossSigningKeysChanged); + this.context.removeListener("Event.decrypted", this.onEventDecrypted); } window.removeEventListener('beforeunload', this.onPageUnload); @@ -630,9 +630,9 @@ export default createReactClass({ case 'message_send_failed': case 'message_sent': this._checkIfAlone(this.state.room); - confetti = new Confetti('100', '100'); - console.log('confetti sent'); - confetti.animateConfetti('test', 'message'); + break; + case 'confetti': + animateConfetti(this._roomView.current.offsetWidth); break; case 'post_sticker_message': this.injectSticker( @@ -750,6 +750,18 @@ export default createReactClass({ }); } } + if (!SettingsStore.getValue('dontShowChatEffects')) { + this.context.on('Event.decrypted', this.onEventDecrypted); + } + }, + onEventDecrypted(ev) { + if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; + this.handleConfetti(); + }, + handleConfetti() { + if (this.context.isInitialSyncComplete()) { + dis.dispatch({action: 'confetti'}); + } }, onRoomName: function(room) { @@ -786,6 +798,7 @@ export default createReactClass({ this._calculateRecommendedVersion(room); this._updateE2EStatus(room); this._updatePermissions(room); + forceStopConfetti(); }, _calculateRecommendedVersion: async function(room) { From 77de63bf4b06c5235e00c74673f2c0082a064195 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 10:44:49 +0200 Subject: [PATCH 0006/1984] confetti file added --- src/components/views/elements/Confetti.js | 252 +++++++++++----------- 1 file changed, 121 insertions(+), 131 deletions(-) diff --git a/src/components/views/elements/Confetti.js b/src/components/views/elements/Confetti.js index e9dc2c34c0..df2b004ce0 100644 --- a/src/components/views/elements/Confetti.js +++ b/src/components/views/elements/Confetti.js @@ -1,52 +1,48 @@ -import React from "react"; -import SettingsStore from "../../../../lib/settings/SettingsStore"; -import PropTypes from "prop-types"; +const confetti = { + //set max confetti count + maxCount: 150, + //syarn addet the particle animation speed + speed: 3, + //the confetti animation frame interval in milliseconds + frameInterval: 15, + //the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) + alpha: 1.0, + //call to start confetti animation (with optional timeout in milliseconds) + start: null, + //call to stop adding confetti + stop: null, + //call to stop the confetti animation and remove all confetti immediately + remove: null, + isRunning: null, + //call and returns true or false depending on whether the animation is running + animate: null, +}; -export default class Confetti extends React.Component { - displayName: 'confetti'; - constructor(props) { - super(props); - this.animateConfetti = this.animateConfetti.bind(this); - this.confetti.start = this.startConfetti; - this.startConfetti = this.startConfetti.bind(this); - this.confetti.stop = this.stopConfetti; - this.confetti.remove = this.removeConfetti; - this.confetti.isRunning = this.isConfettiRunning; - } - static propTypes = { - width: PropTypes.string.isRequired, - height: PropTypes.string.isRequired, - } - confetti = { - //set max confetti count - maxCount: 150, - //set the particle animation speed - speed: 3, - //the confetti animation frame interval in milliseconds - frameInterval: 15, - //the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) - alpha: 1.0, - start: null, - }; - 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,"]; - streamingConfetti = false; - animationTimer = null; - lastFrameTime = Date.now(); - particles = []; - waveAngle = 0; - context = null; - supportsAnimationFrame = window.requestAnimationFrame || +(function() { + confetti.start = startConfetti; + confetti.stop = stopConfetti; + confetti.remove = removeConfetti; + confetti.isRunning = isConfettiRunning; + confetti.animate = animateConfetti; + const supportsAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame; + const 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,"]; + let streamingConfetti = false; + let animationTimer = null; + let lastFrameTime = Date.now(); + let particles = []; + let waveAngle = 0; + let context = null; - resetParticle(particle, width, height) { - particle.color = this.colors[(Math.random() * this.colors.length) | 0] + (this.confetti.alpha + ")"); - particle.color2 = this.colors[(Math.random() * this.colors.length) | 0] + (this.confetti.alpha + ")"); + function resetParticle(particle, width, height) { + particle.color = colors[(Math.random() * colors.length) | 0] + (confetti.alpha + ")"); + particle.color2 = colors[(Math.random() * colors.length) | 0] + (confetti.alpha + ")"); particle.x = Math.random() * width; particle.y = Math.random() * height - height; particle.diameter = Math.random() * 10 + 5; @@ -56,154 +52,148 @@ export default class Confetti extends React.Component { return particle; } - startConfetti(timeout) { - const width = window.innerWidth; + function runAnimation() { + if (particles.length === 0) { + context.clearRect(0, 0, window.innerWidth, window.innerHeight); + animationTimer = null; + } else { + const now = Date.now(); + const delta = now - lastFrameTime; + if (!supportsAnimationFrame || delta > confetti.frameInterval) { + context.clearRect(0, 0, window.innerWidth, window.innerHeight); + updateParticles(); + drawParticles(context); + lastFrameTime = now - (delta % confetti.frameInterval); + } + animationTimer = requestAnimationFrame(runAnimation); + } + } + + function startConfetti(roomWidth, timeout) { + const width = roomWidth; const height = window.innerHeight; - window.requestAnimationFrame = () => { + window.requestAnimationFrame = (function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || - function(callback) { - return window.setTimeout(callback, this.confetti.frameInterval); + function (callback) { + return window.setTimeout(callback, confetti.frameInterval); }; - }; + })(); let canvas = document.getElementById("confetti-canvas"); if (canvas === null) { canvas = document.createElement("canvas"); canvas.setAttribute("id", "confetti-canvas"); - canvas.setAttribute("style", "display:block;z-index:999999;pointer-events:none;position:fixed;top:0"); + canvas.setAttribute("style", "display:block;z-index:999999;pointer-events:none;position:fixed;top:0; right:0"); document.body.prepend(canvas); canvas.width = width; canvas.height = height; - window.addEventListener("resize", function () { - canvas.width = window.innerWidth; + window.addEventListener("resize", function() { + canvas.width = roomWidth; canvas.height = window.innerHeight; }, true); - this.context = canvas.getContext("2d"); - } else if (this.context === null) { - this.context = canvas.getContext("2d"); + context = canvas.getContext("2d"); + } else if (context === null) { + context = canvas.getContext("2d"); } - const count = this.confetti.maxCount; - while (this.particles.length < count) { - this.particles.push(this.resetParticle({}, width, height)); + const count = confetti.maxCount; + while (particles.length < count) { + particles.push(resetParticle({}, width, height)); } - this.streamingConfetti = true; - this.runAnimation(); + streamingConfetti = true; + runAnimation(); if (timeout) { - window.setTimeout(this.stopConfetti, timeout); + window.setTimeout(stopConfetti, timeout); } } - stopConfetti() { - this.streamingConfetti = false; + function stopConfetti() { + streamingConfetti = false; } - runAnimation() { - if (this.particles.length === 0) { - this.context.clearRect(0, 0, window.innerWidth, window.innerHeight); - this.animationTimer = null; - } else { - const now = Date.now(); - const delta = now - this.lastFrameTime; - if (!this.supportsAnimationFrame || delta > this.confetti.frameInterval) { - this.context.clearRect(0, 0, window.innerWidth, window.innerHeight); - this.updateParticles(); - this.drawParticles(this.context); - this.lastFrameTime = now - (delta % this.confetti.frameInterval); - } - this.animationTimer = requestAnimationFrame(this.runAnimation); - } - } - - removeConfetti() { + function removeConfetti() { stop(); - this.particles = []; + particles = []; } - isConfettiRunning() { - return this.streamingConfetti; + function isConfettiRunning() { + return streamingConfetti; } - drawParticles(context) { + function drawParticles(context) { let particle; - let x; - let x2; - let y2; - for (let i = 0; i < this.particles.length; i++) { - particle = this.particles[i]; + let x; let x2; let y2; + for (let i = 0; i < particles.length; i++) { + particle = particles[i]; context.beginPath(); context.lineWidth = particle.diameter; x2 = particle.x + particle.tilt; x = x2 + particle.diameter / 2; y2 = particle.y + particle.tilt + particle.diameter / 2; - context.strokeStyle = particle.color; + if (confetti.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(); } } - updateParticles() { + function updateParticles() { const width = window.innerWidth; const height = window.innerHeight; let particle; - this.waveAngle += 0.01; - for (let i = 0; i < this.particles.length; i++) { - particle = this.particles[i]; - if (!this.streamingConfetti && particle.y < -15) { + waveAngle += 0.01; + for (let i = 0; i < particles.length; i++) { + particle = particles[i]; + if (!streamingConfetti && 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.confetti.speed) * 0.5; + particle.x += Math.sin(waveAngle) - 0.5; + particle.y += (Math.cos(waveAngle) + particle.diameter + confetti.speed) * 0.5; particle.tilt = Math.sin(particle.tiltAngle) * 15; } if (particle.x > width + 20 || particle.x < -20 || particle.y > height) { - if (this.streamingConfetti && this.particles.length <= this.confetti.maxCount) { - this.resetParticle(particle, width, height); + if (streamingConfetti && particles.length <= confetti.maxCount) { + resetParticle(particle, width, height); } else { - this.particles.splice(i, 1); + particles.splice(i, 1); i--; } } } } +})(); - convertToHex(content) { - const contentBodyToHexArray = []; - let hex; +export function convertToHex(content) { + const contentBodyToHexArray = []; + let hex; + if (content.body) { for (let i = 0; i < content.body.length; i++) { hex = content.body.codePointAt(i).toString(16); contentBodyToHexArray.push(hex); } - return contentBodyToHexArray; - } - - isChatEffectsDisabled() { - console.log('return value', SettingsStore.getValue('dontShowChatEffects')); - return SettingsStore.getValue('dontShowChatEffects'); - } - - isConfettiEmoji(content) { - const hexArray = this.convertToHex(content); - return !!(hexArray.includes('1f389') || hexArray.includes('1f38a')); - } - - animateConfetti(userId, message) { - // const shortendUserId = userId.slice(1).split(":").slice(0, 1); - console.log('in animate confetti method'); - if (!this.isChatEffectsDisabled()) { - this.confetti.start(3000); - } - if (!message) { - return ('*' + userId + ' throws confetti '); - } - } - - render() { - return ( ); } + return contentBodyToHexArray; +} + +export function isConfettiEmoji(content) { + const hexArray = convertToHex(content); + return !!(hexArray.includes('1f389') || hexArray.includes('1f38a')); +} + +export function animateConfetti(roomWidth) { + confetti.start(roomWidth, 3000); +} +export function forceStopConfetti() { + console.log('confetti should stop'); + confetti.remove(); } From 03b2a529ef681e0e0777af57418f74ebba458954 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 11:29:46 +0200 Subject: [PATCH 0007/1984] remove unused var --- src/components/views/elements/Confetti.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/Confetti.js b/src/components/views/elements/Confetti.js index df2b004ce0..371c26a4b5 100644 --- a/src/components/views/elements/Confetti.js +++ b/src/components/views/elements/Confetti.js @@ -34,7 +34,7 @@ const confetti = { "rgba(238,130,238,", "rgba(152,251,152,", "rgba(70,130,180,", "rgba(244,164,96,", "rgba(210,105,30,", "rgba(220,20,60,"]; let streamingConfetti = false; - let animationTimer = null; + // let animationTimer = null; let lastFrameTime = Date.now(); let particles = []; let waveAngle = 0; @@ -55,7 +55,7 @@ const confetti = { function runAnimation() { if (particles.length === 0) { context.clearRect(0, 0, window.innerWidth, window.innerHeight); - animationTimer = null; + //animationTimer = null; } else { const now = Date.now(); const delta = now - lastFrameTime; @@ -65,7 +65,7 @@ const confetti = { drawParticles(context); lastFrameTime = now - (delta % confetti.frameInterval); } - animationTimer = requestAnimationFrame(runAnimation); + requestAnimationFrame(runAnimation); } } From 2a8b1e0ccd58628214a496b0b25f18ad96755997 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 11:55:23 +0200 Subject: [PATCH 0008/1984] remove space before function parentheses and maximum allowed line --- src/components/views/elements/Confetti.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/Confetti.js b/src/components/views/elements/Confetti.js index 371c26a4b5..7d4faa3a17 100644 --- a/src/components/views/elements/Confetti.js +++ b/src/components/views/elements/Confetti.js @@ -72,13 +72,13 @@ const confetti = { function startConfetti(roomWidth, timeout) { const width = roomWidth; const height = window.innerHeight; - window.requestAnimationFrame = (function () { + window.requestAnimationFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || - function (callback) { + function(callback) { return window.setTimeout(callback, confetti.frameInterval); }; })(); @@ -86,7 +86,8 @@ const confetti = { if (canvas === null) { canvas = document.createElement("canvas"); canvas.setAttribute("id", "confetti-canvas"); - canvas.setAttribute("style", "display:block;z-index:999999;pointer-events:none;position:fixed;top:0; right:0"); + canvas.setAttribute("style", + "display:block;z-index:999999;pointer-events:none;position:fixed;top:0; right:0"); document.body.prepend(canvas); canvas.width = width; canvas.height = height; From b79cf1e7ad00cd06ae0b38c8b37612877ec59481 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 13:59:11 +0200 Subject: [PATCH 0009/1984] updated translated string --- src/SlashCommands.tsx | 2 +- src/i18n/strings/de_DE.json | 1 - src/i18n/strings/en_EN.json | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 8322512b73..ba0aea73f0 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -1039,7 +1039,7 @@ export const Commands = [ const userName = userId.slice(1).split(":").slice(0, 1); const isChatEffectsDisabled = SettingsStore.getValue('dontShowChatEffects'); if (!args || isChatEffectsDisabled) { - args = '*' + userName + _td(' sends confetti'); + args = _t("* %(userName)s sends confetti", {userName}); } if (!isChatEffectsDisabled) { dis.dispatch({action: 'confetti'}); diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 5e5639942b..46ce139e6e 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2364,5 +2364,4 @@ "Learn more at element.io/previously-riot": "Erfahre mehr unter element.io/previously-riot", "Don't show chat effects": "Chat Effekte nicht zeigen", "Sends the given message with confetti": "Sendet die Nachricht mit Konfetti", - " sends confetti": " sendet Konfetti" } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index efd68d06a6..78a2d51c56 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -214,7 +214,7 @@ "Opens chat with the given user": "Opens chat with the given user", "Sends a message to the given user": "Sends a message to the given user", "Sends the given message with confetti": "Sends the given message with confetti", - " sends confetti": " sends confetti", + "* %(userName)s sends confetti": "* %(userName)s sends confetti", "Displays action": "Displays action", "Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", From 5b7ccb5a7837e134d28795b7cb8ddc68716ca7c2 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 14:04:20 +0200 Subject: [PATCH 0010/1984] fix indentation spaces and readability line spaces --- src/components/structures/RoomView.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index e48063530d..240d300751 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -68,6 +68,7 @@ if (DEBUG) { // using bind means that we get to keep useful line numbers in the console debuglog = console.log.bind(console); } + export default createReactClass({ displayName: 'RoomView', propTypes: { @@ -624,6 +625,7 @@ export default createReactClass({ ev.stopPropagation(); ev.preventDefault(); } + }, onAction: function(payload) { switch (payload.action) { @@ -632,7 +634,7 @@ export default createReactClass({ this._checkIfAlone(this.state.room); break; case 'confetti': - animateConfetti(this._roomView.current.offsetWidth); + animateConfetti(this._roomView.current.offsetWidth); break; case 'post_sticker_message': this.injectSticker( @@ -756,12 +758,12 @@ export default createReactClass({ }, onEventDecrypted(ev) { if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; - this.handleConfetti(); + this.handleConfetti(); }, handleConfetti() { if (this.context.isInitialSyncComplete()) { - dis.dispatch({action: 'confetti'}); - } + dis.dispatch({action: 'confetti'}); + } }, onRoomName: function(room) { From f1c7139711f87dd818f9143fc6ec032e9ce41509 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Mon, 24 Aug 2020 14:25:06 +0200 Subject: [PATCH 0011/1984] remove not needed comma --- src/i18n/strings/de_DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 46ce139e6e..b5a69d7e72 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2363,5 +2363,5 @@ "We’re excited to announce Riot is now Element!": "Wir freuen uns bekanntzugeben: Riot ist jetzt Element!", "Learn more at element.io/previously-riot": "Erfahre mehr unter element.io/previously-riot", "Don't show chat effects": "Chat Effekte nicht zeigen", - "Sends the given message with confetti": "Sendet die Nachricht mit Konfetti", + "Sends the given message with confetti": "Sendet die Nachricht mit Konfetti" } From eef654e0e3189a8fcba03c8463d62ecbe2a3e745 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Tue, 25 Aug 2020 11:09:10 +0200 Subject: [PATCH 0012/1984] fix indentation and remove console.log --- src/components/views/elements/Confetti.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/elements/Confetti.js b/src/components/views/elements/Confetti.js index 7d4faa3a17..b0f88dedb7 100644 --- a/src/components/views/elements/Confetti.js +++ b/src/components/views/elements/Confetti.js @@ -192,9 +192,8 @@ export function isConfettiEmoji(content) { } export function animateConfetti(roomWidth) { - confetti.start(roomWidth, 3000); + confetti.start(roomWidth, 3000); } export function forceStopConfetti() { - console.log('confetti should stop'); confetti.remove(); } From d41ffb1b4be9eeff5330c9e3ca5891cf22bb7f46 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Tue, 25 Aug 2020 11:09:57 +0200 Subject: [PATCH 0013/1984] pass ev to handleConfetti in order to check content before dispatch --- src/components/structures/RoomView.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 240d300751..b24d6efa2a 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -57,7 +57,7 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import { shieldStatusForRoom } from '../../utils/ShieldUtils'; import {Action} from "../../dispatcher/actions"; import {SettingLevel} from "../../settings/SettingLevel"; -import {animateConfetti, forceStopConfetti} from "../views/elements/Confetti"; +import {animateConfetti, forceStopConfetti, isConfettiEmoji} from "../views/elements/Confetti"; const DEBUG = false; let debuglog = function() {}; @@ -758,11 +758,13 @@ export default createReactClass({ }, onEventDecrypted(ev) { if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; - this.handleConfetti(); + this.handleConfetti(ev); }, - handleConfetti() { + handleConfetti(ev) { if (this.context.isInitialSyncComplete()) { - dis.dispatch({action: 'confetti'}); + if (isConfettiEmoji(ev.getContent())) { + dis.dispatch({action: 'confetti'}); + } } }, From 43f266bfe333c2b6c5ece0be86ea5089e5e11c80 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Tue, 25 Aug 2020 11:11:20 +0200 Subject: [PATCH 0014/1984] remove unused import fix if condition trying (pass the dispatcher to sendHtmlMessage) --- src/SlashCommands.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index ba0aea73f0..6b321ce092 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -44,7 +44,6 @@ import { ensureDMExists } from "./createRoom"; import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload"; import { Action } from "./dispatcher/actions"; import { EffectiveMembership, getEffectiveMembership } from "./utils/membership"; -import {func} from "prop-types"; import SettingsStore from "./settings/SettingsStore"; // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 @@ -1038,13 +1037,11 @@ export const Commands = [ const userId = cli.getUserId(); const userName = userId.slice(1).split(":").slice(0, 1); const isChatEffectsDisabled = SettingsStore.getValue('dontShowChatEffects'); - if (!args || isChatEffectsDisabled) { + if ((!args) || (!args && isChatEffectsDisabled)) { args = _t("* %(userName)s sends confetti", {userName}); } - if (!isChatEffectsDisabled) { - dis.dispatch({action: 'confetti'}); - } - cli.sendHtmlMessage(roomId, args); + cli.sendHtmlMessage(roomId, args, + dis.dispatch({action: 'confetti'})); })()); }, category: CommandCategories.actions, From cc71531493df1e5e095b7f173909cbb4606a4f16 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Tue, 25 Aug 2020 13:36:04 +0200 Subject: [PATCH 0015/1984] reverted German language translations --- src/i18n/strings/de_DE.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 2cea1519df..3d5ba3722e 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2409,6 +2409,4 @@ "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "Wenn du jetzt abbrichst, kannst du verschlüsselte Nachrichten und Daten verlieren, wenn du den Zugriff auf deine Logins verlierst.", "You can also set up Secure Backup & manage your keys in Settings.": "Du kannst auch in den Einstellungen eine Sicherung erstellen & deine Schlüssel verwalten.", "Set up Secure backup": "Sicheres Backup einrichten" - "Don't show chat effects": "Chat Effekte nicht zeigen", - "Sends the given message with confetti": "Sendet die Nachricht mit Konfetti" } From 4527755f7e2df6f3b6622f1cd740469df608d587 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Tue, 25 Aug 2020 16:18:01 +0200 Subject: [PATCH 0016/1984] updated translation --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 029551eb34..223e063762 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -210,7 +210,7 @@ "Opens chat with the given user": "Opens chat with the given user", "Sends a message to the given user": "Sends a message to the given user", "Sends the given message with confetti": "Sends the given message with confetti", - "* %(userName)s sends confetti": "* %(userName)s sends confetti", + "sends confetti": "sends confetti", "Displays action": "Displays action", "Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", From 5753c964317ab20b1682874416d00cdf9e6c5820 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Tue, 25 Aug 2020 16:39:57 +0200 Subject: [PATCH 0017/1984] a workaround to make ocnfetti work on recipient side. changed the implementation of on.Event.decrypted function --- src/SlashCommands.tsx | 13 +++++++------ src/components/structures/RoomView.js | 13 ++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index abd4f5449b..03aec46e46 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -1029,15 +1029,16 @@ export const Commands = [ args: '', runFn: function(roomId, args) { return success((async () => { - const cli = MatrixClientPeg.get(); - const userId = cli.getUserId(); - const userName = userId.slice(1).split(":").slice(0, 1); const isChatEffectsDisabled = SettingsStore.getValue('dontShowChatEffects'); if ((!args) || (!args && isChatEffectsDisabled)) { - args = _t("* %(userName)s sends confetti", {userName}); + args = _t("sends confetti"); + MatrixClientPeg.get().sendEmoteMessage(roomId, args); + } else { + MatrixClientPeg.get().sendHtmlMessage(roomId, args); + } + if (!isChatEffectsDisabled) { + dis.dispatch({action: 'confetti'}); } - cli.sendHtmlMessage(roomId, args, - dis.dispatch({action: 'confetti'})); })()); }, category: CommandCategories.actions, diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index dfc92526c7..d5ccbf1c8c 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -511,7 +511,6 @@ export default createReactClass({ this.context.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); this.context.removeListener("crossSigning.keysChanged", this.onCrossSigningKeysChanged); - this.context.removeListener("Event.decrypted", this.onEventDecrypted); } window.removeEventListener('beforeunload', this.onPageUnload); @@ -753,16 +752,16 @@ export default createReactClass({ } } if (!SettingsStore.getValue('dontShowChatEffects')) { - this.context.on('Event.decrypted', this.onEventDecrypted); + this.context.on("Event.decrypted", (ev) => { + if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; + this.handleConfetti(ev); + }); } }, - onEventDecrypted(ev) { - if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; - this.handleConfetti(ev); - }, handleConfetti(ev) { if (this.context.isInitialSyncComplete()) { - if (isConfettiEmoji(ev.getContent())) { + const messageBody = _t('sends confetti'); + if (isConfettiEmoji(ev.getContent()) || ev.getContent().body === messageBody) { dis.dispatch({action: 'confetti'}); } } From 95051a42b1f2755f52a980ef4521edc88ab728b0 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Wed, 26 Aug 2020 18:56:23 +0200 Subject: [PATCH 0018/1984] checking for unreadMessages before sending confetti throwing the confetti on the sender's side change sendHtmlMessage to sendTextMessage in slashCommands --- src/SlashCommands.tsx | 2 +- src/components/structures/RoomView.js | 17 ++++++++++------- .../views/rooms/SendMessageComposer.js | 7 +++++++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 03aec46e46..28eaa8123b 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -1034,7 +1034,7 @@ export const Commands = [ args = _t("sends confetti"); MatrixClientPeg.get().sendEmoteMessage(roomId, args); } else { - MatrixClientPeg.get().sendHtmlMessage(roomId, args); + MatrixClientPeg.get().sendTextMessage(roomId, args); } if (!isChatEffectsDisabled) { dis.dispatch({action: 'confetti'}); diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index d5ccbf1c8c..92f43c75ca 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -189,6 +189,7 @@ export default createReactClass({ this.context.on("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.on("userTrustStatusChanged", this.onUserVerificationChanged); this.context.on("crossSigning.keysChanged", this.onCrossSigningKeysChanged); + this.context.on("Event.decrypted", this.onEventDecrypted); // Start listening for RoomViewStore updates this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); this._rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this._onRightPanelStoreUpdate); @@ -511,6 +512,7 @@ export default createReactClass({ this.context.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); this.context.removeListener("crossSigning.keysChanged", this.onCrossSigningKeysChanged); + this.context.removeListener("Event.decrypted", this.onEventDecrypted); } window.removeEventListener('beforeunload', this.onPageUnload); @@ -751,15 +753,16 @@ export default createReactClass({ }); } } - if (!SettingsStore.getValue('dontShowChatEffects')) { - this.context.on("Event.decrypted", (ev) => { - if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; - this.handleConfetti(ev); - }); - } + }, + onEventDecrypted(ev) { + if (!SettingsStore.getValue('dontShowChatEffects')) { + if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || + this.state.room.getUnreadNotificationCount() === 0) return; + this.handleConfetti(ev); + } }, handleConfetti(ev) { - if (this.context.isInitialSyncComplete()) { + if (this.state.matrixClientIsReady) { const messageBody = _t('sends confetti'); if (isConfettiEmoji(ev.getContent()) || ev.getContent().body === messageBody) { dis.dispatch({action: 'confetti'}); diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 6a7b2fc753..0b873a9bab 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -44,6 +44,8 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import RateLimitedFunc from '../../../ratelimitedfunc'; import {Action} from "../../../dispatcher/actions"; +import {isConfettiEmoji} from "../elements/Confetti"; +import SettingsStore from "../../../settings/SettingsStore"; function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) { const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent); @@ -313,6 +315,11 @@ export default class SendMessageComposer extends React.Component { }); } dis.dispatch({action: "message_sent"}); + if (!SettingsStore.getValue('dontShowChatEffects')) { + if (isConfettiEmoji(content)) { + dis.dispatch({action: 'confetti'}); + } + } } this.sendHistoryManager.save(this.model); From db61d343f5f9f2dc8552b97d9243c0ebfe8baa94 Mon Sep 17 00:00:00 2001 From: Clemens Zeidler Date: Sun, 30 Aug 2020 20:17:08 +1200 Subject: [PATCH 0019/1984] Add option to send/edit a message with Ctrl + Enter / Command + Enter When editing multi-line text this option helps to prevent accidentally sending a message too early. With this option, Enter just inserts a new line. For example, composing programming code in a dev chat becomes much easier when Enter just inserts a new line instead of sending the message. Signed-off-by: Clemens Zeidler --- src/components/views/rooms/EditMessageComposer.js | 8 ++++++-- src/components/views/rooms/SendMessageComposer.js | 8 ++++++-- .../settings/tabs/user/PreferencesUserSettingsTab.js | 1 + src/i18n/strings/en_EN.json | 1 + src/settings/Settings.ts | 5 +++++ 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/EditMessageComposer.js b/src/components/views/rooms/EditMessageComposer.js index 78c7de887d..636c5b27ff 100644 --- a/src/components/views/rooms/EditMessageComposer.js +++ b/src/components/views/rooms/EditMessageComposer.js @@ -29,9 +29,10 @@ import EditorStateTransfer from '../../../utils/EditorStateTransfer'; import classNames from 'classnames'; import {EventStatus} from 'matrix-js-sdk'; import BasicMessageComposer from "./BasicMessageComposer"; -import {Key} from "../../../Keyboard"; +import {Key, isOnlyCtrlOrCmdKeyEvent} from "../../../Keyboard"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {Action} from "../../../dispatcher/actions"; +import SettingsStore from "../../../settings/SettingsStore"; function _isReply(mxEvent) { const relatesTo = mxEvent.getContent()["m.relates_to"]; @@ -135,7 +136,10 @@ export default class EditMessageComposer extends React.Component { if (event.metaKey || event.altKey || event.shiftKey) { return; } - if (event.key === Key.ENTER) { + const ctrlEnterToSend = !!SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend'); + const send = ctrlEnterToSend ? event.key === Key.ENTER && isOnlyCtrlOrCmdKeyEvent(event) + : event.key === Key.ENTER; + if (send) { this._sendEdit(); event.preventDefault(); } else if (event.key === Key.ESCAPE) { diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 25dcf8ccd5..dd1b67c989 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -39,11 +39,12 @@ import * as sdk from '../../../index'; import Modal from '../../../Modal'; import {_t, _td} from '../../../languageHandler'; import ContentMessages from '../../../ContentMessages'; -import {Key} from "../../../Keyboard"; +import {Key, isOnlyCtrlOrCmdKeyEvent} from "../../../Keyboard"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import RateLimitedFunc from '../../../ratelimitedfunc'; import {Action} from "../../../dispatcher/actions"; +import SettingsStore from "../../../settings/SettingsStore"; function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) { const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent); @@ -122,7 +123,10 @@ export default class SendMessageComposer extends React.Component { return; } const hasModifier = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey; - if (event.key === Key.ENTER && !hasModifier) { + const ctrlEnterToSend = !!SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend'); + const send = ctrlEnterToSend ? event.key === Key.ENTER && isOnlyCtrlOrCmdKeyEvent(event) + : event.key === Key.ENTER && !hasModifier; + if (send) { this._sendMessage(); event.preventDefault(); } else if (event.key === Key.ARROW_UP) { diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index a77815a68c..64208cb8cd 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -33,6 +33,7 @@ export default class PreferencesUserSettingsTab extends React.Component { 'MessageComposerInput.autoReplaceEmoji', 'MessageComposerInput.suggestEmoji', 'sendTypingNotifications', + 'MessageComposerInput.ctrlEnterToSend', ]; static TIMELINE_SETTINGS = [ diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 47063bdae4..277d9c5952 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -477,6 +477,7 @@ "Enable big emoji in chat": "Enable big emoji in chat", "Send typing notifications": "Send typing notifications", "Show typing notifications": "Show typing notifications", + "Use Ctrl + Enter to send a message (Mac: Command + Enter)": "Use Ctrl + Enter to send a message (Mac: Command + Enter)", "Automatically replace plain text Emoji": "Automatically replace plain text Emoji", "Mirror local video feed": "Mirror local video feed", "Enable Community Filter Panel": "Enable Community Filter Panel", diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 95861e11df..d2d268b2bb 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -321,6 +321,11 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td("Show typing notifications"), default: true, }, + "MessageComposerInput.ctrlEnterToSend": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td("Use Ctrl + Enter to send a message (Mac: Command + Enter)"), + default: false, + }, "MessageComposerInput.autoReplaceEmoji": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Automatically replace plain text Emoji'), From 9031c58aebd08b7c6ab07e173f9895006491af5c Mon Sep 17 00:00:00 2001 From: Clemens Zeidler Date: Tue, 8 Sep 2020 21:46:09 +1200 Subject: [PATCH 0020/1984] Make settings label platform specific --- src/i18n/strings/en_EN.json | 3 ++- src/settings/Settings.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 277d9c5952..a66478ddc9 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -477,7 +477,8 @@ "Enable big emoji in chat": "Enable big emoji in chat", "Send typing notifications": "Send typing notifications", "Show typing notifications": "Show typing notifications", - "Use Ctrl + Enter to send a message (Mac: Command + Enter)": "Use Ctrl + Enter to send a message (Mac: Command + Enter)", + "Use Command + Enter to send a message": "Use Command + Enter to send a message", + "Use Ctrl + Enter to send a message": "Use Ctrl + Enter to send a message", "Automatically replace plain text Emoji": "Automatically replace plain text Emoji", "Mirror local video feed": "Mirror local video feed", "Enable Community Filter Panel": "Enable Community Filter Panel", diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index d2d268b2bb..afe9a50c1e 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -32,6 +32,7 @@ import UseSystemFontController from './controllers/UseSystemFontController'; import { SettingLevel } from "./SettingLevel"; import SettingController from "./controllers/SettingController"; import { RightPanelPhases } from "../stores/RightPanelStorePhases"; +import { isMac } from '../Keyboard'; // These are just a bunch of helper arrays to avoid copy/pasting a bunch of times const LEVELS_ROOM_SETTINGS = [ @@ -323,7 +324,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "MessageComposerInput.ctrlEnterToSend": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, - displayName: _td("Use Ctrl + Enter to send a message (Mac: Command + Enter)"), + displayName: isMac ? _td("Use Command + Enter to send a message") : _td("Use Ctrl + Enter to send a message"), default: false, }, "MessageComposerInput.autoReplaceEmoji": { From 0604c86779cdea98ace30bdd78eb4db6888ffc40 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sat, 19 Sep 2020 15:30:00 +0100 Subject: [PATCH 0021/1984] added katex package and import --- package.json | 1 + src/HtmlUtils.tsx | 1 + yarn.lock | 7 +++++++ 3 files changed, 9 insertions(+) diff --git a/package.json b/package.json index 156cbb1bc8..7aa3df136b 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "highlight.js": "^10.1.2", "html-entities": "^1.3.1", "is-ip": "^2.0.0", + "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.19", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index bd314c2e5f..99acbfcb0c 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -26,6 +26,7 @@ import _linkifyString from 'linkifyjs/string'; import classNames from 'classnames'; import EMOJIBASE_REGEX from 'emojibase-regex'; import url from 'url'; +import katex from 'katex'; import {MatrixClientPeg} from './MatrixClientPeg'; import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks"; diff --git a/yarn.lock b/yarn.lock index efc1f0eae1..34b99708fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5607,6 +5607,13 @@ jsx-ast-utils@^2.4.1: array-includes "^3.1.1" object.assign "^4.1.0" +katex@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.12.0.tgz#2fb1c665dbd2b043edcf8a1f5c555f46beaa0cb9" + integrity sha512-y+8btoc/CK70XqcHqjxiGWBOeIL8upbS0peTPXTvgrh21n1RiWWcIpSWM+4uXq+IAgNh9YYQWdc7LVDPDAEEAg== + dependencies: + commander "^2.19.0" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" From becc79d67a29a0886f4a6f800daabebae16d655c Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sun, 20 Sep 2020 12:59:22 +0100 Subject: [PATCH 0022/1984] send tex math as data-mx-maths attribute --- src/HtmlUtils.tsx | 26 +++++++++++++++++++++++++- src/editor/serialize.ts | 23 ++++++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 99acbfcb0c..344fb3514c 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -27,6 +27,7 @@ import classNames from 'classnames'; import EMOJIBASE_REGEX from 'emojibase-regex'; import url from 'url'; import katex from 'katex'; +import { AllHtmlEntities } from 'html-entities'; import {MatrixClientPeg} from './MatrixClientPeg'; import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks"; @@ -236,7 +237,8 @@ const sanitizeHtmlParams: sanitizeHtml.IOptions = { allowedAttributes: { // custom ones first: font: ['color', 'data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix - span: ['data-mx-bg-color', 'data-mx-color', 'data-mx-spoiler', 'style'], // custom to matrix + span: ['data-mx-maths', 'data-mx-bg-color', 'data-mx-color', 'data-mx-spoiler', 'style'], // custom to matrix + div: ['data-mx-maths'], a: ['href', 'name', 'target', 'rel'], // remote target: custom to matrix img: ['src', 'width', 'height', 'alt', 'title'], ol: ['start'], @@ -409,6 +411,27 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts if (isHtmlMessage) { isDisplayedWithHtml = true; safeBody = sanitizeHtml(formattedBody, sanitizeParams); + if (true) { // TODO: add katex setting + const mathDelimiters = [ + { left: "
.*?
", display: true }, + { left: ".*?", display: false } + ]; + + mathDelimiters.forEach(function (d) { + var reg = RegExp(d.left + "(.*?)" + d.right, "g"); + + safeBody = safeBody.replace(reg, function(match, p1) { + return katex.renderToString( + AllHtmlEntities.decode(p1), + { + throwOnError: false, + displayMode: d.display, + output: "mathml" + }) + }); + }); + } + } } finally { delete sanitizeParams.textFilter; @@ -450,6 +473,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts 'markdown-body': isHtmlMessage && !emojiBody, }); + return isDisplayedWithHtml ? { @@ -38,7 +39,27 @@ export function mdSerialize(model: EditorModel) { } export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = {}) { - const md = mdSerialize(model); + var md = mdSerialize(model); + + if (true) { // TODO: add katex setting + const mathDelimiters = [ // TODO: make customizable + { left: "\\$\\$\\$", right: "\\$\\$\\$", display: true }, + { left: "\\$\\$", right: "\\$\\$", display: false } + ]; + + mathDelimiters.forEach(function (d) { + var reg = RegExp(d.left + "(.*?)" + d.right, "g"); + md = md.replace(reg, function(match, p1) { + const p1e = AllHtmlEntities.encode(p1); + if (d.display == true) { + return `
${p1e}
`; + } else { + return `${p1e}`; + } + }); + }); + } + const parser = new Markdown(md); if (!parser.isPlainText() || forceHTML) { return parser.toHTML(); From e78734bbf6b2fbf1ebee530921998ff97c56f203 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sun, 20 Sep 2020 14:20:35 +0100 Subject: [PATCH 0023/1984] Deserialize back to math delimiters for editing --- src/HtmlUtils.tsx | 4 +++- src/editor/deserialize.ts | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 344fb3514c..46bc7b441c 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -534,7 +534,6 @@ export function checkBlockNode(node: Node) { case "H6": case "PRE": case "BLOCKQUOTE": - case "DIV": case "P": case "UL": case "OL": @@ -547,6 +546,9 @@ export function checkBlockNode(node: Node) { case "TH": case "TD": return true; + case "DIV": + // don't treat math nodes as block nodes for deserializing + return !(node as HTMLElement).hasAttribute("data-mx-maths"); default: return false; } diff --git a/src/editor/deserialize.ts b/src/editor/deserialize.ts index ec697b193c..edaa330e50 100644 --- a/src/editor/deserialize.ts +++ b/src/editor/deserialize.ts @@ -130,6 +130,18 @@ function parseElement(n: HTMLElement, partCreator: PartCreator, lastNode: HTMLEl } break; } + case "DIV": + case "SPAN": { + // math nodes are translated back into delimited latex strings + if (n.hasAttribute("data-mx-maths")) { + const delim = (n.nodeName == "SPAN") ? "$$" : "$$$"; + const tex = n.getAttribute("data-mx-maths"); + return partCreator.plain(delim + tex + delim); + } else if (!checkDescendInto(n)) { + return partCreator.plain(n.textContent); + } + break; + } case "OL": state.listIndex.push((n).start || 1); /* falls through */ From 428a6b94ff5c34533b8684e5ae8b019a4dbec07c Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sun, 20 Sep 2020 15:07:12 +0100 Subject: [PATCH 0024/1984] math off by default, enable with latex_maths flag --- src/HtmlUtils.tsx | 4 +++- src/editor/serialize.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 46bc7b441c..047a891847 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -28,6 +28,7 @@ import EMOJIBASE_REGEX from 'emojibase-regex'; import url from 'url'; import katex from 'katex'; import { AllHtmlEntities } from 'html-entities'; +import SdkConfig from './SdkConfig'; import {MatrixClientPeg} from './MatrixClientPeg'; import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks"; @@ -50,6 +51,7 @@ const ZWJ_REGEX = new RegExp("\u200D|\u2003", "g"); // Regex pattern for whitespace characters const WHITESPACE_REGEX = new RegExp("\\s", "g"); + const BIGEMOJI_REGEX = new RegExp(`^(${EMOJIBASE_REGEX.source})+$`, 'i'); const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/; @@ -411,7 +413,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts if (isHtmlMessage) { isDisplayedWithHtml = true; safeBody = sanitizeHtml(formattedBody, sanitizeParams); - if (true) { // TODO: add katex setting + if (SdkConfig.get()['latex_maths']) { const mathDelimiters = [ { left: "
.*?
", display: true }, { left: ".*?", display: false } diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 8ec590cba5..72a551a4a3 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -19,6 +19,7 @@ import Markdown from '../Markdown'; import {makeGenericPermalink} from "../utils/permalinks/Permalinks"; import EditorModel from "./model"; import { AllHtmlEntities } from 'html-entities'; +import SdkConfig from '../SdkConfig'; export function mdSerialize(model: EditorModel) { return model.parts.reduce((html, part) => { @@ -41,7 +42,7 @@ export function mdSerialize(model: EditorModel) { export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = {}) { var md = mdSerialize(model); - if (true) { // TODO: add katex setting + if (SdkConfig.get()['latex_maths']) { const mathDelimiters = [ // TODO: make customizable { left: "\\$\\$\\$", right: "\\$\\$\\$", display: true }, { left: "\\$\\$", right: "\\$\\$", display: false } From e4448ae1ad87cbd3e47c73a589012494ec7d4189 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sun, 20 Sep 2020 16:52:29 +0100 Subject: [PATCH 0025/1984] send fallback in pre tags, not code --- src/editor/serialize.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 72a551a4a3..c0d9509ffa 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -53,9 +53,9 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = md = md.replace(reg, function(match, p1) { const p1e = AllHtmlEntities.encode(p1); if (d.display == true) { - return `
${p1e}
`; + return `
${p1e}
`; } else { - return `${p1e}`; + return `
${p1e}
`; } }); }); From 7e6d7053e0a6c55f082153a521de079c7db2d77c Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sun, 20 Sep 2020 17:02:27 +0100 Subject: [PATCH 0026/1984] Revert "send fallback in pre tags, not code" (code looks better) This reverts commit e4448ae1ad87cbd3e47c73a589012494ec7d4189. --- src/editor/serialize.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index c0d9509ffa..72a551a4a3 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -53,9 +53,9 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = md = md.replace(reg, function(match, p1) { const p1e = AllHtmlEntities.encode(p1); if (d.display == true) { - return `
${p1e}
`; + return `
${p1e}
`; } else { - return `
${p1e}
`; + return `${p1e}`; } }); }); From 1f24b5b90c9fe6a743db17d14b726e1aefd15f6f Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sun, 20 Sep 2020 17:48:42 +0100 Subject: [PATCH 0027/1984] made math display slightly larger --- res/css/structures/_RoomView.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 572c7166d2..571c34fcb0 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -205,6 +205,10 @@ limitations under the License. clear: both; } +.mx_RoomView_MessageList .katex { + font-size: 1.3em; +} + li.mx_RoomView_myReadMarker_container { height: 0px; margin: 0px; From 24a1834f9b37993b79ec92c1c3081d6aa7777d37 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Mon, 21 Sep 2020 09:00:24 +0100 Subject: [PATCH 0028/1984] support multi-line and escaped $ --- src/HtmlUtils.tsx | 6 +++--- src/editor/serialize.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 047a891847..569b1662fe 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -415,12 +415,12 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts safeBody = sanitizeHtml(formattedBody, sanitizeParams); if (SdkConfig.get()['latex_maths']) { const mathDelimiters = [ - { left: "
.*?
", display: true }, - { left: ".*?", display: false } + { pattern: "
(.|\\s)*?
", display: true }, + { pattern: "(.|\\s)*?", display: false } ]; mathDelimiters.forEach(function (d) { - var reg = RegExp(d.left + "(.*?)" + d.right, "g"); + var reg = RegExp(d.pattern, "gm"); safeBody = safeBody.replace(reg, function(match, p1) { return katex.renderToString( diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 72a551a4a3..d0a28266eb 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -44,12 +44,12 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = if (SdkConfig.get()['latex_maths']) { const mathDelimiters = [ // TODO: make customizable - { left: "\\$\\$\\$", right: "\\$\\$\\$", display: true }, - { left: "\\$\\$", right: "\\$\\$", display: false } + { pattern: "\\$\\$\\$(([^$]|\\\\\\$)*)\\$\\$\\$", display: true }, + { pattern: "\\$\\$(([^$]|\\\\\\$)*)\\$\\$", display: false } ]; mathDelimiters.forEach(function (d) { - var reg = RegExp(d.left + "(.*?)" + d.right, "g"); + var reg = RegExp(d.pattern, "gm"); md = md.replace(reg, function(match, p1) { const p1e = AllHtmlEntities.encode(p1); if (d.display == true) { From 4df8754aad0333c840eceb1892faa9f3c90f2405 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Mon, 21 Sep 2020 11:00:39 +0100 Subject: [PATCH 0029/1984] allow custom latex delimiters in config.json --- src/editor/deserialize.ts | 10 ++++++++-- src/editor/serialize.ts | 26 ++++++++++++-------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/editor/deserialize.ts b/src/editor/deserialize.ts index edaa330e50..e27eecd2db 100644 --- a/src/editor/deserialize.ts +++ b/src/editor/deserialize.ts @@ -21,6 +21,7 @@ import { walkDOMDepthFirst } from "./dom"; import { checkBlockNode } from "../HtmlUtils"; import { getPrimaryPermalinkEntity } from "../utils/permalinks/Permalinks"; import { PartCreator } from "./parts"; +import SdkConfig from "../SdkConfig"; function parseAtRoomMentions(text: string, partCreator: PartCreator) { const ATROOM = "@room"; @@ -134,9 +135,14 @@ function parseElement(n: HTMLElement, partCreator: PartCreator, lastNode: HTMLEl case "SPAN": { // math nodes are translated back into delimited latex strings if (n.hasAttribute("data-mx-maths")) { - const delim = (n.nodeName == "SPAN") ? "$$" : "$$$"; + const delimLeft = (n.nodeName == "SPAN") ? + (SdkConfig.get()['latex_maths_delims'] || {})['inline_left'] || "$$" : + (SdkConfig.get()['latex_maths_delims'] || {})['display_left'] || "$$$"; + const delimRight = (n.nodeName == "SPAN") ? + (SdkConfig.get()['latex_maths_delims'] || {})['inline_right'] || "$$" : + (SdkConfig.get()['latex_maths_delims'] || {})['display_right'] || "$$$"; const tex = n.getAttribute("data-mx-maths"); - return partCreator.plain(delim + tex + delim); + return partCreator.plain(delimLeft + tex + delimRight); } else if (!checkDescendInto(n)) { return partCreator.plain(n.textContent); } diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index d0a28266eb..da8ae4e820 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -43,21 +43,19 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = var md = mdSerialize(model); if (SdkConfig.get()['latex_maths']) { - const mathDelimiters = [ // TODO: make customizable - { pattern: "\\$\\$\\$(([^$]|\\\\\\$)*)\\$\\$\\$", display: true }, - { pattern: "\\$\\$(([^$]|\\\\\\$)*)\\$\\$", display: false } - ]; + const displayPattern = (SdkConfig.get()['latex_maths_delims'] || {})['display_pattern'] || + "\\$\\$\\$(([^$]|\\\\\\$)*)\\$\\$\\$"; + const inlinePattern = (SdkConfig.get()['latex_maths_delims'] || {})['inline_pattern'] || + "\\$\\$(([^$]|\\\\\\$)*)\\$\\$"; - mathDelimiters.forEach(function (d) { - var reg = RegExp(d.pattern, "gm"); - md = md.replace(reg, function(match, p1) { - const p1e = AllHtmlEntities.encode(p1); - if (d.display == true) { - return `
${p1e}
`; - } else { - return `${p1e}`; - } - }); + md = md.replace(RegExp(displayPattern, "gm"), function(m,p1) { + const p1e = AllHtmlEntities.encode(p1); + return `
${p1e}
`; + }); + + md = md.replace(RegExp(inlinePattern, "gm"), function(m,p1) { + const p1e = AllHtmlEntities.encode(p1); + return `${p1e}`; }); } From 1b689bb4e11c1329072a85002ea90abfaf9043df Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Mon, 21 Sep 2020 22:02:19 +0100 Subject: [PATCH 0030/1984] tell markdown to ignore math tags --- src/Markdown.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Markdown.js b/src/Markdown.js index 492450e87d..dc15e7d6b3 100644 --- a/src/Markdown.js +++ b/src/Markdown.js @@ -16,13 +16,19 @@ limitations under the License. import commonmark from 'commonmark'; import {escape} from "lodash"; +import SdkConfig from './SdkConfig'; -const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u']; +const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u', 'code']; // These types of node are definitely text const TEXT_NODES = ['text', 'softbreak', 'linebreak', 'paragraph', 'document']; function is_allowed_html_tag(node) { + if (SdkConfig.get()['latex_maths'] && + node.literal.match(/^<\/?(div|span)( data-mx-maths="[^"]*")?>$/) != null) { + return true; + } + // Regex won't work for tags with attrs, but we only // allow anyway. const matches = /^<\/?(.*)>$/.exec(node.literal); @@ -30,6 +36,7 @@ function is_allowed_html_tag(node) { const tag = matches[1]; return ALLOWED_HTML_TAGS.indexOf(tag) > -1; } + return false; } From aded3c9de2b14010612b7d9581b10366d9dc3be2 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Tue, 22 Sep 2020 11:54:23 +0100 Subject: [PATCH 0031/1984] cosmetic changes (lint) --- src/HtmlUtils.tsx | 13 +++++-------- src/editor/serialize.ts | 6 +++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 569b1662fe..7bccd47622 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -416,24 +416,21 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts if (SdkConfig.get()['latex_maths']) { const mathDelimiters = [ { pattern: "
(.|\\s)*?
", display: true }, - { pattern: "(.|\\s)*?", display: false } + { pattern: "(.|\\s)*?", display: false }, ]; - mathDelimiters.forEach(function (d) { - var reg = RegExp(d.pattern, "gm"); - - safeBody = safeBody.replace(reg, function(match, p1) { + mathDelimiters.forEach(function(d) { + safeBody = safeBody.replace(RegExp(d.pattern, "gm"), function(m, p1) { return katex.renderToString( AllHtmlEntities.decode(p1), { throwOnError: false, displayMode: d.display, - output: "mathml" + output: "mathml", }) }); }); - } - + } } } finally { delete sanitizeParams.textFilter; diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index da8ae4e820..02194a1d59 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -40,7 +40,7 @@ export function mdSerialize(model: EditorModel) { } export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = {}) { - var md = mdSerialize(model); + let md = mdSerialize(model); if (SdkConfig.get()['latex_maths']) { const displayPattern = (SdkConfig.get()['latex_maths_delims'] || {})['display_pattern'] || @@ -48,12 +48,12 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = const inlinePattern = (SdkConfig.get()['latex_maths_delims'] || {})['inline_pattern'] || "\\$\\$(([^$]|\\\\\\$)*)\\$\\$"; - md = md.replace(RegExp(displayPattern, "gm"), function(m,p1) { + md = md.replace(RegExp(displayPattern, "gm"), function(m, p1) { const p1e = AllHtmlEntities.encode(p1); return `
${p1e}
`; }); - md = md.replace(RegExp(inlinePattern, "gm"), function(m,p1) { + md = md.replace(RegExp(inlinePattern, "gm"), function(m, p1) { const p1e = AllHtmlEntities.encode(p1); return `${p1e}`; }); From 0dc5200b0e858f9c922d67fe5323ab241c51513e Mon Sep 17 00:00:00 2001 From: Glandos Date: Thu, 3 Sep 2020 15:10:33 +0200 Subject: [PATCH 0032/1984] Push name to the end, near text, in IRC layout Currently, the name (and aux message) are align to the start, leaving a blank space between the end of the name and the message. In a lot of IRC themes, names (and actions) are aligned to the end, next to the message, for a better readability. Signed-off-by: Adrien CLERC --- res/css/views/rooms/_IRCLayout.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss index 958d718b11..ece547d02b 100644 --- a/res/css/views/rooms/_IRCLayout.scss +++ b/res/css/views/rooms/_IRCLayout.scss @@ -186,6 +186,7 @@ $irc-line-height: $font-18px; overflow: hidden; text-overflow: ellipsis; min-width: var(--name-width); + text-align: end; } } } From d2054ea685bad49af11ec9a64b5aa4218bc204c0 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Fri, 25 Sep 2020 09:05:22 +0100 Subject: [PATCH 0033/1984] HTML output for cross-browser support --- res/css/structures/_RoomView.scss | 4 ---- src/HtmlUtils.tsx | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 571c34fcb0..572c7166d2 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -205,10 +205,6 @@ limitations under the License. clear: both; } -.mx_RoomView_MessageList .katex { - font-size: 1.3em; -} - li.mx_RoomView_myReadMarker_container { height: 0px; margin: 0px; diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 7bccd47622..70a2a3f000 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -426,7 +426,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts { throwOnError: false, displayMode: d.display, - output: "mathml", + output: "htmlAndMathml", }) }); }); From 43ff97c1789be080b8ec69e3045d7a262e3dfd31 Mon Sep 17 00:00:00 2001 From: Daniel Maslowski Date: Wed, 9 Sep 2020 20:35:26 +0200 Subject: [PATCH 0034/1984] Add support for giving reason when redacting Signed-off-by: Daniel Maslowski --- src/components/views/context_menus/MessageContextMenu.js | 4 +++- src/components/views/dialogs/ConfirmRedactDialog.js | 8 +++++--- src/i18n/strings/en_EN.json | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index d760c8defa..b6d27e45f9 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -145,7 +145,7 @@ export default class MessageContextMenu extends React.Component { onRedactClick = () => { const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog"); Modal.createTrackedDialog('Confirm Redact Dialog', '', ConfirmRedactDialog, { - onFinished: async (proceed) => { + onFinished: async (proceed, reason) => { if (!proceed) return; const cli = MatrixClientPeg.get(); @@ -153,6 +153,8 @@ export default class MessageContextMenu extends React.Component { await cli.redactEvent( this.props.mxEvent.getRoomId(), this.props.mxEvent.getId(), + undefined, + reason ? { reason } : {}, ); } catch (e) { const code = e.errcode || e.statusCode; diff --git a/src/components/views/dialogs/ConfirmRedactDialog.js b/src/components/views/dialogs/ConfirmRedactDialog.js index 3106df1d5b..2216f9a93a 100644 --- a/src/components/views/dialogs/ConfirmRedactDialog.js +++ b/src/components/views/dialogs/ConfirmRedactDialog.js @@ -23,15 +23,17 @@ import { _t } from '../../../languageHandler'; */ export default class ConfirmRedactDialog extends React.Component { render() { - const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog'); + const TextInputDialog = sdk.getComponent('views.dialogs.TextInputDialog'); return ( - - + ); } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 65374ea3ec..ecc4bd2f4c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1624,6 +1624,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", From 65c4460abcdb64bac14bdd72e3b970a96dd52299 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Fri, 9 Oct 2020 15:47:11 +0100 Subject: [PATCH 0035/1984] whitespace fixes --- src/HtmlUtils.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 70a2a3f000..da3eb3b128 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -51,7 +51,6 @@ const ZWJ_REGEX = new RegExp("\u200D|\u2003", "g"); // Regex pattern for whitespace characters const WHITESPACE_REGEX = new RegExp("\\s", "g"); - const BIGEMOJI_REGEX = new RegExp(`^(${EMOJIBASE_REGEX.source})+$`, 'i'); const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/; @@ -472,7 +471,6 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts 'markdown-body': isHtmlMessage && !emojiBody, }); - return isDisplayedWithHtml ? Date: Sat, 10 Oct 2020 09:12:53 +0100 Subject: [PATCH 0036/1984] only allow code tags inside math tag --- src/Markdown.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Markdown.js b/src/Markdown.js index dc15e7d6b3..9914cff85a 100644 --- a/src/Markdown.js +++ b/src/Markdown.js @@ -18,14 +18,21 @@ import commonmark from 'commonmark'; import {escape} from "lodash"; import SdkConfig from './SdkConfig'; -const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u', 'code']; +const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u']; // These types of node are definitely text const TEXT_NODES = ['text', 'softbreak', 'linebreak', 'paragraph', 'document']; +function is_math_node(node) { + return node != null && + node.literal != null && + node.literal.match(/^<((div|span) data-mx-maths="[^"]*"|\/(div|span))>$/) != null; +} + function is_allowed_html_tag(node) { if (SdkConfig.get()['latex_maths'] && - node.literal.match(/^<\/?(div|span)( data-mx-maths="[^"]*")?>$/) != null) { + (is_math_node(node) || + (node.literal.match(/^<\/?code>$/) && is_math_node(node.parent)))) { return true; } From 96742fc3093cc88cd609d731d932a05ab094262f Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sat, 10 Oct 2020 16:32:49 +0100 Subject: [PATCH 0037/1984] latex math as labs setting --- src/HtmlUtils.tsx | 4 ++-- src/Markdown.js | 4 ++-- src/editor/serialize.ts | 3 ++- src/settings/Settings.ts | 6 ++++++ 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index da3eb3b128..ca718cd9aa 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -28,7 +28,7 @@ import EMOJIBASE_REGEX from 'emojibase-regex'; import url from 'url'; import katex from 'katex'; import { AllHtmlEntities } from 'html-entities'; -import SdkConfig from './SdkConfig'; +import SettingsStore from './settings/SettingsStore'; import {MatrixClientPeg} from './MatrixClientPeg'; import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks"; @@ -412,7 +412,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts if (isHtmlMessage) { isDisplayedWithHtml = true; safeBody = sanitizeHtml(formattedBody, sanitizeParams); - if (SdkConfig.get()['latex_maths']) { + if (SettingsStore.getValue("feature_latex_maths")) { const mathDelimiters = [ { pattern: "
(.|\\s)*?
", display: true }, { pattern: "(.|\\s)*?", display: false }, diff --git a/src/Markdown.js b/src/Markdown.js index 9914cff85a..329dcdd996 100644 --- a/src/Markdown.js +++ b/src/Markdown.js @@ -16,7 +16,7 @@ limitations under the License. import commonmark from 'commonmark'; import {escape} from "lodash"; -import SdkConfig from './SdkConfig'; +import SettingsStore from './settings/SettingsStore'; const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u']; @@ -30,7 +30,7 @@ function is_math_node(node) { } function is_allowed_html_tag(node) { - if (SdkConfig.get()['latex_maths'] && + if (SettingsStore.getValue("feature_latex_maths") && (is_math_node(node) || (node.literal.match(/^<\/?code>$/) && is_math_node(node.parent)))) { return true; diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 02194a1d59..9f24cd5eb2 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -19,6 +19,7 @@ import Markdown from '../Markdown'; import {makeGenericPermalink} from "../utils/permalinks/Permalinks"; import EditorModel from "./model"; import { AllHtmlEntities } from 'html-entities'; +import SettingsStore from '../settings/SettingsStore'; import SdkConfig from '../SdkConfig'; export function mdSerialize(model: EditorModel) { @@ -42,7 +43,7 @@ export function mdSerialize(model: EditorModel) { export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = {}) { let md = mdSerialize(model); - if (SdkConfig.get()['latex_maths']) { + if (SettingsStore.getValue("feature_latex_maths")) { const displayPattern = (SdkConfig.get()['latex_maths_delims'] || {})['display_pattern'] || "\\$\\$\\$(([^$]|\\\\\\$)*)\\$\\$\\$"; const inlinePattern = (SdkConfig.get()['latex_maths_delims'] || {})['inline_pattern'] || diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 737c882919..2f817c264c 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -116,6 +116,12 @@ export interface ISetting { } export const SETTINGS: {[setting: string]: ISetting} = { + "feature_latex_maths": { + isFeature: true, + displayName: _td("LaTeX math in messages"), + supportedLevels: LEVELS_FEATURE, + default: false, + }, "feature_communities_v2_prototypes": { isFeature: true, displayName: _td( From a89adb86a5912d3ce71171583181175fe2564a23 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sat, 10 Oct 2020 16:33:25 +0100 Subject: [PATCH 0038/1984] i18n en+nl for latex math labs setting --- src/i18n/strings/en_EN.json | 1 + src/i18n/strings/en_US.json | 1 + src/i18n/strings/nl.json | 1 + 3 files changed, 3 insertions(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d7360430ae..d7b40fc198 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -856,6 +856,7 @@ "click to reveal": "click to reveal", "Clear cache and reload": "Clear cache and reload", "Labs": "Labs", + "LaTeX math in messages": "LaTeX math in messages", "Customise your experience with experimental labs features. Learn more.": "Customise your experience with experimental labs features. Learn more.", "Ignored/Blocked": "Ignored/Blocked", "Error adding ignored user/server": "Error adding ignored user/server", diff --git a/src/i18n/strings/en_US.json b/src/i18n/strings/en_US.json index a1275fb089..c00bf03b29 100644 --- a/src/i18n/strings/en_US.json +++ b/src/i18n/strings/en_US.json @@ -128,6 +128,7 @@ "Kick": "Kick", "Kicks user with given id": "Kicks user with given id", "Labs": "Labs", + "LaTeX math in messages": "LaTeX math in messages", "Ignore": "Ignore", "Unignore": "Unignore", "You are now ignoring %(userId)s": "You are now ignoring %(userId)s", diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index bb0fb5def6..d991962eec 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -199,6 +199,7 @@ "%(targetName)s joined the room.": "%(targetName)s is tot het gesprek toegetreden.", "Jump to first unread message.": "Spring naar het eerste ongelezen bericht.", "Labs": "Experimenteel", + "LaTeX math in messages": "LaTeX wiskunde in berichten", "Last seen": "Laatst gezien", "Leave room": "Gesprek verlaten", "%(targetName)s left the room.": "%(targetName)s heeft het gesprek verlaten.", From bdd332c8b5366398d4af166b49b3eaf1cddb6230 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sat, 10 Oct 2020 20:05:35 +0100 Subject: [PATCH 0039/1984] ran yarn i18n --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a33104ab12..b41a19aa21 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -438,6 +438,7 @@ "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", "Change notification settings": "Change notification settings", + "LaTeX math in messages": "LaTeX math in messages", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.", "New spinner design": "New spinner design", "Message Pinning": "Message Pinning", @@ -848,7 +849,6 @@ "click to reveal": "click to reveal", "Clear cache and reload": "Clear cache and reload", "Labs": "Labs", - "LaTeX math in messages": "LaTeX math in messages", "Customise your experience with experimental labs features. Learn more.": "Customise your experience with experimental labs features. Learn more.", "Ignored/Blocked": "Ignored/Blocked", "Error adding ignored user/server": "Error adding ignored user/server", From f0c4473107d0c3589479809d8accd79b9c4dba08 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Mon, 12 Oct 2020 21:01:11 +0100 Subject: [PATCH 0040/1984] tell markdown parser to ignore properly-formatted math tags --- src/Markdown.js | 51 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/src/Markdown.js b/src/Markdown.js index 329dcdd996..564a2ed0a8 100644 --- a/src/Markdown.js +++ b/src/Markdown.js @@ -23,19 +23,47 @@ const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u']; // These types of node are definitely text const TEXT_NODES = ['text', 'softbreak', 'linebreak', 'paragraph', 'document']; -function is_math_node(node) { - return node != null && - node.literal != null && - node.literal.match(/^<((div|span) data-mx-maths="[^"]*"|\/(div|span))>$/) != null; +// prevent renderer from interpreting contents of AST node +function freeze_node(walker, node) { + const newNode = new commonmark.Node('custom_inline', node.sourcepos); + newNode.onEnter = node.literal; + node.insertAfter(newNode); + node.unlink(); + walker.resumeAt(newNode.next, true); +} + +// prevent renderer from interpreting contents of latex math tags +function freeze_math(parsed) { + const walker = parsed.walker(); + let ev; + let inMath = false; + while ( (ev = walker.next()) ) { + const node = ev.node; + if (ev.entering) { + if (!inMath) { + // entering a math tag + if (node.literal != null && node.literal.match('^<(div|span) data-mx-maths="[^"]*">$') != null) { + inMath = true; + freeze_node(walker, node); + } + } else { + // math tags should only contain a single code block, with URL-escaped latex as fallback output + if (node.literal != null && node.literal.match('^(||[^<>]*)$')) { + freeze_node(walker, node); + // leave when span or div is closed + } else if (node.literal == '
' || node.literal == '') { + inMath = false; + freeze_node(walker, node); + // this case only happens if we have improperly formatted math tags, so bail + } else { + inMath = false; + } + } + } + } } function is_allowed_html_tag(node) { - if (SettingsStore.getValue("feature_latex_maths") && - (is_math_node(node) || - (node.literal.match(/^<\/?code>$/) && is_math_node(node.parent)))) { - return true; - } - // Regex won't work for tags with attrs, but we only // allow anyway. const matches = /^<\/?(.*)>$/.exec(node.literal); @@ -173,6 +201,9 @@ export default class Markdown { */ }; + // prevent strange behaviour when mixing latex math and markdown + freeze_math(this.parsed); + return renderer.render(this.parsed); } From 38d1aac978d49160bed9c96b2a1205a4e7fb707f Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Mon, 12 Oct 2020 21:15:38 +0100 Subject: [PATCH 0041/1984] removed useless import and whitespace --- src/Markdown.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Markdown.js b/src/Markdown.js index 564a2ed0a8..2e6f391818 100644 --- a/src/Markdown.js +++ b/src/Markdown.js @@ -16,7 +16,6 @@ limitations under the License. import commonmark from 'commonmark'; import {escape} from "lodash"; -import SettingsStore from './settings/SettingsStore'; const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u']; @@ -71,7 +70,6 @@ function is_allowed_html_tag(node) { const tag = matches[1]; return ALLOWED_HTML_TAGS.indexOf(tag) > -1; } - return false; } From f36651f5380f1c119577495622365a015b34cdba Mon Sep 17 00:00:00 2001 From: Heiko Carrasco Date: Sat, 26 Sep 2020 23:21:16 +0200 Subject: [PATCH 0042/1984] Add keyboard shortcut to close current conversations Signed-off-by: Heiko Carrasco --- src/accessibility/KeyboardShortcuts.tsx | 6 ++++++ src/components/structures/LoggedInView.tsx | 12 +++++++++++- src/i18n/strings/en_EN.json | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/accessibility/KeyboardShortcuts.tsx b/src/accessibility/KeyboardShortcuts.tsx index 58d8124122..48d0eb2ab1 100644 --- a/src/accessibility/KeyboardShortcuts.tsx +++ b/src/accessibility/KeyboardShortcuts.tsx @@ -257,6 +257,12 @@ const shortcuts: Record = { key: Key.SLASH, }], description: _td("Toggle this dialog"), + }, { + keybinds: [{ + modifiers: [CMD_OR_CTRL, Modifiers.ALT], + key: Key.H, + }], + description: _td("Go to Home View"), }, ], diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 79f2916200..e7256e4cd4 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -21,7 +21,7 @@ import * as PropTypes from 'prop-types'; import { MatrixClient } from 'matrix-js-sdk/src/client'; import { DragDropContext } from 'react-beautiful-dnd'; -import {Key, isOnlyCtrlOrCmdKeyEvent, isOnlyCtrlOrCmdIgnoreShiftKeyEvent} from '../../Keyboard'; +import {Key, isOnlyCtrlOrCmdKeyEvent, isOnlyCtrlOrCmdIgnoreShiftKeyEvent, isMac} from '../../Keyboard'; import PageTypes from '../../PageTypes'; import CallMediaHandler from '../../CallMediaHandler'; import { fixupColorFonts } from '../../utils/FontManager'; @@ -400,6 +400,7 @@ class LoggedInView extends React.Component { const ctrlCmdOnly = isOnlyCtrlOrCmdKeyEvent(ev); const hasModifier = ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey; const isModifier = ev.key === Key.ALT || ev.key === Key.CONTROL || ev.key === Key.META || ev.key === Key.SHIFT; + const modKey = isMac ? ev.metaKey : ev.ctrlKey; switch (ev.key) { case Key.PAGE_UP: @@ -444,6 +445,15 @@ class LoggedInView extends React.Component { } break; + case Key.H: + if (ev.altKey && modKey) { + dis.dispatch({ + action: 'view_home_page', + }); + handled = true; + } + break; + case Key.ARROW_UP: case Key.ARROW_DOWN: if (ev.altKey && !ev.ctrlKey && !ev.metaKey) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index eb8f9100ec..0bc430d87a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2350,6 +2350,7 @@ "Activate selected button": "Activate selected button", "Toggle right panel": "Toggle right panel", "Toggle this dialog": "Toggle this dialog", + "Go to Home View": "Go to Home View", "Move autocomplete selection up/down": "Move autocomplete selection up/down", "Cancel autocomplete": "Cancel autocomplete", "Page Up": "Page Up", From d7f15985f590232a72be980570d98d81b7efd45c Mon Sep 17 00:00:00 2001 From: Heiko Carrasco Date: Tue, 13 Oct 2020 18:26:48 +0200 Subject: [PATCH 0043/1984] Close all active modals when home shortcut is used Signed-off-by: Heiko Carrasco --- src/Modal.tsx | 9 +++++++++ src/components/structures/LoggedInView.tsx | 2 ++ 2 files changed, 11 insertions(+) diff --git a/src/Modal.tsx b/src/Modal.tsx index b0f6ef988e..b6074e4807 100644 --- a/src/Modal.tsx +++ b/src/Modal.tsx @@ -147,6 +147,15 @@ export class ModalManager { return this.appendDialogAsync(...rest); } + public closeCurrentModal(reason: string) { + const modal = this.getCurrentModal(); + if (!modal) { + return; + } + modal.closeReason = reason; + modal.close(); + } + private buildModal( prom: Promise, props?: IProps, diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index e7256e4cd4..98921d03a1 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -52,6 +52,7 @@ import RoomListStore from "../../stores/room-list/RoomListStore"; import NonUrgentToastContainer from "./NonUrgentToastContainer"; import { ToggleRightPanelPayload } from "../../dispatcher/payloads/ToggleRightPanelPayload"; import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; +import Modal from "../../Modal"; // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. @@ -450,6 +451,7 @@ class LoggedInView extends React.Component { dis.dispatch({ action: 'view_home_page', }); + Modal.closeCurrentModal("homeKeyboardShortcut"); handled = true; } break; From cc713aff72c56478edb4f1eafbdc55b8c9fd4248 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Wed, 14 Oct 2020 09:35:57 +0100 Subject: [PATCH 0044/1984] add fallback output in code block AFTER markdown processing --- src/Markdown.js | 49 +++++------------------------------------ src/editor/serialize.ts | 18 ++++++++++++--- 2 files changed, 21 insertions(+), 46 deletions(-) diff --git a/src/Markdown.js b/src/Markdown.js index 2e6f391818..dc4d442aff 100644 --- a/src/Markdown.js +++ b/src/Markdown.js @@ -22,47 +22,12 @@ const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u']; // These types of node are definitely text const TEXT_NODES = ['text', 'softbreak', 'linebreak', 'paragraph', 'document']; -// prevent renderer from interpreting contents of AST node -function freeze_node(walker, node) { - const newNode = new commonmark.Node('custom_inline', node.sourcepos); - newNode.onEnter = node.literal; - node.insertAfter(newNode); - node.unlink(); - walker.resumeAt(newNode.next, true); -} - -// prevent renderer from interpreting contents of latex math tags -function freeze_math(parsed) { - const walker = parsed.walker(); - let ev; - let inMath = false; - while ( (ev = walker.next()) ) { - const node = ev.node; - if (ev.entering) { - if (!inMath) { - // entering a math tag - if (node.literal != null && node.literal.match('^<(div|span) data-mx-maths="[^"]*">$') != null) { - inMath = true; - freeze_node(walker, node); - } - } else { - // math tags should only contain a single code block, with URL-escaped latex as fallback output - if (node.literal != null && node.literal.match('^(||[^<>]*)$')) { - freeze_node(walker, node); - // leave when span or div is closed - } else if (node.literal == '
' || node.literal == '') { - inMath = false; - freeze_node(walker, node); - // this case only happens if we have improperly formatted math tags, so bail - } else { - inMath = false; - } - } - } - } -} - function is_allowed_html_tag(node) { + if (node.literal != null && + node.literal.match('^<((div|span) data-mx-maths="[^"]*"|\/(div|span))>$') != null) { + return true; + } + // Regex won't work for tags with attrs, but we only // allow anyway. const matches = /^<\/?(.*)>$/.exec(node.literal); @@ -70,6 +35,7 @@ function is_allowed_html_tag(node) { const tag = matches[1]; return ALLOWED_HTML_TAGS.indexOf(tag) > -1; } + return false; } @@ -199,9 +165,6 @@ export default class Markdown { */ }; - // prevent strange behaviour when mixing latex math and markdown - freeze_math(this.parsed); - return renderer.render(this.parsed); } diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 9f24cd5eb2..88fd1c90fc 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -21,6 +21,7 @@ import EditorModel from "./model"; import { AllHtmlEntities } from 'html-entities'; import SettingsStore from '../settings/SettingsStore'; import SdkConfig from '../SdkConfig'; +import cheerio from 'cheerio'; export function mdSerialize(model: EditorModel) { return model.parts.reduce((html, part) => { @@ -51,18 +52,29 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = md = md.replace(RegExp(displayPattern, "gm"), function(m, p1) { const p1e = AllHtmlEntities.encode(p1); - return `
${p1e}
`; + return `
`; }); md = md.replace(RegExp(inlinePattern, "gm"), function(m, p1) { const p1e = AllHtmlEntities.encode(p1); - return `${p1e}`; + return ``; }); } const parser = new Markdown(md); if (!parser.isPlainText() || forceHTML) { - return parser.toHTML(); + // feed Markdown output to HTML parser + const phtml = cheerio.load(parser.toHTML(), + { _useHtmlParser2: true, decodeEntities: false }) + + // add fallback output for latex math, which should not be interpreted as markdown + phtml('div, span').each(function() { + const tex = phtml(this).attr('data-mx-maths') + if (tex) { + phtml(this).html(`${tex}`) + } + }); + return phtml.html(); } // ensure removal of escape backslashes in non-Markdown messages if (md.indexOf("\\") > -1) { From 10b732131a7315aca652677857a285d7dabb243b Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Wed, 14 Oct 2020 22:16:28 +0100 Subject: [PATCH 0045/1984] use html parser rather than regexes --- src/HtmlUtils.tsx | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 6bae0b25b6..dc2f45210b 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -30,6 +30,7 @@ import url from 'url'; import katex from 'katex'; import { AllHtmlEntities } from 'html-entities'; import SettingsStore from './settings/SettingsStore'; +import cheerio from 'cheerio'; import {MatrixClientPeg} from './MatrixClientPeg'; import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks"; @@ -414,23 +415,20 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts if (isHtmlMessage) { isDisplayedWithHtml = true; safeBody = sanitizeHtml(formattedBody, sanitizeParams); - if (SettingsStore.getValue("feature_latex_maths")) { - const mathDelimiters = [ - { pattern: "
(.|\\s)*?
", display: true }, - { pattern: "(.|\\s)*?", display: false }, - ]; + const phtml = cheerio.load(safeBody, + { _useHtmlParser2: true, decodeEntities: false }) - mathDelimiters.forEach(function(d) { - safeBody = safeBody.replace(RegExp(d.pattern, "gm"), function(m, p1) { - return katex.renderToString( - AllHtmlEntities.decode(p1), - { - throwOnError: false, - displayMode: d.display, - output: "htmlAndMathml", - }) - }); + if (SettingsStore.getValue("feature_latex_maths")) { + phtml('div, span[data-mx-maths!=""]').replaceWith(function(i, e) { + return katex.renderToString( + AllHtmlEntities.decode(phtml(e).attr('data-mx-maths')), + { + throwOnError: false, + displayMode: e.name == 'div', + output: "htmlAndMathml", + }); }); + safeBody = phtml.html(); } } } finally { From 7506e9a85de9a7a1cb4b5836dc3ca4b234dd17b2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 15 Oct 2020 15:58:18 +0100 Subject: [PATCH 0046/1984] Disable notifications for the room you have recently been active in Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/Notifier.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Notifier.ts b/src/Notifier.ts index 1899896f9b..6460be20ad 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -34,6 +34,8 @@ import SettingsStore from "./settings/SettingsStore"; import { hideToast as hideNotificationsToast } from "./toasts/DesktopNotificationsToast"; import {SettingLevel} from "./settings/SettingLevel"; import {isPushNotifyDisabled} from "./settings/controllers/NotificationControllers"; +import RoomViewStore from "./stores/RoomViewStore"; +import UserActivity from "./UserActivity"; /* * Dispatches: @@ -376,6 +378,11 @@ export const Notifier = { const room = MatrixClientPeg.get().getRoom(ev.getRoomId()); const actions = MatrixClientPeg.get().getPushActionsForEvent(ev); if (actions && actions.notify) { + if (RoomViewStore.getRoomId() === room.roomId && UserActivity.sharedInstance().userActiveRecently()) { + // don't bother notifying as user was recently active in this room + return; + } + if (this.isEnabled()) { this._displayPopupNotification(ev, room); } From 176c7c32da6c8e8314889f6b8bbaa632b03c4f2d Mon Sep 17 00:00:00 2001 From: Bryan Kok Date: Sat, 17 Oct 2020 14:35:11 +0800 Subject: [PATCH 0047/1984] Search through the list of unfiltered rooms rather than the rooms in the state which are already filtered by the search text --- src/components/views/rooms/RoomSublist.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx index 4056f2fbd4..111692786c 100644 --- a/src/components/views/rooms/RoomSublist.tsx +++ b/src/components/views/rooms/RoomSublist.tsx @@ -420,7 +420,7 @@ export default class RoomSublist extends React.Component { room = this.state.rooms && this.state.rooms[0]; } else { // find the first room with a count of the same colour as the badge count - room = this.state.rooms.find((r: Room) => { + room = RoomListStore.instance.unfilteredLists[this.props.tagId].find((r: Room) => { const notifState = this.notificationState.getForRoom(r); return notifState.count > 0 && notifState.color === this.notificationState.color; }); From 7d22bbc00f49356cd5fec3565a19376ef8b0ef05 Mon Sep 17 00:00:00 2001 From: Bryan Kok Date: Sat, 17 Oct 2020 23:52:18 +0800 Subject: [PATCH 0048/1984] Trim spurious whitespace of nicknames --- src/components/views/settings/ProfileSettings.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/ProfileSettings.js b/src/components/views/settings/ProfileSettings.js index 92851ccaa0..294f80acd1 100644 --- a/src/components/views/settings/ProfileSettings.js +++ b/src/components/views/settings/ProfileSettings.js @@ -77,10 +77,12 @@ export default class ProfileSettings extends React.Component { const client = MatrixClientPeg.get(); const newState = {}; + const displayName = this.state.displayName.trim(); try { if (this.state.originalDisplayName !== this.state.displayName) { - await client.setDisplayName(this.state.displayName); - newState.originalDisplayName = this.state.displayName; + await client.setDisplayName(displayName); + newState.originalDisplayName = displayName; + newState.displayName = displayName; } if (this.state.avatarFile) { From fcbaea640daf3a036d55cb1bda5d7fed552c2d4e Mon Sep 17 00:00:00 2001 From: Bryan Kok Date: Sun, 18 Oct 2020 14:36:50 +0800 Subject: [PATCH 0049/1984] Trim room names changed through the UI --- src/components/views/room_settings/RoomProfileSettings.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/room_settings/RoomProfileSettings.js b/src/components/views/room_settings/RoomProfileSettings.js index ca09c3093a..b894663c16 100644 --- a/src/components/views/room_settings/RoomProfileSettings.js +++ b/src/components/views/room_settings/RoomProfileSettings.js @@ -95,10 +95,11 @@ export default class RoomProfileSettings extends React.Component { const newState = {}; // TODO: What do we do about errors? - + const displayName = this.state.displayName.trim(); if (this.state.originalDisplayName !== this.state.displayName) { - await client.setRoomName(this.props.roomId, this.state.displayName); - newState.originalDisplayName = this.state.displayName; + await client.setRoomName(this.props.roomId, displayName); + newState.originalDisplayName = displayName; + newState.displayName = displayName; } if (this.state.avatarFile) { From 091143db525e9eb1a563bd097333b2a6e644af22 Mon Sep 17 00:00:00 2001 From: Resynth Date: Sun, 18 Oct 2020 22:40:19 +0100 Subject: [PATCH 0050/1984] Add border-radius for video Images have a rounded border, so we may as well add it to videos. Works fine in Chrome, and in other spec-compliant browsers. Signed-off-by: Resynth --- res/css/views/messages/_MVideoBody.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/messages/_MVideoBody.scss b/res/css/views/messages/_MVideoBody.scss index 3b05c53f34..ac3491bc8f 100644 --- a/res/css/views/messages/_MVideoBody.scss +++ b/res/css/views/messages/_MVideoBody.scss @@ -18,5 +18,6 @@ span.mx_MVideoBody { video.mx_MVideoBody { max-width: 100%; height: auto; + border-radius: 4px; } } From 6e97baf09f80364d157233a8567c2c1bc106f302 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 12:53:17 +0200 Subject: [PATCH 0051/1984] Added license --- src/components/views/elements/Confetti.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/components/views/elements/Confetti.js b/src/components/views/elements/Confetti.js index b0f88dedb7..bef67cddcc 100644 --- a/src/components/views/elements/Confetti.js +++ b/src/components/views/elements/Confetti.js @@ -1,3 +1,23 @@ +/* +MIT License +Copyright (c) 2018 MathuSum Mut +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + const confetti = { //set max confetti count maxCount: 150, From 3358ed27921d137a2880f00dc0a1c603126b9bca Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 12:57:57 +0200 Subject: [PATCH 0052/1984] Use arrow functions --- src/components/structures/RoomView.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 92f43c75ca..53a964fbb8 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -754,14 +754,14 @@ export default createReactClass({ } } }, - onEventDecrypted(ev) { - if (!SettingsStore.getValue('dontShowChatEffects')) { - if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || - this.state.room.getUnreadNotificationCount() === 0) return; - this.handleConfetti(ev); - } + onEventDecrypted: (ev) => { + if (!SettingsStore.getValue('dontShowChatEffects')) { + if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || + this.state.room.getUnreadNotificationCount() === 0) return; + this.handleConfetti(ev); + } }, - handleConfetti(ev) { + handleConfetti: (ev) => { if (this.state.matrixClientIsReady) { const messageBody = _t('sends confetti'); if (isConfettiEmoji(ev.getContent()) || ev.getContent().body === messageBody) { From 4106f70218ef41e6101752e388b54b481cbe0576 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 13:24:22 +0200 Subject: [PATCH 0053/1984] Fixed merge error --- src/settings/Settings.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 99be728acc..b9ad834c83 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -626,6 +626,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td("Don't show chat effects"), default: false, + }, "Widgets.pinned": { supportedLevels: LEVELS_ROOM_OR_ACCOUNT, default: {}, From 929cc48cef28431cc059055240396ec7a2d173bb Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 13:30:52 +0200 Subject: [PATCH 0054/1984] Fixed eslint error --- src/components/structures/RoomView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 709864bff6..7094a8de1b 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -247,7 +247,7 @@ export default class RoomView extends React.Component { this.context.on("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.on("userTrustStatusChanged", this.onUserVerificationChanged); this.context.on("crossSigning.keysChanged", this.onCrossSigningKeysChanged); - this.context.on("Event.decrypted", this.onEventDecrypted); + this.context.on("Event.decrypted", this.onEventDecrypted); this.context.on("event", this.onEvent); // Start listening for RoomViewStore updates this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); From 3e8e817a3d51b11c6bffefcff2edbef0dd053e6f Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 13:35:18 +0200 Subject: [PATCH 0055/1984] Fixed merge error --- src/components/structures/RoomView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 7094a8de1b..f3ec8b8104 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -693,7 +693,7 @@ export default class RoomView extends React.Component { this.checkIfAlone(this.state.room); break; case 'confetti': - animateConfetti(this._roomView.current.offsetWidth); + animateConfetti(this.roomView.current.offsetWidth); break; case 'post_sticker_message': this.injectSticker( From 41160ff08e827e63851a3823be442d9395771ca6 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 13:54:09 +0200 Subject: [PATCH 0056/1984] Render confetti the react way --- src/components/structures/RoomView.tsx | 8 +++- src/components/views/elements/Confetti.js | 30 ++++---------- .../views/elements/ConfettiOverlay.tsx | 41 +++++++++++++++++++ 3 files changed, 56 insertions(+), 23 deletions(-) create mode 100644 src/components/views/elements/ConfettiOverlay.tsx diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index f3ec8b8104..0905005cf7 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -73,6 +73,7 @@ import TintableSvg from "../views/elements/TintableSvg"; import {XOR} from "../../@types/common"; import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; import { CallState, CallType, MatrixCall } from "matrix-js-sdk/lib/webrtc/call"; +import ConfettiOverlay from "../views/elements/ConfettiOverlay"; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -693,7 +694,7 @@ export default class RoomView extends React.Component { this.checkIfAlone(this.state.room); break; case 'confetti': - animateConfetti(this.roomView.current.offsetWidth); + //TODO: animateConfetti(this.roomView.current.offsetWidth); break; case 'post_sticker_message': this.injectSticker( @@ -853,7 +854,7 @@ export default class RoomView extends React.Component { this.calculateRecommendedVersion(room); this.updateE2EStatus(room); this.updatePermissions(room); - forceStopConfetti(); + //TODO: forceStopConfetti(); }; private async calculateRecommendedVersion(room: Room) { @@ -2072,6 +2073,9 @@ export default class RoomView extends React.Component { return (
+ {this.roomView.current && + + } { + const resize = () => { + const canvas = canvasRef.current; + canvas.height = window.innerHeight; + }; + const canvas = canvasRef.current; + canvas.width = roomWidth; + canvas.height = window.innerHeight; + window.addEventListener("resize", resize, true); + animateConfetti(canvas, roomWidth); + return () => { + window.removeEventListener("resize", resize); + forceStopConfetti(); + }; + }, []); + // on roomWidth change + + useEffect(() => { + const canvas = canvasRef.current; + canvas.width = roomWidth; + }, [roomWidth]); + return ( + + ) +} \ No newline at end of file From 607e33febaa4a6142776fe0126f850d782cca143 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 21:25:01 +0200 Subject: [PATCH 0057/1984] Extensibility, TypeScript and lazy loading --- src/SlashCommands.tsx | 10 +- src/components/structures/RoomView.tsx | 38 +--- src/components/views/elements/Confetti.js | 207 ------------------ .../views/elements/ConfettiOverlay.tsx | 41 ---- .../views/elements/effects/EffectsOverlay.tsx | 77 +++++++ .../views/elements/effects/ICanvasEffect.ts | 5 + .../views/elements/effects/confetti/index.ts | 197 +++++++++++++++++ .../views/rooms/SendMessageComposer.js | 8 +- .../tabs/user/PreferencesUserSettingsTab.js | 2 +- src/i18n/strings/en_EN.json | 2 +- src/settings/Settings.ts | 6 +- 11 files changed, 296 insertions(+), 297 deletions(-) delete mode 100644 src/components/views/elements/Confetti.js delete mode 100644 src/components/views/elements/ConfettiOverlay.tsx create mode 100644 src/components/views/elements/effects/EffectsOverlay.tsx create mode 100644 src/components/views/elements/effects/ICanvasEffect.ts create mode 100644 src/components/views/elements/effects/confetti/index.ts diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 87dc1ccdfd..316249d74d 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -77,6 +77,7 @@ export const CommandCategories = { "actions": _td("Actions"), "admin": _td("Admin"), "advanced": _td("Advanced"), + "effects": _td("Effects"), "other": _td("Other"), }; @@ -1045,19 +1046,16 @@ export const Commands = [ args: '', runFn: function(roomId, args) { return success((async () => { - const isChatEffectsDisabled = SettingsStore.getValue('dontShowChatEffects'); - if ((!args) || (!args && isChatEffectsDisabled)) { + if (!args) { args = _t("sends confetti"); MatrixClientPeg.get().sendEmoteMessage(roomId, args); } else { MatrixClientPeg.get().sendTextMessage(roomId, args); } - if (!isChatEffectsDisabled) { - dis.dispatch({action: 'confetti'}); - } + dis.dispatch({action: 'effects.confetti'}); })()); }, - category: CommandCategories.actions, + category: CommandCategories.effects, }), // Command definitions for autocompletion ONLY: diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 0905005cf7..1b47386789 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -56,7 +56,6 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import {E2EStatus, shieldStatusForRoom} from '../../utils/ShieldUtils'; import {Action} from "../../dispatcher/actions"; import {SettingLevel} from "../../settings/SettingLevel"; -import {animateConfetti, forceStopConfetti, isConfettiEmoji} from "../views/elements/Confetti"; import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; import {IMatrixClientCreds} from "../../MatrixClientPeg"; import ScrollPanel from "./ScrollPanel"; @@ -73,7 +72,7 @@ import TintableSvg from "../views/elements/TintableSvg"; import {XOR} from "../../@types/common"; import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; import { CallState, CallType, MatrixCall } from "matrix-js-sdk/lib/webrtc/call"; -import ConfettiOverlay from "../views/elements/ConfettiOverlay"; +import EffectsOverlay from "../views/elements/effects/EffectsOverlay"; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -248,8 +247,6 @@ export default class RoomView extends React.Component { this.context.on("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.on("userTrustStatusChanged", this.onUserVerificationChanged); this.context.on("crossSigning.keysChanged", this.onCrossSigningKeysChanged); - this.context.on("Event.decrypted", this.onEventDecrypted); - this.context.on("event", this.onEvent); // Start listening for RoomViewStore updates this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate); @@ -570,8 +567,6 @@ export default class RoomView extends React.Component { this.context.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); this.context.removeListener("crossSigning.keysChanged", this.onCrossSigningKeysChanged); - this.context.removeListener("Event.decrypted", this.onEventDecrypted); - this.context.removeListener("event", this.onEvent); } window.removeEventListener('beforeunload', this.onPageUnload); @@ -693,9 +688,6 @@ export default class RoomView extends React.Component { case 'message_sent': this.checkIfAlone(this.state.room); break; - case 'confetti': - //TODO: animateConfetti(this.roomView.current.offsetWidth); - break; case 'post_sticker_message': this.injectSticker( payload.data.content.url, @@ -804,28 +796,6 @@ export default class RoomView extends React.Component { } }; - private onEventDecrypted = (ev) => { - if (!SettingsStore.getValue('dontShowChatEffects')) { - if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || - this.state.room.getUnreadNotificationCount() === 0) return; - this.handleConfetti(ev); - } - }; - - private onEvent = (ev) => { - if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; - this.handleConfetti(ev); - }; - - private handleConfetti = (ev) => { - if (this.state.matrixClientIsReady) { - const messageBody = _t('sends confetti'); - if (isConfettiEmoji(ev.getContent()) || ev.getContent().body === messageBody) { - dis.dispatch({action: 'confetti'}); - } - } - }; - private onRoomName = (room: Room) => { if (this.state.room && room.roomId == this.state.room.roomId) { this.forceUpdate(); @@ -2070,11 +2040,13 @@ export default class RoomView extends React.Component { mx_RoomView_inCall: Boolean(activeCall), }); + const showChatEffects = SettingsStore.getValue('showChatEffects'); + return (
- {this.roomView.current && - + {showChatEffects && this.roomView.current && + } confetti.frameInterval) { - context.clearRect(0, 0, window.innerWidth, window.innerHeight); - updateParticles(); - drawParticles(context); - lastFrameTime = now - (delta % confetti.frameInterval); - } - requestAnimationFrame(runAnimation); - } - } - - function startConfetti(canvas, roomWidth, timeout) { - window.requestAnimationFrame = (function() { - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(callback) { - return window.setTimeout(callback, confetti.frameInterval); - }; - })(); - if (context === null) { - context = canvas.getContext("2d"); - } - const count = confetti.maxCount; - while (particles.length < count) { - particles.push(resetParticle({}, canvas.width, canvas.height)); - } - streamingConfetti = true; - runAnimation(); - if (timeout) { - window.setTimeout(stopConfetti, timeout); - } - } - - function stopConfetti() { - streamingConfetti = false; - } - - function removeConfetti() { - stop(); - particles = []; - } - - function isConfettiRunning() { - return streamingConfetti; - } - - function drawParticles(context) { - let particle; - let x; let x2; let y2; - for (let i = 0; i < particles.length; i++) { - particle = particles[i]; - 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 (confetti.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(); - } - } - - function updateParticles() { - const width = window.innerWidth; - const height = window.innerHeight; - let particle; - waveAngle += 0.01; - for (let i = 0; i < particles.length; i++) { - particle = particles[i]; - if (!streamingConfetti && particle.y < -15) { - particle.y = height + 100; - } else { - particle.tiltAngle += particle.tiltAngleIncrement; - particle.x += Math.sin(waveAngle) - 0.5; - particle.y += (Math.cos(waveAngle) + particle.diameter + confetti.speed) * 0.5; - particle.tilt = Math.sin(particle.tiltAngle) * 15; - } - if (particle.x > width + 20 || particle.x < -20 || particle.y > height) { - if (streamingConfetti && particles.length <= confetti.maxCount) { - resetParticle(particle, width, height); - } else { - particles.splice(i, 1); - i--; - } - } - } - } -})(); - -export function convertToHex(content) { - const contentBodyToHexArray = []; - let hex; - if (content.body) { - for (let i = 0; i < content.body.length; i++) { - hex = content.body.codePointAt(i).toString(16); - contentBodyToHexArray.push(hex); - } - } - return contentBodyToHexArray; -} - -export function isConfettiEmoji(content) { - const hexArray = convertToHex(content); - return !!(hexArray.includes('1f389') || hexArray.includes('1f38a')); -} - -export function animateConfetti(canvas, roomWidth) { - confetti.start(canvas, roomWidth, 3000); -} -export function forceStopConfetti() { - confetti.remove(); -} diff --git a/src/components/views/elements/ConfettiOverlay.tsx b/src/components/views/elements/ConfettiOverlay.tsx deleted file mode 100644 index 63d38d834c..0000000000 --- a/src/components/views/elements/ConfettiOverlay.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, {useEffect, useRef} from 'react'; -import {animateConfetti, forceStopConfetti} from './Confetti.js'; - -export default function ConfettiOverlay({roomWidth}) { - const canvasRef = useRef(null); - // on mount - useEffect(() => { - const resize = () => { - const canvas = canvasRef.current; - canvas.height = window.innerHeight; - }; - const canvas = canvasRef.current; - canvas.width = roomWidth; - canvas.height = window.innerHeight; - window.addEventListener("resize", resize, true); - animateConfetti(canvas, roomWidth); - return () => { - window.removeEventListener("resize", resize); - forceStopConfetti(); - }; - }, []); - // on roomWidth change - - useEffect(() => { - const canvas = canvasRef.current; - canvas.width = roomWidth; - }, [roomWidth]); - return ( - - ) -} \ No newline at end of file diff --git a/src/components/views/elements/effects/EffectsOverlay.tsx b/src/components/views/elements/effects/EffectsOverlay.tsx new file mode 100644 index 0000000000..1f8e7a97ad --- /dev/null +++ b/src/components/views/elements/effects/EffectsOverlay.tsx @@ -0,0 +1,77 @@ +import React, {FunctionComponent, useEffect, useRef} from 'react'; +import dis from '../../../../dispatcher/dispatcher'; +import ICanvasEffect from './ICanvasEffect.js'; + +type EffectsOverlayProps = { + roomWidth: number; +} + +const EffectsOverlay: FunctionComponent = ({roomWidth}) => { + const canvasRef = useRef(null); + const effectsRef = useRef>(new Map()); + + const resize = () => { + canvasRef.current.height = window.innerHeight; + }; + + const lazyLoadEffectModule = async (name: string): Promise => { + if(!name) return null; + let effect = effectsRef.current[name] ?? null; + if(effect === null) { + try { + var { default: Effect } = await import(`./${name}`); + effect = new Effect(); + effectsRef.current[name] = effect; + } catch (err) { + console.warn('Unable to load effect module at \'./${name}\'.', err) + } + } + return effect; + } + + const onAction = (payload: { action: string }) => { + const actionPrefix = 'effects.'; + if(payload.action.indexOf(actionPrefix) === 0) { + const effect = payload.action.substr(actionPrefix.length); + lazyLoadEffectModule(effect).then((module) => module?.start(canvasRef.current)); + } + }; + + // on mount + useEffect(() => { + const dispatcherRef = dis.register(onAction); + const canvas = canvasRef.current; + canvas.width = roomWidth; + canvas.height = window.innerHeight; + window.addEventListener('resize', resize, true); + + return () => { + dis.unregister(dispatcherRef); + window.removeEventListener('resize', resize); + for(const effect in effectsRef.current) { + effectsRef.current[effect]?.stop(); + } + }; + }, []); + + // on roomWidth change + useEffect(() => { + canvasRef.current.width = roomWidth; + }, [roomWidth]); + + return ( + + ) +} + +export default EffectsOverlay; \ No newline at end of file diff --git a/src/components/views/elements/effects/ICanvasEffect.ts b/src/components/views/elements/effects/ICanvasEffect.ts new file mode 100644 index 0000000000..c463235880 --- /dev/null +++ b/src/components/views/elements/effects/ICanvasEffect.ts @@ -0,0 +1,5 @@ +export default interface ICanvasEffect { + start: (canvas: HTMLCanvasElement, timeout?: number) => Promise, + stop: () => Promise, + isRunning: boolean +} \ No newline at end of file diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts new file mode 100644 index 0000000000..dd4e869078 --- /dev/null +++ b/src/components/views/elements/effects/confetti/index.ts @@ -0,0 +1,197 @@ +import ICanvasEffect from '../ICanvasEffect' + +declare global { + interface Window { + mozRequestAnimationFrame: any; + oRequestAnimationFrame: any; + msRequestAnimationFrame: any; + } +} + +export type ConfettiOptions = { + maxCount: number, + speed: number, + frameInterval: number, + alpha: number, + gradient: boolean, +} + +type ConfettiParticle = { + color: string, + color2: string, + x: number, + y: number, + diameter: number, + tilt: number, + tiltAngleIncrement: number, + tiltAngle: number, +} + +const DefaultOptions: ConfettiOptions = { + //set max confetti count + maxCount: 150, + //syarn addet the particle animation speed + speed: 3, + //the confetti animation frame interval in milliseconds + frameInterval: 15, + //the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) + alpha: 1.0, + //use gradient instead of solid particle color + gradient: false, +}; + +export default class Confetti implements ICanvasEffect { + private readonly options: ConfettiOptions; + + constructor(options: ConfettiOptions = DefaultOptions) { + this.options = options; + } + + private context: CanvasRenderingContext2D | null; + private supportsAnimationFrame = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame; + 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?: number) => { + if(!canvas) { + return; + } + window.requestAnimationFrame = (function () { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function (callback) { + return window.setTimeout(callback, this.options.frameInterval); + }; + })(); + if (this.context === null) { + this.context = canvas.getContext('2d'); + } + 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 || 3000); + } + } + + public stop = async () => { + this.isRunning = false; + this.particles = []; + } + + private resetParticle = (particle: ConfettiParticle, width: number, height: number): ConfettiParticle => { + particle.color = this.colors[(Math.random() * this.colors.length) | 0] + (this.options.alpha + ')'); + particle.color2 = this.colors[(Math.random() * this.colors.length) | 0] + (this.options.alpha + ')'); + particle.x = Math.random() * width; + particle.y = Math.random() * height - height; + particle.diameter = Math.random() * 10 + 5; + particle.tilt = Math.random() * 10 - 10; + particle.tiltAngleIncrement = Math.random() * 0.07 + 0.05; + particle.tiltAngle = Math.random() * Math.PI; + return particle; + } + + private runAnimation = (): void => { + if (this.particles.length === 0) { + this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height); + //animationTimer = null; + } 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 => { + let particle; + let x; let x2; let y2; + for (let i = 0; i < this.particles.length; i++) { + particle = this.particles[i]; + 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 = () => { + 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--; + } + } + } + } +} + +const convertToHex = (data: string): Array => { + const contentBodyToHexArray = []; + if (!data) return contentBodyToHexArray; + let hex; + if (data) { + for (let i = 0; i < data.length; i++) { + hex = data.codePointAt(i).toString(16); + contentBodyToHexArray.push(hex); + } + } + return contentBodyToHexArray; +} + +export const isConfettiEmoji = (content: { msgtype: string, body: string }): boolean => { + const hexArray = convertToHex(content.body); + return !!(hexArray.includes('1f389') || hexArray.includes('1f38a')); +} diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index d148d38b23..4fbea9d043 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -42,7 +42,7 @@ import {Key} from "../../../Keyboard"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import RateLimitedFunc from '../../../ratelimitedfunc'; import {Action} from "../../../dispatcher/actions"; -import {isConfettiEmoji} from "../elements/Confetti"; +import {isConfettiEmoji} from "../elements/effects/confetti"; import SettingsStore from "../../../settings/SettingsStore"; function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) { @@ -318,10 +318,8 @@ export default class SendMessageComposer extends React.Component { }); } dis.dispatch({action: "message_sent"}); - if (!SettingsStore.getValue('dontShowChatEffects')) { - if (isConfettiEmoji(content)) { - dis.dispatch({action: 'confetti'}); - } + if (isConfettiEmoji(content)) { + dis.dispatch({action: 'effects.confetti'}); } } diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 95d0f4be46..078d4dd2c7 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -49,7 +49,7 @@ export default class PreferencesUserSettingsTab extends React.Component { 'showAvatarChanges', 'showDisplaynameChanges', 'showImages', - 'dontShowChatEffects', + 'showChatEffects', 'Pill.shouldShowPillAvatar', ]; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4fc7a3ad25..c3943eb764 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -510,7 +510,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", - "Don't show chat effects": "Don't show chat effects", + "Show chat effects": "Show chat effects", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", "Uploading logs": "Uploading logs", diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b9ad834c83..ab4665c401 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -622,10 +622,10 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td("Enable experimental, compact IRC style layout"), default: false, }, - "dontShowChatEffects": { + "showChatEffects": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, - displayName: _td("Don't show chat effects"), - default: false, + displayName: _td("Show chat effects"), + default: true, }, "Widgets.pinned": { supportedLevels: LEVELS_ROOM_OR_ACCOUNT, From 6d98335368b82ed6c7d5539bacfbd2f424fd8be7 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 21:28:22 +0200 Subject: [PATCH 0058/1984] Removed old todo --- src/components/structures/RoomView.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 1b47386789..2c3aa4793a 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -824,7 +824,6 @@ export default class RoomView extends React.Component { this.calculateRecommendedVersion(room); this.updateE2EStatus(room); this.updatePermissions(room); - //TODO: forceStopConfetti(); }; private async calculateRecommendedVersion(room: Room) { From 48633f76ab92c5238a4f2e4c99f73d2598129f64 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Mon, 19 Oct 2020 23:10:43 +0200 Subject: [PATCH 0059/1984] added event handling back --- src/SlashCommands.tsx | 2 +- src/components/structures/RoomView.tsx | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 316249d74d..68be5de0a0 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -1047,7 +1047,7 @@ export const Commands = [ runFn: function(roomId, args) { return success((async () => { if (!args) { - args = _t("sends confetti"); + args = "sends confetti"; MatrixClientPeg.get().sendEmoteMessage(roomId, args); } else { MatrixClientPeg.get().sendTextMessage(roomId, args); diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 2c3aa4793a..f975a7cc6e 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -73,6 +73,7 @@ import {XOR} from "../../@types/common"; import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; import { CallState, CallType, MatrixCall } from "matrix-js-sdk/lib/webrtc/call"; import EffectsOverlay from "../views/elements/effects/EffectsOverlay"; +import { isConfettiEmoji } from '../views/elements/effects/confetti'; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -247,6 +248,8 @@ export default class RoomView extends React.Component { this.context.on("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.on("userTrustStatusChanged", this.onUserVerificationChanged); this.context.on("crossSigning.keysChanged", this.onCrossSigningKeysChanged); + this.context.on("Event.decrypted", this.onEventDecrypted); + this.context.on("event", this.onEvent); // Start listening for RoomViewStore updates this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate); @@ -567,6 +570,8 @@ export default class RoomView extends React.Component { this.context.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); this.context.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); this.context.removeListener("crossSigning.keysChanged", this.onCrossSigningKeysChanged); + this.context.removeListener("Event.decrypted", this.onEventDecrypted); + this.context.removeListener("event", this.onEvent); } window.removeEventListener('beforeunload', this.onPageUnload); @@ -795,6 +800,26 @@ export default class RoomView extends React.Component { } } }; + + private onEventDecrypted = (ev) => { + if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || + this.state.room.getUnreadNotificationCount() === 0) return; + this.handleConfetti(ev); + }; + + private onEvent = (ev) => { + if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; + this.handleConfetti(ev); + }; + + private handleConfetti = (ev) => { + if (this.state.matrixClientIsReady) { + const messageBody = 'sends confetti'; + if (isConfettiEmoji(ev.getContent()) || ev.getContent().body === messageBody) { + dis.dispatch({action: 'effects.confetti'}); + } + } + }; private onRoomName = (room: Room) => { if (this.state.room && room.roomId == this.state.room.roomId) { From 1c6d28b861c8b6bee20c29056733ef08cc3dfa76 Mon Sep 17 00:00:00 2001 From: nurjinn jafar Date: Wed, 21 Oct 2020 13:37:36 +0200 Subject: [PATCH 0060/1984] refactoring roomView / slashCommands / SendMessageComposer with the new effects configurations and fix confetti animation timeout --- src/SlashCommands.tsx | 39 +++++++++++-------- src/components/structures/RoomView.tsx | 18 +++++---- .../views/elements/effects/ICanvasEffect.ts | 4 +- .../views/elements/effects/confetti/index.ts | 26 ++----------- .../views/elements/effects/effectUtilities.ts | 3 ++ .../views/elements/effects/index.ts | 11 ++++++ .../views/rooms/SendMessageComposer.js | 10 +++-- 7 files changed, 59 insertions(+), 52 deletions(-) create mode 100644 src/components/views/elements/effects/effectUtilities.ts create mode 100644 src/components/views/elements/effects/index.ts diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 68be5de0a0..3f51614028 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -46,6 +46,7 @@ import { EffectiveMembership, getEffectiveMembership, leaveRoomBehaviour } from import SdkConfig from "./SdkConfig"; import SettingsStore from "./settings/SettingsStore"; import {UIFeature} from "./settings/UIFeature"; +import effects from "./components/views/elements/effects" // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 interface HTMLInputEvent extends Event { @@ -1040,22 +1041,28 @@ export const Commands = [ }, category: CommandCategories.actions, }), - new Command({ - command: "confetti", - description: _td("Sends the given message with confetti"), - args: '', - runFn: function(roomId, args) { - return success((async () => { - if (!args) { - args = "sends confetti"; - MatrixClientPeg.get().sendEmoteMessage(roomId, args); - } else { - MatrixClientPeg.get().sendTextMessage(roomId, args); - } - dis.dispatch({action: 'effects.confetti'}); - })()); - }, - category: CommandCategories.effects, + ...effects.map((effect) => { + return new Command({ + command: effect.command, + description: effect.description(), + args: '', + runFn: function(roomId, args) { + return success((async () => { + if (!args) { + args = effect.fallbackMessage(); + MatrixClientPeg.get().sendEmoteMessage(roomId, args); + } else { + const content = { + msgtype: effect.msgType, + body: args, + }; + MatrixClientPeg.get().sendMessage(roomId, content); + } + dis.dispatch({action: `effects.${effect.command}`}); + })()); + }, + category: CommandCategories.effects, + }) }), // Command definitions for autocompletion ONLY: diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index f975a7cc6e..0b7aa08288 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -73,7 +73,8 @@ import {XOR} from "../../@types/common"; import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; import { CallState, CallType, MatrixCall } from "matrix-js-sdk/lib/webrtc/call"; import EffectsOverlay from "../views/elements/effects/EffectsOverlay"; -import { isConfettiEmoji } from '../views/elements/effects/confetti'; +import {containsEmoji} from '../views/elements/effects/effectUtilities'; +import effects from '../views/elements/effects' const DEBUG = false; let debuglog = function(msg: string) {}; @@ -800,10 +801,9 @@ export default class RoomView extends React.Component { } } }; - + private onEventDecrypted = (ev) => { - if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || - this.state.room.getUnreadNotificationCount() === 0) return; + if (ev.isDecryptionFailure()) return; this.handleConfetti(ev); }; @@ -813,11 +813,13 @@ export default class RoomView extends React.Component { }; private handleConfetti = (ev) => { + if (this.state.room.getUnreadNotificationCount() === 0) return; if (this.state.matrixClientIsReady) { - const messageBody = 'sends confetti'; - if (isConfettiEmoji(ev.getContent()) || ev.getContent().body === messageBody) { - dis.dispatch({action: 'effects.confetti'}); - } + effects.map(effect => { + if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) { + dis.dispatch({action: `effects.${effect.command}`}); + } + }) } }; diff --git a/src/components/views/elements/effects/ICanvasEffect.ts b/src/components/views/elements/effects/ICanvasEffect.ts index c463235880..a8b9a83514 100644 --- a/src/components/views/elements/effects/ICanvasEffect.ts +++ b/src/components/views/elements/effects/ICanvasEffect.ts @@ -1,5 +1,5 @@ export default interface ICanvasEffect { - start: (canvas: HTMLCanvasElement, timeout?: number) => Promise, + start: (canvas: HTMLCanvasElement, timeout: number) => Promise, stop: () => Promise, isRunning: boolean -} \ No newline at end of file +} diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index dd4e869078..c5874311c5 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -47,7 +47,7 @@ export default class Confetti implements ICanvasEffect { this.options = options; } - private context: CanvasRenderingContext2D | null; + private context: CanvasRenderingContext2D | null = null; private supportsAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || @@ -64,7 +64,7 @@ export default class Confetti implements ICanvasEffect { public isRunning: boolean; - public start = async (canvas: HTMLCanvasElement, timeout?: number) => { + public start = async (canvas: HTMLCanvasElement, timeout = 3000) => { if(!canvas) { return; } @@ -88,13 +88,13 @@ export default class Confetti implements ICanvasEffect { this.isRunning = true; this.runAnimation(); if (timeout) { - window.setTimeout(this.stop, timeout || 3000); + window.setTimeout(this.stop, timeout); } } public stop = async () => { this.isRunning = false; - this.particles = []; + // this.particles = []; } private resetParticle = (particle: ConfettiParticle, width: number, height: number): ConfettiParticle => { @@ -177,21 +177,3 @@ export default class Confetti implements ICanvasEffect { } } } - -const convertToHex = (data: string): Array => { - const contentBodyToHexArray = []; - if (!data) return contentBodyToHexArray; - let hex; - if (data) { - for (let i = 0; i < data.length; i++) { - hex = data.codePointAt(i).toString(16); - contentBodyToHexArray.push(hex); - } - } - return contentBodyToHexArray; -} - -export const isConfettiEmoji = (content: { msgtype: string, body: string }): boolean => { - const hexArray = convertToHex(content.body); - return !!(hexArray.includes('1f389') || hexArray.includes('1f38a')); -} diff --git a/src/components/views/elements/effects/effectUtilities.ts b/src/components/views/elements/effects/effectUtilities.ts new file mode 100644 index 0000000000..927b445a61 --- /dev/null +++ b/src/components/views/elements/effects/effectUtilities.ts @@ -0,0 +1,3 @@ +export const containsEmoji = (content: { msgtype: string, body: string }, emojis: Array): boolean => { + return emojis.some((emoji) => content.body.includes(emoji)); +} diff --git a/src/components/views/elements/effects/index.ts b/src/components/views/elements/effects/index.ts new file mode 100644 index 0000000000..d4c12fa7ce --- /dev/null +++ b/src/components/views/elements/effects/index.ts @@ -0,0 +1,11 @@ +import {_t, _td} from "../../../../languageHandler"; + +export default [ + { + emojis: ['🎊', '🎉'], + msgType: 'nic.custom.confetti', + command: 'confetti', + description: () => _td("Sends the given message with confetti"), + fallbackMessage: () => _t("sends confetti") + " 🎉", + }, +] diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 4fbea9d043..94ad934067 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -42,8 +42,8 @@ import {Key} from "../../../Keyboard"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import RateLimitedFunc from '../../../ratelimitedfunc'; import {Action} from "../../../dispatcher/actions"; -import {isConfettiEmoji} from "../elements/effects/confetti"; -import SettingsStore from "../../../settings/SettingsStore"; +import {containsEmoji} from "../elements/effects/effectUtilities"; +import effects from '../elements/effects'; function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) { const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent); @@ -318,9 +318,11 @@ export default class SendMessageComposer extends React.Component { }); } dis.dispatch({action: "message_sent"}); - if (isConfettiEmoji(content)) { - dis.dispatch({action: 'effects.confetti'}); + effects.map( (effect) => { + if (containsEmoji(content, effect.emojis)) { + dis.dispatch({action: `effects.${effect.command}`}); } + }); } this.sendHistoryManager.save(this.model, replyToEvent); From d4ec1dd7750f5a901eaf079a7b5fd8153701aed3 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 13:56:58 +0200 Subject: [PATCH 0061/1984] Refactoring --- src/components/structures/RoomView.tsx | 2 +- .../views/elements/effects/confetti/index.ts | 1 - src/components/views/elements/effects/index.ts | 16 ++++++++++++++-- .../views/rooms/SendMessageComposer.js | 6 +++--- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 0b7aa08288..c84a3bf783 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -815,7 +815,7 @@ export default class RoomView extends React.Component { private handleConfetti = (ev) => { if (this.state.room.getUnreadNotificationCount() === 0) return; if (this.state.matrixClientIsReady) { - effects.map(effect => { + effects.forEach(effect => { if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) { dis.dispatch({action: `effects.${effect.command}`}); } diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index c5874311c5..e8a139387b 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -94,7 +94,6 @@ export default class Confetti implements ICanvasEffect { public stop = async () => { this.isRunning = false; - // this.particles = []; } private resetParticle = (particle: ConfettiParticle, width: number, height: number): ConfettiParticle => { diff --git a/src/components/views/elements/effects/index.ts b/src/components/views/elements/effects/index.ts index d4c12fa7ce..6311135c1e 100644 --- a/src/components/views/elements/effects/index.ts +++ b/src/components/views/elements/effects/index.ts @@ -1,6 +1,14 @@ import {_t, _td} from "../../../../languageHandler"; -export default [ +type Effect = { + emojis: Array; + msgType: string; + command: string; + description: () => string; + fallbackMessage: () => string; +} + +const effects: Array = [ { emojis: ['🎊', '🎉'], msgType: 'nic.custom.confetti', @@ -8,4 +16,8 @@ export default [ description: () => _td("Sends the given message with confetti"), fallbackMessage: () => _t("sends confetti") + " 🎉", }, -] +]; + +export default effects; + + diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 94ad934067..a413a9917c 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -318,10 +318,10 @@ export default class SendMessageComposer extends React.Component { }); } dis.dispatch({action: "message_sent"}); - effects.map( (effect) => { + effects.forEach((effect) => { if (containsEmoji(content, effect.emojis)) { - dis.dispatch({action: `effects.${effect.command}`}); - } + dis.dispatch({action: `effects.${effect.command}`}); + } }); } From 047c29731b319bdf48027ae1e2966100763530f4 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 14:15:27 +0200 Subject: [PATCH 0062/1984] Added missing translation --- src/components/views/elements/effects/ICanvasEffect.ts | 2 +- src/i18n/strings/en_EN.json | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/effects/ICanvasEffect.ts b/src/components/views/elements/effects/ICanvasEffect.ts index a8b9a83514..71210d7ec3 100644 --- a/src/components/views/elements/effects/ICanvasEffect.ts +++ b/src/components/views/elements/effects/ICanvasEffect.ts @@ -1,5 +1,5 @@ export default interface ICanvasEffect { - start: (canvas: HTMLCanvasElement, timeout: number) => Promise, + start: (canvas: HTMLCanvasElement, timeout?: number) => Promise, stop: () => Promise, isRunning: boolean } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c3943eb764..974658aa3d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -148,6 +148,7 @@ "Messages": "Messages", "Actions": "Actions", "Advanced": "Advanced", + "Effects": "Effects", "Other": "Other", "Command error": "Command error", "Usage": "Usage", @@ -211,8 +212,6 @@ "Send a bug report with logs": "Send a bug report with logs", "Opens chat with the given user": "Opens chat with the given user", "Sends a message to the given user": "Sends a message to the given user", - "Sends the given message with confetti": "Sends the given message with confetti", - "sends confetti": "sends confetti", "Displays action": "Displays action", "Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", @@ -1580,6 +1579,8 @@ "Sign in with single sign-on": "Sign in with single sign-on", "And %(count)s more...|other": "And %(count)s more...", "Home": "Home", + "Sends the given message with confetti": "Sends the given message with confetti", + "sends confetti": "sends confetti", "Enter a server name": "Enter a server name", "Looks good": "Looks good", "Can't find this server or its room list": "Can't find this server or its room list", From c7d535d2d3d07ae6490365cb242d1f1e1f1d25a3 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 14:29:25 +0200 Subject: [PATCH 0063/1984] Fixed some formatting issues --- .../views/elements/effects/EffectsOverlay.tsx | 18 +++++++++--------- .../views/elements/effects/confetti/index.ts | 6 +++--- src/components/views/elements/effects/index.ts | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/views/elements/effects/EffectsOverlay.tsx b/src/components/views/elements/effects/EffectsOverlay.tsx index 1f8e7a97ad..437d1f127f 100644 --- a/src/components/views/elements/effects/EffectsOverlay.tsx +++ b/src/components/views/elements/effects/EffectsOverlay.tsx @@ -1,4 +1,4 @@ -import React, {FunctionComponent, useEffect, useRef} from 'react'; +import React, { FunctionComponent, useEffect, useRef } from 'react'; import dis from '../../../../dispatcher/dispatcher'; import ICanvasEffect from './ICanvasEffect.js'; @@ -6,7 +6,7 @@ type EffectsOverlayProps = { roomWidth: number; } -const EffectsOverlay: FunctionComponent = ({roomWidth}) => { +const EffectsOverlay: FunctionComponent = ({ roomWidth }) => { const canvasRef = useRef(null); const effectsRef = useRef>(new Map()); @@ -15,9 +15,9 @@ const EffectsOverlay: FunctionComponent = ({roomWidth}) => }; const lazyLoadEffectModule = async (name: string): Promise => { - if(!name) return null; + if (!name) return null; let effect = effectsRef.current[name] ?? null; - if(effect === null) { + if (effect === null) { try { var { default: Effect } = await import(`./${name}`); effect = new Effect(); @@ -31,7 +31,7 @@ const EffectsOverlay: FunctionComponent = ({roomWidth}) => const onAction = (payload: { action: string }) => { const actionPrefix = 'effects.'; - if(payload.action.indexOf(actionPrefix) === 0) { + if (payload.action.indexOf(actionPrefix) === 0) { const effect = payload.action.substr(actionPrefix.length); lazyLoadEffectModule(effect).then((module) => module?.start(canvasRef.current)); } @@ -44,11 +44,11 @@ const EffectsOverlay: FunctionComponent = ({roomWidth}) => canvas.width = roomWidth; canvas.height = window.innerHeight; window.addEventListener('resize', resize, true); - - return () => { + + return () => { dis.unregister(dispatcherRef); window.removeEventListener('resize', resize); - for(const effect in effectsRef.current) { + for (const effect in effectsRef.current) { effectsRef.current[effect]?.stop(); } }; @@ -58,7 +58,7 @@ const EffectsOverlay: FunctionComponent = ({roomWidth}) => useEffect(() => { canvasRef.current.width = roomWidth; }, [roomWidth]); - + return ( { - if(!canvas) { + if (!canvas) { return; } - window.requestAnimationFrame = (function () { + window.requestAnimationFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || - function (callback) { + function(callback) { return window.setTimeout(callback, this.options.frameInterval); }; })(); diff --git a/src/components/views/elements/effects/index.ts b/src/components/views/elements/effects/index.ts index 6311135c1e..8a95b1c9d0 100644 --- a/src/components/views/elements/effects/index.ts +++ b/src/components/views/elements/effects/index.ts @@ -1,4 +1,4 @@ -import {_t, _td} from "../../../../languageHandler"; +import { _t, _td } from "../../../../languageHandler"; type Effect = { emojis: Array; From 8728e12242d0a751407e9a2c08c95ecaa5844dfd Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 14:43:09 +0200 Subject: [PATCH 0064/1984] Some code optimizations --- .../views/elements/effects/EffectsOverlay.tsx | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/components/views/elements/effects/EffectsOverlay.tsx b/src/components/views/elements/effects/EffectsOverlay.tsx index 437d1f127f..4b40f7cbb1 100644 --- a/src/components/views/elements/effects/EffectsOverlay.tsx +++ b/src/components/views/elements/effects/EffectsOverlay.tsx @@ -10,16 +10,12 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) = const canvasRef = useRef(null); const effectsRef = useRef>(new Map()); - const resize = () => { - canvasRef.current.height = window.innerHeight; - }; - const lazyLoadEffectModule = async (name: string): Promise => { if (!name) return null; let effect = effectsRef.current[name] ?? null; if (effect === null) { try { - var { default: Effect } = await import(`./${name}`); + const { default: Effect } = await import(`./${name}`); effect = new Effect(); effectsRef.current[name] = effect; } catch (err) { @@ -27,41 +23,41 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) = } } return effect; - } - - const onAction = (payload: { action: string }) => { - const actionPrefix = 'effects.'; - if (payload.action.indexOf(actionPrefix) === 0) { - const effect = payload.action.substr(actionPrefix.length); - lazyLoadEffectModule(effect).then((module) => module?.start(canvasRef.current)); - } }; - // on mount useEffect(() => { + const resize = () => { + canvasRef.current.height = window.innerHeight; + }; + const onAction = (payload: { action: string }) => { + const actionPrefix = 'effects.'; + if (payload.action.indexOf(actionPrefix) === 0) { + const effect = payload.action.substr(actionPrefix.length); + lazyLoadEffectModule(effect).then((module) => module?.start(canvasRef.current)); + } + } const dispatcherRef = dis.register(onAction); const canvas = canvasRef.current; - canvas.width = roomWidth; canvas.height = window.innerHeight; window.addEventListener('resize', resize, true); return () => { dis.unregister(dispatcherRef); window.removeEventListener('resize', resize); - for (const effect in effectsRef.current) { - effectsRef.current[effect]?.stop(); + const currentEffects = effectsRef.current; + for (const effect in currentEffects) { + const effectModule: ICanvasEffect = currentEffects[effect]; + if(effectModule && effectModule.isRunning) { + effectModule.stop(); + } } }; }, []); - // on roomWidth change - useEffect(() => { - canvasRef.current.width = roomWidth; - }, [roomWidth]); - return ( = ({ roomWidth }) = ) } -export default EffectsOverlay; \ No newline at end of file +export default EffectsOverlay; From 88475617955c72a538269259a448f54f8e5e27fd Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 14:48:11 +0200 Subject: [PATCH 0065/1984] Added additional module exports --- src/components/views/elements/effects/EffectsOverlay.tsx | 2 +- src/components/views/elements/effects/confetti/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/effects/EffectsOverlay.tsx b/src/components/views/elements/effects/EffectsOverlay.tsx index 4b40f7cbb1..0ff2b228ad 100644 --- a/src/components/views/elements/effects/EffectsOverlay.tsx +++ b/src/components/views/elements/effects/EffectsOverlay.tsx @@ -2,7 +2,7 @@ import React, { FunctionComponent, useEffect, useRef } from 'react'; import dis from '../../../../dispatcher/dispatcher'; import ICanvasEffect from './ICanvasEffect.js'; -type EffectsOverlayProps = { +export type EffectsOverlayProps = { roomWidth: number; } diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index 3cb7db5ec4..e45961006b 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -27,7 +27,7 @@ type ConfettiParticle = { tiltAngle: number, } -const DefaultOptions: ConfettiOptions = { +export const DefaultOptions: ConfettiOptions = { //set max confetti count maxCount: 150, //syarn addet the particle animation speed From 6f4c5d1b080f8ddcbd6053f6778f52d6f15f2125 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 14:56:04 +0200 Subject: [PATCH 0066/1984] fixed build error --- src/components/views/elements/effects/EffectsOverlay.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/effects/EffectsOverlay.tsx b/src/components/views/elements/effects/EffectsOverlay.tsx index 0ff2b228ad..803fd18042 100644 --- a/src/components/views/elements/effects/EffectsOverlay.tsx +++ b/src/components/views/elements/effects/EffectsOverlay.tsx @@ -43,11 +43,12 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) = return () => { dis.unregister(dispatcherRef); - window.removeEventListener('resize', resize); - const currentEffects = effectsRef.current; + window.removeEventListener('resize', resize); + // eslint-disable-next-line react-hooks/exhaustive-deps + const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored for (const effect in currentEffects) { const effectModule: ICanvasEffect = currentEffects[effect]; - if(effectModule && effectModule.isRunning) { + if (effectModule && effectModule.isRunning) { effectModule.stop(); } } From 906686b640ff22812f3cb628bd3f8267af6c8144 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 15:06:05 +0200 Subject: [PATCH 0067/1984] Fixed more linter warnings --- src/components/views/elements/effects/confetti/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index e45961006b..4537683030 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -68,13 +68,13 @@ export default class Confetti implements ICanvasEffect { if (!canvas) { return; } - window.requestAnimationFrame = (function() { + window.requestAnimationFrame = (function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || - function(callback) { + function (callback) { return window.setTimeout(callback, this.options.frameInterval); }; })(); From 2f83771eab27a2e6441a34224c9acbd120c6c5b8 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 15:15:26 +0200 Subject: [PATCH 0068/1984] Fixed more eslint errors --- src/components/views/elements/effects/EffectsOverlay.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/effects/EffectsOverlay.tsx b/src/components/views/elements/effects/EffectsOverlay.tsx index 803fd18042..5ec3566f18 100644 --- a/src/components/views/elements/effects/EffectsOverlay.tsx +++ b/src/components/views/elements/effects/EffectsOverlay.tsx @@ -43,7 +43,7 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) = return () => { dis.unregister(dispatcherRef); - window.removeEventListener('resize', resize); + window.removeEventListener('resize', resize); // eslint-disable-next-line react-hooks/exhaustive-deps const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored for (const effect in currentEffects) { From 335774b6ff3bd558f11eea9ca58e2f1d7f73c262 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 16:03:22 +0200 Subject: [PATCH 0069/1984] Fixed more linter errors --- src/components/views/elements/effects/confetti/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index 4537683030..7428651490 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -1,4 +1,4 @@ -import ICanvasEffect from '../ICanvasEffect' +import ICanvasEffect from '../ICanvasEffect'; declare global { interface Window { @@ -68,13 +68,13 @@ export default class Confetti implements ICanvasEffect { if (!canvas) { return; } - window.requestAnimationFrame = (function () { + window.requestAnimationFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || - function (callback) { + function(callback) { return window.setTimeout(callback, this.options.frameInterval); }; })(); From fbe2d7e0f86ceae6511b6dc553a70d428b773290 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 16:15:15 +0200 Subject: [PATCH 0070/1984] Optimized naming --- src/components/structures/RoomView.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 817b2d2cea..1a18ece008 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -822,15 +822,15 @@ export default class RoomView extends React.Component { private onEventDecrypted = (ev) => { if (ev.isDecryptionFailure()) return; - this.handleConfetti(ev); + this.handleEffects(ev); }; private onEvent = (ev) => { if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; - this.handleConfetti(ev); + this.handleEffects(ev); }; - private handleConfetti = (ev) => { + private handleEffects = (ev) => { if (this.state.room.getUnreadNotificationCount() === 0) return; if (this.state.matrixClientIsReady) { effects.forEach(effect => { From cb79e38377165b4cb233caa804fadc9272e5e2a8 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 17:04:01 +0200 Subject: [PATCH 0071/1984] Better initialization and check if canvas gets unmounted --- .../views/elements/effects/confetti/index.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index 7428651490..b613c32043 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -78,9 +78,8 @@ export default class Confetti implements ICanvasEffect { return window.setTimeout(callback, this.options.frameInterval); }; })(); - if (this.context === null) { - this.context = canvas.getContext('2d'); - } + 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)); @@ -109,9 +108,11 @@ export default class Confetti implements ICanvasEffect { } 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); - //animationTimer = null; } else { const now = Date.now(); const delta = now - this.lastFrameTime; @@ -127,6 +128,9 @@ export default class Confetti implements ICanvasEffect { private drawParticles = (context: CanvasRenderingContext2D): void => { + if (!this.context || !this.context.canvas) { + return; + } let particle; let x; let x2; let y2; for (let i = 0; i < this.particles.length; i++) { @@ -151,6 +155,9 @@ export default class Confetti implements ICanvasEffect { } private updateParticles = () => { + if (!this.context || !this.context.canvas) { + return; + } const width = this.context.canvas.width; const height = this.context.canvas.height; let particle: ConfettiParticle; From 3ea4560019f111c5ce73382644d6d6aae9fb753a Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 17:58:54 +0200 Subject: [PATCH 0072/1984] Moved effect options to configuration --- .../views/elements/effects/EffectsOverlay.tsx | 12 +++++---- .../views/elements/effects/ICanvasEffect.ts | 4 +++ .../views/elements/effects/confetti/index.ts | 4 +-- .../views/elements/effects/index.ts | 25 ++++++++++++++++++- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/components/views/elements/effects/EffectsOverlay.tsx b/src/components/views/elements/effects/EffectsOverlay.tsx index 5ec3566f18..b2ecec8753 100644 --- a/src/components/views/elements/effects/EffectsOverlay.tsx +++ b/src/components/views/elements/effects/EffectsOverlay.tsx @@ -1,6 +1,7 @@ import React, { FunctionComponent, useEffect, useRef } from 'react'; import dis from '../../../../dispatcher/dispatcher'; -import ICanvasEffect from './ICanvasEffect.js'; +import ICanvasEffect, { ICanvasEffectConstructable } from './ICanvasEffect.js'; +import effects from './index' export type EffectsOverlayProps = { roomWidth: number; @@ -8,15 +9,16 @@ export type EffectsOverlayProps = { const EffectsOverlay: FunctionComponent = ({ roomWidth }) => { const canvasRef = useRef(null); - const effectsRef = useRef>(new Map()); + const effectsRef = useRef>(new Map()); const lazyLoadEffectModule = async (name: string): Promise => { if (!name) return null; - let effect = effectsRef.current[name] ?? null; + let effect: ICanvasEffect | null = effectsRef.current[name] || null; if (effect === null) { + const options = effects.find((e) => e.command === name)?.options try { - const { default: Effect } = await import(`./${name}`); - effect = new Effect(); + const { default: Effect }: { default: ICanvasEffectConstructable } = await import(`./${name}`); + effect = new Effect(options); effectsRef.current[name] = effect; } catch (err) { console.warn('Unable to load effect module at \'./${name}\'.', err) diff --git a/src/components/views/elements/effects/ICanvasEffect.ts b/src/components/views/elements/effects/ICanvasEffect.ts index 71210d7ec3..c2a3046c8f 100644 --- a/src/components/views/elements/effects/ICanvasEffect.ts +++ b/src/components/views/elements/effects/ICanvasEffect.ts @@ -1,3 +1,7 @@ +export interface ICanvasEffectConstructable { + new(options?: { [key: string]: any }): ICanvasEffect +} + export default interface ICanvasEffect { start: (canvas: HTMLCanvasElement, timeout?: number) => Promise, stop: () => Promise, diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index b613c32043..07b6f1632a 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -43,8 +43,8 @@ export const DefaultOptions: ConfettiOptions = { export default class Confetti implements ICanvasEffect { private readonly options: ConfettiOptions; - constructor(options: ConfettiOptions = DefaultOptions) { - this.options = options; + constructor(options: { [key: string]: any }) { + this.options = {...DefaultOptions, ...options}; } private context: CanvasRenderingContext2D | null = null; diff --git a/src/components/views/elements/effects/index.ts b/src/components/views/elements/effects/index.ts index 8a95b1c9d0..3986d6e841 100644 --- a/src/components/views/elements/effects/index.ts +++ b/src/components/views/elements/effects/index.ts @@ -1,11 +1,22 @@ import { _t, _td } from "../../../../languageHandler"; -type Effect = { +export type Effect = { emojis: Array; msgType: string; command: string; description: () => string; fallbackMessage: () => string; + options: { + [key: string]: any + } +} + +type ConfettiOptions = { + maxCount: number, + speed: number, + frameInterval: number, + alpha: number, + gradient: boolean, } const effects: Array = [ @@ -15,6 +26,18 @@ const effects: Array = [ command: 'confetti', description: () => _td("Sends the given message with confetti"), fallbackMessage: () => _t("sends confetti") + " 🎉", + options: { + //set max confetti count + maxCount: 150, + //syarn addet the particle animation speed + speed: 3, + //the confetti animation frame interval in milliseconds + frameInterval: 15, + //the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) + alpha: 1.0, + //use gradient instead of solid particle color + gradient: false, + } as ConfettiOptions, }, ]; From 1c556c97d3a23d95389ef3f1ce7be642a1885195 Mon Sep 17 00:00:00 2001 From: Steffen Kolmer Date: Wed, 21 Oct 2020 19:06:10 +0200 Subject: [PATCH 0073/1984] Added some code documentation --- .../views/elements/effects/ICanvasEffect.ts | 20 +++++++ .../views/elements/effects/confetti/index.ts | 20 +++++-- .../views/elements/effects/effectUtilities.ts | 5 ++ .../views/elements/effects/index.ts | 53 ++++++++++++++----- 4 files changed, 81 insertions(+), 17 deletions(-) diff --git a/src/components/views/elements/effects/ICanvasEffect.ts b/src/components/views/elements/effects/ICanvasEffect.ts index c2a3046c8f..400f42af73 100644 --- a/src/components/views/elements/effects/ICanvasEffect.ts +++ b/src/components/views/elements/effects/ICanvasEffect.ts @@ -1,9 +1,29 @@ +/** + * 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/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index 07b6f1632a..29f70d1a57 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -9,10 +9,25 @@ declare global { } 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, } @@ -28,15 +43,10 @@ type ConfettiParticle = { } export const DefaultOptions: ConfettiOptions = { - //set max confetti count maxCount: 150, - //syarn addet the particle animation speed speed: 3, - //the confetti animation frame interval in milliseconds frameInterval: 15, - //the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) alpha: 1.0, - //use gradient instead of solid particle color gradient: false, }; diff --git a/src/components/views/elements/effects/effectUtilities.ts b/src/components/views/elements/effects/effectUtilities.ts index 927b445a61..212c477b39 100644 --- a/src/components/views/elements/effects/effectUtilities.ts +++ b/src/components/views/elements/effects/effectUtilities.ts @@ -1,3 +1,8 @@ +/** + * 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.includes(emoji)); } diff --git a/src/components/views/elements/effects/index.ts b/src/components/views/elements/effects/index.ts index 3986d6e841..0f01f2624e 100644 --- a/src/components/views/elements/effects/index.ts +++ b/src/components/views/elements/effects/index.ts @@ -1,25 +1,59 @@ import { _t, _td } from "../../../../languageHandler"; -export type Effect = { +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; - options: { - [key: string]: any - } + /** + * 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, } -const effects: Array = [ +/** + * This configuration defines room effects that can be triggered by custom message types and emojis + */ +const effects: Array> = [ { emojis: ['🎊', '🎉'], msgType: 'nic.custom.confetti', @@ -27,18 +61,13 @@ const effects: Array = [ description: () => _td("Sends the given message with confetti"), fallbackMessage: () => _t("sends confetti") + " 🎉", options: { - //set max confetti count maxCount: 150, - //syarn addet the particle animation speed speed: 3, - //the confetti animation frame interval in milliseconds frameInterval: 15, - //the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible) alpha: 1.0, - //use gradient instead of solid particle color gradient: false, - } as ConfettiOptions, - }, + }, + } as Effect, ]; export default effects; From 46eb5cdb1b036f2e696cd97fa9540c8ff18285be Mon Sep 17 00:00:00 2001 From: MaHa-Nordeck Date: Thu, 22 Oct 2020 14:01:16 +0200 Subject: [PATCH 0074/1984] Minor improvements * Made color generation dependant on gradient usage. * Changed a loop to use for of --- .../views/elements/effects/confetti/index.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index 29f70d1a57..309fc9dd9c 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -107,11 +107,15 @@ export default class Confetti implements ICanvasEffect { private resetParticle = (particle: ConfettiParticle, width: number, height: number): ConfettiParticle => { particle.color = this.colors[(Math.random() * this.colors.length) | 0] + (this.options.alpha + ')'); - particle.color2 = 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 - height; + particle.y = Math.random() * -height; particle.diameter = Math.random() * 10 + 5; - particle.tilt = Math.random() * 10 - 10; + particle.tilt = Math.random() * -10; particle.tiltAngleIncrement = Math.random() * 0.07 + 0.05; particle.tiltAngle = Math.random() * Math.PI; return particle; @@ -141,10 +145,8 @@ export default class Confetti implements ICanvasEffect { if (!this.context || !this.context.canvas) { return; } - let particle; let x; let x2; let y2; - for (let i = 0; i < this.particles.length; i++) { - particle = this.particles[i]; + for (const particle of this.particles) { this.context.beginPath(); context.lineWidth = particle.diameter; x2 = particle.x + particle.tilt; From 674060ed9373b1a2ca210449e4a4110415ce21ba Mon Sep 17 00:00:00 2001 From: MaHa-Nordeck Date: Thu, 22 Oct 2020 15:28:30 +0200 Subject: [PATCH 0075/1984] fixed spacing --- src/components/views/elements/effects/confetti/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/effects/confetti/index.ts b/src/components/views/elements/effects/confetti/index.ts index 309fc9dd9c..aee8f54a3a 100644 --- a/src/components/views/elements/effects/confetti/index.ts +++ b/src/components/views/elements/effects/confetti/index.ts @@ -107,7 +107,7 @@ export default class Confetti implements ICanvasEffect { 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) { + if (this.options.gradient) { particle.color2 = this.colors[(Math.random() * this.colors.length) | 0] + (this.options.alpha + ')'); } else { particle.color2 = particle.color; From 173d79886544bc57c8de0b1ae4b16a346cd73bae Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Fri, 23 Oct 2020 18:41:24 +0100 Subject: [PATCH 0076/1984] added cheerio as explicit dep in package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 0a3fd7a8b7..ca7d6ee0b7 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "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", From 06b20fad9543063409823540fcd4416a12c3ee21 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Fri, 23 Oct 2020 18:49:56 +0100 Subject: [PATCH 0077/1984] removed implicit "this" --- src/editor/serialize.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 88fd1c90fc..f31dd67ae7 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -68,10 +68,10 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = { _useHtmlParser2: true, decodeEntities: false }) // add fallback output for latex math, which should not be interpreted as markdown - phtml('div, span').each(function() { - const tex = phtml(this).attr('data-mx-maths') + phtml('div, span').each(function(i, e) { + const tex = phtml(e).attr('data-mx-maths') if (tex) { - phtml(this).html(`${tex}`) + phtml(e).html(`${tex}`) } }); return phtml.html(); From da60e4dba69d0475d837a55eb98293285fb116b0 Mon Sep 17 00:00:00 2001 From: Resynth Date: Sun, 25 Oct 2020 16:47:15 +0000 Subject: [PATCH 0078/1984] Lighten blockquote colour in dark mode Signed-off-by: Resynth --- 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 6e0c9acdfe..df68bf0e2f 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -272,6 +272,10 @@ $composer-shadow-color: rgba(0, 0, 0, 0.28); background-color: #080808; } } + + blockquote { + + } } // diff highlight colors From 2204e6c64e0042e0b937cf7d42e07816608e0234 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sun, 25 Oct 2020 18:32:24 +0000 Subject: [PATCH 0079/1984] generate valid block html for commonmark spec --- src/editor/serialize.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index f31dd67ae7..bd7845315e 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -52,13 +52,17 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = md = md.replace(RegExp(displayPattern, "gm"), function(m, p1) { const p1e = AllHtmlEntities.encode(p1); - return `
`; + return `
\n\n
\n\n`; }); md = md.replace(RegExp(inlinePattern, "gm"), function(m, p1) { const p1e = AllHtmlEntities.encode(p1); return ``; }); + + // make sure div tags always start on a new line, otherwise it will confuse + // the markdown parser + md = md.replace(/(.)
Date: Mon, 26 Oct 2020 16:37:45 +0100 Subject: [PATCH 0080/1984] null checks added --- src/components/structures/RoomView.tsx | 16 ++++++++-------- .../views/elements/effects/effectUtilities.ts | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 1a18ece008..57c9afb17b 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -831,14 +831,14 @@ export default class RoomView extends React.Component { }; private handleEffects = (ev) => { - if (this.state.room.getUnreadNotificationCount() === 0) return; - if (this.state.matrixClientIsReady) { - effects.forEach(effect => { - if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) { - dis.dispatch({action: `effects.${effect.command}`}); - } - }) - } + if (!this.state.room || + !this.state.matrixClientIsReady || + this.state.room.getUnreadNotificationCount() === 0) return; + effects.forEach(effect => { + if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) { + dis.dispatch({action: `effects.${effect.command}`}); + } + }) }; private onRoomName = (room: Room) => { diff --git a/src/components/views/elements/effects/effectUtilities.ts b/src/components/views/elements/effects/effectUtilities.ts index 212c477b39..e94287c745 100644 --- a/src/components/views/elements/effects/effectUtilities.ts +++ b/src/components/views/elements/effects/effectUtilities.ts @@ -4,5 +4,5 @@ * @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.includes(emoji)); + return emojis.some((emoji) => content.body && content.body.includes(emoji)); } From dc28616a6f366af8cf34970ce363d2e31fe74555 Mon Sep 17 00:00:00 2001 From: Resynth Date: Mon, 26 Oct 2020 22:53:37 +0000 Subject: [PATCH 0081/1984] Remove empty CSS block Signed-off-by: Resynth --- res/themes/dark/css/_dark.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index df68bf0e2f..6e0c9acdfe 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -272,10 +272,6 @@ $composer-shadow-color: rgba(0, 0, 0, 0.28); background-color: #080808; } } - - blockquote { - - } } // diff highlight colors From 6dc709a045204862279629d1e69f799537e27305 Mon Sep 17 00:00:00 2001 From: Resynth Date: Tue, 27 Oct 2020 20:10:23 +0000 Subject: [PATCH 0082/1984] =?UTF-8?q?=F0=9F=98=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Resynth --- 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 6e0c9acdfe..fdf64d52f8 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -272,6 +272,10 @@ $composer-shadow-color: rgba(0, 0, 0, 0.28); background-color: #080808; } } + + blockquote { + color: #919191; + } } // diff highlight colors From 24ba566877e5d9a897a3fe21a7b09cf860f0b5ab Mon Sep 17 00:00:00 2001 From: Matthew Kenigsberg Date: Wed, 28 Oct 2020 18:34:04 -0500 Subject: [PATCH 0083/1984] Specify community description img must be mxc urls Closes vector-im/element-web#7100 Signed-off-by: Matthew Kenigsberg --- src/components/structures/GroupView.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 482b9f6da2..bbc4187298 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -47,7 +47,7 @@ const LONG_DESC_PLACEHOLDER = _td( some important links

- You can even use 'img' tags + You can even add images with Matrix URLs

`); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1548dd5c13..600319a874 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2001,7 +2001,7 @@ "Attach files from chat or just drag and drop them anywhere in a room.": "Attach files from chat or just drag and drop them anywhere in a room.", "Communities": "Communities", "Create community": "Create community", - "

HTML for your community's page

\n

\n Use the long description to introduce new members to the community, or distribute\n some important links\n

\n

\n You can even use 'img' tags\n

\n": "

HTML for your community's page

\n

\n Use the long description to introduce new members to the community, or distribute\n some important links\n

\n

\n You can even use 'img' tags\n

\n", + "

HTML for your community's page

\n

\n Use the long description to introduce new members to the community, or distribute\n some important links\n

\n

\n You can even add images with Matrix URLs \n

\n": "

HTML for your community's page

\n

\n Use the long description to introduce new members to the community, or distribute\n some important links\n

\n

\n You can even add images with Matrix URLs \n

\n", "Add rooms to the community summary": "Add rooms to the community summary", "Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?", "Add to summary": "Add to summary", From 3f9f1d03c8445002e053ff15054aa538cc83c514 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Thu, 29 Oct 2020 13:22:09 +0000 Subject: [PATCH 0084/1984] stubbed isGuest for unit tests --- test/components/views/messages/TextualBody-test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/components/views/messages/TextualBody-test.js b/test/components/views/messages/TextualBody-test.js index 07cd51edbd..bf55e9c430 100644 --- a/test/components/views/messages/TextualBody-test.js +++ b/test/components/views/messages/TextualBody-test.js @@ -36,6 +36,7 @@ describe("", () => { MatrixClientPeg.matrixClient = { getRoom: () => mkStubRoom("room_id"), getAccountData: () => undefined, + isGuest: () => false, }; const ev = mkEvent({ @@ -59,6 +60,7 @@ describe("", () => { MatrixClientPeg.matrixClient = { getRoom: () => mkStubRoom("room_id"), getAccountData: () => undefined, + isGuest: () => false, }; const ev = mkEvent({ @@ -83,6 +85,7 @@ describe("", () => { MatrixClientPeg.matrixClient = { getRoom: () => mkStubRoom("room_id"), getAccountData: () => undefined, + isGuest: () => false, }; }); @@ -135,6 +138,7 @@ describe("", () => { getHomeserverUrl: () => "https://my_server/", on: () => undefined, removeListener: () => undefined, + isGuest: () => false, }; }); From f828c6d49467bcb9f9efc5104637a94b1ef13a6a Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 29 Oct 2020 17:56:24 +0000 Subject: [PATCH 0085/1984] Implement call hold Currently just by adding /holdcall and /unholdcall slash commands The only place the hold status of the call is currently represented is when the call is a voice call and you're viewing a different room: it's not wired up when you're viewing the room because that currently uses the room status bar which it won't do with the new UI. Also convert VideoFeed to typescript, and remove videoview because it essentially just managed the fullscreen functionality, but we'll want and 'on hold' representation (and probably chrome for hagnup etc) in the fullscreen UI too, so let's just make CallView the thing that gets fullscreened. --- res/css/_components.scss | 4 +- res/css/views/voip/_CallContainer.scss | 4 +- res/css/views/voip/_CallView.scss | 7 + res/css/views/voip/_VideoView.scss | 49 ------ src/@types/global.d.ts | 14 ++ src/CallHandler.tsx | 25 ++- src/SlashCommands.tsx | 27 +++ src/components/structures/RoomView.tsx | 2 +- src/components/views/elements/AppTile.js | 2 +- src/components/views/voip/CallPreview.tsx | 2 +- src/components/views/voip/CallView.tsx | 196 ++++++++++++++-------- src/components/views/voip/VideoFeed.js | 58 ------- src/components/views/voip/VideoFeed.tsx | 82 +++++++++ src/components/views/voip/VideoView.js | 142 ---------------- src/i18n/strings/en_EN.json | 3 + 15 files changed, 287 insertions(+), 330 deletions(-) delete mode 100644 res/css/views/voip/_VideoView.scss delete mode 100644 src/components/views/voip/VideoFeed.js create mode 100644 src/components/views/voip/VideoFeed.tsx delete mode 100644 src/components/views/voip/VideoView.js diff --git a/res/css/_components.scss b/res/css/_components.scss index ad3cfbdcea..4a9301d085 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -9,6 +9,7 @@ @import "./structures/_CustomRoomTagPanel.scss"; @import "./structures/_FilePanel.scss"; @import "./structures/_GenericErrorPage.scss"; +@import "./structures/_GroupFilterPanel.scss"; @import "./structures/_GroupView.scss"; @import "./structures/_HeaderButtons.scss"; @import "./structures/_HomePage.scss"; @@ -27,7 +28,6 @@ @import "./structures/_ScrollPanel.scss"; @import "./structures/_SearchBox.scss"; @import "./structures/_TabbedView.scss"; -@import "./structures/_GroupFilterPanel.scss"; @import "./structures/_ToastContainer.scss"; @import "./structures/_UploadBar.scss"; @import "./structures/_UserMenu.scss"; @@ -227,4 +227,4 @@ @import "./views/verification/_VerificationShowSas.scss"; @import "./views/voip/_CallContainer.scss"; @import "./views/voip/_CallView.scss"; -@import "./views/voip/_VideoView.scss"; +@import "./views/voip/_VideoFeed.scss"; diff --git a/res/css/views/voip/_CallContainer.scss b/res/css/views/voip/_CallContainer.scss index 759797ae7b..eec8a1f188 100644 --- a/res/css/views/voip/_CallContainer.scss +++ b/res/css/views/voip/_CallContainer.scss @@ -33,11 +33,11 @@ limitations under the License. pointer-events: initial; // restore pointer events so the user can leave/interact cursor: pointer; - .mx_VideoView { + .mx_CallView_video { width: 350px; } - .mx_VideoView_localVideoFeed { + .mx_VideoFeed_local { border-radius: 8px; overflow: hidden; } diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index f6f3d40308..2aeaaa87dc 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -92,3 +92,10 @@ limitations under the License. background-color: $primary-fg-color; } } + +.mx_CallView_video { + width: 100%; + position: relative; + z-index: 30; +} + diff --git a/res/css/views/voip/_VideoView.scss b/res/css/views/voip/_VideoView.scss deleted file mode 100644 index feb60f4763..0000000000 --- a/res/css/views/voip/_VideoView.scss +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_VideoView { - width: 100%; - position: relative; - z-index: 30; -} - -.mx_VideoView video { - width: 100%; -} - -.mx_VideoView_remoteVideoFeed { - width: 100%; - background-color: #000; - z-index: 50; -} - -.mx_VideoView_localVideoFeed { - width: 25%; - height: 25%; - position: absolute; - left: 10px; - bottom: 10px; - z-index: 100; -} - -.mx_VideoView_localVideoFeed video { - width: auto; - height: 100%; -} - -.mx_VideoView_localVideoFeed.mx_VideoView_localVideoFeed_flipped video { - transform: scale(-1, 1); -} diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index ed28a5c479..acb2c40031 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -65,6 +65,13 @@ declare global { interface Document { // https://developer.mozilla.org/en-US/docs/Web/API/Document/hasStorageAccess hasStorageAccess?: () => Promise; + + // Safari & IE11 only have this prefixed: we used prefixed versions + // previously so let's continue to support them for now + webkitExitFullscreen(): Promise; + msExitFullscreen(): Promise; + readonly webkitFullscreenElement: Element | null; + readonly msFullscreenElement: Element | null; } interface Navigator { @@ -94,4 +101,11 @@ declare global { interface HTMLAudioElement { type?: string; } + + interface Element { + // Safari & IE11 only have this prefixed: we used prefixed versions + // previously so let's continue to support them for now + webkitRequestFullScreen(options?: FullscreenOptions): Promise; + msRequestFullscreen(options?: FullscreenOptions): Promise; + } } diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index e303dd3819..17867536ed 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -59,8 +59,7 @@ import {MatrixClientPeg} from './MatrixClientPeg'; import PlatformPeg from './PlatformPeg'; import Modal from './Modal'; import { _t } from './languageHandler'; -// @ts-ignore - XXX: tsc doesn't like this: our js-sdk imports are complex so this isn't surprising -import Matrix from 'matrix-js-sdk'; +import Matrix from 'matrix-js-sdk/src/browser-index'; import dis from './dispatcher/dispatcher'; import WidgetUtils from './utils/WidgetUtils'; import WidgetEchoStore from './stores/WidgetEchoStore'; @@ -77,7 +76,7 @@ import ErrorDialog from "./components/views/dialogs/ErrorDialog"; import WidgetStore from "./stores/WidgetStore"; import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore"; import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions"; -import { MatrixCall, CallErrorCode, CallState, CallEvent, CallParty } from "matrix-js-sdk/lib/webrtc/call"; +import { MatrixCall, CallErrorCode, CallState, CallEvent, CallParty } from "matrix-js-sdk/src/webrtc/call"; import Analytics from './Analytics'; enum AudioID { @@ -97,6 +96,18 @@ export enum PlaceCallType { ScreenSharing = 'screensharing', } +function getRemoteAudioElement(): HTMLAudioElement { + // this needs to be somewhere at the top of the DOM which + // always exists to avoid audio interruptions. + // Might as well just use DOM. + const remoteAudioElement = document.getElementById("remoteAudio") as HTMLAudioElement; + if (!remoteAudioElement) { + console.error("Failed to find remoteAudio element - cannot play audio!" + + "You need to add an