Merge remote-tracking branch 'origin/main' into deps

This commit is contained in:
Elias Nahum
2022-12-21 20:43:58 +02:00
47 changed files with 406 additions and 129 deletions

View File

@@ -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

View File

@@ -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(

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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));

View File

@@ -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 (

View File

@@ -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),

View File

@@ -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);

View File

@@ -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}
/>

View File

@@ -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({

View File

@@ -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>

View File

@@ -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}
/>
);

View File

@@ -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,

View File

@@ -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>
)}

View File

@@ -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) {

View File

@@ -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'});

View File

@@ -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 (

View File

@@ -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';

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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');
}
};

View File

@@ -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),
);
};

View File

@@ -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);
}),
);
};

View File

@@ -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)));
}

View File

@@ -162,8 +162,6 @@ const enhance = withObservables(['category', 'isTablet', 'locale'], ({category,
return {
limit,
sortedChannels,
notifyProps,
lastUnreadId,
unreadsOnTop,
unreadIds,
category,

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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});
};

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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');

View File

@@ -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;
},
});
}

View File

@@ -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

View 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)
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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 */

View File

@@ -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",

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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 = {

View File

@@ -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
View 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';