From d8f4512439bc844e5df75050afb67aef0d578f72 Mon Sep 17 00:00:00 2001 From: Sorunome Date: Wed, 22 May 2019 20:41:27 +0200 Subject: [PATCH 1/4] add basic spoiler support --- res/css/views/rooms/_EventTile.scss | 27 +++++++++++++++++ src/HtmlUtils.js | 2 +- src/components/views/elements/Spoiler.js | 32 ++++++++++++++++++++ src/components/views/messages/TextualBody.js | 30 ++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/components/views/elements/Spoiler.js diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 72881231f8..dd078d7f30 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -280,6 +280,33 @@ limitations under the License. overflow-y: hidden; } +/* Spoiler stuff */ +.mx_EventTile_spoiler { + cursor: pointer; +} + +.mx_EventTile_spoiler_reason { + color: $event-timestamp-color; + font-size: 11px; +} + +.mx_EventTile_spoiler_content { + background-color: black; +} + +.mx_EventTile_spoiler_content > span { + visibility: hidden; +} + + +.mx_EventTile_spoiler.visible > .mx_EventTile_spoiler_content { + background-color: initial; +} + +.mx_EventTile_spoiler.visible > .mx_EventTile_spoiler_content > span { + visibility: visible; +} + /* End to end encryption stuff */ .mx_EventTile:hover .mx_EventTile_e2eIcon { opacity: 1; diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index d06c31682d..626b228357 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -250,7 +250,7 @@ const sanitizeHtmlParams = { 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', 'style'], // custom to matrix + span: ['data-mx-bg-color', 'data-mx-color', 'data-mx-spoiler', 'style'], // custom to matrix a: ['href', 'name', 'target', 'rel'], // remote target: custom to matrix img: ['src', 'width', 'height', 'alt', 'title'], ol: ['start'], diff --git a/src/components/views/elements/Spoiler.js b/src/components/views/elements/Spoiler.js new file mode 100644 index 0000000000..e1e0febbab --- /dev/null +++ b/src/components/views/elements/Spoiler.js @@ -0,0 +1,32 @@ +'use strict'; + +import React from 'react'; + +module.exports = React.createClass({ + displayName: 'Spoiler', + + getInitialState() { + return { + visible: false, + }; + }, + + toggleVisible() { + this.setState({ visible: !this.state.visible }); + }, + + render: function() { + const reason = this.props.reason ? ( + {"(" + this.props.reason + ")"} + ) : null; + return ( + + { reason } +   + + + + + ); + } +}) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 1fc16d6a53..e6d67b034d 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -96,6 +96,8 @@ module.exports = React.createClass({ }, _applyFormatting() { + this.activateSpoilers(this.refs.content.children); + // pillifyLinks BEFORE linkifyElement because plain room/user URLs in the composer // are still sent as plaintext URLs. If these are ever pillified in the composer, // we should be pillify them here by doing the linkifying BEFORE the pillifying. @@ -184,6 +186,34 @@ module.exports = React.createClass({ } }, + activateSpoilers: function(nodes) { + let node = nodes[0]; + while (node) { + if (node.tagName === "SPAN" && typeof node.getAttribute("data-mx-spoiler") === "string") { + const spoilerContainer = document.createElement('span'); + + const reason = node.getAttribute("data-mx-spoiler"); + const Spoiler = sdk.getComponent('elements.Spoiler'); + node.removeAttribute("data-mx-spoiler"); // we don't want to recurse + const spoiler = ; + + ReactDOM.render(spoiler, spoilerContainer); + node.parentNode.replaceChild(spoilerContainer, node); + + node = spoilerContainer; + } + + if (node.childNodes && node.childNodes.length) { + this.activateSpoilers(node.childNodes); + } + + node = node.nextSibling; + } + }, + pillifyLinks: function(nodes) { const shouldShowPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar"); let node = nodes[0]; From eddac4b188303d4fe456dd8150a32c17c6a41c28 Mon Sep 17 00:00:00 2001 From: Sorunome Date: Tue, 11 Jun 2019 21:08:45 +0200 Subject: [PATCH 2/4] blur spoilers --- res/css/views/rooms/_EventTile.scss | 14 +++----------- src/components/views/elements/Spoiler.js | 4 +--- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index dd078d7f30..cf3e5b7985 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -291,20 +291,12 @@ limitations under the License. } .mx_EventTile_spoiler_content { - background-color: black; + filter: blur(5px) saturate(0.1) sepia(1); + transition-duration: 0.5s; } -.mx_EventTile_spoiler_content > span { - visibility: hidden; -} - - .mx_EventTile_spoiler.visible > .mx_EventTile_spoiler_content { - background-color: initial; -} - -.mx_EventTile_spoiler.visible > .mx_EventTile_spoiler_content > span { - visibility: visible; + filter: none; } /* End to end encryption stuff */ diff --git a/src/components/views/elements/Spoiler.js b/src/components/views/elements/Spoiler.js index e1e0febbab..d2de7d70e0 100644 --- a/src/components/views/elements/Spoiler.js +++ b/src/components/views/elements/Spoiler.js @@ -23,9 +23,7 @@ module.exports = React.createClass({ { reason }   - - - + ); } From d0f78e9d4449e1d2c23d54819663252d874dfe23 Mon Sep 17 00:00:00 2001 From: Sorunome Date: Tue, 11 Jun 2019 22:13:47 +0200 Subject: [PATCH 3/4] stop propagation of click events on un-hiding the spoiler --- src/components/views/elements/Spoiler.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/views/elements/Spoiler.js b/src/components/views/elements/Spoiler.js index d2de7d70e0..9be7bc7784 100644 --- a/src/components/views/elements/Spoiler.js +++ b/src/components/views/elements/Spoiler.js @@ -11,7 +11,12 @@ module.exports = React.createClass({ }; }, - toggleVisible() { + toggleVisible(e) { + if (!this.state.visible) { + // we are un-blurring, we don't want this click to propagate to potential child pills + e.preventDefault(); + e.stopPropagation(); + } this.setState({ visible: !this.state.visible }); }, From 4ae130bd2747102e0455a2f601a0b37b8c02bb94 Mon Sep 17 00:00:00 2001 From: Sorunome Date: Thu, 29 Aug 2019 18:13:52 +0200 Subject: [PATCH 4/4] add license header, descriptive comment and change to class --- src/components/views/elements/Spoiler.js | 36 +++++++++++++++++------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/components/views/elements/Spoiler.js b/src/components/views/elements/Spoiler.js index 9be7bc7784..b75967b225 100644 --- a/src/components/views/elements/Spoiler.js +++ b/src/components/views/elements/Spoiler.js @@ -1,15 +1,28 @@ -'use strict'; +/* + Copyright 2019 Sorunome + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ import React from 'react'; -module.exports = React.createClass({ - displayName: 'Spoiler', - - getInitialState() { - return { +export default class Spoiler extends React.Component { + constructor(props) { + super(props); + this.state = { visible: false, }; - }, + } toggleVisible(e) { if (!this.state.visible) { @@ -18,12 +31,15 @@ module.exports = React.createClass({ e.stopPropagation(); } this.setState({ visible: !this.state.visible }); - }, + } - render: function() { + render() { const reason = this.props.reason ? ( {"(" + this.props.reason + ")"} ) : null; + // react doesn't allow appending a DOM node as child. + // as such, we pass the this.props.contentHtml instead and then set the raw + // HTML content. This is secure as the contents have already been parsed previously return ( { reason } @@ -32,4 +48,4 @@ module.exports = React.createClass({ ); } -}) +}