Skip to content

Commit 8f75c5e

Browse files
Leslie NgoLeslie Ngo
Leslie Ngo
authored and
Leslie Ngo
committed
topic edit modal: Add new edit-topic UI.
Fixes: zulip#5365
1 parent cf5e387 commit 8f75c5e

File tree

12 files changed

+297
-9
lines changed

12 files changed

+297
-9
lines changed

src/ZulipMobile.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import CompatibilityChecker from './boot/CompatibilityChecker';
1616
import AppEventHandlers from './boot/AppEventHandlers';
1717
import { initializeSentry } from './sentry';
1818
import ZulipSafeAreaProvider from './boot/ZulipSafeAreaProvider';
19+
import TopicEditModalProvider from './boot/TopicEditModalProvider';
1920

2021
initializeSentry();
2122

@@ -55,9 +56,11 @@ export default function ZulipMobile(): Node {
5556
<AppEventHandlers>
5657
<TranslationProvider>
5758
<ThemeProvider>
58-
<ActionSheetProvider>
59-
<ZulipNavigationContainer />
60-
</ActionSheetProvider>
59+
<TopicEditModalProvider>
60+
<ActionSheetProvider>
61+
<ZulipNavigationContainer />
62+
</ActionSheetProvider>
63+
</TopicEditModalProvider>
6164
</ThemeProvider>
6265
</TranslationProvider>
6366
</AppEventHandlers>

src/action-sheets/index.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ type TopicArgs = {
7777
zulipFeatureLevel: number,
7878
dispatch: Dispatch,
7979
_: GetText,
80+
startEditTopic: (streamId: number, topic: string) => void,
8081
...
8182
};
8283

@@ -169,6 +170,14 @@ const deleteMessage = {
169170
},
170171
};
171172

173+
const editTopic = {
174+
title: 'Edit topic',
175+
errorMessage: 'Failed to edit topic',
176+
action: ({ streamId, topic, startEditTopic }) => {
177+
startEditTopic(streamId, topic);
178+
},
179+
};
180+
172181
const markTopicAsRead = {
173182
title: 'Mark topic as read',
174183
errorMessage: 'Failed to mark topic as read',
@@ -502,9 +511,18 @@ export const constructTopicActionButtons = (args: {|
502511

503512
const buttons = [];
504513
const unreadCount = getUnreadCountForTopic(unread, streamId, topic);
514+
const isAdmin = roleIsAtLeast(ownUserRole, Role.Admin);
505515
if (unreadCount > 0) {
506516
buttons.push(markTopicAsRead);
507517
}
518+
// At present, the permissions for editing the topic of a message are highly complex.
519+
// Until we move to a better set of policy options, we'll only display the edit topic
520+
// button to admins.
521+
// Issue: https://github.com/zulip/zulip/issues/21739
522+
// Relevant comment: https://github.com/zulip/zulip-mobile/issues/5365#issuecomment-1197093294
523+
if (isAdmin) {
524+
buttons.push(editTopic);
525+
}
508526
if (isTopicMuted(streamId, topic, mute)) {
509527
buttons.push(unmuteTopic);
510528
} else {
@@ -515,7 +533,7 @@ export const constructTopicActionButtons = (args: {|
515533
} else {
516534
buttons.push(unresolveTopic);
517535
}
518-
if (roleIsAtLeast(ownUserRole, Role.Admin)) {
536+
if (isAdmin) {
519537
buttons.push(deleteTopic);
520538
}
521539
const sub = subscriptions.get(streamId);
@@ -666,6 +684,7 @@ export const showTopicActionSheet = (args: {|
666684
showActionSheetWithOptions: ShowActionSheetWithOptions,
667685
callbacks: {|
668686
dispatch: Dispatch,
687+
startEditTopic: (streamId: number, topic: string) => void,
669688
_: GetText,
670689
|},
671690
backgroundData: $ReadOnly<{

src/boot/TopicEditModalProvider.js

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/* @flow strict-local */
2+
import React, { createContext, useState, useCallback, useContext } from 'react';
3+
import type { Context, Node } from 'react';
4+
5+
import TopicEditModal from '../topics/TopicEditModal';
6+
7+
type Props = $ReadOnly<{|
8+
children: Node,
9+
|}>;
10+
11+
type StartEditTopicContext = (streamId: number, oldTopic: string) => void;
12+
13+
const TopicEditModalContext: Context<StartEditTopicContext> = createContext(() => {
14+
throw new Error(
15+
'Tried to open the edit-topic UI from a component without TopicEditModalProvider above it in the tree.',
16+
);
17+
});
18+
19+
export const useStartEditTopic = (): StartEditTopicContext => useContext(TopicEditModalContext);
20+
21+
export default function TopicEditModalProvider(props: Props): Node {
22+
const { children } = props;
23+
24+
const [topicModalProviderState, setTopicModalProviderState] = useState({
25+
streamId: null,
26+
oldTopic: null,
27+
});
28+
29+
const startEditTopic = useCallback(
30+
(streamIdArg, oldTopicArg) => {
31+
const { streamId, oldTopic } = topicModalProviderState;
32+
if (streamId === null && oldTopic === null) {
33+
setTopicModalProviderState({
34+
streamId: streamIdArg,
35+
oldTopic: oldTopicArg,
36+
});
37+
}
38+
},
39+
[topicModalProviderState],
40+
);
41+
42+
const closeEditTopicModal = () => {
43+
setTopicModalProviderState({
44+
streamId: null,
45+
oldTopic: null,
46+
});
47+
};
48+
49+
return (
50+
<TopicEditModalContext.Provider value={startEditTopic}>
51+
<TopicEditModal
52+
topicModalProviderState={topicModalProviderState}
53+
closeEditTopicModal={closeEditTopicModal}
54+
/>
55+
{children}
56+
</TopicEditModalContext.Provider>
57+
);
58+
}

src/chat/ChatScreen.js

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { showErrorAlert } from '../utils/info';
3030
import { TranslationContext } from '../boot/TranslationProvider';
3131
import * as api from '../api';
3232
import { useConditionalEffect } from '../reactUtils';
33+
import { useStartEditTopic } from '../boot/TopicEditModalProvider';
3334

3435
type Props = $ReadOnly<{|
3536
navigation: AppNavigationProp<'chat'>,
@@ -133,6 +134,7 @@ export default function ChatScreen(props: Props): Node {
133134
(value: EditMessage | null) => navigation.setParams({ editMessage: value }),
134135
[navigation],
135136
);
137+
const startEditTopic = useStartEditTopic();
136138

137139
const isNarrowValid = useSelector(state => getIsNarrowValid(state, narrow));
138140
const draft = useSelector(state => getDraftForNarrow(state, narrow));
@@ -221,6 +223,7 @@ export default function ChatScreen(props: Props): Node {
221223
}
222224
showMessagePlaceholders={showMessagePlaceholders}
223225
startEditMessage={setEditMessage}
226+
startEditTopic={startEditTopic}
224227
/>
225228
);
226229
}

src/search/SearchMessagesCard.js

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { createStyleSheet } from '../styles';
99
import LoadingIndicator from '../common/LoadingIndicator';
1010
import SearchEmptyState from '../common/SearchEmptyState';
1111
import MessageList from '../webview/MessageList';
12+
import { useStartEditTopic } from '../boot/TopicEditModalProvider';
1213

1314
const styles = createStyleSheet({
1415
results: {
@@ -24,6 +25,7 @@ type Props = $ReadOnly<{|
2425

2526
export default function SearchMessagesCard(props: Props): Node {
2627
const { narrow, isFetching, messages } = props;
28+
const startEditTopic = useStartEditTopic();
2729

2830
if (isFetching) {
2931
// Display loading indicator only if there are no messages to
@@ -55,6 +57,7 @@ export default function SearchMessagesCard(props: Props): Node {
5557
// TODO: handle editing a message from the search results,
5658
// or make this prop optional
5759
startEditMessage={() => undefined}
60+
startEditTopic={startEditTopic}
5861
/>
5962
</View>
6063
);

src/streams/TopicItem.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
import { getMute } from '../mute/muteModel';
2626
import { getUnread } from '../unread/unreadModel';
2727
import { getOwnUserRole } from '../permissionSelectors';
28+
import { useStartEditTopic } from '../boot/TopicEditModalProvider';
2829

2930
const componentStyles = createStyleSheet({
3031
selectedRow: {
@@ -70,6 +71,7 @@ export default function TopicItem(props: Props): Node {
7071
useActionSheet().showActionSheetWithOptions;
7172
const _ = useContext(TranslationContext);
7273
const dispatch = useDispatch();
74+
const startEditTopic = useStartEditTopic();
7375
const backgroundData = useSelector(state => ({
7476
auth: getAuth(state),
7577
mute: getMute(state),
@@ -88,7 +90,7 @@ export default function TopicItem(props: Props): Node {
8890
onLongPress={() => {
8991
showTopicActionSheet({
9092
showActionSheetWithOptions,
91-
callbacks: { dispatch, _ },
93+
callbacks: { dispatch, startEditTopic, _ },
9294
backgroundData,
9395
streamId,
9496
topic: name,

src/title/TitleStream.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { showStreamActionSheet, showTopicActionSheet } from '../action-sheets';
2727
import type { ShowActionSheetWithOptions } from '../action-sheets';
2828
import { getUnread } from '../unread/unreadModel';
2929
import { getOwnUserRole } from '../permissionSelectors';
30+
import { useStartEditTopic } from '../boot/TopicEditModalProvider';
3031

3132
type Props = $ReadOnly<{|
3233
narrow: Narrow,
@@ -51,6 +52,7 @@ export default function TitleStream(props: Props): Node {
5152
const { narrow, color } = props;
5253
const dispatch = useDispatch();
5354
const stream = useSelector(state => getStreamInNarrow(state, narrow));
55+
const startEditTopic = useStartEditTopic();
5456
const backgroundData = useSelector(state => ({
5557
auth: getAuth(state),
5658
mute: getMute(state),
@@ -75,7 +77,7 @@ export default function TitleStream(props: Props): Node {
7577
? () => {
7678
showTopicActionSheet({
7779
showActionSheetWithOptions,
78-
callbacks: { dispatch, _ },
80+
callbacks: { dispatch, startEditTopic, _ },
7981
backgroundData,
8082
streamId: stream.stream_id,
8183
topic: topicOfNarrow(narrow),

0 commit comments

Comments
 (0)