diff --git a/src/ZulipMobile.js b/src/ZulipMobile.js
index 2c85af4ada5..12eaaeb2860 100644
--- a/src/ZulipMobile.js
+++ b/src/ZulipMobile.js
@@ -16,7 +16,7 @@ import CompatibilityChecker from './boot/CompatibilityChecker';
import AppEventHandlers from './boot/AppEventHandlers';
import { initializeSentry } from './sentry';
import ZulipSafeAreaProvider from './boot/ZulipSafeAreaProvider';
-import TopicModalProvider from './boot/TopicModalProvider';
+import TopicEditModalProvider from './boot/TopicEditModalProvider';
initializeSentry();
@@ -56,11 +56,11 @@ export default function ZulipMobile(): Node {
-
+
-
+
diff --git a/src/action-sheets/index.js b/src/action-sheets/index.js
index 687bf8dd132..bb6993d09f3 100644
--- a/src/action-sheets/index.js
+++ b/src/action-sheets/index.js
@@ -77,12 +77,7 @@ type TopicArgs = {
zulipFeatureLevel: number,
dispatch: Dispatch,
_: GetText,
- startEditTopic: (
- streamId: number,
- topic: string,
- streamsById: Map,
- _: GetText,
- ) => Promise,
+ startEditTopic: (streamId: number, topic: string) => Promise,
...
};
@@ -260,8 +255,8 @@ const toggleResolveTopic = async ({ auth, streamId, topic, _, streams, zulipFeat
const editTopic = {
title: 'Edit topic',
errorMessage: 'Failed to resolve topic',
- action: ({ streamId, topic, streams, _, startEditTopic }) => {
- startEditTopic(streamId, topic, streams, _);
+ action: ({ streamId, topic, startEditTopic }) => {
+ startEditTopic(streamId, topic);
},
};
@@ -516,10 +511,14 @@ export const constructTopicActionButtons = (args: {|
const buttons = [];
const unreadCount = getUnreadCountForTopic(unread, streamId, topic);
+ const isAdmin = roleIsAtLeast(ownUserRole, Role.Admin);
if (unreadCount > 0) {
buttons.push(markTopicAsRead);
}
- buttons.push(editTopic);
+ // Set back to isAdmin after testing feature
+ if (true) {
+ buttons.push(editTopic);
+ }
if (isTopicMuted(streamId, topic, mute)) {
buttons.push(unmuteTopic);
} else {
@@ -530,7 +529,7 @@ export const constructTopicActionButtons = (args: {|
} else {
buttons.push(unresolveTopic);
}
- if (roleIsAtLeast(ownUserRole, Role.Admin)) {
+ if (isAdmin) {
buttons.push(deleteTopic);
}
const sub = subscriptions.get(streamId);
@@ -681,12 +680,7 @@ export const showTopicActionSheet = (args: {|
showActionSheetWithOptions: ShowActionSheetWithOptions,
callbacks: {|
dispatch: Dispatch,
- startEditTopic: (
- streamId: number,
- topic: string,
- streamsById: Map,
- _: GetText,
- ) => Promise,
+ startEditTopic: (streamId: number, topic: string) => Promise,
_: GetText,
|},
backgroundData: $ReadOnly<{
diff --git a/src/boot/TopicEditModalProvider.js b/src/boot/TopicEditModalProvider.js
new file mode 100644
index 00000000000..50f0e10c64b
--- /dev/null
+++ b/src/boot/TopicEditModalProvider.js
@@ -0,0 +1,70 @@
+/* @flow strict-local */
+import React, { createContext, useState, useCallback, useContext } from 'react';
+import type { Context, Node } from 'react';
+import { useSelector } from '../react-redux';
+import TopicEditModal from '../topics/TopicEditModal';
+import { getAuth, getZulipFeatureLevel, getStreamsById } from '../selectors';
+import { TranslationContext } from './TranslationProvider';
+
+type Props = $ReadOnly<{|
+ children: Node,
+|}>;
+
+type StartEditTopicContext = (
+ streamId: number,
+ topic: string,
+) => Promise;
+
+// $FlowIssue[incompatible-type]
+const TopicModal: Context = createContext(undefined);
+
+export const useStartEditTopic = ():StartEditTopicContext => useContext(TopicModal);
+
+export default function TopicEditModalProvider(props: Props): Node {
+ const { children } = props;
+ const auth = useSelector(getAuth);
+ const zulipFeatureLevel = useSelector(getZulipFeatureLevel);
+ const streamsById = useSelector(getStreamsById);
+ const _ = useContext(TranslationContext);
+
+ const [topicModalProviderState, setTopicModalProviderState] = useState({
+ visible: false,
+ streamId: -1,
+ topic: '',
+ });
+
+ const startEditTopic = useCallback(
+ async (streamId: number, topic: string) => {
+ const { visible } = topicModalProviderState;
+ if (visible) {
+ return;
+ }
+ setTopicModalProviderState({
+ visible: true,
+ streamId,
+ topic,
+ });
+ }, [topicModalProviderState]);
+
+ const closeEditTopicModal = useCallback(() => {
+ setTopicModalProviderState({
+ visible: false,
+ streamId: -1,
+ topic: '',
+ });
+ }, []);
+
+ return (
+
+
+ {children}
+
+ );
+}
diff --git a/src/boot/TopicModalProvider.js b/src/boot/TopicModalProvider.js
deleted file mode 100644
index 91bb0b399a3..00000000000
--- a/src/boot/TopicModalProvider.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/* @flow strict-local */
-import React, { createContext, useState, useMemo, useCallback, useContext } from 'react';
-import type { Context, Node } from 'react';
-import { useSelector } from '../react-redux';
-import TopicEditModal from '../topics/TopicEditModal';
-import type { Stream, GetText } from '../types';
-import { fetchSomeMessageIdForConversation } from '../message/fetchActions';
-import { getAuth, getZulipFeatureLevel } from '../selectors';
-
-type Props = $ReadOnly<{|
- children: Node,
-|}>;
-
-type TopicModalContext = $ReadOnly<{|
- startEditTopic: (
- streamId: number,
- topic: string,
- streamsById: Map,
- _: GetText,
- ) => Promise,
- closeEditTopicModal: () => void,
-|}>;
-
-// $FlowIssue[incompatible-type]
-const TopicModal: Context = createContext(undefined);
-
-export const useTopicModalHandler = (): TopicModalContext => useContext(TopicModal);
-
-export default function TopicModalProvider(props: Props): Node {
- const { children } = props;
- const auth = useSelector(getAuth);
- const zulipFeatureLevel = useSelector(getZulipFeatureLevel);
- const [topicModalState, setTopicModalState] = useState({
- visible: false,
- topic: '',
- fetchArgs: {
- auth: null,
- messageId: null,
- zulipFeatureLevel: null,
- },
- });
-
- const startEditTopic = useCallback(
- async (streamId, topic, streamsById, _) => {
- const messageId = await fetchSomeMessageIdForConversation(
- auth,
- streamId,
- topic,
- streamsById,
- zulipFeatureLevel,
- );
- if (messageId == null) {
- throw new Error(
- _('No messages in topic: {streamAndTopic}', {
- streamAndTopic: `#${streamsById.get(streamId)?.name ?? 'unknown'} > ${topic}`,
- }),
- );
- }
- setTopicModalState({
- visible: true,
- topic,
- fetchArgs: { auth, messageId, zulipFeatureLevel },
- });
- },
- [auth, zulipFeatureLevel],
- );
-
- const closeEditTopicModal = useCallback(() => {
- setTopicModalState({
- visible: false,
- topic: null,
- fetchArgs: { auth: null, messageId: null, zulipFeatureLevel: null },
- });
- }, []);
-
- const topicModalHandler = useMemo(
- () => ({
- startEditTopic,
- closeEditTopicModal,
- }),
- [startEditTopic, closeEditTopicModal],
- );
-
- return (
-
- {topicModalState.visible && (
-
- )}
- {children}
-
- );
-}
diff --git a/src/chat/ChatScreen.js b/src/chat/ChatScreen.js
index 472e0fcd163..0dd1a74f23c 100644
--- a/src/chat/ChatScreen.js
+++ b/src/chat/ChatScreen.js
@@ -30,7 +30,7 @@ import { showErrorAlert } from '../utils/info';
import { TranslationContext } from '../boot/TranslationProvider';
import * as api from '../api';
import { useConditionalEffect } from '../reactUtils';
-import { useTopicModalHandler } from '../boot/TopicModalProvider';
+import { useStartEditTopic } from '../boot/TopicEditModalProvider';
type Props = $ReadOnly<{|
navigation: AppNavigationProp<'chat'>,
@@ -128,7 +128,7 @@ const useMessagesWithFetch = args => {
export default function ChatScreen(props: Props): Node {
const { route, navigation } = props;
const { backgroundColor } = React.useContext(ThemeContext);
- const { startEditTopic } = useTopicModalHandler();
+ const startEditTopic = useStartEditTopic();
const { narrow, editMessage } = route.params;
const setEditMessage = useCallback(
diff --git a/src/search/SearchMessagesCard.js b/src/search/SearchMessagesCard.js
index 4d00da798a2..ede07efd0d4 100644
--- a/src/search/SearchMessagesCard.js
+++ b/src/search/SearchMessagesCard.js
@@ -9,7 +9,7 @@ import { createStyleSheet } from '../styles';
import LoadingIndicator from '../common/LoadingIndicator';
import SearchEmptyState from '../common/SearchEmptyState';
import MessageList from '../webview/MessageList';
-import { useTopicModalHandler } from '../boot/TopicModalProvider';
+import { useStartEditTopic } from '../boot/TopicEditModalProvider';
const styles = createStyleSheet({
results: {
@@ -25,7 +25,7 @@ type Props = $ReadOnly<{|
export default function SearchMessagesCard(props: Props): Node {
const { narrow, isFetching, messages } = props;
- const { startEditTopic } = useTopicModalHandler();
+ const startEditTopic = useStartEditTopic();
if (isFetching) {
// Display loading indicator only if there are no messages to
diff --git a/src/streams/TopicItem.js b/src/streams/TopicItem.js
index c197e3e9abb..2d58dc2e3b6 100644
--- a/src/streams/TopicItem.js
+++ b/src/streams/TopicItem.js
@@ -25,7 +25,7 @@ import {
import { getMute } from '../mute/muteModel';
import { getUnread } from '../unread/unreadModel';
import { getOwnUserRole } from '../permissionSelectors';
-import { useTopicModalHandler } from '../boot/TopicModalProvider';
+import { useStartEditTopic } from '../boot/TopicEditModalProvider';
const componentStyles = createStyleSheet({
selectedRow: {
@@ -71,7 +71,7 @@ export default function TopicItem(props: Props): Node {
useActionSheet().showActionSheetWithOptions;
const _ = useContext(TranslationContext);
const dispatch = useDispatch();
- const { startEditTopic } = useTopicModalHandler();
+ const startEditTopic = useStartEditTopic();
const backgroundData = useSelector(state => ({
auth: getAuth(state),
mute: getMute(state),
diff --git a/src/title/TitleStream.js b/src/title/TitleStream.js
index 5ffce4e9509..d1b927d5e8e 100644
--- a/src/title/TitleStream.js
+++ b/src/title/TitleStream.js
@@ -27,7 +27,7 @@ import { showStreamActionSheet, showTopicActionSheet } from '../action-sheets';
import type { ShowActionSheetWithOptions } from '../action-sheets';
import { getUnread } from '../unread/unreadModel';
import { getOwnUserRole } from '../permissionSelectors';
-import { useTopicModalHandler } from '../boot/TopicModalProvider';
+import { useStartEditTopic } from '../boot/TopicEditModalProvider';
type Props = $ReadOnly<{|
narrow: Narrow,
@@ -68,7 +68,7 @@ export default function TitleStream(props: Props): Node {
const showActionSheetWithOptions: ShowActionSheetWithOptions =
useActionSheet().showActionSheetWithOptions;
const _ = useContext(TranslationContext);
- const { startEditTopic } = useTopicModalHandler();
+ const startEditTopic = useStartEditTopic();
return (
,
- _: GetText,
- ) => Promise,
- closeEditTopicModal: () => void,
+ streamId: number,
},
-};
+ auth: Auth,
+ zulipFeatureLevel: number,
+ streamsById: Map,
+ _: GetText,
+ closeEditTopicModal: () => void,
+|}>;
+
+export default function TopicEditModal(props: Props): Node {
+ const {
+ topicModalProviderState,
+ closeEditTopicModal,
+ auth,
+ zulipFeatureLevel,
+ streamsById,
+ _,
+ } = props;
-export default function TopicEditModal({
- topicModalState,
- topicModalHandler,
-}: TopicEditModalArgs): JSX$Element {
- const { topic, fetchArgs } = topicModalState;
- const [topicState, onChangeTopic] = useState(topic);
- const { closeEditTopicModal } = topicModalHandler;
- const { auth, messageId, zulipFeatureLevel } = fetchArgs;
+ const { visible, topic, streamId } = topicModalProviderState;
+
+ const [topicName, onChangeTopicName] = useState();
+
+ useEffect(() => {
+ onChangeTopicName(topic);
+ }, [topic]);
const { backgroundColor } = useContext(ThemeContext);
@@ -110,9 +113,23 @@ export default function TopicEditModal({
});
const handleSubmit = async () => {
+ const messageId = await fetchSomeMessageIdForConversation(
+ auth,
+ streamId,
+ topic,
+ streamsById,
+ zulipFeatureLevel,
+ );
+ if (messageId == null) {
+ throw new Error(
+ _('No messages in topic: {streamAndTopic}', {
+ streamAndTopic: `#${streamsById.get(streamId)?.name ?? 'unknown'} > ${topic}`,
+ }),
+ );
+ }
await updateMessage(auth, messageId, {
propagate_mode: 'change_all',
- subject: topicState,
+ subject: topicName,
...(zulipFeatureLevel >= 9 && {
send_notification_to_old_thread: true,
send_notification_to_new_thread: true,
@@ -121,21 +138,14 @@ export default function TopicEditModal({
closeEditTopicModal();
};
return (
- {
- closeEditTopicModal();
- }}
- >
+
- Edit topic
+
@@ -147,22 +157,26 @@ export default function TopicEditModal({
borderColor: BRAND_COLOR,
...styles.button,
}}
- onPress={() => {
- closeEditTopicModal();
- }}
+ onPress={closeEditTopicModal}
>
- Cancel
+
- Submit
+
diff --git a/src/webview/MessageList.js b/src/webview/MessageList.js
index 74132f9fea9..68699bccd8d 100644
--- a/src/webview/MessageList.js
+++ b/src/webview/MessageList.js
@@ -14,7 +14,6 @@ import type {
MessageListElement,
UserOrBot,
EditMessage,
- Stream,
} from '../types';
import { assumeSecretlyGlobalState } from '../reduxTypes';
import { connect } from '../react-redux';
@@ -46,12 +45,7 @@ type OuterProps = $ReadOnly<{|
initialScrollMessageId: number | null,
showMessagePlaceholders: boolean,
startEditMessage: (editMessage: EditMessage) => void,
- startEditTopic: (
- streamId: number,
- topic: string,
- streamsById: Map,
- _: GetText,
- ) => Promise,
+ startEditTopic: (streamId: number, topic: string) => Promise,
|}>;
type SelectorProps = {|
diff --git a/src/webview/handleOutboundEvents.js b/src/webview/handleOutboundEvents.js
index eae496f7cc6..cf8a7127bdb 100644
--- a/src/webview/handleOutboundEvents.js
+++ b/src/webview/handleOutboundEvents.js
@@ -4,16 +4,7 @@ import { Clipboard, Alert } from 'react-native';
import * as NavigationService from '../nav/NavigationService';
import * as api from '../api';
import config from '../config';
-import type {
- Dispatch,
- GetText,
- Message,
- Narrow,
- Outbox,
- EditMessage,
- UserId,
- Stream,
-} from '../types';
+import type { Dispatch, GetText, Message, Narrow, Outbox, EditMessage, UserId } from '../types';
import type { BackgroundData } from './backgroundData';
import type { ShowActionSheetWithOptions } from '../action-sheets';
import type { JSONableDict } from '../utils/jsonable';
@@ -178,12 +169,7 @@ type Props = $ReadOnly<{
doNotMarkMessagesAsRead: boolean,
showActionSheetWithOptions: ShowActionSheetWithOptions,
startEditMessage: (editMessage: EditMessage) => void,
- startEditTopic: (
- streamId: number,
- topic: string,
- streamsById: Map,
- _: GetText,
- ) => Promise,
+ startEditTopic: (streamId: number, topic: string) => Promise,
...
}>;
diff --git a/static/translations/messages_en.json b/static/translations/messages_en.json
index a6dc550672e..65b74e2fb26 100644
--- a/static/translations/messages_en.json
+++ b/static/translations/messages_en.json
@@ -338,5 +338,6 @@
"Failed to copy stream link": "Failed to copy stream link",
"A stream with this name already exists.": "A stream with this name already exists.",
"Streams": "Streams",
- "Edit topic": "Edit topic"
+ "Edit topic": "Edit topic",
+ "Submit": "Submit"
}