diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index c53bc86400..a25e172c73 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -103,6 +103,7 @@ $pulse-color: $alert; mask-position: center; } } + .mx_RightPanel_headerButton_unreadIndicator_bg { position: absolute; right: $dot-offset; @@ -121,14 +122,6 @@ $pulse-color: $alert; right: $dot-offset; top: $dot-offset; margin: 4px; - width: $dot-size; - height: $dot-size; - border-radius: 50%; - transform: scale(1); - background: rgba($pulse-color, 1); - box-shadow: 0 0 0 0 rgba($pulse-color, 1); - animation: mx_RightPanel_indicator_pulse 2s infinite; - animation-iteration-count: 1; &.mx_Indicator_red { background: rgba($alert, 1); @@ -144,22 +137,6 @@ $pulse-color: $alert; background: rgba($primary-content, 1); box-shadow: rgba($primary-content, 1); } - - &::after { - content: ""; - position: absolute; - width: inherit; - height: inherit; - top: 0; - left: 0; - transform: scale(1); - transform-origin: center center; - animation-name: mx_RightPanel_indicator_pulse_shadow; - animation-duration: inherit; - animation-iteration-count: inherit; - border-radius: 50%; - background: inherit; - } } .mx_RightPanel_timelineCardButton { @@ -250,7 +227,8 @@ $pulse-color: $alert; margin: 16px 0; } - h2, p { + h2, + p { font-size: $font-14px; } diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index a008a83aa6..7990db8cf1 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -105,7 +105,9 @@ limitations under the License. flex: 1; min-width: 0; - .mx_RoomView_messagePanel, .mx_RoomView_messagePanelSpinner, .mx_RoomView_messagePanelSearchSpinner { + .mx_RoomView_messagePanel, + .mx_RoomView_messagePanelSpinner, + .mx_RoomView_messagePanelSearchSpinner { order: 2; } } @@ -147,20 +149,17 @@ limitations under the License. } .mx_RoomView_messageListWrapper { - min-height: 100%; - display: flex; - flex-direction: column; - justify-content: flex-end; + position: relative; } .mx_RoomView_searchResultsPanel { .mx_RoomView_messageListWrapper { justify-content: flex-start; - > .mx_RoomView_MessageList > li > ol { + >.mx_RoomView_MessageList > li > ol { list-style-type: none; } } @@ -295,3 +294,62 @@ hr.mx_RoomView_myReadMarker { min-height: 42px; } } + +@keyframes mx_Indicator_pulse { + 0% { + transform: scale(0.95); + } + + 70% { + transform: scale(1); + } + + 100% { + transform: scale(0.95); + } +} + +@keyframes mx_Indicator_pulse_shadow { + 0% { + opacity: 0.7; + } + + 70% { + transform: scale(2.2); + opacity: 0; + } + + 100% { + opacity: 0; + } +} + +.mx_Indicator { + position: absolute; + right: 0; + top: 0; + width: $dot-size; + height: $dot-size; + border-radius: 50%; + transform: scale(1); + background: rgba($pulse-color, 1); + box-shadow: 0 0 0 0 rgba($pulse-color, 1); + animation: mx_Indicator_pulse 2s infinite; + animation-iteration-count: 1; + + &::after { + content: ""; + position: absolute; + width: inherit; + height: inherit; + top: 0; + left: 0; + transform: scale(1); + transform-origin: center center; + animation-name: mx_Indicator_pulse_shadow; + animation-duration: inherit; + animation-iteration-count: inherit; + border-radius: 50%; + background: inherit; + } +} diff --git a/res/css/views/elements/_Tooltip.scss b/res/css/views/elements/_Tooltip.scss index f5621a61d5..c89c654178 100644 --- a/res/css/views/elements/_Tooltip.scss +++ b/res/css/views/elements/_Tooltip.scss @@ -16,13 +16,23 @@ limitations under the License. */ @keyframes mx_fadein { - from { opacity: 0; } - to { opacity: 1; } + from { + opacity: 0; + } + + to { + opacity: 1; + } } @keyframes mx_fadeout { - from { opacity: 1; } - to { opacity: 0; } + from { + opacity: 1; + } + + to { + opacity: 0; + } } .mx_Tooltip_chevron { diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index ab434d8286..75318757a7 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -48,7 +48,7 @@ limitations under the License. cursor: initial; } - > * { + >* { white-space: nowrap; display: inline-block; position: relative; @@ -102,6 +102,11 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/message/thread.svg'); } +.mx_MessageActionBar_threadButton .mx_Indicator { + background: $links; + animation-iteration-count: infinite; +} + .mx_MessageActionBar_editButton::after { mask-image: url('$(res)/img/element-icons/room/message-bar/edit.svg'); } diff --git a/res/css/views/right_panel/_ThreadPanel.scss b/res/css/views/right_panel/_ThreadPanel.scss index 280404a649..37cabe5e08 100644 --- a/res/css/views/right_panel/_ThreadPanel.scss +++ b/res/css/views/right_panel/_ThreadPanel.scss @@ -17,6 +17,8 @@ limitations under the License. .mx_ThreadPanel { display: flex; flex-direction: column; + height: 100px; + overflow: visible; .mx_BaseCard_header { margin-bottom: 12px; @@ -225,6 +227,20 @@ limitations under the License. display: none; // hide the hidden event expand button, not enough space, view source can still be used } } + + .mx_BaseCard_footer { + text-align: left; + font-size: $font-12px; + align-items: center; + justify-content: end; + gap: 4px; + position: relative; + top: 2px; + + .mx_AccessibleButton_kind_link_inline { + color: $secondary-content; + } + } } .mx_ThreadPanel_replies { @@ -269,10 +285,10 @@ limitations under the License. align-items: center; justify-content: center; position: absolute; - top: 48px; - bottom: 8px; - left: 8px; - right: 8px; + top: 0; + bottom: 0; + left: 0; + right: 0; padding: 20px; h2 { diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index ccd6b88d02..4238dca938 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -940,6 +940,7 @@ $threadInfoLineHeight: calc(2 * $font-12px); .mx_ThreadView { display: flex; flex-direction: column; + max-height: 100%; .mx_ThreadView_List { flex: 1; diff --git a/res/img/betas/threads.png b/res/img/betas/threads.png new file mode 100644 index 0000000000..f34fb5f895 Binary files /dev/null and b/res/img/betas/threads.png differ diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 36efeda5c4..73e2ee71c0 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -8,9 +8,22 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: 'Inter', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', sans-serif, 'Noto Color Emoji'; +$font-family: 'Inter', +'Twemoji', +'Apple Color Emoji', +'Segoe UI Emoji', +'Arial', +'Helvetica', +sans-serif, +'Noto Color Emoji'; -$monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', monospace, 'Noto Color Emoji'; +$monospace-font-family: 'Inconsolata', +'Twemoji', +'Apple Color Emoji', +'Segoe UI Emoji', +'Courier', +monospace, +'Noto Color Emoji'; // Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A120 // ******************** @@ -57,7 +70,7 @@ $icon-button-color: $quaternary-content; // Colors that aren't in Figma and are theme specific - we need to get rid of these // ******************** $selection-fg-color: $background; -$yellow-background: #fff8e3; +$yellow-background: #fff8e3; $secondary-accent-color: #f2f5f8; $button-fg-color: $background; $neutral-badge-color: #dbdbdb; @@ -79,7 +92,8 @@ $event-selected-color: #f6f7f8; $topleftmenu-color: #212121; $roomtopic-color: #9e9e9e; $spacePanel-bg-color: rgba(232, 232, 232, 0.77); -$panel-gradient: rgba(242, 245, 248, 0), rgba(242, 245, 248, 1); +$panel-gradient: rgba(242, 245, 248, 0), +rgba(242, 245, 248, 1); $h3-color: #3d3b39; $event-highlight-bg-color: $yellow-background; $header-panel-text-primary-color: #91A1C0; @@ -296,6 +310,7 @@ $focus-brightness: 105%; :root { --lp-background-blur: 40px; } + // ******************** // Icon URLs @@ -330,8 +345,7 @@ $location-live-secondary-color: #deddfd; outline: none; } -@define-mixin mx_DialogButton_hover { -} +@define-mixin mx_DialogButton_hover {} @define-mixin mx_DialogButton_danger { background-color: $accent; @@ -355,6 +369,7 @@ $location-live-secondary-color: #deddfd; color: $accent; text-decoration: none; } + // ******************** // diff highlight colors @@ -366,4 +381,5 @@ $location-live-secondary-color: #deddfd; .hljs-deletion { background: #fdd; } + // ******************** diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index b9f227780e..7d75f91858 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -31,7 +31,14 @@ import { Layout } from '../../settings/enums/Layout'; import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; import Measured from '../views/elements/Measured'; import PosthogTrackers from "../../PosthogTrackers"; -import { ButtonEvent } from "../views/elements/AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton"; +import { BetaPill } from '../views/beta/BetaCard'; +import SdkConfig from '../../SdkConfig'; +import Modal from '../../Modal'; +import BetaFeedbackDialog from '../views/dialogs/BetaFeedbackDialog'; +import { Action } from '../../dispatcher/actions'; +import { UserTab } from '../views/dialogs/UserSettingsDialog'; +import dis from '../../dispatcher/dispatcher'; interface IProps { roomId: string; @@ -233,6 +240,12 @@ const ThreadPanel: React.FC = ({ } }, [timelineSet, timelinePanel]); + const openFeedback = SdkConfig.get().bug_report_endpoint_url ? () => { + Modal.createTrackedDialog("Threads Feedback", "feature_thread", BetaFeedbackDialog, { + featureId: "feature_thread", + }); + } : null; + return ( = ({ setFilterOption={setFilterOption} empty={threadCount === 0} />} + footer={<> + { + dis.dispatch({ + action: Action.ViewUserSettings, + initialTabId: UserTab.Labs, + }); + }} + /> + { openFeedback && _t("Give feedback", {}, { + a: sub => + { sub }, + }) } + } className="mx_ThreadPanel" onClose={onClose} withoutScrollContainer={true} diff --git a/src/components/views/beta/BetaCard.tsx b/src/components/views/beta/BetaCard.tsx index 1d625fc843..4639886b70 100644 --- a/src/components/views/beta/BetaCard.tsx +++ b/src/components/views/beta/BetaCard.tsx @@ -36,17 +36,27 @@ interface IProps { featureId: string; } -export const BetaPill = ({ onClick }: { onClick?: () => void }) => { +interface IBetaPillProps { + onClick?: () => void; + tooltipTitle?: string; + tooltipCaption?: string; +} + +export const BetaPill = ({ + onClick, + tooltipTitle = _t("This is a beta feature"), + tooltipCaption = _t("Click for more info"), +}: IBetaPillProps) => { if (onClick) { return
- { _t("This is a beta feature") } + { tooltipTitle }
- { _t("Click for more info") } + { tooltipCaption }
} onClick={onClick} diff --git a/src/components/views/dialogs/BetaFeedbackDialog.tsx b/src/components/views/dialogs/BetaFeedbackDialog.tsx index c5fba52b51..b0afec4e91 100644 --- a/src/components/views/dialogs/BetaFeedbackDialog.tsx +++ b/src/components/views/dialogs/BetaFeedbackDialog.tsx @@ -35,7 +35,7 @@ const BetaFeedbackDialog: React.FC = ({ featureId, onFinished }) => { const info = SettingsStore.getBetaInfo(featureId); return { - showThread({ rootEvent: this.props.mxEvent, push: isCard }); + if (localStorage.getItem("mx_seen_feature_thread") === null) { + localStorage.setItem("mx_seen_feature_thread", "true"); + } + + if (!SettingsStore.getValue("feature_thread")) { + dis.dispatch({ + action: Action.ViewUserSettings, + initialTabId: UserTab.Labs, + }); + } else { + showThread({ rootEvent: this.props.mxEvent, push: isCard }); + } }; private onEditClick = (): void => { @@ -235,14 +248,13 @@ export default class MessageActionBar extends React.PureComponent; - const hasARelation = !!this.props.mxEvent?.getRelation()?.rel_type; - + const relationType = this.props.mxEvent?.getRelation()?.rel_type; + const hasARelation = !!relationType && relationType !== RelationType.Thread; + const firstTimeSeeingThreads = localStorage.getItem("mx_seen_feature_thread") === null && + !SettingsStore.getValue("feature_thread"); const threadTooltipButton = { context => +
+ { !hasARelation + ? _t("Reply in thread") + : _t("Can't create a thread from an event with an existing relation") } +
+ { !hasARelation && ( +
+ { SettingsStore.getValue("feature_thread") + ? _t("Beta feature") + : _t("Beta feature. Click to learn more.") + } +
+ ) } + } + title={!hasARelation ? _t("Reply in thread") - : _t("Can't create a thread from an event with an existing relation") - } + : _t("Can't create a thread from an event with an existing relation")} onClick={this.onThreadClick.bind(null, context.isCard)} - /> + > + { firstTimeSeeingThreads && ( +
+ ) } + } ; @@ -387,14 +420,14 @@ export default class MessageActionBar extends React.PureComponent + const tooltip = <>
{ this.props.isQuoteExpanded ? _t("Collapse quotes") : _t("Expand quotes") }
{ _t(ALTERNATE_KEY_NAME[Key.SHIFT]) + " + " + _t("Click") }
-
; + ; toolbarOpts.push( { } const classes = classNames({ + "mx_Indicator": true, "mx_RightPanel_headerButton_unreadIndicator": true, "mx_Indicator_bold": color === NotificationColor.Bold, "mx_Indicator_gray": color === NotificationColor.Grey, diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index e811a37508..46379a8815 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -612,7 +612,7 @@ export class UnwrappedEventTile extends React.Component { * when we are at the sync stage */ const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); - const thread = room?.threads.get(this.props.mxEvent.getId()); + const thread = room?.threads?.get(this.props.mxEvent.getId()); return thread || null; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 795167ac67..28584afaf6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -867,6 +867,13 @@ "Render LaTeX maths in messages": "Render LaTeX maths in messages", "Message Pinning": "Message Pinning", "Threaded messaging": "Threaded messaging", + "Keep discussions organised with threads.": "Keep discussions organised with threads.", + "Threads help keep conversations on-topic and easy to track. Learn more.": "Threads help keep conversations on-topic and easy to track. Learn more.", + "How can I start a thread?": "How can I start a thread?", + "Use \"Reply in thread\" when hovering over a message.": "Use \"Reply in thread\" when hovering over a message.", + "How can I leave the beta?": "How can I leave the beta?", + "To leave, return to this page and use the “Leave the beta” button.": "To leave, return to this page and use the “Leave the beta” button.", + "Thank you for trying the beta, please go into as much detail as you can so we can improve it.": "Thank you for trying the beta, please go into as much detail as you can so we can improve it.", "Custom user status messages": "Custom user status messages", "Video rooms (under active development)": "Video rooms (under active development)", "Render simple counters in room header": "Render simple counters in room header", @@ -886,9 +893,7 @@ "This feature is a work in progress, we'd love to hear your feedback.": "This feature is a work in progress, we'd love to hear your feedback.", "How can I give feedback?": "How can I give feedback?", "To feedback, join the beta, start a search and click on feedback.": "To feedback, join the beta, start a search and click on feedback.", - "How can I leave the beta?": "How can I leave the beta?", "To leave, just return to this page or click on the beta badge when you search.": "To leave, just return to this page or click on the beta badge when you search.", - "Thank you for trying the beta, please go into as much detail as you can so we can improve it.": "Thank you for trying the beta, please go into as much detail as you can so we can improve it.", "Right panel stays open (defaults to room member list)": "Right panel stays open (defaults to room member list)", "Jump to date (adds /jumptodate and jump to date headers)": "Jump to date (adds /jumptodate and jump to date headers)", "Don't send read receipts": "Don't send read receipts", @@ -2075,6 +2080,8 @@ "Edit": "Edit", "Reply in thread": "Reply in thread", "Can't create a thread from an event with an existing relation": "Can't create a thread from an event with an existing relation", + "Beta feature": "Beta feature", + "Beta feature. Click to learn more.": "Beta feature. Click to learn more.", "Reply": "Reply", "Collapse quotes": "Collapse quotes", "Expand quotes": "Expand quotes", @@ -2371,7 +2378,7 @@ "Invite anyway and never warn me again": "Invite anyway and never warn me again", "Invite anyway": "Invite anyway", "Close dialog": "Close dialog", - "%(featureName)s beta feedback": "%(featureName)s beta feedback", + "%(featureName)s Beta feedback": "%(featureName)s Beta feedback", "To leave the beta, visit your settings.": "To leave the beta, visit your settings.", "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.", "Preparing to send logs": "Preparing to send logs", @@ -2886,7 +2893,6 @@ "Revoke permissions": "Revoke permissions", "Move left": "Move left", "Move right": "Move right", - "This is a beta feature. Click for more info": "This is a beta feature. Click for more info", "This is a beta feature": "This is a beta feature", "Click for more info": "Click for more info", "Beta": "Beta", @@ -3104,6 +3110,8 @@ "Threads help keep your conversations on-topic and easy to track.": "Threads help keep your conversations on-topic and easy to track.", "Tip: Use \"Reply in thread\" when hovering over a message.": "Tip: Use \"Reply in thread\" when hovering over a message.", "Keep discussions organised with threads": "Keep discussions organised with threads", + "Threads are a beta feature": "Threads are a beta feature", + "Give feedback": "Give feedback", "Thread": "Thread", "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index f25a4bf8ce..8cd842b74e 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -165,7 +165,7 @@ export interface IBaseSetting { title: string; // _td caption: () => ReactNode; disclaimer?: (enabled: boolean) => ReactNode; - image: string; // require(...) + image?: string; // require(...) feedbackSubheading?: string; feedbackLabel?: string; extraSettings?: string[]; @@ -228,6 +228,30 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td("Threaded messaging"), supportedLevels: LEVELS_FEATURE, default: false, + betaInfo: { + title: _td("Threads"), + caption: () => <> +

{ _t("Keep discussions organised with threads.") }

+

{ _t("Threads help keep conversations on-topic and easy to track. Learn more.", {}, { + a: (sub) => + { sub } + , + }) }

+ , + disclaimer: () => + SdkConfig.get().bug_report_endpoint_url && <> +

{ _t("How can I start a thread?") }

+

{ _t("Use \"Reply in thread\" when hovering over a message.") }

+

{ _t("How can I leave the beta?") }

+

{ _t("To leave, return to this page and use the “Leave the beta” button.") }

+ , + feedbackLabel: "thread-feedback", + feedbackSubheading: _td("Thank you for trying the beta, " + + "please go into as much detail as you can so we can improve it."), + image: require("../../res/img/betas/threads.png"), + requiresRefresh: true, + }, + }, "feature_custom_status": { isFeature: true,