Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

🧵 Enable threads by default #9736

Merged
merged 11 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion cypress/e2e/polls/polls.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ describe("Polls", () => {
};

beforeEach(() => {
cy.enableLabsFeature("feature_thread");
cy.window().then((win) => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
});
Expand Down
36 changes: 0 additions & 36 deletions cypress/e2e/threads/threads.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,10 @@ limitations under the License.
import { SynapseInstance } from "../../plugins/synapsedocker";
import { MatrixClient } from "../../global";

function markWindowBeforeReload(): void {
// mark our window object to "know" when it gets reloaded
cy.window().then((w) => (w.beforeReload = true));
}

describe("Threads", () => {
let synapse: SynapseInstance;

beforeEach(() => {
// Default threads to ON for this spec
cy.enableLabsFeature("feature_thread");
cy.window().then((win) => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
});
Expand All @@ -44,35 +37,6 @@ describe("Threads", () => {
cy.stopSynapse(synapse);
});

it("should reload when enabling threads beta", () => {
markWindowBeforeReload();

// Turn off
cy.openUserSettings("Labs").within(() => {
// initially the new property is there
cy.window().should("have.prop", "beforeReload", true);

cy.leaveBeta("Threads");
cy.wait(1000);
// after reload the property should be gone
cy.window().should("not.have.prop", "beforeReload");
});

cy.get(".mx_MatrixChat", { timeout: 15000 }); // wait for the app
markWindowBeforeReload();

// Turn on
cy.openUserSettings("Labs").within(() => {
// initially the new property is there
cy.window().should("have.prop", "beforeReload", true);

cy.joinBeta("Threads");
cy.wait(1000);
// after reload the property should be gone
cy.window().should("not.have.prop", "beforeReload");
});
});

it("should be usable for a conversation", () => {
let bot: MatrixClient;
cy.getBot(synapse, {
Expand Down
7 changes: 0 additions & 7 deletions res/css/views/messages/_MessageActionBar.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,6 @@ limitations under the License.
color: $primary-content;
}

&.mx_MessageActionBar_threadButton {
.mx_Indicator {
background: $links;
animation-iteration-count: infinite;
}
}

&.mx_MessageActionBar_favouriteButton_fillstar {
color: var(--MessageActionBar-star-button-color);
}
Expand Down
Binary file removed res/img/betas/threads.png
Binary file not shown.
2 changes: 1 addition & 1 deletion src/MatrixClientPeg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
opts.pendingEventOrdering = PendingEventOrdering.Detached;
opts.lazyLoadMembers = true;
opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours
opts.experimentalThreadSupport = SettingsStore.getValue("feature_thread");
opts.experimentalThreadSupport = SettingsStore.getValue("feature_threadstable");

if (SettingsStore.getValue("feature_sliding_sync")) {
const proxyUrl = SettingsStore.getValue("feature_sliding_sync_proxy_url");
Expand Down
2 changes: 1 addition & 1 deletion src/components/structures/MessagePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
// and we check this in a hot code path. This is also cached in our
// RoomContext, however we still need a fallback for roomless MessagePanels.
this._showHiddenEvents = SettingsStore.getValue("showHiddenEventsInTimeline");
this.threadsEnabled = SettingsStore.getValue("feature_thread");
this.threadsEnabled = SettingsStore.getValue("feature_threadstable");

this.showTypingNotificationsWatcherRef = SettingsStore.watchSetting(
"showTypingNotifications",
Expand Down
3 changes: 2 additions & 1 deletion src/components/structures/RoomSearchView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import ResizeNotifier from "../../utils/ResizeNotifier";
import MatrixClientContext from "../../contexts/MatrixClientContext";
import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
import RoomContext from "../../contexts/RoomContext";
import SettingsStore from "../../settings/SettingsStore";

const DEBUG = false;
let debuglog = function (msg: string) {};
Expand Down Expand Up @@ -98,7 +99,7 @@ export const RoomSearchView = forwardRef<ScrollPanel, Props>(
return b.length - a.length;
});

if (client.supportsExperimentalThreads()) {
if (SettingsStore.getValue("feature_threadstable")) {
// Process all thread roots returned in this batch of search results
// XXX: This won't work for results coming from Seshat which won't include the bundled relationship
for (const result of results.results) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/structures/RoomView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1177,7 +1177,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
CHAT_EFFECTS.forEach((effect) => {
if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) {
// For initial threads launch, chat effects are disabled see #19731
if (!SettingsStore.getValue("feature_thread") || !ev.isRelation(THREAD_RELATION_TYPE.name)) {
if (!SettingsStore.getValue("feature_threadstable") || !ev.isRelation(THREAD_RELATION_TYPE.name)) {
dis.dispatch({ action: `effects.${effect.command}` });
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/structures/ThreadPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
const openFeedback = shouldShowFeedback()
? () => {
Modal.createDialog(BetaFeedbackDialog, {
featureId: "feature_thread",
featureId: "feature_threadstable",
});
}
: null;
Expand Down
2 changes: 1 addition & 1 deletion src/components/structures/TimelinePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1683,7 +1683,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
is very tied to the main room timeline, we are forcing the timeline to
send read receipts for threaded events */
const isThreadTimeline = this.context.timelineRenderingType === TimelineRenderingType.Thread;
if (SettingsStore.getValue("feature_thread") && isThreadTimeline) {
if (SettingsStore.getValue("feature_threadstable") && isThreadTimeline) {
return 0;
}
const index = this.state.events.findIndex((ev) => ev.getId() === evId);
Expand Down
8 changes: 2 additions & 6 deletions src/components/views/context_menus/MessageContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,7 @@ const ReplyInThreadButton = ({ mxEvent, closeMenu }: IReplyInThreadButton) => {
if (Boolean(relationType) && relationType !== RelationType.Thread) return null;

const onClick = (): void => {
if (!localStorage.getItem("mx_seen_feature_thread")) {
localStorage.setItem("mx_seen_feature_thread", "true");
}

if (!SettingsStore.getValue("feature_thread")) {
if (!SettingsStore.getValue("feature_threadstable")) {
dis.dispatch({
action: Action.ViewUserSettings,
initialTabId: UserTab.Labs,
Expand Down Expand Up @@ -644,7 +640,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
rightClick &&
contentActionable &&
canSendMessages &&
SettingsStore.getValue("feature_thread") &&
SettingsStore.getValue("feature_threadstable") &&
Thread.hasServerSideSupport &&
timelineRenderingType !== TimelineRenderingType.Thread
) {
Expand Down
30 changes: 4 additions & 26 deletions src/components/views/messages/MessageActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ import { Key } from "../../../Keyboard";
import { ALTERNATE_KEY_NAME } from "../../../accessibility/KeyboardShortcuts";
import { UserTab } from "../dialogs/UserTab";
import { Action } from "../../../dispatcher/actions";
import SdkConfig from "../../../SdkConfig";
import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
import useFavouriteMessages from "../../../hooks/useFavouriteMessages";
import { GetRelationsForEvent } from "../rooms/EventTile";
Expand Down Expand Up @@ -204,8 +203,7 @@ const ReplyInThreadButton = ({ mxEvent }: IReplyInThreadButton) => {

const relationType = mxEvent?.getRelation()?.rel_type;
const hasARelation = !!relationType && relationType !== RelationType.Thread;
const firstTimeSeeingThreads = !localStorage.getItem("mx_seen_feature_thread");
const threadsEnabled = SettingsStore.getValue("feature_thread");
const threadsEnabled = SettingsStore.getValue("feature_threadstable");

if (!threadsEnabled && !Thread.hasServerSideSupport) {
// hide the prompt if the user would only have degraded mode
Expand All @@ -217,11 +215,7 @@ const ReplyInThreadButton = ({ mxEvent }: IReplyInThreadButton) => {
e.preventDefault();
e.stopPropagation();

if (firstTimeSeeingThreads) {
localStorage.setItem("mx_seen_feature_thread", "true");
}

if (!SettingsStore.getValue("feature_thread")) {
if (!SettingsStore.getValue("feature_threadstable")) {
dis.dispatch({
action: Action.ViewUserSettings,
initialTabId: UserTab.Labs,
Expand Down Expand Up @@ -257,7 +251,7 @@ const ReplyInThreadButton = ({ mxEvent }: IReplyInThreadButton) => {
</div>
{!hasARelation && (
<div className="mx_Tooltip_sub">
{SettingsStore.getValue("feature_thread")
{SettingsStore.getValue("feature_threadstable")
? _t("Beta feature")
: _t("Beta feature. Click to learn more.")}
</div>
Expand All @@ -273,7 +267,6 @@ const ReplyInThreadButton = ({ mxEvent }: IReplyInThreadButton) => {
onContextMenu={onClick}
>
<ThreadIcon />
{firstTimeSeeingThreads && !threadsEnabled && <div className="mx_Indicator" />}
</RovingAccessibleTooltipButton>
);
};
Expand Down Expand Up @@ -393,21 +386,6 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
private readonly forbiddenThreadHeadMsgType = [MsgType.KeyVerificationRequest];

private get showReplyInThreadAction(): boolean {
if (!SettingsStore.getValue("feature_thread") && !Thread.hasServerSideSupport) {
// hide the prompt if the user would only have degraded mode
return null;
}

if (
!SettingsStore.getBetaInfo("feature_thread") &&
!SettingsStore.getValue("feature_thread") &&
!SdkConfig.get("show_labs_settings")
) {
// Hide the beta prompt if there is no UI to enable it,
// e.g if config.json disables it and doesn't enable show labs flags
return false;
}

const inNotThreadTimeline = this.context.timelineRenderingType !== TimelineRenderingType.Thread;

const isAllowedMessageType =
Expand Down Expand Up @@ -568,7 +546,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
);
}
} else if (
SettingsStore.getValue("feature_thread") &&
SettingsStore.getValue("feature_threadstable") &&
// Show thread icon even for deleted messages, but only within main timeline
this.context.timelineRenderingType === TimelineRenderingType.Room &&
this.props.mxEvent.getThread()
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/right_panel/RoomHeaderButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
);
rightPanelPhaseButtons.set(
RightPanelPhases.ThreadPanel,
SettingsStore.getValue("feature_thread") ? (
SettingsStore.getValue("feature_threadstable") ? (
<HeaderButton
key={RightPanelPhases.ThreadPanel}
name="threadsButton"
Expand Down
6 changes: 3 additions & 3 deletions src/components/views/rooms/EventTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
}
}

if (SettingsStore.getValue("feature_thread")) {
if (SettingsStore.getValue("feature_threadstable")) {
this.props.mxEvent.on(ThreadEvent.Update, this.updateThread);

if (this.thread && !this.supportsThreadNotifications) {
Expand Down Expand Up @@ -469,7 +469,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
if (this.props.showReactions) {
this.props.mxEvent.removeListener(MatrixEventEvent.RelationsCreated, this.onReactionsCreated);
}
if (SettingsStore.getValue("feature_thread")) {
if (SettingsStore.getValue("feature_threadstable")) {
this.props.mxEvent.off(ThreadEvent.Update, this.updateThread);
}
this.threadState?.off(NotificationStateEvents.Update, this.onThreadStateUpdate);
Expand All @@ -496,7 +496,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
};

private get thread(): Thread | null {
if (!SettingsStore.getValue("feature_thread")) {
if (!SettingsStore.getValue("feature_threadstable")) {
return null;
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/views/rooms/SearchResultTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default class SearchResultTile extends React.Component<IProps> {
const layout = SettingsStore.getValue("layout");
const isTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
const alwaysShowTimestamps = SettingsStore.getValue("alwaysShowTimestamps");
const threadsEnabled = SettingsStore.getValue("feature_thread");
const threadsEnabled = SettingsStore.getValue("feature_threadstable");

const timeline = result.context.getTimeline();
for (let j = 0; j < timeline.length; j++) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/rooms/SendMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
// For initial threads launch, chat effects are disabled
// see #19731
const isNotThread = this.props.relation?.rel_type !== THREAD_RELATION_TYPE.name;
if (!SettingsStore.getValue("feature_thread") || isNotThread) {
if (!SettingsStore.getValue("feature_threadstable") || isNotThread) {
dis.dispatch({ action: `effects.${effect.command}` });
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export function sendMessage(message: string, isHTML: boolean, { roomContext, mxC
// For initial threads launch, chat effects are disabled
// see #19731
const isNotThread = relation?.rel_type !== THREAD_RELATION_TYPE.name;
if (!SettingsStore.getValue("feature_thread") || isNotThread) {
if (!SettingsStore.getValue("feature_threadstable") || isNotThread) {
dis.dispatch({ action: `effects.${effect.command}` });
}
}
Expand Down
10 changes: 3 additions & 7 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -915,15 +915,9 @@
"In rooms that support moderation, the “Report” button will let you report abuse to room moderators.": "In rooms that support moderation, the “Report” button will let you report abuse to room moderators.",
"Render LaTeX maths in messages": "Render LaTeX maths in messages",
"Message Pinning": "Message Pinning",
"Threaded messaging": "Threaded messaging",
"Threaded messages": "Threaded messages",
"Keep discussions organised with threads.": "Keep discussions organised with threads.",
"Threads help keep conversations on-topic and easy to track. <a>Learn more</a>.": "Threads help keep conversations on-topic and easy to track. <a>Learn more</a>.",
"How can I start a thread?": "How can I start a thread?",
"Use “%(replyInThread)s” when hovering over a message.": "Use “%(replyInThread)s” when hovering over a message.",
"Reply in thread": "Reply in thread",
"How can I leave the beta?": "How can I leave the beta?",
"To leave, return to this page and use the “%(leaveTheBeta)s” button.": "To leave, return to this page and use the “%(leaveTheBeta)s” button.",
"Leave the beta": "Leave the beta",
"Rich text editor": "Rich text editor",
"Use rich text instead of Markdown in the message composer. Plain text mode coming soon.": "Use rich text instead of Markdown in the message composer. Plain text mode coming soon.",
"Render simple counters in room header": "Render simple counters in room header",
Expand Down Expand Up @@ -2322,6 +2316,7 @@
"Error processing audio message": "Error processing audio message",
"View live location": "View live location",
"React": "React",
"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.",
Expand Down Expand Up @@ -3198,6 +3193,7 @@
"Beta": "Beta",
"Leaving the beta will reload %(brand)s.": "Leaving the beta will reload %(brand)s.",
"Joining the beta will reload %(brand)s.": "Joining the beta will reload %(brand)s.",
"Leave the beta": "Leave the beta",
"Join the beta": "Join the beta",
"Updated %(humanizedUpdateTime)s": "Updated %(humanizedUpdateTime)s",
"Live until %(expiryTime)s": "Live until %(expiryTime)s",
Expand Down
30 changes: 4 additions & 26 deletions src/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -255,15 +255,15 @@ export const SETTINGS: { [setting: string]: ISetting } = {
supportedLevels: LEVELS_FEATURE,
default: false,
},
"feature_thread": {
"feature_threadstable": {
isFeature: true,
labsGroup: LabGroup.Messaging,
controller: new ThreadBetaController(),
displayName: _td("Threaded messaging"),
displayName: _td("Threaded messages"),
supportedLevels: LEVELS_FEATURE,
default: false,
default: true,
germain-gg marked this conversation as resolved.
Show resolved Hide resolved
betaInfo: {
title: _td("Threads"),
title: _td("Threaded messages"),
caption: () => (
<>
<p>{_t("Keep discussions organised with threads.")}</p>
Expand All @@ -282,28 +282,6 @@ export const SETTINGS: { [setting: string]: ISetting } = {
</p>
</>
),
faq: () =>
SdkConfig.get().bug_report_endpoint_url && (
<>
<h4>{_t("How can I start a thread?")}</h4>
<p>
{_t("Use “%(replyInThread)s” when hovering over a message.", {
replyInThread: _t("Reply in thread"),
})}
</p>
<h4>{_t("How can I leave the beta?")}</h4>
<p>
{_t("To leave, return to this page and use the “%(leaveTheBeta)s” button.", {
leaveTheBeta: _t("Leave the beta"),
})}
</p>
</>
),
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,
},
},
Expand Down
2 changes: 1 addition & 1 deletion src/stores/TypingStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export default class TypingStore {
if (SettingsStore.getValue("lowBandwidth")) return;
// Disable typing notification for threads for the initial launch
// before we figure out a better user experience for them
if (SettingsStore.getValue("feature_thread") && threadId) return;
if (SettingsStore.getValue("feature_threadstable") && threadId) return;

let currentTyping = this.typingStates[roomId];
if ((!isTyping && !currentTyping) || (currentTyping && currentTyping.isTyping === isTyping)) {
Expand Down
Loading