forked from Ivasoft/mattermost-mobile
Merge remote-tracking branch 'origin/main' into deps
This commit is contained in:
84
NOTICE.txt
84
NOTICE.txt
@@ -200,6 +200,41 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## @mattermost/react-native-turbo-mailer
|
||||
|
||||
This product contains '@mattermost/react-native-turbo-mailer' by Avinash Lingaloo.
|
||||
|
||||
An adaptation of react-native-mail that supports Turbo Module
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/mattermost/react-native-turbo-mailer#readme
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Mattermost
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## @msgpack/msgpack
|
||||
@@ -493,6 +528,21 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## @react-navigation/stack
|
||||
|
||||
This product contains '@react-navigation/stack'.
|
||||
|
||||
Stack navigator component for iOS and Android with animated transitions and gestures
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://reactnavigation.org/docs/stack-navigator/
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## @rudderstack/rudder-sdk-react-native
|
||||
@@ -1109,6 +1159,40 @@ Lightweight fuzzy-search
|
||||
limitations under the License.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## html-entities
|
||||
|
||||
This product contains 'html-entities' by Marat Dulin.
|
||||
|
||||
Fastest HTML entities encode/decode library.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/mdevils/html-entities#readme
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
Copyright (c) 2021 Dulin Marat
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## jail-monkey
|
||||
|
||||
@@ -138,7 +138,7 @@ export const handleGotoLocation = async (serverUrl: string, intl: IntlShape, loc
|
||||
const match = matchDeepLink(location, serverUrl, config?.SiteURL);
|
||||
|
||||
if (match) {
|
||||
handleDeepLink(location, intl, location);
|
||||
handleDeepLink(match, intl, location);
|
||||
} else {
|
||||
const {formatMessage} = intl;
|
||||
const onError = () => Alert.alert(
|
||||
|
||||
@@ -86,7 +86,7 @@ export async function handleFirstConnect(serverUrl: string) {
|
||||
|
||||
// ESR: 5.37
|
||||
if (lastDisconnect && config?.EnableReliableWebSockets !== 'true' && alreadyConnected.has(serverUrl)) {
|
||||
handleReconnect(serverUrl);
|
||||
await handleReconnect(serverUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -100,8 +100,8 @@ export async function handleFirstConnect(serverUrl: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function handleReconnect(serverUrl: string) {
|
||||
doReconnect(serverUrl);
|
||||
export async function handleReconnect(serverUrl: string) {
|
||||
await doReconnect(serverUrl);
|
||||
}
|
||||
|
||||
export async function handleClose(serverUrl: string, lastDisconnect: number) {
|
||||
|
||||
@@ -20,7 +20,7 @@ import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
type Props = {
|
||||
isConnected: boolean;
|
||||
websocketState: WebsocketConnectedState;
|
||||
}
|
||||
|
||||
const getStyle = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
@@ -74,7 +74,7 @@ const TIME_TO_OPEN = toMilliseconds({seconds: 3});
|
||||
const TIME_TO_CLOSE = toMilliseconds({seconds: 1});
|
||||
|
||||
const ConnectionBanner = ({
|
||||
isConnected,
|
||||
websocketState,
|
||||
}: Props) => {
|
||||
const intl = useIntl();
|
||||
const closeTimeout = useRef<NodeJS.Timeout | null>();
|
||||
@@ -86,6 +86,8 @@ const ConnectionBanner = ({
|
||||
const appState = useAppState();
|
||||
const netInfo = useNetInfo();
|
||||
|
||||
const isConnected = websocketState === 'connected';
|
||||
|
||||
const openCallback = useCallback(() => {
|
||||
setVisible(true);
|
||||
clearTimeoutRef(openTimeout);
|
||||
@@ -97,7 +99,9 @@ const ConnectionBanner = ({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isConnected) {
|
||||
if (websocketState === 'connecting') {
|
||||
openCallback();
|
||||
} else if (!isConnected) {
|
||||
openTimeout.current = setTimeout(openCallback, TIME_TO_OPEN);
|
||||
}
|
||||
return () => {
|
||||
@@ -158,6 +162,8 @@ const ConnectionBanner = ({
|
||||
let text;
|
||||
if (isConnected) {
|
||||
text = intl.formatMessage({id: 'connection_banner.connected', defaultMessage: 'Connection restored'});
|
||||
} else if (websocketState === 'connecting') {
|
||||
text = intl.formatMessage({id: 'connection_banner.connecting', defaultMessage: 'Connecting...'});
|
||||
} else if (netInfo.isInternetReachable) {
|
||||
text = intl.formatMessage({id: 'connection_banner.not_reachable', defaultMessage: 'The server is not reachable'});
|
||||
} else {
|
||||
|
||||
@@ -9,7 +9,7 @@ import websocket_manager from '@managers/websocket_manager';
|
||||
import ConnectionBanner from './connection_banner';
|
||||
|
||||
const enhanced = withObservables(['serverUrl'], ({serverUrl}: {serverUrl: string}) => ({
|
||||
isConnected: websocket_manager.observeConnected(serverUrl),
|
||||
websocketState: websocket_manager.observeWebsocketState(serverUrl),
|
||||
}));
|
||||
|
||||
export default withServerUrl(enhanced(ConnectionBanner));
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {StyleProp, Text, TextStyle} from 'react-native';
|
||||
import {DeviceEventEmitter, StyleProp, Text, TextStyle} from 'react-native';
|
||||
|
||||
import {Navigation, Screens} from '@constants';
|
||||
import {popToRoot, dismissAllModals} from '@screens/navigation';
|
||||
|
||||
type HashtagProps = {
|
||||
@@ -17,7 +18,12 @@ const Hashtag = ({hashtag, linkStyle}: HashtagProps) => {
|
||||
await dismissAllModals();
|
||||
await popToRoot();
|
||||
|
||||
// showSearchModal('#' + hashtag);
|
||||
DeviceEventEmitter.emit(Navigation.NAVIGATE_TO_TAB, {
|
||||
screen: Screens.SEARCH,
|
||||
params: {
|
||||
searchTerm: hashtag,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -59,7 +59,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
height: 24,
|
||||
},
|
||||
container: {
|
||||
marginBottom: 5,
|
||||
marginVertical: 5,
|
||||
top: 5,
|
||||
},
|
||||
svg: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
|
||||
@@ -76,9 +76,9 @@ const MarkdownLink = ({children, experimentalNormalizeMarkdownLinks, href, siteU
|
||||
const match = matchDeepLink(url, serverUrl, siteURL);
|
||||
|
||||
if (match) {
|
||||
const {error} = await handleDeepLink(url, intl);
|
||||
const {error} = await handleDeepLink(match, intl);
|
||||
if (error) {
|
||||
tryOpenURL(url, onError);
|
||||
tryOpenURL(match, onError);
|
||||
}
|
||||
} else {
|
||||
tryOpenURL(url, onError);
|
||||
|
||||
@@ -24,8 +24,8 @@ type Props = {
|
||||
canShowPostPriority?: boolean;
|
||||
|
||||
// Post Props
|
||||
postProps: Post['props'];
|
||||
updatePostProps: (postProps: Post['props']) => void;
|
||||
postPriority: PostPriorityData;
|
||||
updatePostPriority: (postPriority: PostPriorityData) => void;
|
||||
|
||||
// Cursor Position Handler
|
||||
updateCursorPosition: React.Dispatch<React.SetStateAction<number>>;
|
||||
@@ -110,8 +110,8 @@ export default function DraftInput({
|
||||
updateCursorPosition,
|
||||
cursorPosition,
|
||||
updatePostInputTop,
|
||||
postProps,
|
||||
updatePostProps,
|
||||
postPriority,
|
||||
updatePostPriority,
|
||||
setIsFocused,
|
||||
}: Props) {
|
||||
const theme = useTheme();
|
||||
@@ -155,9 +155,9 @@ export default function DraftInput({
|
||||
overScrollMode={'never'}
|
||||
disableScrollViewPanResponder={true}
|
||||
>
|
||||
{Boolean(postProps.priority) && (
|
||||
{Boolean(postPriority?.priority) && (
|
||||
<View style={style.postPriorityLabel}>
|
||||
<PostPriorityLabel label={postProps.priority}/>
|
||||
<PostPriorityLabel label={postPriority!.priority}/>
|
||||
</View>
|
||||
)}
|
||||
<PostInput
|
||||
@@ -188,8 +188,8 @@ export default function DraftInput({
|
||||
addFiles={addFiles}
|
||||
updateValue={updateValue}
|
||||
value={value}
|
||||
postProps={postProps}
|
||||
updatePostProps={updatePostProps}
|
||||
postPriority={postPriority}
|
||||
updatePostPriority={updatePostPriority}
|
||||
canShowPostPriority={canShowPostPriority}
|
||||
focus={focus}
|
||||
/>
|
||||
|
||||
@@ -6,7 +6,7 @@ import {useIntl} from 'react-intl';
|
||||
import {StyleSheet} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import PostPriorityPicker, {PostPriorityData} from '@components/post_priority/post_priority_picker';
|
||||
import PostPriorityPicker from '@components/post_priority/post_priority_picker';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {ICON_SIZE} from '@constants/post_draft';
|
||||
import {useTheme} from '@context/theme';
|
||||
@@ -15,8 +15,8 @@ import {changeOpacity} from '@utils/theme';
|
||||
|
||||
type Props = {
|
||||
testID?: string;
|
||||
postProps: Post['props'];
|
||||
updatePostProps: (postProps: Post['props']) => void;
|
||||
postPriority: PostPriorityData;
|
||||
updatePostPriority: (postPriority: PostPriorityData) => void;
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
@@ -29,30 +29,27 @@ const style = StyleSheet.create({
|
||||
|
||||
export default function PostPriorityAction({
|
||||
testID,
|
||||
postProps,
|
||||
updatePostProps,
|
||||
postPriority,
|
||||
updatePostPriority,
|
||||
}: Props) {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
|
||||
const handlePostPriorityPicker = useCallback((postPriorityData: PostPriorityData) => {
|
||||
updatePostProps((oldPostProps: Post['props']) => ({
|
||||
...oldPostProps,
|
||||
...postPriorityData,
|
||||
}));
|
||||
updatePostPriority(postPriorityData);
|
||||
dismissBottomSheet();
|
||||
}, [updatePostProps]);
|
||||
}, [updatePostPriority]);
|
||||
|
||||
const renderContent = useCallback(() => {
|
||||
return (
|
||||
<PostPriorityPicker
|
||||
data={{
|
||||
priority: postProps?.priority || '',
|
||||
priority: postPriority?.priority || '',
|
||||
}}
|
||||
onSubmit={handlePostPriorityPicker}
|
||||
/>
|
||||
);
|
||||
}, [handlePostPriorityPicker, postProps]);
|
||||
}, [handlePostPriorityPicker, postPriority]);
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
bottomSheet({
|
||||
|
||||
@@ -22,8 +22,8 @@ type Props = {
|
||||
value: string;
|
||||
updateValue: (value: string) => void;
|
||||
addFiles: (file: FileInfo[]) => void;
|
||||
postProps: Post['props'];
|
||||
updatePostProps: (postProps: Post['props']) => void;
|
||||
postPriority: PostPriorityData;
|
||||
updatePostPriority: (postPriority: PostPriorityData) => void;
|
||||
focus: () => void;
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ export default function QuickActions({
|
||||
maxFileCount,
|
||||
updateValue,
|
||||
addFiles,
|
||||
postProps,
|
||||
updatePostProps,
|
||||
postPriority,
|
||||
updatePostPriority,
|
||||
focus,
|
||||
}: Props) {
|
||||
const atDisabled = value[value.length - 1] === '@';
|
||||
@@ -101,8 +101,8 @@ export default function QuickActions({
|
||||
{isPostPriorityEnabled && canShowPostPriority && (
|
||||
<PostPriorityAction
|
||||
testID={postPriorityActionTestID}
|
||||
postProps={postProps}
|
||||
updatePostProps={updatePostProps}
|
||||
postPriority={postPriority}
|
||||
updatePostPriority={updatePostPriority}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -13,6 +13,7 @@ import {setStatus} from '@actions/remote/user';
|
||||
import {canEndCall, endCall, getEndCallMessage} from '@calls/actions/calls';
|
||||
import ClientError from '@client/rest/error';
|
||||
import {Events, Screens} from '@constants';
|
||||
import {PostPriorityType} from '@constants/post';
|
||||
import {NOTIFY_ALL_MEMBERS} from '@constants/post_draft';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import DraftUploadManager from '@managers/draft_upload_manager';
|
||||
@@ -54,6 +55,10 @@ type Props = {
|
||||
uploadFileError: React.ReactNode;
|
||||
}
|
||||
|
||||
const INITIAL_PRIORITY = {
|
||||
priority: PostPriorityType.STANDARD,
|
||||
};
|
||||
|
||||
export default function SendHandler({
|
||||
testID,
|
||||
channelId,
|
||||
@@ -83,8 +88,7 @@ export default function SendHandler({
|
||||
|
||||
const [channelTimezoneCount, setChannelTimezoneCount] = useState(0);
|
||||
const [sendingMessage, setSendingMessage] = useState(false);
|
||||
|
||||
const [postProps, setPostProps] = useState<Post['props']>({});
|
||||
const [postPriority, setPostPriority] = useState<PostPriorityData>(INITIAL_PRIORITY);
|
||||
|
||||
const canSend = useCallback(() => {
|
||||
if (sendingMessage) {
|
||||
@@ -120,17 +124,19 @@ export default function SendHandler({
|
||||
message: value,
|
||||
} as Post;
|
||||
|
||||
if (Object.keys(postProps).length) {
|
||||
post.props = postProps;
|
||||
if (Object.keys(postPriority).length) {
|
||||
post.metadata = {
|
||||
priority: postPriority,
|
||||
};
|
||||
}
|
||||
|
||||
createPost(serverUrl, post, postFiles);
|
||||
|
||||
clearDraft();
|
||||
setSendingMessage(false);
|
||||
setPostProps({});
|
||||
setPostPriority(INITIAL_PRIORITY);
|
||||
DeviceEventEmitter.emit(Events.POST_LIST_SCROLL_TO_BOTTOM, rootId ? Screens.THREAD : Screens.CHANNEL);
|
||||
}, [files, currentUserId, channelId, rootId, value, clearDraft, postProps]);
|
||||
}, [files, currentUserId, channelId, rootId, value, clearDraft, postPriority]);
|
||||
|
||||
const showSendToAllOrChannelOrHereAlert = useCallback((calculatedMembersCount: number, atHere: boolean) => {
|
||||
const notifyAllMessage = DraftUtils.buildChannelWideMentionMessage(intl, calculatedMembersCount, Boolean(isTimezoneEnabled), channelTimezoneCount, atHere);
|
||||
@@ -297,8 +303,8 @@ export default function SendHandler({
|
||||
canSend={canSend()}
|
||||
maxMessageLength={maxMessageLength}
|
||||
updatePostInputTop={updatePostInputTop}
|
||||
postProps={postProps}
|
||||
updatePostProps={setPostProps}
|
||||
postPriority={postPriority}
|
||||
updatePostPriority={setPostPriority}
|
||||
setIsFocused={setIsFocused}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -44,6 +44,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
message: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 200),
|
||||
lineHeight: undefined, // remove line height, not needed and causes problems with md images
|
||||
},
|
||||
pendingPost: {
|
||||
opacity: 0.5,
|
||||
|
||||
@@ -132,10 +132,10 @@ const Header = (props: HeaderProps) => {
|
||||
style={style.time}
|
||||
testID='post_header.date_time'
|
||||
/>
|
||||
{showPostPriority && (
|
||||
{showPostPriority && post.metadata?.priority?.priority && (
|
||||
<View style={style.postPriority}>
|
||||
<PostPriorityLabel
|
||||
label={post.props?.priority}
|
||||
label={post.metadata.priority.priority}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -226,7 +226,7 @@ const Post = ({
|
||||
// If the post is a priority post:
|
||||
// 1. Show the priority label in channel screen
|
||||
// 2. Show the priority label in thread screen for the root post
|
||||
const showPostPriority = Boolean(isPostPriorityEnabled && post.props?.priority) && (location !== Screens.THREAD || !post.rootId);
|
||||
const showPostPriority = Boolean(isPostPriorityEnabled && post.metadata?.priority?.priority) && (location !== Screens.THREAD || !post.rootId);
|
||||
|
||||
const sameSequence = hasReplies ? (hasReplies && post.rootId) : !post.rootId;
|
||||
if (!showPostPriority && hasSameRoot && isConsecutivePost && sameSequence) {
|
||||
|
||||
@@ -35,7 +35,7 @@ const style = StyleSheet.create({
|
||||
});
|
||||
|
||||
type Props = {
|
||||
label: PostPriorityType;
|
||||
label: PostPriorityData['priority'];
|
||||
};
|
||||
|
||||
const PostPriorityLabel = ({label}: Props) => {
|
||||
@@ -48,7 +48,7 @@ const PostPriorityLabel = ({label}: Props) => {
|
||||
containerStyle.push(style.urgent);
|
||||
iconName = 'alert-outline';
|
||||
labelText = intl.formatMessage({id: 'post_priority.label.urgent', defaultMessage: 'URGENT'});
|
||||
} else {
|
||||
} else if (label === PostPriorityType.IMPORTANT) {
|
||||
containerStyle.push(style.important);
|
||||
iconName = 'alert-circle-outline';
|
||||
labelText = intl.formatMessage({id: 'post_priority.label.important', defaultMessage: 'IMPORTANT'});
|
||||
|
||||
@@ -14,10 +14,6 @@ import {typography} from '@utils/typography';
|
||||
|
||||
import PostPriorityPickerItem from './post_priority_picker_item';
|
||||
|
||||
export type PostPriorityData = {
|
||||
priority: PostPriorityType;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
data: PostPriorityData;
|
||||
onSubmit: (data: PostPriorityData) => void;
|
||||
@@ -61,8 +57,8 @@ const PostPriorityPicker = ({data, onSubmit}: Props) => {
|
||||
// For now, we just have one option but the spec suggest we have more in the next phase
|
||||
// const [data, setData] = React.useState<PostPriorityData>(defaultData);
|
||||
|
||||
const handleUpdatePriority = React.useCallback((priority: PostPriorityType) => {
|
||||
onSubmit({priority});
|
||||
const handleUpdatePriority = React.useCallback((priority: PostPriorityData['priority']) => {
|
||||
onSubmit({priority: priority || ''});
|
||||
}, [onSubmit]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||
import {github, monokai, solarizedDark, solarizedLight} from 'react-syntax-highlighter/dist/cjs/styles/hljs';
|
||||
import {githubGist as github, monokai, solarizedDark, solarizedLight} from 'react-syntax-highlighter/dist/cjs/styles/hljs';
|
||||
|
||||
import {useTheme} from '@context/theme';
|
||||
|
||||
|
||||
@@ -104,11 +104,18 @@ export default class CategoryModel extends Model implements CategoryInterface {
|
||||
Q.sortBy('last_post_at', Q.desc),
|
||||
);
|
||||
|
||||
/** hasChannels : Returns a boolean indicating if the category has channels */
|
||||
@lazy hasChannels = this.categoryChannels.observeCount().pipe(
|
||||
map((c) => c > 0),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
observeHasChannels = (canViewArchived: boolean) => {
|
||||
return this.channels.observeWithColumns(['delete_at']).pipe(
|
||||
map((channels) => {
|
||||
if (canViewArchived) {
|
||||
return channels.length > 0;
|
||||
}
|
||||
|
||||
return channels.filter((c) => c.deleteAt === 0).length > 0;
|
||||
}),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
};
|
||||
|
||||
toCategoryWithChannels = async (): Promise<CategoryWithChannels> => {
|
||||
const categoryChannels = await this.categoryChannels.fetch();
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {AppState, DeviceEventEmitter, Platform} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import {
|
||||
Notification,
|
||||
NotificationAction,
|
||||
@@ -29,6 +28,7 @@ import {getIsCRTEnabled, getThreadById} from '@queries/servers/thread';
|
||||
import {dismissOverlay, showOverlay} from '@screens/navigation';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import NavigationStore from '@store/navigation_store';
|
||||
import {isBetaApp} from '@utils/general';
|
||||
import {isMainActivity, isTablet} from '@utils/helpers';
|
||||
import {logInfo} from '@utils/log';
|
||||
import {convertToNotificationData} from '@utils/notification';
|
||||
@@ -248,7 +248,7 @@ class PushNotifications {
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
prefix = Device.PUSH_NOTIFY_APPLE_REACT_NATIVE;
|
||||
if (DeviceInfo.getBundleId().includes('rnbeta')) {
|
||||
if (isBetaApp) {
|
||||
prefix = `${prefix}beta`;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -21,10 +21,10 @@ import {isMainActivity} from '@utils/helpers';
|
||||
import {logError} from '@utils/log';
|
||||
|
||||
const WAIT_TO_CLOSE = toMilliseconds({seconds: 15});
|
||||
const WAIT_UNTIL_NEXT = toMilliseconds({seconds: 20});
|
||||
const WAIT_UNTIL_NEXT = toMilliseconds({seconds: 5});
|
||||
|
||||
class WebsocketManager {
|
||||
private connectedSubjects: {[serverUrl: string]: BehaviorSubject<boolean>} = {};
|
||||
private connectedSubjects: {[serverUrl: string]: BehaviorSubject<WebsocketConnectedState>} = {};
|
||||
|
||||
private clients: Record<string, WebSocketClient> = {};
|
||||
private connectionTimerIDs: Record<string, DebouncedFunc<() => void>> = {};
|
||||
@@ -69,7 +69,7 @@ class WebsocketManager {
|
||||
}
|
||||
delete this.clients[serverUrl];
|
||||
|
||||
this.getConnectedSubject(serverUrl).next(false);
|
||||
this.getConnectedSubject(serverUrl).next('not_connected');
|
||||
delete this.connectedSubjects[serverUrl];
|
||||
};
|
||||
|
||||
@@ -96,7 +96,7 @@ class WebsocketManager {
|
||||
const client = this.clients[url];
|
||||
if (client.isConnected()) {
|
||||
client.close(true);
|
||||
this.getConnectedSubject(url).next(false);
|
||||
this.getConnectedSubject(url).next('not_connected');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -107,6 +107,7 @@ class WebsocketManager {
|
||||
if (clientUrl === activeServerUrl) {
|
||||
this.initializeClient(clientUrl);
|
||||
} else {
|
||||
this.getConnectedSubject(clientUrl).next('connecting');
|
||||
const bounce = debounce(this.initializeClient.bind(this, clientUrl), WAIT_UNTIL_NEXT);
|
||||
this.connectionTimerIDs[clientUrl] = bounce;
|
||||
bounce();
|
||||
@@ -118,7 +119,7 @@ class WebsocketManager {
|
||||
return this.clients[serverUrl]?.isConnected();
|
||||
};
|
||||
|
||||
public observeConnected = (serverUrl: string) => {
|
||||
public observeWebsocketState = (serverUrl: string) => {
|
||||
return this.getConnectedSubject(serverUrl).asObservable().pipe(
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
@@ -126,7 +127,7 @@ class WebsocketManager {
|
||||
|
||||
private getConnectedSubject = (serverUrl: string) => {
|
||||
if (!this.connectedSubjects[serverUrl]) {
|
||||
this.connectedSubjects[serverUrl] = new BehaviorSubject(this.isConnected(serverUrl));
|
||||
this.connectedSubjects[serverUrl] = new BehaviorSubject(this.isConnected(serverUrl) ? 'connected' : 'not_connected');
|
||||
}
|
||||
|
||||
return this.connectedSubjects[serverUrl];
|
||||
@@ -153,13 +154,13 @@ class WebsocketManager {
|
||||
private onFirstConnect = (serverUrl: string) => {
|
||||
this.startPeriodicStatusUpdates(serverUrl);
|
||||
handleFirstConnect(serverUrl);
|
||||
this.getConnectedSubject(serverUrl).next(true);
|
||||
this.getConnectedSubject(serverUrl).next('connected');
|
||||
};
|
||||
|
||||
private onReconnect = (serverUrl: string) => {
|
||||
private onReconnect = async (serverUrl: string) => {
|
||||
this.startPeriodicStatusUpdates(serverUrl);
|
||||
handleReconnect(serverUrl);
|
||||
this.getConnectedSubject(serverUrl).next(true);
|
||||
await handleReconnect(serverUrl);
|
||||
this.getConnectedSubject(serverUrl).next('connected');
|
||||
};
|
||||
|
||||
private onWebsocketClose = async (serverUrl: string, connectFailCount: number, lastDisconnect: number) => {
|
||||
@@ -168,7 +169,7 @@ class WebsocketManager {
|
||||
await handleClose(serverUrl, lastDisconnect);
|
||||
|
||||
this.stopPeriodicStatusUpdates(serverUrl);
|
||||
this.getConnectedSubject(serverUrl).next(false);
|
||||
this.getConnectedSubject(serverUrl).next('not_connected');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -209,6 +209,7 @@ export const queryPinnedPostsInChannel = (database: Database, channelId: string)
|
||||
Q.where('channel_id', channelId),
|
||||
Q.where('is_pinned', Q.eq(true)),
|
||||
),
|
||||
Q.sortBy('create_at', Q.asc),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -404,7 +404,13 @@ export async function setCurrentTeamAndChannelId(operator: ServerDataOperator, t
|
||||
export const observeLastUnreadChannelId = (database: Database): Observable<string> => {
|
||||
return querySystemValue(database, SYSTEM_IDENTIFIERS.LAST_UNREAD_CHANNEL_ID).observe().pipe(
|
||||
switchMap((result) => (result.length ? result[0].observe() : of$({value: ''}))),
|
||||
switchMap((model) => of$(model.value)),
|
||||
switchMap((model) => {
|
||||
if (model.value) {
|
||||
return of$(model.value);
|
||||
}
|
||||
|
||||
return observeCurrentChannelId(database);
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -141,7 +141,10 @@ export const prepareThreadsFromReceivedPosts = async (operator: ServerDataOperat
|
||||
};
|
||||
|
||||
export const queryThreadsInTeam = (database: Database, teamId: string, onlyUnreads?: boolean, hasReplies?: boolean, isFollowing?: boolean, sort?: boolean, earliest?: number): Query<ThreadModel> => {
|
||||
const query: Q.Clause[] = [];
|
||||
const query: Q.Clause[] = [
|
||||
Q.experimentalNestedJoin(POST, CHANNEL),
|
||||
Q.on(POST, Q.on(CHANNEL, Q.where('delete_at', 0))),
|
||||
];
|
||||
|
||||
if (isFollowing) {
|
||||
query.push(Q.where('is_following', true));
|
||||
@@ -189,30 +192,34 @@ export const queryThreads = (database: Database, teamId?: string, onlyUnreads =
|
||||
Q.where('reply_count', Q.gt(0)),
|
||||
];
|
||||
|
||||
// Only get threads from available channel
|
||||
const channelCondition: Q.Condition[] = [
|
||||
Q.where('delete_at', 0),
|
||||
];
|
||||
|
||||
// If teamId is specified, only get threads in that team
|
||||
if (teamId) {
|
||||
let condition: Q.Condition = Q.where('team_id', teamId);
|
||||
|
||||
if (includeDmGm) {
|
||||
condition = Q.or(
|
||||
Q.where('team_id', teamId),
|
||||
Q.where('team_id', ''),
|
||||
channelCondition.push(
|
||||
Q.or(
|
||||
Q.where('team_id', teamId),
|
||||
Q.where('team_id', ''),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
channelCondition.push(Q.where('team_id', teamId));
|
||||
}
|
||||
|
||||
query.push(
|
||||
Q.experimentalNestedJoin(POST, CHANNEL),
|
||||
Q.on(POST, Q.on(CHANNEL, condition)),
|
||||
);
|
||||
} else if (!includeDmGm) {
|
||||
// fetching all threads from all teams
|
||||
// excluding DM/GM channels
|
||||
query.push(
|
||||
Q.experimentalNestedJoin(POST, CHANNEL),
|
||||
Q.on(POST, Q.on(CHANNEL, Q.where('team_id', Q.notEq('')))),
|
||||
);
|
||||
channelCondition.push(Q.where('team_id', Q.notEq('')));
|
||||
}
|
||||
|
||||
query.push(
|
||||
Q.experimentalNestedJoin(POST, CHANNEL),
|
||||
Q.on(POST, Q.on(CHANNEL, Q.and(...channelCondition))),
|
||||
);
|
||||
|
||||
if (onlyUnreads) {
|
||||
query.push(Q.where('unread_replies', Q.gt(0)));
|
||||
}
|
||||
|
||||
@@ -162,8 +162,6 @@ const enhance = withObservables(['category', 'isTablet', 'locale'], ({category,
|
||||
return {
|
||||
limit,
|
||||
sortedChannels,
|
||||
notifyProps,
|
||||
lastUnreadId,
|
||||
unreadsOnTop,
|
||||
unreadIds,
|
||||
category,
|
||||
|
||||
@@ -2,14 +2,23 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observeConfigBooleanValue} from '@queries/servers/system';
|
||||
|
||||
import CategoryHeader from './header';
|
||||
|
||||
import type CategoryModel from '@typings/database/models/servers/category';
|
||||
|
||||
const enhanced = withObservables(['category'], ({category}: {category: CategoryModel}) => ({
|
||||
category,
|
||||
hasChannels: category.hasChannels,
|
||||
}));
|
||||
const enhanced = withObservables(['category'], ({category}: {category: CategoryModel}) => {
|
||||
const canViewArchived = observeConfigBooleanValue(category.database, 'ExperimentalViewArchivedChannels');
|
||||
|
||||
return {
|
||||
category,
|
||||
hasChannels: canViewArchived.pipe(
|
||||
switchMap((canView) => category.observeHasChannels(canView)),
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
export default enhanced(CategoryHeader);
|
||||
|
||||
@@ -8,7 +8,7 @@ import WebsocketManager from '@managers/websocket_manager';
|
||||
import WebSocket from './websocket';
|
||||
|
||||
const enhanced = withObservables(['serverUrl'], ({serverUrl}: {serverUrl: string}) => ({
|
||||
isConnected: WebsocketManager.observeConnected(serverUrl),
|
||||
websocketState: WebsocketManager.observeWebsocketState(serverUrl),
|
||||
}));
|
||||
|
||||
export default enhanced(WebSocket);
|
||||
|
||||
@@ -11,7 +11,7 @@ import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
type Props = {
|
||||
isConnected: boolean;
|
||||
websocketState: WebsocketConnectedState;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
@@ -28,10 +28,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const WebSocket = ({isConnected}: Props) => {
|
||||
const WebSocket = ({websocketState}: Props) => {
|
||||
const theme = useTheme();
|
||||
|
||||
if (isConnected) {
|
||||
if (websocketState === 'connected' || websocketState === 'connecting') {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,14 @@ const SearchScreen = ({teamId, teams}: Props) => {
|
||||
setSearchTeamId(teamId);
|
||||
}, [teamId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (searchTerm) {
|
||||
resetToInitial();
|
||||
setSearchValue(searchTerm);
|
||||
handleSearch(searchTeamId, searchTerm);
|
||||
}
|
||||
}, [searchTerm]);
|
||||
|
||||
const onSnap = (offset: number, animated = true) => {
|
||||
scrollRef.current?.scrollToOffset({offset, animated});
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import merge from 'deepmerge';
|
||||
import {Appearance, DeviceEventEmitter, NativeModules, StatusBar, Platform, Alert} from 'react-native';
|
||||
import {ImageResource, Navigation, Options, OptionsModalPresentationStyle, OptionsTopBarButton, ScreenPoppedEvent} from 'react-native-navigation';
|
||||
import {ComponentWillAppearEvent, ImageResource, Navigation, Options, OptionsModalPresentationStyle, OptionsTopBarButton, ScreenPoppedEvent} from 'react-native-navigation';
|
||||
import tinyColor from 'tinycolor2';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
@@ -32,6 +32,7 @@ const alpha = {
|
||||
export function registerNavigationListeners() {
|
||||
Navigation.events().registerScreenPoppedListener(onPoppedListener);
|
||||
Navigation.events().registerCommandListener(onCommandListener);
|
||||
Navigation.events().registerComponentWillAppearListener(onScreenWillAppear);
|
||||
}
|
||||
|
||||
function onCommandListener(name: string, params: any) {
|
||||
@@ -66,7 +67,10 @@ function onCommandListener(name: string, params: any) {
|
||||
function onPoppedListener({componentId}: ScreenPoppedEvent) {
|
||||
// screen pop does not trigger registerCommandListener, but does trigger screenPoppedListener
|
||||
NavigationStore.removeScreenFromStack(componentId);
|
||||
if (NavigationStore.getVisibleScreen() === Screens.HOME) {
|
||||
}
|
||||
|
||||
function onScreenWillAppear(event: ComponentWillAppearEvent) {
|
||||
if (event.componentId === Screens.HOME) {
|
||||
DeviceEventEmitter.emit(Events.TAB_BAR_VISIBLE, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ import React, {useEffect, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Linking, Platform, Text, View} from 'react-native';
|
||||
import Button from 'react-native-button';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import urlParse from 'url-parse';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {Sso} from '@constants';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
|
||||
import {isBetaApp} from '@utils/general';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {tryOpenURL} from '@utils/url';
|
||||
@@ -62,7 +62,7 @@ const SSOWithRedirectURL = ({doSSOLogin, loginError, loginUrl, serverUrl, setLog
|
||||
const style = getStyleSheet(theme);
|
||||
const intl = useIntl();
|
||||
let customUrlScheme = Sso.REDIRECT_URL_SCHEME;
|
||||
if (DeviceInfo.getBundleId && DeviceInfo.getBundleId().includes('rnbeta')) {
|
||||
if (isBetaApp) {
|
||||
customUrlScheme = Sso.REDIRECT_URL_SCHEME_DEV;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import {DEFAULT_LOCALE, getTranslations, t} from '@i18n';
|
||||
import {dismissAllModals} from '@screens/navigation';
|
||||
import {ClientError} from '@utils/client_error';
|
||||
import {isBetaApp} from '@utils/general';
|
||||
import {
|
||||
captureException,
|
||||
captureJSException,
|
||||
@@ -42,7 +43,10 @@ class JavascriptAndNativeErrorHandler {
|
||||
}
|
||||
|
||||
logWarning('Handling Javascript error', e, isFatal);
|
||||
captureJSException(e, isFatal);
|
||||
|
||||
if (isBetaApp || isFatal) {
|
||||
captureJSException(e, isFatal);
|
||||
}
|
||||
|
||||
if (isFatal && e instanceof Error) {
|
||||
const translations = getTranslations(DEFAULT_LOCALE);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import ReactNativeHapticFeedback, {HapticFeedbackTypes} from 'react-native-haptic-feedback';
|
||||
|
||||
type SortByCreatAt = (Session | Channel | Team | Post) & {
|
||||
@@ -51,3 +52,5 @@ export const sortByNewest = (a: SortByCreatAt, b: SortByCreatAt) => {
|
||||
|
||||
return 1;
|
||||
};
|
||||
|
||||
export const isBetaApp = DeviceInfo.getBundleId && DeviceInfo.getBundleId().includes('rnbeta');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Database} from '@nozbe/watermelondb';
|
||||
import {Breadcrumb} from '@sentry/types';
|
||||
import {Breadcrumb, Event} from '@sentry/types';
|
||||
import {Platform} from 'react-native';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
|
||||
@@ -10,6 +10,7 @@ import Config from '@assets/config.json';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getConfig} from '@queries/servers/system';
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
import {isBetaApp} from '@utils/general';
|
||||
|
||||
import {ClientError} from './client_error';
|
||||
import {logError, logWarning} from './log';
|
||||
@@ -39,9 +40,18 @@ export function initializeSentry() {
|
||||
return;
|
||||
}
|
||||
|
||||
const mmConfig = {
|
||||
environment: isBetaApp ? 'beta' : 'production',
|
||||
tracesSampleRate: isBetaApp ? 1.0 : 0.2,
|
||||
sampleRate: isBetaApp ? 1.0 : 0.2,
|
||||
attachStacktrace: isBetaApp, // For Beta, stack traces are automatically attached to all messages logged
|
||||
};
|
||||
|
||||
Sentry.init({
|
||||
dsn,
|
||||
tracesSampleRate: 0.2,
|
||||
sendDefaultPii: false,
|
||||
...mmConfig,
|
||||
...Config.SentryOptions,
|
||||
integrations: [
|
||||
new Sentry.ReactNativeTracing({
|
||||
|
||||
@@ -51,8 +61,13 @@ export function initializeSentry() {
|
||||
),
|
||||
}),
|
||||
],
|
||||
sendDefaultPii: false,
|
||||
...Config.SentryOptions,
|
||||
beforeSend: (event: Event) => {
|
||||
if (isBetaApp || event?.level === 'fatal') {
|
||||
return event;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -568,6 +568,9 @@ platform :ios do
|
||||
app_name_sub = app_name.gsub(" ", "_")
|
||||
config_mode = ENV['BUILD_FOR_RELEASE'] == 'true' ? 'Release' : 'Debug'
|
||||
method = ENV['IOS_BUILD_EXPORT_METHOD'].nil? || ENV['IOS_BUILD_EXPORT_METHOD'].empty? ? 'ad-hoc' : ENV['IOS_BUILD_EXPORT_METHOD']
|
||||
|
||||
# Need to add xcargs to only notification and
|
||||
xcargs = ENV['SENTRY_ENABLED'] == 'true' ? "SENTRY_DSN_IOS='#{ENV['SENTRY_DSN_IOS']}' SENTRY_ENABLED='#{ENV['SENTRY_ENABLED']}'" : ''
|
||||
|
||||
setup_code_signing
|
||||
|
||||
@@ -583,7 +586,8 @@ platform :ios do
|
||||
export_options: {
|
||||
signingStyle: 'manual',
|
||||
iCloudContainerEnvironment: 'Production'
|
||||
}
|
||||
},
|
||||
xcargs:xcargs
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
27
ios/ErrorReporting/Sentry.swift
Normal file
27
ios/ErrorReporting/Sentry.swift
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Sentry.swift
|
||||
// Mattermost
|
||||
//
|
||||
// Created by Avinash Lingaloo on 20/12/2022.
|
||||
// Copyright © 2022 Facebook. All rights reserved.
|
||||
//
|
||||
import Foundation
|
||||
|
||||
import Sentry
|
||||
|
||||
func initSentryAppExt(){
|
||||
if let SENTRY_ENABLED = Bundle.main.infoDictionary?["SENTRY_ENABLED"] as? String,
|
||||
let SENTRY_DSN = Bundle.main.infoDictionary?["SENTRY_DSN_IOS"] as? String {
|
||||
if(SENTRY_ENABLED=="true"){
|
||||
SentrySDK.start { options in
|
||||
options.dsn = SENTRY_DSN
|
||||
options.enableAppHangTracking = true
|
||||
options.enableCaptureFailedRequests = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testSentry(msg: String){
|
||||
SentrySDK.capture(message: msg)
|
||||
}
|
||||
@@ -35,14 +35,25 @@ extension Database {
|
||||
}
|
||||
|
||||
public func getChannelMentions(_ db: Connection) -> Int {
|
||||
let mentionsCol = Expression<Int?>("mentions_count")
|
||||
let mentions = try? db.scalar(myChannelTable.select(mentionsCol.total))
|
||||
let stmtString = """
|
||||
SELECT SUM(my.mentions_count) \
|
||||
FROM MyChannel my \
|
||||
INNER JOIN Channel c ON c.id=my.id \
|
||||
WHERE c.delete_at = 0
|
||||
"""
|
||||
let mentions = try? db.prepare(stmtString).scalar() as? Double
|
||||
return Int(mentions ?? 0)
|
||||
}
|
||||
|
||||
|
||||
public func getThreadMentions(_ db: Connection) -> Int {
|
||||
let mentionsCol = Expression<Int?>("unread_mentions")
|
||||
let mentions = try? db.scalar(threadTable.select(mentionsCol.total))
|
||||
let stmtString = """
|
||||
SELECT SUM(unread_mentions) \
|
||||
FROM Thread t
|
||||
INNER JOIN Post p ON t.id=p.id \
|
||||
INNER JOIN Channel c ON p.channel_id=c.id
|
||||
WHERE c.delete_at = 0
|
||||
"""
|
||||
let mentions = try? db.prepare(stmtString).scalar() as? Double
|
||||
return Int(mentions ?? 0)
|
||||
}
|
||||
|
||||
|
||||
@@ -181,8 +181,9 @@ extension Database {
|
||||
public func handlePostData(_ db: Connection, _ postData: PostData, _ channelId: String, _ usedSince: Bool = false, _ receivingThreads: Bool = false) throws {
|
||||
let sortedChainedPosts = chainAndSortPosts(postData)
|
||||
try insertOrUpdatePosts(db, sortedChainedPosts, channelId)
|
||||
let earliest = sortedChainedPosts.first!.create_at
|
||||
let latest = sortedChainedPosts.last!.create_at
|
||||
let sortedAndNotDeletedPosts = sortedChainedPosts.filter({$0.delete_at == 0})
|
||||
let earliest = sortedAndNotDeletedPosts.first!.create_at
|
||||
let latest = sortedAndNotDeletedPosts.last!.create_at
|
||||
if (!receivingThreads) {
|
||||
try handlePostsInChannel(db, channelId, earliest, latest, usedSince)
|
||||
|
||||
@@ -564,7 +565,7 @@ extension Database {
|
||||
var postsInThread = [String: [Post]]()
|
||||
|
||||
for post in posts {
|
||||
if !post.root_id.isEmpty {
|
||||
if !post.root_id.isEmpty && post.delete_at == 0 {
|
||||
var threadPosts = postsInThread[post.root_id] ?? [Post]()
|
||||
threadPosts.append(post)
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
|
||||
27C667A329523ECA00E590D5 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 27C667A229523ECA00E590D5 /* Sentry */; };
|
||||
27C667A529523F0A00E590D5 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 27C667A429523F0A00E590D5 /* Sentry */; };
|
||||
27C667A9295241B600E590D5 /* Sentry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27C667A8295241B600E590D5 /* Sentry.swift */; };
|
||||
27C667AA295241B600E590D5 /* Sentry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27C667A8295241B600E590D5 /* Sentry.swift */; };
|
||||
49AE370126D4455D00EF4E52 /* Gekidou in Frameworks */ = {isa = PBXBuildFile; productRef = 49AE370026D4455D00EF4E52 /* Gekidou */; };
|
||||
536CC6C323E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 536CC6C123E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m */; };
|
||||
58495E36BF1A4EAB93609E57 /* Metropolis-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 54956DEFEBB74EF78C3A6AE5 /* Metropolis-SemiBold.ttf */; };
|
||||
@@ -156,6 +160,7 @@
|
||||
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Mattermost/main.m; sourceTree = "<group>"; };
|
||||
182D203F539AF68F1647EFAF /* Pods-Mattermost-MattermostTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mattermost-MattermostTests.release.xcconfig"; path = "Target Support Files/Pods-Mattermost-MattermostTests/Pods-Mattermost-MattermostTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
25BF2BACE89201DE6E585B7E /* Pods-Mattermost.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mattermost.release.xcconfig"; path = "Target Support Files/Pods-Mattermost/Pods-Mattermost.release.xcconfig"; sourceTree = "<group>"; };
|
||||
27C667A8295241B600E590D5 /* Sentry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sentry.swift; sourceTree = "<group>"; };
|
||||
297AAFCCF0BD99FC109DA2BC /* Pods-MattermostTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MattermostTests.release.xcconfig"; path = "Target Support Files/Pods-MattermostTests/Pods-MattermostTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
32AC3D4EA79E44738A6E9766 /* OpenSans-BoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-BoldItalic.ttf"; path = "../assets/fonts/OpenSans-BoldItalic.ttf"; sourceTree = "<group>"; };
|
||||
3647DF63D6764CF093375861 /* OpenSans-ExtraBold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-ExtraBold.ttf"; path = "../assets/fonts/OpenSans-ExtraBold.ttf"; sourceTree = "<group>"; };
|
||||
@@ -279,6 +284,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
49AE370126D4455D00EF4E52 /* Gekidou in Frameworks */,
|
||||
27C667A329523ECA00E590D5 /* Sentry in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -288,6 +294,7 @@
|
||||
files = (
|
||||
7FD4822C2864D73300A5B18B /* OpenGraph in Frameworks */,
|
||||
7F4288042865D340006B48E1 /* Gekidou in Frameworks */,
|
||||
27C667A529523F0A00E590D5 /* Sentry in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -352,6 +359,14 @@
|
||||
name = Mattermost;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
27C667AB2952425700E590D5 /* ErrorReporting */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
27C667A8295241B600E590D5 /* Sentry.swift */,
|
||||
);
|
||||
path = ErrorReporting;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33E107B4DC21A5C48B09F800 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -625,6 +640,7 @@
|
||||
83CBB9F61A601CBA00E9B192 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
27C667AB2952425700E590D5 /* ErrorReporting */,
|
||||
13B07FAE1A68108700A75B9A /* Mattermost */,
|
||||
7F581D33221ED5C60099E66B /* NotificationService */,
|
||||
7F292A701E8AB73400A450A3 /* SplashScreenResource */,
|
||||
@@ -698,6 +714,7 @@
|
||||
name = NotificationService;
|
||||
packageProductDependencies = (
|
||||
49AE370026D4455D00EF4E52 /* Gekidou */,
|
||||
27C667A229523ECA00E590D5 /* Sentry */,
|
||||
);
|
||||
productName = NotificationService;
|
||||
productReference = 7F581D32221ED5C60099E66B /* NotificationService.appex */;
|
||||
@@ -719,6 +736,7 @@
|
||||
packageProductDependencies = (
|
||||
7FD4822B2864D73300A5B18B /* OpenGraph */,
|
||||
7F4288032865D340006B48E1 /* Gekidou */,
|
||||
27C667A429523F0A00E590D5 /* Sentry */,
|
||||
);
|
||||
productName = MattermostShare;
|
||||
productReference = 7FC5698628563FDB000B0905 /* MattermostShare.appex */;
|
||||
@@ -782,7 +800,8 @@
|
||||
);
|
||||
mainGroup = 83CBB9F61A601CBA00E9B192;
|
||||
packageReferences = (
|
||||
7FD4822A2864D73300A5B18B /* XCRemoteSwiftPackageReference "OpenGraph.git" */,
|
||||
7FD4822A2864D73300A5B18B /* XCRemoteSwiftPackageReference "OpenGraph" */,
|
||||
27C667A129523ECA00E590D5 /* XCRemoteSwiftPackageReference "sentry-cocoa" */,
|
||||
);
|
||||
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
|
||||
projectDirPath = "";
|
||||
@@ -1005,6 +1024,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7F581D35221ED5C60099E66B /* NotificationService.swift in Sources */,
|
||||
27C667A9295241B600E590D5 /* Sentry.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1021,6 +1041,7 @@
|
||||
7F93AAB8287778090047B89F /* Publishers.swift in Sources */,
|
||||
7F7E9F732864E8060064BFAF /* CompassIcons.swift in Sources */,
|
||||
7F93AABA28777A390047B89F /* Notification.swift in Sources */,
|
||||
27C667AA295241B600E590D5 /* Sentry.swift in Sources */,
|
||||
7FA9A9902868BD8800AB35A1 /* LocalFileManager.swift in Sources */,
|
||||
7F93AA9E2875FD310047B89F /* CancelButton.swift in Sources */,
|
||||
7F42880A286672F6006B48E1 /* ServerService.swift in Sources */,
|
||||
@@ -1502,7 +1523,15 @@
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
7FD4822A2864D73300A5B18B /* XCRemoteSwiftPackageReference "OpenGraph.git" */ = {
|
||||
27C667A129523ECA00E590D5 /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/getsentry/sentry-cocoa.git";
|
||||
requirement = {
|
||||
branch = 8.0.0;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
7FD4822A2864D73300A5B18B /* XCRemoteSwiftPackageReference "OpenGraph" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/satoshi-takano/OpenGraph.git";
|
||||
requirement = {
|
||||
@@ -1513,6 +1542,16 @@
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
27C667A229523ECA00E590D5 /* Sentry */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 27C667A129523ECA00E590D5 /* XCRemoteSwiftPackageReference "sentry-cocoa" */;
|
||||
productName = Sentry;
|
||||
};
|
||||
27C667A429523F0A00E590D5 /* Sentry */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 27C667A129523ECA00E590D5 /* XCRemoteSwiftPackageReference "sentry-cocoa" */;
|
||||
productName = Sentry;
|
||||
};
|
||||
49AE370026D4455D00EF4E52 /* Gekidou */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Gekidou;
|
||||
@@ -1527,7 +1566,7 @@
|
||||
};
|
||||
7FD4822B2864D73300A5B18B /* OpenGraph */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 7FD4822A2864D73300A5B18B /* XCRemoteSwiftPackageReference "OpenGraph.git" */;
|
||||
package = 7FD4822A2864D73300A5B18B /* XCRemoteSwiftPackageReference "OpenGraph" */;
|
||||
productName = OpenGraph;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
@@ -10,6 +10,15 @@
|
||||
"version": "1.4.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Sentry",
|
||||
"repositoryURL": "https://github.com/getsentry/sentry-cocoa.git",
|
||||
"state": {
|
||||
"branch": "8.0.0",
|
||||
"revision": "1a18683901844a2970ccfb633e4ebae565361817",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SQLite.swift",
|
||||
"repositoryURL": "https://github.com/stephencelis/SQLite.swift.git",
|
||||
|
||||
@@ -50,8 +50,8 @@
|
||||
<dict>
|
||||
<key>NSExtensionActivationRule</key>
|
||||
<dict>
|
||||
<key>NSExtensionActivationDictionaryVersion</key>
|
||||
<integer>2</integer>
|
||||
<key>NSExtensionActivationDictionaryVersion</key>
|
||||
<integer>2</integer>
|
||||
<key>NSExtensionActivationSupportsAttachmentsWithMaxCount</key>
|
||||
<integer>10</integer>
|
||||
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
|
||||
@@ -73,5 +73,9 @@
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.share-services</string>
|
||||
</dict>
|
||||
<key>SENTRY_DSN_IOS</key>
|
||||
<string>$(SENTRY_DSN_IOS)</string>
|
||||
<key>SENTRY_ENABLED</key>
|
||||
<string>$(SENTRY_ENABLED)</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -10,6 +10,7 @@ import Gekidou
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import os.log
|
||||
import Sentry
|
||||
|
||||
class ShareViewController: UIViewController {
|
||||
private var fileManager: LocalFileManager?
|
||||
@@ -20,7 +21,6 @@ class ShareViewController: UIViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
self.isModalInPresentation = true
|
||||
|
||||
self.addObservers()
|
||||
fileManager = LocalFileManager()
|
||||
if let inputItems = extensionContext?.inputItems {
|
||||
@@ -34,6 +34,9 @@ class ShareViewController: UIViewController {
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// Initialize Sentry
|
||||
initSentryAppExt()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
@@ -94,6 +97,8 @@ class ShareViewController: UIViewController {
|
||||
let fileCount = attachments.count
|
||||
let files: [String] = attachments.map{ $0.fileUrl.absoluteString }
|
||||
|
||||
|
||||
|
||||
var message = text
|
||||
if linkPreviewUrl != nil && !linkPreviewUrl!.isEmpty {
|
||||
if text.isEmpty {
|
||||
|
||||
@@ -29,5 +29,9 @@
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
|
||||
</dict>
|
||||
<key>SENTRY_DSN_IOS</key>
|
||||
<string>$(SENTRY_DSN_IOS)</string>
|
||||
<key>SENTRY_ENABLED</key>
|
||||
<string>$(SENTRY_ENABLED)</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -9,6 +9,11 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
var retryIndex = 0
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
initSentryAppExt()
|
||||
}
|
||||
|
||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||
self.contentHandler = contentHandler
|
||||
|
||||
@@ -24,7 +29,6 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
func processResponse(serverUrl: String, data: Data, bestAttemptContent: UNMutableNotificationContent, contentHandler: ((UNNotificationContent) -> Void)?) {
|
||||
bestAttemptContent.userInfo["server_url"] = serverUrl
|
||||
|
||||
let json = try? JSONSerialization.jsonObject(with: data) as! [String: Any]
|
||||
if let json = json {
|
||||
if let message = json["message"] as? String {
|
||||
|
||||
5
types/api/posts.d.ts
vendored
5
types/api/posts.d.ts
vendored
@@ -20,6 +20,10 @@ type PostType =
|
||||
|
||||
type PostEmbedType = 'image' | 'message_attachment' | 'opengraph';
|
||||
|
||||
type PostPriorityData = {
|
||||
priority: ''|'urgent'|'important';
|
||||
};
|
||||
|
||||
type PostEmbed = {
|
||||
type: PostEmbedType;
|
||||
url: string;
|
||||
@@ -39,6 +43,7 @@ type PostMetadata = {
|
||||
files?: FileInfo[];
|
||||
images?: Dictionary<PostImage>;
|
||||
reactions?: Reaction[];
|
||||
priority?: PostPriorityData;
|
||||
};
|
||||
|
||||
type Post = {
|
||||
|
||||
@@ -58,7 +58,7 @@ declare class CategoryModel extends Model {
|
||||
@lazy myChannels: Query<MyChannelModel>;
|
||||
|
||||
/** hasChannels : Whether the category has any channels */
|
||||
@lazy hasChannels: Observable<boolean>;
|
||||
observeHasChannels(canViewArchived: boolean): Observable<boolean>;
|
||||
|
||||
/** toCategoryWithChannels returns a map of the Category with an array of ordered channel ids */
|
||||
toCategoryWithChannels(): Promise<CategoryWithChannels>;
|
||||
|
||||
4
types/global/websocket.d.ts
vendored
Normal file
4
types/global/websocket.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
type WebsocketConnectedState = 'not_connected' | 'connected' | 'connecting';
|
||||
Reference in New Issue
Block a user