Merge branch 'develop' into chore.migrate-redux-server-to-ts
This commit is contained in:
commit
f8a37fe082
|
@ -359,7 +359,7 @@ jobs:
|
||||||
- run:
|
- run:
|
||||||
name: Test
|
name: Test
|
||||||
command: |
|
command: |
|
||||||
yarn test
|
yarn test -w 8
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Codecov
|
name: Codecov
|
||||||
|
|
|
@ -22,18 +22,13 @@ global.Date.now = jest.fn(() => new Date('2019-10-10').getTime());
|
||||||
|
|
||||||
const converter = new Stories2SnapsConverter();
|
const converter = new Stories2SnapsConverter();
|
||||||
|
|
||||||
// Runner
|
|
||||||
initStoryshots({
|
initStoryshots({
|
||||||
asyncJest: true,
|
test: ({ story, context }) => {
|
||||||
test: ({ story, context, done }) => {
|
|
||||||
const snapshotFilename = converter.getSnapshotFileName(context);
|
const snapshotFilename = converter.getSnapshotFileName(context);
|
||||||
const storyElement = story.render();
|
const storyElement = story.render();
|
||||||
const { update, toJSON } = render(storyElement);
|
const { update, toJSON } = render(storyElement);
|
||||||
update(storyElement);
|
update(storyElement);
|
||||||
setTimeout(() => {
|
|
||||||
const json = toJSON();
|
const json = toJSON();
|
||||||
expect(JSON.stringify(json)).toMatchSpecificSnapshot(snapshotFilename);
|
expect(JSON.stringify(json)).toMatchSpecificSnapshot(snapshotFilename);
|
||||||
done();
|
|
||||||
}, 10);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Q } from '@nozbe/watermelondb';
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
import { Observable, Subscription } from 'rxjs';
|
||||||
|
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
import { getUserSelector } from '../../selectors/login';
|
||||||
|
import { TSubscriptionModel, TUserModel } from '../../definitions';
|
||||||
import Avatar from './Avatar';
|
import Avatar from './Avatar';
|
||||||
import { IAvatar } from './interfaces';
|
import { IAvatar } from './interfaces';
|
||||||
|
|
||||||
class AvatarContainer extends React.Component<IAvatar, any> {
|
class AvatarContainer extends React.Component<IAvatar, any> {
|
||||||
private mounted: boolean;
|
private mounted: boolean;
|
||||||
|
|
||||||
private subscription: any;
|
private subscription?: Subscription;
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
text: '',
|
text: '',
|
||||||
|
@ -59,15 +61,17 @@ class AvatarContainer extends React.Component<IAvatar, any> {
|
||||||
record = user;
|
record = user;
|
||||||
} else {
|
} else {
|
||||||
const { rid } = this.props;
|
const { rid } = this.props;
|
||||||
|
if (rid) {
|
||||||
record = await subsCollection.find(rid);
|
record = await subsCollection.find(rid);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Record not found
|
// Record not found
|
||||||
}
|
}
|
||||||
|
|
||||||
if (record) {
|
if (record) {
|
||||||
const observable = record.observe();
|
const observable = record.observe() as Observable<TSubscriptionModel | TUserModel>;
|
||||||
this.subscription = observable.subscribe((r: any) => {
|
this.subscription = observable.subscribe(r => {
|
||||||
const { avatarETag } = r;
|
const { avatarETag } = r;
|
||||||
if (this.mounted) {
|
if (this.mounted) {
|
||||||
this.setState({ avatarETag });
|
this.setState({ avatarETag });
|
||||||
|
|
|
@ -35,7 +35,7 @@ const styles = StyleSheet.create({
|
||||||
const BackgroundContainer = ({ theme, text, loading }: IBackgroundContainer) => (
|
const BackgroundContainer = ({ theme, text, loading }: IBackgroundContainer) => (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<ImageBackground source={{ uri: `message_empty_${theme}` }} style={styles.image} />
|
<ImageBackground source={{ uri: `message_empty_${theme}` }} style={styles.image} />
|
||||||
{text ? <Text style={[styles.text, { color: themes[theme!].auxiliaryTintColor }]}>{text}</Text> : null}
|
{text && !loading ? <Text style={[styles.text, { color: themes[theme!].auxiliaryTintColor }]}>{text}</Text> : null}
|
||||||
{loading ? <ActivityIndicator style={styles.text} color={themes[theme!].auxiliaryTintColor} /> : null}
|
{loading ? <ActivityIndicator style={styles.text} color={themes[theme!].auxiliaryTintColor} /> : null}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
@ -36,7 +36,7 @@ interface IEmojiPickerProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IEmojiPickerState {
|
interface IEmojiPickerState {
|
||||||
frequentlyUsed: [];
|
frequentlyUsed: (string | { content?: string; extension?: string; isCustom: boolean })[];
|
||||||
customEmojis: any;
|
customEmojis: any;
|
||||||
show: boolean;
|
show: boolean;
|
||||||
width: number | null;
|
width: number | null;
|
||||||
|
@ -114,7 +114,7 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.action(async () => {
|
await db.write(async () => {
|
||||||
if (freqEmojiRecord) {
|
if (freqEmojiRecord) {
|
||||||
await freqEmojiRecord.update((f: any) => {
|
await freqEmojiRecord.update((f: any) => {
|
||||||
f.count += 1;
|
f.count += 1;
|
||||||
|
@ -132,8 +132,8 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
||||||
updateFrequentlyUsed = async () => {
|
updateFrequentlyUsed = async () => {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch();
|
const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch();
|
||||||
let frequentlyUsed: any = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
|
const frequentlyUsedOrdered = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
|
||||||
frequentlyUsed = frequentlyUsed.map((item: IEmoji) => {
|
const frequentlyUsed = frequentlyUsedOrdered.map(item => {
|
||||||
if (item.isCustom) {
|
if (item.isCustom) {
|
||||||
return { content: item.content, extension: item.extension, isCustom: item.isCustom };
|
return { content: item.content, extension: item.extension, isCustom: item.isCustom };
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import database from '../../lib/database';
|
||||||
import { Button } from '../ActionSheet';
|
import { Button } from '../ActionSheet';
|
||||||
import { useDimensions } from '../../dimensions';
|
import { useDimensions } from '../../dimensions';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
|
import { TFrequentlyUsedEmojiModel } from '../../definitions/IFrequentlyUsedEmoji';
|
||||||
import { IEmoji } from '../EmojiPicker/interfaces';
|
import { IEmoji } from '../EmojiPicker/interfaces';
|
||||||
|
|
||||||
interface IHeader {
|
interface IHeader {
|
||||||
|
@ -90,14 +91,14 @@ const HeaderFooter = React.memo(({ onReaction, theme }: THeaderFooter) => (
|
||||||
));
|
));
|
||||||
|
|
||||||
const Header = React.memo(({ handleReaction, server, message, isMasterDetail, theme }: IHeader) => {
|
const Header = React.memo(({ handleReaction, server, message, isMasterDetail, theme }: IHeader) => {
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState<(TFrequentlyUsedEmojiModel | string)[]>([]);
|
||||||
const { width, height }: any = useDimensions();
|
const { width, height }: any = useDimensions();
|
||||||
|
|
||||||
const setEmojis = async () => {
|
const setEmojis = async () => {
|
||||||
try {
|
try {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const freqEmojiCollection = db.get('frequently_used_emojis');
|
const freqEmojiCollection = db.get('frequently_used_emojis');
|
||||||
let freqEmojis = await freqEmojiCollection.query().fetch();
|
let freqEmojis: (TFrequentlyUsedEmojiModel | string)[] = await freqEmojiCollection.query().fetch();
|
||||||
|
|
||||||
const isLandscape = width > height;
|
const isLandscape = width > height;
|
||||||
const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2;
|
const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2;
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { showConfirmationAlert } from '../../utils/info';
|
||||||
import { useActionSheet } from '../ActionSheet';
|
import { useActionSheet } from '../ActionSheet';
|
||||||
import Header, { HEADER_HEIGHT } from './Header';
|
import Header, { HEADER_HEIGHT } from './Header';
|
||||||
import events from '../../utils/log/events';
|
import events from '../../utils/log/events';
|
||||||
|
import { TMessageModel } from '../../definitions/IMessage';
|
||||||
|
|
||||||
interface IMessageActions {
|
interface IMessageActions {
|
||||||
room: {
|
room: {
|
||||||
|
@ -182,9 +183,9 @@ const MessageActions = React.memo(
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const subCollection = db.get('subscriptions');
|
const subCollection = db.get('subscriptions');
|
||||||
const subRecord = await subCollection.find(rid);
|
const subRecord = await subCollection.find(rid);
|
||||||
await db.action(async () => {
|
await db.write(async () => {
|
||||||
try {
|
try {
|
||||||
await subRecord.update((sub: any) => (sub.lastOpen = ts));
|
await subRecord.update(sub => (sub.lastOpen = ts));
|
||||||
} catch {
|
} catch {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
@ -269,11 +270,11 @@ const MessageActions = React.memo(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleTranslation = async (message: any) => {
|
const handleToggleTranslation = async (message: TMessageModel) => {
|
||||||
try {
|
try {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
await db.action(async () => {
|
await db.write(async () => {
|
||||||
await message.update((m: any) => {
|
await message.update(m => {
|
||||||
m.autoTranslate = !m.autoTranslate;
|
m.autoTranslate = !m.autoTranslate;
|
||||||
m._updatedAt = new Date();
|
m._updatedAt = new Date();
|
||||||
});
|
});
|
||||||
|
@ -320,7 +321,7 @@ const MessageActions = React.memo(
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOptions = (message: any) => {
|
const getOptions = (message: TMessageModel) => {
|
||||||
let options: any = [];
|
let options: any = [];
|
||||||
|
|
||||||
// Reply
|
// Reply
|
||||||
|
@ -446,7 +447,7 @@ const MessageActions = React.memo(
|
||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
const showMessageActions = async (message: any) => {
|
const showMessageActions = async (message: TMessageModel) => {
|
||||||
logEvent(events.ROOM_SHOW_MSG_ACTIONS);
|
logEvent(events.ROOM_SHOW_MSG_ACTIONS);
|
||||||
await getPermissions();
|
await getPermissions();
|
||||||
showActionSheet({
|
showActionSheet({
|
||||||
|
|
|
@ -36,7 +36,7 @@ const MessageErrorActions = forwardRef(({ tmid }: any, ref): any => {
|
||||||
try {
|
try {
|
||||||
// Find the thread header and update it
|
// Find the thread header and update it
|
||||||
const msg = await msgCollection.find(tmid);
|
const msg = await msgCollection.find(tmid);
|
||||||
if (msg.tcount <= 1) {
|
if (msg?.tcount && msg.tcount <= 1) {
|
||||||
deleteBatch.push(
|
deleteBatch.push(
|
||||||
msg.prepareUpdate((m: any) => {
|
msg.prepareUpdate((m: any) => {
|
||||||
m.tcount = null;
|
m.tcount = null;
|
||||||
|
@ -62,7 +62,7 @@ const MessageErrorActions = forwardRef(({ tmid }: any, ref): any => {
|
||||||
// Do nothing: message not found
|
// Do nothing: message not found
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await db.action(async () => {
|
await db.write(async () => {
|
||||||
await db.batch(...deleteBatch);
|
await db.batch(...deleteBatch);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
|
||||||
import { withTheme } from '../theme';
|
import I18n from '../i18n';
|
||||||
|
import { useTheme } from '../theme';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import { themes } from '../constants/colors';
|
import { themes } from '../constants/colors';
|
||||||
import TextInput from '../presentation/TextInput';
|
import TextInput from '../presentation/TextInput';
|
||||||
|
@ -19,14 +20,13 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
interface ISearchHeader {
|
interface ISearchHeaderProps {
|
||||||
theme?: string;
|
|
||||||
onSearchChangeText?: (text: string) => void;
|
onSearchChangeText?: (text: string) => void;
|
||||||
|
testID: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: it might be useful to refactor this component for reusage
|
const SearchHeader = ({ onSearchChangeText, testID }: ISearchHeaderProps): JSX.Element => {
|
||||||
const SearchHeader = ({ theme, onSearchChangeText }: ISearchHeader) => {
|
const { theme } = useTheme();
|
||||||
const titleColorStyle = { color: themes[theme!].headerTitleColor };
|
|
||||||
const isLight = theme === 'light';
|
const isLight = theme === 'light';
|
||||||
const { isLandscape } = useOrientation();
|
const { isLandscape } = useOrientation();
|
||||||
const scale = isIOS && isLandscape && !isTablet ? 0.8 : 1;
|
const scale = isIOS && isLandscape && !isTablet ? 0.8 : 1;
|
||||||
|
@ -36,14 +36,14 @@ const SearchHeader = ({ theme, onSearchChangeText }: ISearchHeader) => {
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<TextInput
|
<TextInput
|
||||||
autoFocus
|
autoFocus
|
||||||
style={[styles.title, isLight && titleColorStyle, { fontSize: titleFontSize }]}
|
style={[styles.title, isLight && { color: themes[theme].headerTitleColor }, { fontSize: titleFontSize }]}
|
||||||
placeholder='Search'
|
placeholder={I18n.t('Search')}
|
||||||
onChangeText={onSearchChangeText}
|
onChangeText={onSearchChangeText}
|
||||||
theme={theme!}
|
theme={theme}
|
||||||
testID='thread-messages-view-search-header'
|
testID={testID}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTheme(SearchHeader);
|
export default SearchHeader;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Touchable from 'react-native-platform-touchable';
|
||||||
import { CustomIcon } from '../lib/Icons';
|
import { CustomIcon } from '../lib/Icons';
|
||||||
import { themes } from '../constants/colors';
|
import { themes } from '../constants/colors';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import { withTheme } from '../theme';
|
import { useTheme } from '../theme';
|
||||||
import { TThreadModel } from '../definitions/IThread';
|
import { TThreadModel } from '../definitions/IThread';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
@ -48,12 +48,12 @@ interface IThreadDetails {
|
||||||
badgeColor?: string;
|
badgeColor?: string;
|
||||||
toggleFollowThread: Function;
|
toggleFollowThread: Function;
|
||||||
style: ViewStyle;
|
style: ViewStyle;
|
||||||
theme?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, theme }: IThreadDetails) => {
|
const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style }: IThreadDetails): JSX.Element => {
|
||||||
|
const { theme } = useTheme();
|
||||||
let { tcount } = item;
|
let { tcount } = item;
|
||||||
if (tcount! >= 1000) {
|
if (tcount && tcount >= 1000) {
|
||||||
tcount = '+999';
|
tcount = '+999';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,6 @@ const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, them
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.badgeContainer}>
|
<View style={styles.badgeContainer}>
|
||||||
{badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null}
|
{badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null}
|
||||||
<Touchable onPress={() => toggleFollowThread?.(isFollowing, item.id)}>
|
<Touchable onPress={() => toggleFollowThread?.(isFollowing, item.id)}>
|
||||||
|
@ -96,4 +95,4 @@ const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, them
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTheme(ThreadDetails);
|
export default ThreadDetails;
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { isValidURL } from '../../utils/url';
|
||||||
import NewMarkdown from './new';
|
import NewMarkdown from './new';
|
||||||
|
|
||||||
interface IMarkdownProps {
|
interface IMarkdownProps {
|
||||||
msg: string;
|
msg?: string;
|
||||||
md: MarkdownAST;
|
md: MarkdownAST;
|
||||||
mentions: UserMention[];
|
mentions: UserMention[];
|
||||||
getCustomEmoji: Function;
|
getCustomEmoji: Function;
|
||||||
|
|
|
@ -182,7 +182,7 @@ const Fields = React.memo(
|
||||||
<Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text>
|
<Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text>
|
||||||
{/* @ts-ignore*/}
|
{/* @ts-ignore*/}
|
||||||
<Markdown
|
<Markdown
|
||||||
msg={field.value!}
|
msg={field?.value || ''}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
username={user.username}
|
username={user.username}
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
|
|
|
@ -24,7 +24,6 @@ const Thread = React.memo(
|
||||||
item={{
|
item={{
|
||||||
tcount,
|
tcount,
|
||||||
replies,
|
replies,
|
||||||
tlm,
|
|
||||||
id
|
id
|
||||||
}}
|
}}
|
||||||
user={user}
|
user={user}
|
||||||
|
|
|
@ -147,6 +147,12 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
||||||
if ((item.tlm || item.tmid) && !isThreadRoom) {
|
if ((item.tlm || item.tmid) && !isThreadRoom) {
|
||||||
this.onThreadPress();
|
this.onThreadPress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { onDiscussionPress } = this.props;
|
||||||
|
|
||||||
|
if (onDiscussionPress) {
|
||||||
|
onDiscussionPress(item);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
300,
|
300,
|
||||||
true
|
true
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { MarkdownAST } from '@rocket.chat/message-parser';
|
import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
|
export type TMessageType = 'discussion-created' | 'jitsi_call_started';
|
||||||
|
|
||||||
export interface IMessageAttachments {
|
export interface IMessageAttachments {
|
||||||
attachments: any;
|
attachments: any;
|
||||||
timeFormat: string;
|
timeFormat: string;
|
||||||
|
@ -101,7 +103,7 @@ export interface IMessageThread {
|
||||||
msg: string;
|
msg: string;
|
||||||
tcount: number;
|
tcount: number;
|
||||||
theme: string;
|
theme: string;
|
||||||
tlm: string;
|
tlm: Date;
|
||||||
isThreadRoom: boolean;
|
isThreadRoom: boolean;
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
@ -140,7 +142,7 @@ export interface IMessageInner
|
||||||
IMessageThread,
|
IMessageThread,
|
||||||
IMessageAttachments,
|
IMessageAttachments,
|
||||||
IMessageBroadcast {
|
IMessageBroadcast {
|
||||||
type: string;
|
type: TMessageType;
|
||||||
blocks: [];
|
blocks: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { TMessageModel } from '../../definitions/IMessage';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { DISCUSSION } from './constants';
|
import { DISCUSSION } from './constants';
|
||||||
|
|
||||||
|
@ -149,7 +150,7 @@ export const getInfoMessage = ({ type, role, msg, author }: TInfoMessage) => {
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMessageTranslation = (message: { translations: any }, autoTranslateLanguage: string) => {
|
export const getMessageTranslation = (message: TMessageModel, autoTranslateLanguage: string) => {
|
||||||
if (!autoTranslateLanguage) {
|
if (!autoTranslateLanguage) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,4 +7,4 @@ export interface IFrequentlyUsedEmoji {
|
||||||
count: number;
|
count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TFrequentlyUsedEmoji = IFrequentlyUsedEmoji & Model;
|
export type TFrequentlyUsedEmojiModel = IFrequentlyUsedEmoji & Model;
|
||||||
|
|
|
@ -15,4 +15,4 @@ export interface ILoggedUser {
|
||||||
enableMessageParserEarlyAdoption?: boolean;
|
enableMessageParserEarlyAdoption?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TLoggedUser = ILoggedUser & Model;
|
export type TLoggedUserModel = ILoggedUser & Model;
|
||||||
|
|
|
@ -7,4 +7,4 @@ export interface IServerHistory {
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TServerHistory = IServerHistory & Model;
|
export type TServerHistoryModel = IServerHistory & Model;
|
||||||
|
|
|
@ -79,8 +79,6 @@ export interface ISubscription {
|
||||||
avatarETag?: string;
|
avatarETag?: string;
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
teamMain?: boolean;
|
teamMain?: boolean;
|
||||||
search?: boolean;
|
|
||||||
username?: string;
|
|
||||||
// https://nozbe.github.io/WatermelonDB/Relation.html#relation-api
|
// https://nozbe.github.io/WatermelonDB/Relation.html#relation-api
|
||||||
messages: Relation<TMessageModel>;
|
messages: Relation<TMessageModel>;
|
||||||
threads: Relation<TThreadModel>;
|
threads: Relation<TThreadModel>;
|
||||||
|
|
|
@ -43,7 +43,7 @@ export interface IThread {
|
||||||
id: string;
|
id: string;
|
||||||
msg?: string;
|
msg?: string;
|
||||||
t?: SubscriptionType;
|
t?: SubscriptionType;
|
||||||
rid?: string;
|
rid: string;
|
||||||
_updatedAt?: Date;
|
_updatedAt?: Date;
|
||||||
ts?: Date;
|
ts?: Date;
|
||||||
u?: IUserMessage;
|
u?: IUserMessage;
|
||||||
|
@ -61,10 +61,10 @@ export interface IThread {
|
||||||
reactions?: IReaction[];
|
reactions?: IReaction[];
|
||||||
role?: string;
|
role?: string;
|
||||||
drid?: string;
|
drid?: string;
|
||||||
dcount?: number;
|
dcount?: number | string;
|
||||||
dlm?: number;
|
dlm?: number;
|
||||||
tmid?: string;
|
tmid?: string;
|
||||||
tcount: number | string;
|
tcount?: number | string;
|
||||||
tlm?: string;
|
tlm?: string;
|
||||||
replies?: string[];
|
replies?: string[];
|
||||||
mentions?: IUserMention[];
|
mentions?: IUserMention[];
|
||||||
|
|
|
@ -3,12 +3,23 @@ import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
|
|
||||||
export * from './IAttachment';
|
export * from './IAttachment';
|
||||||
export * from './IMessage';
|
|
||||||
export * from './INotification';
|
export * from './INotification';
|
||||||
export * from './IRoom';
|
|
||||||
export * from './IServer';
|
|
||||||
export * from './ISubscription';
|
|
||||||
export * from './IPreferences';
|
export * from './IPreferences';
|
||||||
|
export * from './ISubscription';
|
||||||
|
export * from './IRoom';
|
||||||
|
export * from './IMessage';
|
||||||
|
export * from './IThread';
|
||||||
|
export * from './IThreadMessage';
|
||||||
|
export * from './ICustomEmoji';
|
||||||
|
export * from './IFrequentlyUsedEmoji';
|
||||||
|
export * from './IUpload';
|
||||||
|
export * from './ISettings';
|
||||||
|
export * from './IRole';
|
||||||
|
export * from './IPermission';
|
||||||
|
export * from './ISlashCommand';
|
||||||
|
export * from './IUser';
|
||||||
|
export * from './IServer';
|
||||||
|
export * from './ILoggedUser';
|
||||||
export * from './IServerHistory';
|
export * from './IServerHistory';
|
||||||
|
|
||||||
export interface IBaseScreen<T extends Record<string, object | undefined>, S extends string> {
|
export interface IBaseScreen<T extends Record<string, object | undefined>, S extends string> {
|
||||||
|
|
|
@ -775,6 +775,7 @@
|
||||||
"creating_discussion": "creating discussion",
|
"creating_discussion": "creating discussion",
|
||||||
"Canned_Responses": "Canned Responses",
|
"Canned_Responses": "Canned Responses",
|
||||||
"No_match_found": "No match found.",
|
"No_match_found": "No match found.",
|
||||||
|
"No_discussions": "No discussions",
|
||||||
"Check_canned_responses": "Check on canned responses.",
|
"Check_canned_responses": "Check on canned responses.",
|
||||||
"Searching": "Searching",
|
"Searching": "Searching",
|
||||||
"Use": "Use",
|
"Use": "Use",
|
||||||
|
|
|
@ -25,6 +25,7 @@ import serversSchema from './schema/servers';
|
||||||
import appSchema from './schema/app';
|
import appSchema from './schema/app';
|
||||||
import migrations from './model/migrations';
|
import migrations from './model/migrations';
|
||||||
import serversMigrations from './model/servers/migrations';
|
import serversMigrations from './model/servers/migrations';
|
||||||
|
import { TAppDatabase, TServerDatabase } from './interfaces';
|
||||||
|
|
||||||
const appGroupPath = isIOS ? appGroup.path : '';
|
const appGroupPath = isIOS ? appGroup.path : '';
|
||||||
|
|
||||||
|
@ -32,9 +33,9 @@ if (__DEV__ && isIOS) {
|
||||||
console.log(appGroupPath);
|
console.log(appGroupPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDatabasePath = name => `${appGroupPath}${name}${isOfficial ? '' : '-experimental'}.db`;
|
const getDatabasePath = (name: string) => `${appGroupPath}${name}${isOfficial ? '' : '-experimental'}.db`;
|
||||||
|
|
||||||
export const getDatabase = (database = '') => {
|
export const getDatabase = (database = ''): Database => {
|
||||||
const path = database.replace(/(^\w+:|^)\/\//, '').replace(/\//g, '.');
|
const path = database.replace(/(^\w+:|^)\/\//, '').replace(/\//g, '.');
|
||||||
const dbName = getDatabasePath(path);
|
const dbName = getDatabasePath(path);
|
||||||
|
|
||||||
|
@ -64,8 +65,14 @@ export const getDatabase = (database = '') => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface IDatabases {
|
||||||
|
shareDB?: TAppDatabase;
|
||||||
|
serversDB: TServerDatabase;
|
||||||
|
activeDB?: TAppDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
class DB {
|
class DB {
|
||||||
databases = {
|
databases: IDatabases = {
|
||||||
serversDB: new Database({
|
serversDB: new Database({
|
||||||
adapter: new SQLiteAdapter({
|
adapter: new SQLiteAdapter({
|
||||||
dbName: getDatabasePath('default'),
|
dbName: getDatabasePath('default'),
|
||||||
|
@ -73,11 +80,12 @@ class DB {
|
||||||
migrations: serversMigrations
|
migrations: serversMigrations
|
||||||
}),
|
}),
|
||||||
modelClasses: [Server, LoggedUser, ServersHistory]
|
modelClasses: [Server, LoggedUser, ServersHistory]
|
||||||
})
|
}) as TServerDatabase
|
||||||
};
|
};
|
||||||
|
|
||||||
get active() {
|
// Expected at least one database
|
||||||
return this.databases.shareDB || this.databases.activeDB;
|
get active(): TAppDatabase {
|
||||||
|
return this.databases.shareDB || this.databases.activeDB!;
|
||||||
}
|
}
|
||||||
|
|
||||||
get share() {
|
get share() {
|
||||||
|
@ -116,11 +124,11 @@ class DB {
|
||||||
Setting,
|
Setting,
|
||||||
User
|
User
|
||||||
]
|
]
|
||||||
});
|
}) as TAppDatabase;
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveDB(database) {
|
setActiveDB(database: string) {
|
||||||
this.databases.activeDB = getDatabase(database);
|
this.databases.activeDB = getDatabase(database) as TAppDatabase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { Database, Collection } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
|
import * as models from './model';
|
||||||
|
import * as definitions from '../../definitions';
|
||||||
|
|
||||||
|
export type TAppDatabaseNames =
|
||||||
|
| typeof models.SUBSCRIPTIONS_TABLE
|
||||||
|
| typeof models.ROOMS_TABLE
|
||||||
|
| typeof models.MESSAGES_TABLE
|
||||||
|
| typeof models.THREADS_TABLE
|
||||||
|
| typeof models.THREAD_MESSAGES_TABLE
|
||||||
|
| typeof models.CUSTOM_EMOJIS_TABLE
|
||||||
|
| typeof models.FREQUENTLY_USED_EMOJIS_TABLE
|
||||||
|
| typeof models.UPLOADS_TABLE
|
||||||
|
| typeof models.SETTINGS_TABLE
|
||||||
|
| typeof models.ROLES_TABLE
|
||||||
|
| typeof models.PERMISSIONS_TABLE
|
||||||
|
| typeof models.SLASH_COMMANDS_TABLE
|
||||||
|
| typeof models.USERS_TABLE;
|
||||||
|
|
||||||
|
// Verify if T extends one type from TAppDatabaseNames, and if is truly,
|
||||||
|
// returns the specific model type.
|
||||||
|
// https://stackoverflow.com/a/54166010 TypeScript function return type based on input parameter
|
||||||
|
type ObjectType<T> = T extends typeof models.SUBSCRIPTIONS_TABLE
|
||||||
|
? definitions.TSubscriptionModel
|
||||||
|
: T extends typeof models.ROOMS_TABLE
|
||||||
|
? definitions.TRoomModel
|
||||||
|
: T extends typeof models.MESSAGES_TABLE
|
||||||
|
? definitions.TMessageModel
|
||||||
|
: T extends typeof models.THREADS_TABLE
|
||||||
|
? definitions.TThreadModel
|
||||||
|
: T extends typeof models.THREAD_MESSAGES_TABLE
|
||||||
|
? definitions.TThreadMessageModel
|
||||||
|
: T extends typeof models.CUSTOM_EMOJIS_TABLE
|
||||||
|
? definitions.TCustomEmojiModel
|
||||||
|
: T extends typeof models.FREQUENTLY_USED_EMOJIS_TABLE
|
||||||
|
? definitions.TFrequentlyUsedEmojiModel
|
||||||
|
: T extends typeof models.UPLOADS_TABLE
|
||||||
|
? definitions.TUploadModel
|
||||||
|
: T extends typeof models.SETTINGS_TABLE
|
||||||
|
? definitions.TSettingsModel
|
||||||
|
: T extends typeof models.ROLES_TABLE
|
||||||
|
? definitions.TRoleModel
|
||||||
|
: T extends typeof models.PERMISSIONS_TABLE
|
||||||
|
? definitions.TPermissionModel
|
||||||
|
: T extends typeof models.SLASH_COMMANDS_TABLE
|
||||||
|
? definitions.TSlashCommandModel
|
||||||
|
: T extends typeof models.USERS_TABLE
|
||||||
|
? definitions.TUserModel
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type TAppDatabase = {
|
||||||
|
get: <T extends TAppDatabaseNames>(db: T) => Collection<ObjectType<T>>;
|
||||||
|
} & Database;
|
||||||
|
|
||||||
|
// Migration to server database
|
||||||
|
export type TServerDatabaseNames =
|
||||||
|
| typeof models.SERVERS_TABLE
|
||||||
|
| typeof models.LOGGED_USERS_TABLE
|
||||||
|
| typeof models.SERVERS_HISTORY_TABLE;
|
||||||
|
|
||||||
|
type ObjectServerType<T> = T extends typeof models.SERVERS_TABLE
|
||||||
|
? definitions.TServerModel
|
||||||
|
: T extends typeof models.LOGGED_USERS_TABLE
|
||||||
|
? definitions.TLoggedUserModel
|
||||||
|
: T extends typeof models.SERVERS_HISTORY_TABLE
|
||||||
|
? definitions.TServerHistoryModel
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type TServerDatabase = {
|
||||||
|
get: <T extends TServerDatabaseNames>(db: T) => Collection<ObjectServerType<T>>;
|
||||||
|
} & Database;
|
|
@ -3,8 +3,10 @@ import { date, field, json } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
import { sanitizer } from '../utils';
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
|
export const CUSTOM_EMOJIS_TABLE = 'custom_emojis';
|
||||||
|
|
||||||
export default class CustomEmoji extends Model {
|
export default class CustomEmoji extends Model {
|
||||||
static table = 'custom_emojis';
|
static table = CUSTOM_EMOJIS_TABLE;
|
||||||
|
|
||||||
@field('name') name;
|
@field('name') name;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { Model } from '@nozbe/watermelondb';
|
import { Model } from '@nozbe/watermelondb';
|
||||||
import { field } from '@nozbe/watermelondb/decorators';
|
import { field } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
export const FREQUENTLY_USED_EMOJIS_TABLE = 'frequently_used_emojis';
|
||||||
export default class FrequentlyUsedEmoji extends Model {
|
export default class FrequentlyUsedEmoji extends Model {
|
||||||
static table = 'frequently_used_emojis';
|
static table = FREQUENTLY_USED_EMOJIS_TABLE;
|
||||||
|
|
||||||
@field('content') content;
|
@field('content') content;
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@ import { date, field, json, relation } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
import { sanitizer } from '../utils';
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
export const TABLE_NAME = 'messages';
|
export const MESSAGES_TABLE = 'messages';
|
||||||
|
|
||||||
export default class Message extends Model {
|
export default class Message extends Model {
|
||||||
static table = TABLE_NAME;
|
static table = MESSAGES_TABLE;
|
||||||
|
|
||||||
static associations = {
|
static associations = {
|
||||||
subscriptions: { type: 'belongs_to', key: 'rid' }
|
subscriptions: { type: 'belongs_to', key: 'rid' }
|
||||||
|
|
|
@ -3,8 +3,10 @@ import { date, json } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
import { sanitizer } from '../utils';
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
|
export const PERMISSIONS_TABLE = 'permissions';
|
||||||
|
|
||||||
export default class Permission extends Model {
|
export default class Permission extends Model {
|
||||||
static table = 'permissions';
|
static table = PERMISSIONS_TABLE;
|
||||||
|
|
||||||
@json('roles', sanitizer) roles;
|
@json('roles', sanitizer) roles;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { Model } from '@nozbe/watermelondb';
|
import { Model } from '@nozbe/watermelondb';
|
||||||
import { field } from '@nozbe/watermelondb/decorators';
|
import { field } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
export const ROLES_TABLE = 'roles';
|
||||||
|
|
||||||
export default class Role extends Model {
|
export default class Role extends Model {
|
||||||
static table = 'roles';
|
static table = ROLES_TABLE;
|
||||||
|
|
||||||
@field('description') description;
|
@field('description') description;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,10 @@ import { field, json } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
import { sanitizer } from '../utils';
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
|
export const ROOMS_TABLE = 'rooms';
|
||||||
|
|
||||||
export default class Room extends Model {
|
export default class Room extends Model {
|
||||||
static table = 'rooms';
|
static table = ROOMS_TABLE;
|
||||||
|
|
||||||
@json('custom_fields', sanitizer) customFields;
|
@json('custom_fields', sanitizer) customFields;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { Model } from '@nozbe/watermelondb';
|
import { Model } from '@nozbe/watermelondb';
|
||||||
import { date, field, readonly } from '@nozbe/watermelondb/decorators';
|
import { date, field, readonly } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
export const SERVERS_HISTORY_TABLE = 'servers_history';
|
||||||
|
|
||||||
export default class ServersHistory extends Model {
|
export default class ServersHistory extends Model {
|
||||||
static table = 'servers_history';
|
static table = SERVERS_HISTORY_TABLE;
|
||||||
|
|
||||||
@field('url') url;
|
@field('url') url;
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,10 @@ import { date, field, json } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
import { sanitizer } from '../utils';
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
|
export const SETTINGS_TABLE = 'settings';
|
||||||
|
|
||||||
export default class Setting extends Model {
|
export default class Setting extends Model {
|
||||||
static table = 'settings';
|
static table = SETTINGS_TABLE;
|
||||||
|
|
||||||
@field('value_as_string') valueAsString;
|
@field('value_as_string') valueAsString;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { Model } from '@nozbe/watermelondb';
|
import { Model } from '@nozbe/watermelondb';
|
||||||
import { field } from '@nozbe/watermelondb/decorators';
|
import { field } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
export const SLASH_COMMANDS_TABLE = 'slash_commands';
|
||||||
|
|
||||||
export default class SlashCommand extends Model {
|
export default class SlashCommand extends Model {
|
||||||
static table = 'slash_commands';
|
static table = SLASH_COMMANDS_TABLE;
|
||||||
|
|
||||||
@field('params') params;
|
@field('params') params;
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@ import { children, date, field, json } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
import { sanitizer } from '../utils';
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
export const TABLE_NAME = 'subscriptions';
|
export const SUBSCRIPTIONS_TABLE = 'subscriptions';
|
||||||
|
|
||||||
export default class Subscription extends Model {
|
export default class Subscription extends Model {
|
||||||
static table = TABLE_NAME;
|
static table = SUBSCRIPTIONS_TABLE;
|
||||||
|
|
||||||
static associations = {
|
static associations = {
|
||||||
messages: { type: 'has_many', foreignKey: 'rid' },
|
messages: { type: 'has_many', foreignKey: 'rid' },
|
||||||
|
|
|
@ -3,10 +3,10 @@ import { date, field, json, relation } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
import { sanitizer } from '../utils';
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
export const TABLE_NAME = 'threads';
|
export const THREADS_TABLE = 'threads';
|
||||||
|
|
||||||
export default class Thread extends Model {
|
export default class Thread extends Model {
|
||||||
static table = TABLE_NAME;
|
static table = THREADS_TABLE;
|
||||||
|
|
||||||
static associations = {
|
static associations = {
|
||||||
subscriptions: { type: 'belongs_to', key: 'rid' }
|
subscriptions: { type: 'belongs_to', key: 'rid' }
|
||||||
|
|
|
@ -3,10 +3,10 @@ import { date, field, json, relation } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
import { sanitizer } from '../utils';
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
export const TABLE_NAME = 'thread_messages';
|
export const THREAD_MESSAGES_TABLE = 'thread_messages';
|
||||||
|
|
||||||
export default class ThreadMessage extends Model {
|
export default class ThreadMessage extends Model {
|
||||||
static table = TABLE_NAME;
|
static table = THREAD_MESSAGES_TABLE;
|
||||||
|
|
||||||
static associations = {
|
static associations = {
|
||||||
subscriptions: { type: 'belongs_to', key: 'subscription_id' }
|
subscriptions: { type: 'belongs_to', key: 'subscription_id' }
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { Model } from '@nozbe/watermelondb';
|
import { Model } from '@nozbe/watermelondb';
|
||||||
import { field, relation } from '@nozbe/watermelondb/decorators';
|
import { field, relation } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
export const UPLOADS_TABLE = 'uploads';
|
||||||
|
|
||||||
export default class Upload extends Model {
|
export default class Upload extends Model {
|
||||||
static table = 'uploads';
|
static table = UPLOADS_TABLE;
|
||||||
|
|
||||||
static associations = {
|
static associations = {
|
||||||
subscriptions: { type: 'belongs_to', key: 'rid' }
|
subscriptions: { type: 'belongs_to', key: 'rid' }
|
||||||
|
|
|
@ -3,8 +3,10 @@ import { field, json } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
import { sanitizer } from '../utils';
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
|
export const USERS_TABLE = 'users';
|
||||||
|
|
||||||
export default class User extends Model {
|
export default class User extends Model {
|
||||||
static table = 'users';
|
static table = USERS_TABLE;
|
||||||
|
|
||||||
@field('_id') _id;
|
@field('_id') _id;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
export * from './CustomEmoji';
|
||||||
|
export * from './FrequentlyUsedEmoji';
|
||||||
|
export * from './Message';
|
||||||
|
export * from './Permission';
|
||||||
|
export * from './Role';
|
||||||
|
export * from './Room';
|
||||||
|
export * from './Setting';
|
||||||
|
export * from './SlashCommand';
|
||||||
|
export * from './Subscription';
|
||||||
|
export * from './Thread';
|
||||||
|
export * from './ThreadMessage';
|
||||||
|
export * from './Upload';
|
||||||
|
export * from './User';
|
||||||
|
export * from './ServersHistory';
|
||||||
|
export * from './servers/Server';
|
||||||
|
export * from './servers/User';
|
|
@ -1,8 +1,10 @@
|
||||||
import { Model } from '@nozbe/watermelondb';
|
import { Model } from '@nozbe/watermelondb';
|
||||||
import { date, field } from '@nozbe/watermelondb/decorators';
|
import { date, field } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
export const SERVERS_TABLE = 'servers';
|
||||||
|
|
||||||
export default class Server extends Model {
|
export default class Server extends Model {
|
||||||
static table = 'servers';
|
static table = SERVERS_TABLE;
|
||||||
|
|
||||||
@field('name') name;
|
@field('name') name;
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,10 @@ import { field, json } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
import { sanitizer } from '../../utils';
|
import { sanitizer } from '../../utils';
|
||||||
|
|
||||||
|
export const LOGGED_USERS_TABLE = 'users';
|
||||||
|
|
||||||
export default class User extends Model {
|
export default class User extends Model {
|
||||||
static table = 'users';
|
static table = LOGGED_USERS_TABLE;
|
||||||
|
|
||||||
@field('token') token;
|
@field('token') token;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import database from '..';
|
import database from '..';
|
||||||
import { TABLE_NAME } from '../model/Message';
|
import { MESSAGES_TABLE } from '../model/Message';
|
||||||
|
|
||||||
const getCollection = db => db.get(TABLE_NAME);
|
const getCollection = db => db.get(MESSAGES_TABLE);
|
||||||
|
|
||||||
export const getMessageById = async messageId => {
|
export const getMessageById = async messageId => {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import database from '..';
|
import database from '..';
|
||||||
import { TABLE_NAME } from '../model/Subscription';
|
import { SUBSCRIPTIONS_TABLE } from '../model/Subscription';
|
||||||
|
|
||||||
const getCollection = db => db.get(TABLE_NAME);
|
const getCollection = db => db.get(SUBSCRIPTIONS_TABLE);
|
||||||
|
|
||||||
export const getSubscriptionByRoomId = async rid => {
|
export const getSubscriptionByRoomId = async rid => {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import database from '..';
|
import database from '..';
|
||||||
import { TABLE_NAME } from '../model/Thread';
|
import { THREADS_TABLE } from '../model/Thread';
|
||||||
|
|
||||||
const getCollection = db => db.get(TABLE_NAME);
|
const getCollection = db => db.get(THREADS_TABLE);
|
||||||
|
|
||||||
export const getThreadById = async tmid => {
|
export const getThreadById = async tmid => {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import database from '..';
|
import database from '..';
|
||||||
import { TABLE_NAME } from '../model/ThreadMessage';
|
import { THREAD_MESSAGES_TABLE } from '../model/ThreadMessage';
|
||||||
|
|
||||||
const getCollection = db => db.get(TABLE_NAME);
|
const getCollection = db => db.get(THREAD_MESSAGES_TABLE);
|
||||||
|
|
||||||
export const getThreadMessageById = async messageId => {
|
export const getThreadMessageById = async messageId => {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
|
|
|
@ -61,7 +61,7 @@ const PERMISSIONS = [
|
||||||
|
|
||||||
export async function setPermissions() {
|
export async function setPermissions() {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const permissionsCollection = db.collections.get('permissions');
|
const permissionsCollection = db.get('permissions');
|
||||||
const allPermissions = await permissionsCollection.query(Q.where('id', Q.oneOf(PERMISSIONS))).fetch();
|
const allPermissions = await permissionsCollection.query(Q.where('id', Q.oneOf(PERMISSIONS))).fetch();
|
||||||
const parsed = allPermissions.reduce((acc, item) => ({ ...acc, [item.id]: item.roles }), {});
|
const parsed = allPermissions.reduce((acc, item) => ({ ...acc, [item.id]: item.roles }), {});
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import protectedFunction from './helpers/protectedFunction';
|
||||||
|
|
||||||
export async function setRoles() {
|
export async function setRoles() {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const rolesCollection = db.collections.get('roles');
|
const rolesCollection = db.get('roles');
|
||||||
const allRoles = await rolesCollection.query().fetch();
|
const allRoles = await rolesCollection.query().fetch();
|
||||||
const parsed = allRoles.reduce((acc, item) => ({ ...acc, [item.id]: item.description || item.id }), {});
|
const parsed = allRoles.reduce((acc, item) => ({ ...acc, [item.id]: item.description || item.id }), {});
|
||||||
reduxStore.dispatch(setRolesAction(parsed));
|
reduxStore.dispatch(setRolesAction(parsed));
|
||||||
|
|
|
@ -811,6 +811,16 @@ const RocketChat = {
|
||||||
encrypted
|
encrypted
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
getDiscussions({ roomId, offset, count, text }) {
|
||||||
|
const params = {
|
||||||
|
roomId,
|
||||||
|
offset,
|
||||||
|
count,
|
||||||
|
...(text && { text })
|
||||||
|
};
|
||||||
|
// RC 2.4.0
|
||||||
|
return this.sdk.get('chat.getDiscussions', params);
|
||||||
|
},
|
||||||
createTeam({ name, users, type, readOnly, broadcast, encrypted }) {
|
createTeam({ name, users, type, readOnly, broadcast, encrypted }) {
|
||||||
const params = {
|
const params = {
|
||||||
name,
|
name,
|
||||||
|
|
|
@ -66,6 +66,7 @@ import QueueListView from '../ee/omnichannel/views/QueueListView';
|
||||||
import AddChannelTeamView from '../views/AddChannelTeamView';
|
import AddChannelTeamView from '../views/AddChannelTeamView';
|
||||||
import AddExistingChannelView from '../views/AddExistingChannelView';
|
import AddExistingChannelView from '../views/AddExistingChannelView';
|
||||||
import SelectListView from '../views/SelectListView';
|
import SelectListView from '../views/SelectListView';
|
||||||
|
import DiscussionsView from '../views/DiscussionsView';
|
||||||
import {
|
import {
|
||||||
AdminPanelStackParamList,
|
AdminPanelStackParamList,
|
||||||
ChatsStackParamList,
|
ChatsStackParamList,
|
||||||
|
@ -92,7 +93,8 @@ const ChatsStackNavigator = () => {
|
||||||
<ChatsStack.Screen name='SelectListView' component={SelectListView} options={SelectListView.navigationOptions} />
|
<ChatsStack.Screen name='SelectListView' component={SelectListView} options={SelectListView.navigationOptions} />
|
||||||
<ChatsStack.Screen name='RoomInfoView' component={RoomInfoView} options={RoomInfoView.navigationOptions} />
|
<ChatsStack.Screen name='RoomInfoView' component={RoomInfoView} options={RoomInfoView.navigationOptions} />
|
||||||
<ChatsStack.Screen name='RoomInfoEditView' component={RoomInfoEditView} options={RoomInfoEditView.navigationOptions} />
|
<ChatsStack.Screen name='RoomInfoEditView' component={RoomInfoEditView} options={RoomInfoEditView.navigationOptions} />
|
||||||
<ChatsStack.Screen name='RoomMembersView' component={RoomMembersView} />
|
<ChatsStack.Screen name='RoomMembersView' component={RoomMembersView} options={RoomMembersView.navigationOptions} />
|
||||||
|
<ChatsStack.Screen name='DiscussionsView' component={DiscussionsView} />
|
||||||
<ChatsStack.Screen
|
<ChatsStack.Screen
|
||||||
name='SearchMessagesView'
|
name='SearchMessagesView'
|
||||||
component={SearchMessagesView}
|
component={SearchMessagesView}
|
||||||
|
|
|
@ -58,6 +58,7 @@ import QueueListView from '../../ee/omnichannel/views/QueueListView';
|
||||||
import AddChannelTeamView from '../../views/AddChannelTeamView';
|
import AddChannelTeamView from '../../views/AddChannelTeamView';
|
||||||
import AddExistingChannelView from '../../views/AddExistingChannelView';
|
import AddExistingChannelView from '../../views/AddExistingChannelView';
|
||||||
import SelectListView from '../../views/SelectListView';
|
import SelectListView from '../../views/SelectListView';
|
||||||
|
import DiscussionsView from '../../views/DiscussionsView';
|
||||||
import { ModalContainer } from './ModalContainer';
|
import { ModalContainer } from './ModalContainer';
|
||||||
import {
|
import {
|
||||||
MasterDetailChatsStackParamList,
|
MasterDetailChatsStackParamList,
|
||||||
|
@ -167,6 +168,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
|
||||||
<ModalStack.Screen name='LivechatEditView' component={LivechatEditView} options={LivechatEditView.navigationOptions} />
|
<ModalStack.Screen name='LivechatEditView' component={LivechatEditView} options={LivechatEditView.navigationOptions} />
|
||||||
<ModalStack.Screen name='PickerView' component={PickerView} options={PickerView.navigationOptions} />
|
<ModalStack.Screen name='PickerView' component={PickerView} options={PickerView.navigationOptions} />
|
||||||
<ModalStack.Screen name='ThreadMessagesView' component={ThreadMessagesView} />
|
<ModalStack.Screen name='ThreadMessagesView' component={ThreadMessagesView} />
|
||||||
|
<ModalStack.Screen name='DiscussionsView' component={DiscussionsView} />
|
||||||
<ModalStack.Screen name='TeamChannelsView' component={TeamChannelsView} options={TeamChannelsView.navigationOptions} />
|
<ModalStack.Screen name='TeamChannelsView' component={TeamChannelsView} options={TeamChannelsView.navigationOptions} />
|
||||||
<ModalStack.Screen name='MarkdownTableView' component={MarkdownTableView} options={MarkdownTableView.navigationOptions} />
|
<ModalStack.Screen name='MarkdownTableView' component={MarkdownTableView} options={MarkdownTableView.navigationOptions} />
|
||||||
<ModalStack.Screen
|
<ModalStack.Screen
|
||||||
|
|
|
@ -56,6 +56,10 @@ export type ModalStackParamList = {
|
||||||
rid: string;
|
rid: string;
|
||||||
room: ISubscription;
|
room: ISubscription;
|
||||||
};
|
};
|
||||||
|
DiscussionsView: {
|
||||||
|
rid: string;
|
||||||
|
t: SubscriptionType;
|
||||||
|
};
|
||||||
SearchMessagesView: {
|
SearchMessagesView: {
|
||||||
rid: string;
|
rid: string;
|
||||||
t: SubscriptionType;
|
t: SubscriptionType;
|
||||||
|
|
|
@ -54,6 +54,10 @@ export type ChatsStackParamList = {
|
||||||
rid: string;
|
rid: string;
|
||||||
room: ISubscription;
|
room: ISubscription;
|
||||||
};
|
};
|
||||||
|
DiscussionsView: {
|
||||||
|
rid: string;
|
||||||
|
t: SubscriptionType;
|
||||||
|
};
|
||||||
SearchMessagesView: {
|
SearchMessagesView: {
|
||||||
rid: string;
|
rid: string;
|
||||||
t: SubscriptionType;
|
t: SubscriptionType;
|
||||||
|
|
|
@ -31,9 +31,8 @@ const navigate = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IItem extends Partial<ISubscription> {
|
interface IItem extends Partial<ISubscription> {
|
||||||
rid: string;
|
search?: boolean; // comes from spotlight
|
||||||
name: string;
|
username?: string;
|
||||||
t: SubscriptionType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const goRoom = async ({
|
export const goRoom = async ({
|
||||||
|
@ -46,7 +45,7 @@ export const goRoom = async ({
|
||||||
navigationMethod?: any;
|
navigationMethod?: any;
|
||||||
jumpToMessageId?: string;
|
jumpToMessageId?: string;
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
if (item.t === 'd' && item.search) {
|
if (item.t === SubscriptionType.DIRECT && item?.search) {
|
||||||
// if user is using the search we need first to join/create room
|
// if user is using the search we need first to join/create room
|
||||||
try {
|
try {
|
||||||
const { username } = item;
|
const { username } = item;
|
||||||
|
@ -55,7 +54,7 @@ export const goRoom = async ({
|
||||||
return navigate({
|
return navigate({
|
||||||
item: {
|
item: {
|
||||||
rid: result.room._id,
|
rid: result.room._id,
|
||||||
name: username!,
|
name: username,
|
||||||
t: SubscriptionType.DIRECT
|
t: SubscriptionType.DIRECT
|
||||||
},
|
},
|
||||||
isMasterDetail,
|
isMasterDetail,
|
||||||
|
|
|
@ -22,11 +22,11 @@ import { goRoom } from '../utils/goRoom';
|
||||||
import { showErrorAlert } from '../utils/info';
|
import { showErrorAlert } from '../utils/info';
|
||||||
import debounce from '../utils/debounce';
|
import debounce from '../utils/debounce';
|
||||||
import { ChatsStackParamList } from '../stacks/types';
|
import { ChatsStackParamList } from '../stacks/types';
|
||||||
import { IRoom } from '../definitions/IRoom';
|
import { TSubscriptionModel, SubscriptionType } from '../definitions';
|
||||||
|
|
||||||
interface IAddExistingChannelViewState {
|
interface IAddExistingChannelViewState {
|
||||||
search: Array<IRoom>;
|
search: TSubscriptionModel[];
|
||||||
channels: Array<IRoom>;
|
channels: TSubscriptionModel[];
|
||||||
selected: string[];
|
selected: string[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ class AddExistingChannelView extends React.Component<IAddExistingChannelViewProp
|
||||||
try {
|
try {
|
||||||
const { addTeamChannelPermission } = this.props;
|
const { addTeamChannelPermission } = this.props;
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const channels = await db.collections
|
const channels = await db
|
||||||
.get('subscriptions')
|
.get('subscriptions')
|
||||||
.query(
|
.query(
|
||||||
Q.where('team_id', ''),
|
Q.where('team_id', ''),
|
||||||
|
@ -94,9 +94,9 @@ class AddExistingChannelView extends React.Component<IAddExistingChannelViewProp
|
||||||
)
|
)
|
||||||
.fetch();
|
.fetch();
|
||||||
|
|
||||||
const asyncFilter = async (channelsArray: Array<IRoom>) => {
|
const asyncFilter = async (channelsArray: TSubscriptionModel[]) => {
|
||||||
const results = await Promise.all(
|
const results = await Promise.all(
|
||||||
channelsArray.map(async (channel: IRoom) => {
|
channelsArray.map(async channel => {
|
||||||
if (channel.prid) {
|
if (channel.prid) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -173,11 +173,10 @@ class AddExistingChannelView extends React.Component<IAddExistingChannelViewProp
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: refactor with Room Model
|
renderItem = ({ item }: { item: TSubscriptionModel }) => {
|
||||||
renderItem = ({ item }: { item: any }) => {
|
|
||||||
const isChecked = this.isChecked(item.rid);
|
const isChecked = this.isChecked(item.rid);
|
||||||
// TODO: reuse logic inside RoomTypeIcon
|
// TODO: reuse logic inside RoomTypeIcon
|
||||||
const icon = item.t === 'p' && !item.teamId ? 'channel-private' : 'channel-public';
|
const icon = item.t === SubscriptionType.DIRECT && !item?.teamId ? 'channel-private' : 'channel-public';
|
||||||
return (
|
return (
|
||||||
<List.Item
|
<List.Item
|
||||||
title={RocketChat.getRoomTitle(item)}
|
title={RocketChat.getRoomTitle(item)}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import RocketChat from '../../lib/rocketchat';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
|
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
import { TSubscriptionModel } from '../../definitions/ISubscription';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { ICreateDiscussionViewSelectChannel } from './interfaces';
|
import { ICreateDiscussionViewSelectChannel } from './interfaces';
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@ const SelectChannel = ({
|
||||||
serverVersion,
|
serverVersion,
|
||||||
theme
|
theme
|
||||||
}: ICreateDiscussionViewSelectChannel): JSX.Element => {
|
}: ICreateDiscussionViewSelectChannel): JSX.Element => {
|
||||||
const [channels, setChannels] = useState([]);
|
const [channels, setChannels] = useState<TSubscriptionModel[]>([]);
|
||||||
|
|
||||||
const getChannels = debounce(async (keyword = '') => {
|
const getChannels = debounce(async (keyword = '') => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -38,11 +38,11 @@ const SelectUsers = ({
|
||||||
const res = await RocketChat.search({ text: keyword, filterRooms: false });
|
const res = await RocketChat.search({ text: keyword, filterRooms: false });
|
||||||
let items = [
|
let items = [
|
||||||
...users.filter((u: IUser) => selected.includes(u.name)),
|
...users.filter((u: IUser) => selected.includes(u.name)),
|
||||||
...res.filter((r: IUser) => !users.find((u: IUser) => u.name === r.name))
|
...res.filter(r => !users.find((u: IUser) => u.name === r.name))
|
||||||
];
|
];
|
||||||
const records = await usersCollection.query(Q.where('username', Q.oneOf(items.map(u => u.name)))).fetch();
|
const records = await usersCollection.query(Q.where('username', Q.oneOf(items.map(u => u.name)))).fetch();
|
||||||
items = items.map(item => {
|
items = items.map(item => {
|
||||||
const index = records.findIndex((r: IUser) => r.username === item.name);
|
const index = records.findIndex(r => r.username === item.name);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
const record = records[index];
|
const record = records[index];
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
|
import { TThreadModel } from '../../definitions/IThread';
|
||||||
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
|
import { themes } from '../../constants/colors';
|
||||||
|
import sharedStyles from '../Styles';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
marginTop: 8,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
detailsContainer: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
detailContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: 8
|
||||||
|
},
|
||||||
|
detailText: {
|
||||||
|
fontSize: 10,
|
||||||
|
marginLeft: 2,
|
||||||
|
...sharedStyles.textSemibold
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IDiscussionDetails {
|
||||||
|
item: TThreadModel;
|
||||||
|
date: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DiscussionDetails = ({ item, date }: IDiscussionDetails): JSX.Element => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
let { dcount } = item;
|
||||||
|
|
||||||
|
if (dcount && dcount >= 1000) {
|
||||||
|
dcount = '+999';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.container]}>
|
||||||
|
<View style={styles.detailsContainer}>
|
||||||
|
<View style={styles.detailContainer}>
|
||||||
|
<CustomIcon name={'discussions'} size={24} color={themes[theme!].auxiliaryText} />
|
||||||
|
<Text style={[styles.detailText, { color: themes[theme!].auxiliaryText }]} numberOfLines={1}>
|
||||||
|
{dcount}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.detailContainer}>
|
||||||
|
<CustomIcon name={'clock'} size={24} color={themes[theme!].auxiliaryText} />
|
||||||
|
<Text style={[styles.detailText, { color: themes[theme!].auxiliaryText }]} numberOfLines={1}>
|
||||||
|
{date}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DiscussionDetails;
|
|
@ -0,0 +1,96 @@
|
||||||
|
/* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions, react/prop-types */
|
||||||
|
import React from 'react';
|
||||||
|
import { storiesOf } from '@storybook/react-native';
|
||||||
|
import { ScrollView } from 'react-native';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import * as List from '../../containers/List';
|
||||||
|
import { themes } from '../../constants/colors';
|
||||||
|
import { ThemeContext } from '../../theme';
|
||||||
|
import { store } from '../../../storybook/stories';
|
||||||
|
import Item from './Item';
|
||||||
|
|
||||||
|
const author = {
|
||||||
|
_id: 'userid',
|
||||||
|
username: 'rocket.cat',
|
||||||
|
name: 'Rocket Cat'
|
||||||
|
};
|
||||||
|
const baseUrl = 'https://open.rocket.chat';
|
||||||
|
const date = new Date(2020, 10, 10, 10);
|
||||||
|
const longText =
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';
|
||||||
|
const defaultItem = {
|
||||||
|
msg: 'Message content',
|
||||||
|
tcount: 1,
|
||||||
|
replies: [1],
|
||||||
|
ts: date,
|
||||||
|
tlm: date,
|
||||||
|
u: author,
|
||||||
|
attachments: []
|
||||||
|
};
|
||||||
|
|
||||||
|
const BaseItem = ({ item, ...props }) => (
|
||||||
|
<Item
|
||||||
|
baseUrl={baseUrl}
|
||||||
|
item={{
|
||||||
|
...defaultItem,
|
||||||
|
...item
|
||||||
|
}}
|
||||||
|
onPress={() => alert('pressed')}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const listDecorator = story => (
|
||||||
|
<ScrollView>
|
||||||
|
<List.Separator />
|
||||||
|
{story()}
|
||||||
|
<List.Separator />
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
|
||||||
|
const stories = storiesOf('Discussions.Item', module)
|
||||||
|
.addDecorator(listDecorator)
|
||||||
|
.addDecorator(story => <Provider store={store}>{story()}</Provider>);
|
||||||
|
|
||||||
|
stories.add('content', () => (
|
||||||
|
<>
|
||||||
|
<BaseItem />
|
||||||
|
<List.Separator />
|
||||||
|
<BaseItem
|
||||||
|
item={{
|
||||||
|
msg: longText
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<List.Separator />
|
||||||
|
<BaseItem
|
||||||
|
item={{
|
||||||
|
dcount: 1000,
|
||||||
|
replies: [...new Array(1000)]
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<List.Separator />
|
||||||
|
<BaseItem
|
||||||
|
item={{
|
||||||
|
msg: '',
|
||||||
|
attachments: [{ title: 'Attachment title' }]
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<List.Separator />
|
||||||
|
<BaseItem useRealName />
|
||||||
|
</>
|
||||||
|
));
|
||||||
|
|
||||||
|
const ThemeStory = ({ theme }) => (
|
||||||
|
<ThemeContext.Provider value={{ theme }}>
|
||||||
|
<BaseItem badgeColor={themes[theme].mentionMeColor} />
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
stories.add('themes', () => (
|
||||||
|
<>
|
||||||
|
<ThemeStory theme='light' />
|
||||||
|
<ThemeStory theme='dark' />
|
||||||
|
<ThemeStory theme='black' />
|
||||||
|
</>
|
||||||
|
));
|
|
@ -0,0 +1,105 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import Avatar from '../../containers/Avatar';
|
||||||
|
import sharedStyles from '../Styles';
|
||||||
|
import { themes } from '../../constants/colors';
|
||||||
|
import Markdown from '../../containers/markdown';
|
||||||
|
import { formatDateThreads, makeThreadName } from '../../utils/room';
|
||||||
|
import DiscussionDetails from './DiscussionDetails';
|
||||||
|
import { TThreadModel } from '../../definitions/IThread';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
padding: 16
|
||||||
|
},
|
||||||
|
contentContainer: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
titleContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginBottom: 2,
|
||||||
|
justifyContent: 'space-between'
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
flexShrink: 1,
|
||||||
|
fontSize: 18,
|
||||||
|
...sharedStyles.textMedium
|
||||||
|
},
|
||||||
|
time: {
|
||||||
|
fontSize: 14,
|
||||||
|
marginLeft: 4,
|
||||||
|
...sharedStyles.textRegular
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
marginRight: 8
|
||||||
|
},
|
||||||
|
messageContainer: {
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
markdown: {
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IItem {
|
||||||
|
item: TThreadModel;
|
||||||
|
baseUrl: string;
|
||||||
|
onPress: {
|
||||||
|
(...args: any[]): void;
|
||||||
|
stop(): void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const Item = ({ item, baseUrl, onPress }: IItem): JSX.Element => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const username = item?.u?.username;
|
||||||
|
let messageTime = '';
|
||||||
|
let messageDate = '';
|
||||||
|
|
||||||
|
if (item?.ts) {
|
||||||
|
messageTime = moment(item.ts).format('LT');
|
||||||
|
messageDate = formatDateThreads(item.ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Touchable
|
||||||
|
onPress={() => onPress(item)}
|
||||||
|
testID={`discussions-view-${item.msg}`}
|
||||||
|
style={{ backgroundColor: themes[theme].backgroundColor }}>
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Avatar style={styles.avatar} text={item?.u?.username} size={36} borderRadius={4} theme={theme} />
|
||||||
|
<View style={styles.contentContainer}>
|
||||||
|
<View style={styles.titleContainer}>
|
||||||
|
<Text style={[styles.title, { color: themes[theme].titleText }]} numberOfLines={1}>
|
||||||
|
{username}
|
||||||
|
</Text>
|
||||||
|
{messageTime ? <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{messageTime}</Text> : null}
|
||||||
|
</View>
|
||||||
|
<View style={styles.messageContainer}>
|
||||||
|
{username ? (
|
||||||
|
/* @ts-ignore */
|
||||||
|
<Markdown
|
||||||
|
msg={makeThreadName(item)}
|
||||||
|
baseUrl={baseUrl}
|
||||||
|
username={username}
|
||||||
|
theme={theme}
|
||||||
|
numberOfLines={2}
|
||||||
|
style={[styles.markdown]}
|
||||||
|
preview
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
{messageDate ? <DiscussionDetails item={item} date={messageDate} /> : null}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Touchable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Item;
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,208 @@
|
||||||
|
import React, { useEffect, useLayoutEffect, useState } from 'react';
|
||||||
|
import { FlatList, StyleSheet } from 'react-native';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
import { HeaderBackButton, StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
||||||
|
import { RouteProp } from '@react-navigation/core';
|
||||||
|
|
||||||
|
import { IApplicationState } from '../../definitions';
|
||||||
|
import { ChatsStackParamList } from '../../stacks/types';
|
||||||
|
import ActivityIndicator from '../../containers/ActivityIndicator';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import StatusBar from '../../containers/StatusBar';
|
||||||
|
import log from '../../utils/log';
|
||||||
|
import debounce from '../../utils/debounce';
|
||||||
|
import { themes } from '../../constants/colors';
|
||||||
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
|
import * as HeaderButton from '../../containers/HeaderButton';
|
||||||
|
import * as List from '../../containers/List';
|
||||||
|
import BackgroundContainer from '../../containers/BackgroundContainer';
|
||||||
|
import { isIOS } from '../../utils/deviceInfo';
|
||||||
|
import { getHeaderTitlePosition } from '../../containers/Header';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import RocketChat from '../../lib/rocketchat';
|
||||||
|
import SearchHeader from '../../containers/SearchHeader';
|
||||||
|
import { TThreadModel } from '../../definitions/IThread';
|
||||||
|
import Item from './Item';
|
||||||
|
|
||||||
|
const API_FETCH_COUNT = 50;
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
contentContainer: {
|
||||||
|
marginBottom: 30
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IDiscussionsViewProps {
|
||||||
|
navigation: StackNavigationProp<ChatsStackParamList, 'DiscussionsView'>;
|
||||||
|
route: RouteProp<ChatsStackParamList, 'DiscussionsView'>;
|
||||||
|
item: TThreadModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DiscussionsView = ({ navigation, route }: IDiscussionsViewProps): JSX.Element => {
|
||||||
|
const rid = route.params?.rid;
|
||||||
|
const t = route.params?.t;
|
||||||
|
|
||||||
|
const baseUrl = useSelector((state: IApplicationState) => state.server?.server);
|
||||||
|
const isMasterDetail = useSelector((state: IApplicationState) => state.app?.isMasterDetail);
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [discussions, setDiscussions] = useState([]);
|
||||||
|
const [search, setSearch] = useState([]);
|
||||||
|
const [isSearching, setIsSearching] = useState(false);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const [searchTotal, setSearchTotal] = useState(0);
|
||||||
|
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
|
const load = async (text = '') => {
|
||||||
|
if (loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const result = await RocketChat.getDiscussions({
|
||||||
|
roomId: rid,
|
||||||
|
offset: isSearching ? search.length : discussions.length,
|
||||||
|
count: API_FETCH_COUNT,
|
||||||
|
text
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
if (isSearching) {
|
||||||
|
setSearch(result.messages);
|
||||||
|
setSearchTotal(result.total);
|
||||||
|
} else {
|
||||||
|
setDiscussions(result.messages);
|
||||||
|
setTotal(result.total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSearchChangeText = debounce(async (text: string) => {
|
||||||
|
setIsSearching(true);
|
||||||
|
await load(text);
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
const onCancelSearchPress = () => {
|
||||||
|
setIsSearching(false);
|
||||||
|
setSearch([]);
|
||||||
|
setSearchTotal(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSearchPress = () => {
|
||||||
|
setIsSearching(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setHeader = () => {
|
||||||
|
let options: Partial<StackNavigationOptions>;
|
||||||
|
if (isSearching) {
|
||||||
|
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 1 });
|
||||||
|
options = {
|
||||||
|
headerTitleAlign: 'left',
|
||||||
|
headerLeft: () => (
|
||||||
|
<HeaderButton.Container left>
|
||||||
|
<HeaderButton.Item iconName='close' onPress={onCancelSearchPress} />
|
||||||
|
</HeaderButton.Container>
|
||||||
|
),
|
||||||
|
headerTitle: () => (
|
||||||
|
<SearchHeader onSearchChangeText={onSearchChangeText} testID='discussion-messages-view-search-header' />
|
||||||
|
),
|
||||||
|
headerTitleContainerStyle: {
|
||||||
|
left: headerTitlePosition.left,
|
||||||
|
right: headerTitlePosition.right
|
||||||
|
},
|
||||||
|
headerRight: () => null
|
||||||
|
};
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
options = {
|
||||||
|
headerLeft: () => (
|
||||||
|
<HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} />
|
||||||
|
),
|
||||||
|
headerTitleAlign: 'center',
|
||||||
|
headerTitle: I18n.t('Discussions'),
|
||||||
|
headerTitleContainerStyle: {
|
||||||
|
left: null,
|
||||||
|
right: null
|
||||||
|
},
|
||||||
|
headerRight: () => (
|
||||||
|
<HeaderButton.Container>
|
||||||
|
<HeaderButton.Item iconName='search' onPress={onSearchPress} />
|
||||||
|
</HeaderButton.Container>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isMasterDetail) {
|
||||||
|
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const options = setHeader();
|
||||||
|
navigation.setOptions(options);
|
||||||
|
}, [navigation, isSearching]);
|
||||||
|
|
||||||
|
const onDiscussionPress = debounce(
|
||||||
|
(item: TThreadModel) => {
|
||||||
|
if (item.drid && item.t) {
|
||||||
|
navigation.push('RoomView', {
|
||||||
|
rid: item.drid,
|
||||||
|
prid: item.rid,
|
||||||
|
name: item.msg,
|
||||||
|
t
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
1000,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderItem = ({ item }: { item: TThreadModel }) => (
|
||||||
|
<Item
|
||||||
|
{...{
|
||||||
|
item,
|
||||||
|
baseUrl
|
||||||
|
}}
|
||||||
|
onPress={onDiscussionPress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!discussions?.length) {
|
||||||
|
return <BackgroundContainer loading={loading} text={I18n.t('No_discussions')} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView testID='discussions-view'>
|
||||||
|
<StatusBar />
|
||||||
|
<FlatList
|
||||||
|
data={isSearching ? search : discussions}
|
||||||
|
renderItem={renderItem}
|
||||||
|
keyExtractor={(item: any) => item.msg}
|
||||||
|
style={{ backgroundColor: themes[theme].backgroundColor }}
|
||||||
|
contentContainerStyle={styles.contentContainer}
|
||||||
|
onEndReachedThreshold={0.5}
|
||||||
|
removeClippedSubviews={isIOS}
|
||||||
|
onEndReached={() => (isSearching ? searchTotal : total) > API_FETCH_COUNT ?? load()}
|
||||||
|
ItemSeparatorComponent={List.Separator}
|
||||||
|
ListFooterComponent={loading ? <ActivityIndicator theme={theme} /> : null}
|
||||||
|
scrollIndicatorInsets={{ right: 1 }}
|
||||||
|
/>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DiscussionsView;
|
|
@ -24,6 +24,7 @@ import { createChannelRequest } from '../actions/createChannel';
|
||||||
import { goRoom } from '../utils/goRoom';
|
import { goRoom } from '../utils/goRoom';
|
||||||
import SafeAreaView from '../containers/SafeAreaView';
|
import SafeAreaView from '../containers/SafeAreaView';
|
||||||
import { compareServerVersion, methods } from '../lib/utils';
|
import { compareServerVersion, methods } from '../lib/utils';
|
||||||
|
import { TSubscriptionModel } from '../definitions';
|
||||||
import sharedStyles from './Styles';
|
import sharedStyles from './Styles';
|
||||||
|
|
||||||
const QUERY_SIZE = 50;
|
const QUERY_SIZE = 50;
|
||||||
|
@ -68,9 +69,8 @@ interface ISearch {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface INewMessageViewState {
|
interface INewMessageViewState {
|
||||||
search: ISearch[];
|
search: (ISearch | TSubscriptionModel)[];
|
||||||
// TODO: Refactor when migrate room
|
chats: TSubscriptionModel[];
|
||||||
chats: any[];
|
|
||||||
permissions: boolean[];
|
permissions: boolean[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ class NewMessageView extends React.Component<INewMessageViewProps, INewMessageVi
|
||||||
init = async () => {
|
init = async () => {
|
||||||
try {
|
try {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const chats = await db.collections
|
const chats = await db
|
||||||
.get('subscriptions')
|
.get('subscriptions')
|
||||||
.query(Q.where('t', 'd'), Q.experimentalTake(QUERY_SIZE), Q.experimentalSortBy('room_updated_at', Q.desc))
|
.query(Q.where('t', 'd'), Q.experimentalTake(QUERY_SIZE), Q.experimentalSortBy('room_updated_at', Q.desc))
|
||||||
.fetch();
|
.fetch();
|
||||||
|
@ -153,7 +153,7 @@ class NewMessageView extends React.Component<INewMessageViewProps, INewMessageVi
|
||||||
};
|
};
|
||||||
|
|
||||||
search = async (text: string) => {
|
search = async (text: string) => {
|
||||||
const result = await RocketChat.search({ text, filterRooms: false });
|
const result: ISearch[] | TSubscriptionModel[] = await RocketChat.search({ text, filterRooms: false });
|
||||||
this.setState({
|
this.setState({
|
||||||
search: result
|
search: result
|
||||||
});
|
});
|
||||||
|
@ -280,8 +280,7 @@ class NewMessageView extends React.Component<INewMessageViewProps, INewMessageVi
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Refactor when migrate room
|
renderItem = ({ item, index }: { item: ISearch | TSubscriptionModel; index: number }) => {
|
||||||
renderItem = ({ item, index }: { item: ISearch | any; index: number }) => {
|
|
||||||
const { search, chats } = this.state;
|
const { search, chats } = this.state;
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
|
|
||||||
|
@ -295,10 +294,14 @@ class NewMessageView extends React.Component<INewMessageViewProps, INewMessageVi
|
||||||
if (search.length === 0 && index === chats.length - 1) {
|
if (search.length === 0 && index === chats.length - 1) {
|
||||||
style = { ...style, ...sharedStyles.separatorBottom };
|
style = { ...style, ...sharedStyles.separatorBottom };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const itemSearch = item as ISearch;
|
||||||
|
const itemModel = item as TSubscriptionModel;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserItem
|
<UserItem
|
||||||
name={item.search ? item.name : item.fname}
|
name={itemSearch.search ? itemSearch.name : itemModel.fname || ''}
|
||||||
username={item.search ? item.username : item.name}
|
username={itemSearch.search ? itemSearch.username : itemModel.name}
|
||||||
onPress={() => this.goRoom(item)}
|
onPress={() => this.goRoom(item)}
|
||||||
testID={`new-message-view-item-${item.name}`}
|
testID={`new-message-view-item-${item.name}`}
|
||||||
style={style}
|
style={style}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { themes } from '../../../constants/colors';
|
||||||
import { CustomIcon } from '../../../lib/Icons';
|
import { CustomIcon } from '../../../lib/Icons';
|
||||||
import sharedStyles from '../../Styles';
|
import sharedStyles from '../../Styles';
|
||||||
import Touch from '../../../utils/touch';
|
import Touch from '../../../utils/touch';
|
||||||
import { TServerHistory } from '../../../definitions/IServerHistory';
|
import { TServerHistoryModel } from '../../../definitions/IServerHistory';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -28,10 +28,10 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IItem {
|
interface IItem {
|
||||||
item: TServerHistory;
|
item: TServerHistoryModel;
|
||||||
theme: string;
|
theme: string;
|
||||||
onPress(url: string): void;
|
onPress(url: string): void;
|
||||||
onDelete(item: TServerHistory): void;
|
onDelete(item: TServerHistoryModel): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Item = ({ item, theme, onPress, onDelete }: IItem): JSX.Element => (
|
const Item = ({ item, theme, onPress, onDelete }: IItem): JSX.Element => (
|
||||||
|
|
|
@ -5,7 +5,7 @@ import TextInput from '../../../containers/TextInput';
|
||||||
import * as List from '../../../containers/List';
|
import * as List from '../../../containers/List';
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
import I18n from '../../../i18n';
|
import I18n from '../../../i18n';
|
||||||
import { TServerHistory } from '../../../definitions/IServerHistory';
|
import { TServerHistoryModel } from '../../../definitions/IServerHistory';
|
||||||
import Item from './Item';
|
import Item from './Item';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
@ -33,8 +33,8 @@ interface IServerInput extends TextInputProps {
|
||||||
theme: string;
|
theme: string;
|
||||||
serversHistory: any[];
|
serversHistory: any[];
|
||||||
onSubmit(): void;
|
onSubmit(): void;
|
||||||
onDelete(item: TServerHistory): void;
|
onDelete(item: TServerHistoryModel): void;
|
||||||
onPressServerHistory(serverHistory: TServerHistory): void;
|
onPressServerHistory(serverHistory: TServerHistoryModel): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ServerInput = ({
|
const ServerInput = ({
|
||||||
|
|
|
@ -14,7 +14,7 @@ import Button from '../../containers/Button';
|
||||||
import FormContainer, { FormContainerInner } from '../../containers/FormContainer';
|
import FormContainer, { FormContainerInner } from '../../containers/FormContainer';
|
||||||
import * as HeaderButton from '../../containers/HeaderButton';
|
import * as HeaderButton from '../../containers/HeaderButton';
|
||||||
import OrSeparator from '../../containers/OrSeparator';
|
import OrSeparator from '../../containers/OrSeparator';
|
||||||
import { IBaseScreen, TServerHistory } from '../../definitions';
|
import { IBaseScreen, TServerHistoryModel } from '../../definitions';
|
||||||
import { withDimensions } from '../../dimensions';
|
import { withDimensions } from '../../dimensions';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
|
@ -77,7 +77,7 @@ interface INewServerViewState {
|
||||||
text: string;
|
text: string;
|
||||||
connectingOpen: boolean;
|
connectingOpen: boolean;
|
||||||
certificate: any;
|
certificate: any;
|
||||||
serversHistory: TServerHistory[];
|
serversHistory: TServerHistoryModel[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISubmitParams {
|
interface ISubmitParams {
|
||||||
|
@ -153,7 +153,7 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS
|
||||||
const likeString = sanitizeLikeString(text);
|
const likeString = sanitizeLikeString(text);
|
||||||
whereClause = [...whereClause, Q.where('url', Q.like(`%${likeString}%`))];
|
whereClause = [...whereClause, Q.where('url', Q.like(`%${likeString}%`))];
|
||||||
}
|
}
|
||||||
const serversHistory = (await serversHistoryCollection.query(...whereClause).fetch()) as TServerHistory[];
|
const serversHistory = await serversHistoryCollection.query(...whereClause).fetch();
|
||||||
this.setState({ serversHistory });
|
this.setState({ serversHistory });
|
||||||
} catch {
|
} catch {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
@ -177,7 +177,7 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS
|
||||||
dispatch(serverRequest(server));
|
dispatch(serverRequest(server));
|
||||||
};
|
};
|
||||||
|
|
||||||
onPressServerHistory = (serverHistory: TServerHistory) => {
|
onPressServerHistory = (serverHistory: TServerHistoryModel) => {
|
||||||
this.setState({ text: serverHistory.url }, () => this.submit({ fromServerHistory: true, username: serverHistory?.username }));
|
this.setState({ text: serverHistory.url }, () => this.submit({ fromServerHistory: true, username: serverHistory?.username }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -269,14 +269,14 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteServerHistory = async (item: TServerHistory) => {
|
deleteServerHistory = async (item: TServerHistoryModel) => {
|
||||||
const db = database.servers;
|
const db = database.servers;
|
||||||
try {
|
try {
|
||||||
await db.write(async () => {
|
await db.write(async () => {
|
||||||
await item.destroyPermanently();
|
await item.destroyPermanently();
|
||||||
});
|
});
|
||||||
this.setState((prevstate: INewServerViewState) => ({
|
this.setState((prevstate: INewServerViewState) => ({
|
||||||
serversHistory: prevstate.serversHistory.filter((server: TServerHistory) => server.id !== item.id)
|
serversHistory: prevstate.serversHistory.filter(server => server.id !== item.id)
|
||||||
}));
|
}));
|
||||||
} catch {
|
} catch {
|
||||||
// Nothing
|
// Nothing
|
||||||
|
|
|
@ -80,7 +80,7 @@ class NotificationPreferencesView extends React.Component<INotificationPreferenc
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.action(async () => {
|
await db.write(async () => {
|
||||||
await room.update(
|
await room.update(
|
||||||
protectedFunction((r: any) => {
|
protectedFunction((r: any) => {
|
||||||
r[key] = value;
|
r[key] = value;
|
||||||
|
@ -97,7 +97,7 @@ class NotificationPreferencesView extends React.Component<INotificationPreferenc
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.action(async () => {
|
await db.write(async () => {
|
||||||
await room.update(
|
await room.update(
|
||||||
protectedFunction((r: any) => {
|
protectedFunction((r: any) => {
|
||||||
r[key] = room[key];
|
r[key] = room[key];
|
||||||
|
|
|
@ -643,7 +643,7 @@ class RoomActionsView extends React.Component {
|
||||||
const { addTeamChannelPermission, createTeamPermission } = this.props;
|
const { addTeamChannelPermission, createTeamPermission } = this.props;
|
||||||
const QUERY_SIZE = 50;
|
const QUERY_SIZE = 50;
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const teams = await db.collections
|
const teams = await db
|
||||||
.get('subscriptions')
|
.get('subscriptions')
|
||||||
.query(
|
.query(
|
||||||
Q.where('team_main', true),
|
Q.where('team_main', true),
|
||||||
|
@ -941,7 +941,7 @@ class RoomActionsView extends React.Component {
|
||||||
canReturnQueue,
|
canReturnQueue,
|
||||||
canViewCannedResponse
|
canViewCannedResponse
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { rid, t } = room;
|
const { rid, t, prid } = room;
|
||||||
const isGroupChat = RocketChat.isGroupChat(room);
|
const isGroupChat = RocketChat.isGroupChat(room);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1009,6 +1009,27 @@ class RoomActionsView extends React.Component {
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{['c', 'p', 'd'].includes(t) && !prid ? (
|
||||||
|
<>
|
||||||
|
<List.Item
|
||||||
|
title='Discussions'
|
||||||
|
onPress={() =>
|
||||||
|
this.onPressTouchable({
|
||||||
|
route: 'DiscussionsView',
|
||||||
|
params: {
|
||||||
|
rid,
|
||||||
|
t
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
testID='room-actions-discussions'
|
||||||
|
left={() => <List.Icon name='discussions' />}
|
||||||
|
showActionIndicator
|
||||||
|
/>
|
||||||
|
<List.Separator />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{['c', 'p', 'd'].includes(t) ? (
|
{['c', 'p', 'd'].includes(t) ? (
|
||||||
<>
|
<>
|
||||||
<List.Item
|
<List.Item
|
||||||
|
|
|
@ -144,11 +144,11 @@ class ListContainer extends React.Component {
|
||||||
|
|
||||||
if (tmid) {
|
if (tmid) {
|
||||||
try {
|
try {
|
||||||
this.thread = await db.collections.get('threads').find(tmid);
|
this.thread = await db.get('threads').find(tmid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
this.messagesObservable = db.collections
|
this.messagesObservable = db
|
||||||
.get('thread_messages')
|
.get('thread_messages')
|
||||||
.query(Q.where('rid', tmid), Q.experimentalSortBy('ts', Q.desc), Q.experimentalSkip(0), Q.experimentalTake(this.count))
|
.query(Q.where('rid', tmid), Q.experimentalSortBy('ts', Q.desc), Q.experimentalSkip(0), Q.experimentalTake(this.count))
|
||||||
.observe();
|
.observe();
|
||||||
|
@ -162,7 +162,7 @@ class ListContainer extends React.Component {
|
||||||
if (!showMessageInMainThread) {
|
if (!showMessageInMainThread) {
|
||||||
whereClause.push(Q.or(Q.where('tmid', null), Q.where('tshow', Q.eq(true))));
|
whereClause.push(Q.or(Q.where('tmid', null), Q.where('tshow', Q.eq(true))));
|
||||||
}
|
}
|
||||||
this.messagesObservable = db.collections
|
this.messagesObservable = db
|
||||||
.get('messages')
|
.get('messages')
|
||||||
.query(...whereClause)
|
.query(...whereClause)
|
||||||
.observe();
|
.observe();
|
||||||
|
|
|
@ -91,7 +91,7 @@ class UploadProgress extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
this.uploadsObservable = db.collections.get('uploads').query(Q.where('rid', rid)).observeWithColumns(['progress', 'error']);
|
this.uploadsObservable = db.get('uploads').query(Q.where('rid', rid)).observeWithColumns(['progress', 'error']);
|
||||||
|
|
||||||
this.uploadsSubscription = this.uploadsObservable.subscribe(uploads => {
|
this.uploadsSubscription = this.uploadsObservable.subscribe(uploads => {
|
||||||
if (this.mounted) {
|
if (this.mounted) {
|
||||||
|
|
|
@ -459,7 +459,7 @@ class RoomView extends React.Component {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
navigation.navigate('RoomActionsView', {
|
navigation.push('RoomActionsView', {
|
||||||
rid: this.rid,
|
rid: this.rid,
|
||||||
t: this.t,
|
t: this.t,
|
||||||
room,
|
room,
|
||||||
|
@ -690,7 +690,7 @@ class RoomView extends React.Component {
|
||||||
// eslint-disable-next-line react/sort-comp
|
// eslint-disable-next-line react/sort-comp
|
||||||
updateUnreadCount = async () => {
|
updateUnreadCount = async () => {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const observable = await db.collections
|
const observable = await db
|
||||||
.get('subscriptions')
|
.get('subscriptions')
|
||||||
.query(Q.where('archived', false), Q.where('open', true), Q.where('rid', Q.notEq(this.rid)))
|
.query(Q.where('archived', false), Q.where('open', true), Q.where('rid', Q.notEq(this.rid)))
|
||||||
.observeWithColumns(['unread']);
|
.observeWithColumns(['unread']);
|
||||||
|
|
|
@ -47,7 +47,7 @@ class ServerDropdown extends Component {
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
const serversDB = database.servers;
|
const serversDB = database.servers;
|
||||||
const observable = await serversDB.collections.get('servers').query().observeWithColumns(['name']);
|
const observable = await serversDB.get('servers').query().observeWithColumns(['name']);
|
||||||
|
|
||||||
this.subscription = observable.subscribe(data => {
|
this.subscription = observable.subscribe(data => {
|
||||||
this.setState({ servers: data });
|
this.setState({ servers: data });
|
||||||
|
|
|
@ -450,7 +450,7 @@ class RoomsListView extends React.Component {
|
||||||
|
|
||||||
// When we're grouping by something
|
// When we're grouping by something
|
||||||
if (this.isGrouping) {
|
if (this.isGrouping) {
|
||||||
observable = await db.collections
|
observable = await db
|
||||||
.get('subscriptions')
|
.get('subscriptions')
|
||||||
.query(...defaultWhereClause)
|
.query(...defaultWhereClause)
|
||||||
.observeWithColumns(['alert']);
|
.observeWithColumns(['alert']);
|
||||||
|
@ -458,7 +458,7 @@ class RoomsListView extends React.Component {
|
||||||
// When we're NOT grouping
|
// When we're NOT grouping
|
||||||
} else {
|
} else {
|
||||||
this.count += QUERY_SIZE;
|
this.count += QUERY_SIZE;
|
||||||
observable = await db.collections
|
observable = await db
|
||||||
.get('subscriptions')
|
.get('subscriptions')
|
||||||
.query(...defaultWhereClause, Q.experimentalSkip(0), Q.experimentalTake(this.count))
|
.query(...defaultWhereClause, Q.experimentalSkip(0), Q.experimentalTake(this.count))
|
||||||
.observe();
|
.observe();
|
||||||
|
|
|
@ -92,7 +92,7 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I
|
||||||
const serversDB = database.servers;
|
const serversDB = database.servers;
|
||||||
const serversCollection = serversDB.get('servers');
|
const serversCollection = serversDB.get('servers');
|
||||||
try {
|
try {
|
||||||
this.serverRecord = (await serversCollection.find(server)) as TServerModel;
|
this.serverRecord = await serversCollection.find(server);
|
||||||
this.setState({
|
this.setState({
|
||||||
autoLock: this.serverRecord?.autoLock,
|
autoLock: this.serverRecord?.autoLock,
|
||||||
autoLockTime: this.serverRecord?.autoLockTime === null ? DEFAULT_AUTO_LOCK : this.serverRecord?.autoLockTime,
|
autoLockTime: this.serverRecord?.autoLockTime === null ? DEFAULT_AUTO_LOCK : this.serverRecord?.autoLockTime,
|
||||||
|
@ -115,7 +115,7 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I
|
||||||
*/
|
*/
|
||||||
observe = () => {
|
observe = () => {
|
||||||
this.observable = this.serverRecord?.observe()?.subscribe(({ biometry }) => {
|
this.observable = this.serverRecord?.observe()?.subscribe(({ biometry }) => {
|
||||||
this.setState({ biometry });
|
this.setState({ biometry: !!biometry });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -109,10 +109,7 @@ class SelectedUsersView extends React.Component<ISelectedUsersViewProps, ISelect
|
||||||
init = async () => {
|
init = async () => {
|
||||||
try {
|
try {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const observable = await db.collections
|
const observable = await db.get('subscriptions').query(Q.where('t', 'd')).observeWithColumns(['room_updated_at']);
|
||||||
.get('subscriptions')
|
|
||||||
.query(Q.where('t', 'd'))
|
|
||||||
.observeWithColumns(['room_updated_at']);
|
|
||||||
|
|
||||||
// TODO: Refactor when migrate room
|
// TODO: Refactor when migrate room
|
||||||
this.querySubscription = observable.subscribe((data: any) => {
|
this.querySubscription = observable.subscribe((data: any) => {
|
||||||
|
@ -129,7 +126,9 @@ class SelectedUsersView extends React.Component<ISelectedUsersViewProps, ISelect
|
||||||
}
|
}
|
||||||
|
|
||||||
search = async (text: string) => {
|
search = async (text: string) => {
|
||||||
const result = await RocketChat.search({ text, filterRooms: false });
|
// TODO: When migrate rocketchat.js pass the param IUser to there and the return should be
|
||||||
|
// IUser | TSubscriptionModel, this because we do a local search too
|
||||||
|
const result = (await RocketChat.search({ text, filterRooms: false })) as ISelectedUser[];
|
||||||
this.setState({
|
this.setState({
|
||||||
search: result
|
search: result
|
||||||
});
|
});
|
||||||
|
|
|
@ -218,7 +218,9 @@ class TeamChannelsView extends React.Component<ITeamChannelsViewProps, ITeamChan
|
||||||
<HeaderButton.Item iconName='close' onPress={this.onCancelSearchPress} />
|
<HeaderButton.Item iconName='close' onPress={this.onCancelSearchPress} />
|
||||||
</HeaderButton.Container>
|
</HeaderButton.Container>
|
||||||
),
|
),
|
||||||
headerTitle: () => <SearchHeader onSearchChangeText={this.onSearchChangeText} />,
|
headerTitle: () => (
|
||||||
|
<SearchHeader onSearchChangeText={this.onSearchChangeText} testID='team-channels-view-search-header' />
|
||||||
|
),
|
||||||
headerTitleContainerStyle: {
|
headerTitleContainerStyle: {
|
||||||
left: headerTitlePosition.left,
|
left: headerTitlePosition.left,
|
||||||
right: headerTitlePosition.right
|
right: headerTitlePosition.right
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { storiesOf } from '@storybook/react-native';
|
import { storiesOf } from '@storybook/react-native';
|
||||||
import { ScrollView } from 'react-native';
|
import { ScrollView } from 'react-native';
|
||||||
import { combineReducers, createStore } from 'redux';
|
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import * as List from '../../containers/List';
|
import * as List from '../../containers/List';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { ThemeContext } from '../../theme';
|
import { ThemeContext } from '../../theme';
|
||||||
|
import { store } from '../../../storybook/stories';
|
||||||
import Item from './Item';
|
import Item from './Item';
|
||||||
|
|
||||||
const author = {
|
const author = {
|
||||||
|
@ -49,28 +49,6 @@ const listDecorator = story => (
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
|
||||||
const reducers = combineReducers({
|
|
||||||
login: () => ({
|
|
||||||
user: {
|
|
||||||
id: 'abc',
|
|
||||||
username: 'rocket.cat',
|
|
||||||
name: 'Rocket Cat'
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
server: () => ({
|
|
||||||
server: 'https://open.rocket.chat',
|
|
||||||
version: '3.7.0'
|
|
||||||
}),
|
|
||||||
share: () => ({
|
|
||||||
server: 'https://open.rocket.chat',
|
|
||||||
version: '3.7.0'
|
|
||||||
}),
|
|
||||||
settings: () => ({
|
|
||||||
blockUnauthenticatedAccess: false
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const store = createStore(reducers);
|
|
||||||
|
|
||||||
const stories = storiesOf('Thread Messages.Item', module)
|
const stories = storiesOf('Thread Messages.Item', module)
|
||||||
.addDecorator(listDecorator)
|
.addDecorator(listDecorator)
|
||||||
.addDecorator(story => <Provider store={store}>{story()}</Provider>);
|
.addDecorator(story => <Provider store={store}>{story()}</Provider>);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { StyleSheet, Text, View } from 'react-native';
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import { withTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import Avatar from '../../containers/Avatar';
|
import Avatar from '../../containers/Avatar';
|
||||||
import sharedStyles from '../Styles';
|
import sharedStyles from '../Styles';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
@ -59,7 +59,6 @@ const styles = StyleSheet.create({
|
||||||
interface IItem {
|
interface IItem {
|
||||||
item: TThreadModel;
|
item: TThreadModel;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
theme?: string;
|
|
||||||
useRealName: boolean;
|
useRealName: boolean;
|
||||||
user: any;
|
user: any;
|
||||||
badgeColor?: string;
|
badgeColor?: string;
|
||||||
|
@ -67,7 +66,8 @@ interface IItem {
|
||||||
toggleFollowThread: (isFollowing: boolean, id: string) => void;
|
toggleFollowThread: (isFollowing: boolean, id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Item = ({ item, baseUrl, theme, useRealName, user, badgeColor, onPress, toggleFollowThread }: IItem) => {
|
const Item = ({ item, baseUrl, useRealName, user, badgeColor, onPress, toggleFollowThread }: IItem) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
const username = (useRealName && item?.u?.name) || item?.u?.username;
|
const username = (useRealName && item?.u?.name) || item?.u?.username;
|
||||||
let time;
|
let time;
|
||||||
if (item?.ts) {
|
if (item?.ts) {
|
||||||
|
@ -89,16 +89,18 @@ const Item = ({ item, baseUrl, theme, useRealName, user, badgeColor, onPress, to
|
||||||
<Text style={[styles.time, { color: themes[theme!].auxiliaryText }]}>{time}</Text>
|
<Text style={[styles.time, { color: themes[theme!].auxiliaryText }]}>{time}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.messageContainer}>
|
<View style={styles.messageContainer}>
|
||||||
|
{makeThreadName(item) && username ? (
|
||||||
|
/* @ts-ignore */
|
||||||
<Markdown
|
<Markdown
|
||||||
// @ts-ignore
|
|
||||||
msg={makeThreadName(item)}
|
msg={makeThreadName(item)}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
username={username!}
|
username={username}
|
||||||
theme={theme!}
|
theme={theme}
|
||||||
numberOfLines={2}
|
numberOfLines={2}
|
||||||
style={[styles.markdown]}
|
style={[styles.markdown]}
|
||||||
preview
|
preview
|
||||||
/>
|
/>
|
||||||
|
) : null}
|
||||||
{badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null}
|
{badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null}
|
||||||
</View>
|
</View>
|
||||||
<ThreadDetails item={item} user={user} toggleFollowThread={toggleFollowThread} style={styles.threadDetails} />
|
<ThreadDetails item={item} user={user} toggleFollowThread={toggleFollowThread} style={styles.threadDetails} />
|
||||||
|
@ -108,4 +110,4 @@ const Item = ({ item, baseUrl, theme, useRealName, user, badgeColor, onPress, to
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTheme(Item);
|
export default Item;
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { HeaderBackButton, StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
import { HeaderBackButton, StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
||||||
import { RouteProp } from '@react-navigation/native';
|
import { RouteProp } from '@react-navigation/native';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
import Model from '@nozbe/watermelondb/Model';
|
|
||||||
import Database from '@nozbe/watermelondb/Database';
|
import Database from '@nozbe/watermelondb/Database';
|
||||||
|
|
||||||
import ActivityIndicator from '../../containers/ActivityIndicator';
|
import ActivityIndicator from '../../containers/ActivityIndicator';
|
||||||
|
@ -86,7 +85,7 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre
|
||||||
|
|
||||||
private messagesSubscription?: Subscription;
|
private messagesSubscription?: Subscription;
|
||||||
|
|
||||||
private messagesObservable!: Observable<Model>;
|
private messagesObservable?: Observable<TThreadModel[]>;
|
||||||
|
|
||||||
constructor(props: IThreadMessagesViewProps) {
|
constructor(props: IThreadMessagesViewProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -144,7 +143,9 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre
|
||||||
<HeaderButton.Item iconName='close' onPress={this.onCancelSearchPress} />
|
<HeaderButton.Item iconName='close' onPress={this.onCancelSearchPress} />
|
||||||
</HeaderButton.Container>
|
</HeaderButton.Container>
|
||||||
),
|
),
|
||||||
headerTitle: () => <SearchHeader onSearchChangeText={this.onSearchChangeText} />,
|
headerTitle: () => (
|
||||||
|
<SearchHeader onSearchChangeText={this.onSearchChangeText} testID='thread-messages-view-search-header' />
|
||||||
|
),
|
||||||
headerTitleContainerStyle: {
|
headerTitleContainerStyle: {
|
||||||
left: headerTitlePosition.left,
|
left: headerTitlePosition.left,
|
||||||
right: headerTitlePosition.right
|
right: headerTitlePosition.right
|
||||||
|
@ -188,7 +189,7 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
|
|
||||||
// subscription query
|
// subscription query
|
||||||
const subscription = (await db.collections.get('subscriptions').find(this.rid)) as TSubscriptionModel;
|
const subscription = await db.get('subscriptions').find(this.rid);
|
||||||
const observable = subscription.observe();
|
const observable = subscription.observe();
|
||||||
this.subSubscription = observable.subscribe(data => {
|
this.subSubscription = observable.subscribe(data => {
|
||||||
this.setState({ subscription: data });
|
this.setState({ subscription: data });
|
||||||
|
@ -214,15 +215,15 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre
|
||||||
whereClause.push(Q.where('msg', Q.like(`%${sanitizeLikeString(searchText.trim())}%`)));
|
whereClause.push(Q.where('msg', Q.like(`%${sanitizeLikeString(searchText.trim())}%`)));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messagesObservable = db.collections
|
this.messagesObservable = db
|
||||||
.get('threads')
|
.get('threads')
|
||||||
.query(...whereClause)
|
.query(...whereClause)
|
||||||
.observeWithColumns(['updated_at']);
|
.observeWithColumns(['updated_at']);
|
||||||
|
|
||||||
// TODO: Refactor when migrate messages
|
// TODO: Refactor when migrate messages
|
||||||
this.messagesSubscription = this.messagesObservable.subscribe((messages: any) => {
|
this.messagesSubscription = this.messagesObservable.subscribe(messages => {
|
||||||
const { currentFilter } = this.state;
|
const { currentFilter } = this.state;
|
||||||
const displayingThreads = this.getFilteredThreads(messages, subscription!, currentFilter);
|
const displayingThreads = this.getFilteredThreads(messages, subscription, currentFilter);
|
||||||
if (this.mounted) {
|
if (this.mounted) {
|
||||||
this.setState({ messages, displayingThreads });
|
this.setState({ messages, displayingThreads });
|
||||||
} else {
|
} else {
|
||||||
|
@ -419,14 +420,14 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre
|
||||||
};
|
};
|
||||||
|
|
||||||
// helper to query threads
|
// helper to query threads
|
||||||
getFilteredThreads = (messages: any, subscription: TSubscriptionModel, currentFilter?: Filter): TThreadModel[] => {
|
getFilteredThreads = (messages: TThreadModel[], subscription?: TSubscriptionModel, currentFilter?: Filter): TThreadModel[] => {
|
||||||
// const { currentFilter } = this.state;
|
// const { currentFilter } = this.state;
|
||||||
const { user } = this.props;
|
const { user } = this.props;
|
||||||
if (currentFilter === Filter.Following) {
|
if (currentFilter === Filter.Following) {
|
||||||
return messages?.filter((item: { replies: any[] }) => item?.replies?.find(u => u === user.id));
|
return messages?.filter(item => item?.replies?.find(u => u === user.id));
|
||||||
}
|
}
|
||||||
if (currentFilter === Filter.Unread) {
|
if (currentFilter === Filter.Unread) {
|
||||||
return messages?.filter((item: { id: string }) => subscription?.tunread?.includes(item?.id));
|
return messages?.filter(item => subscription?.tunread?.includes(item?.id));
|
||||||
}
|
}
|
||||||
return messages;
|
return messages;
|
||||||
};
|
};
|
||||||
|
|
|
@ -107,7 +107,7 @@ describe('Discussion', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Check RoomActionsView render', () => {
|
describe('Check RoomActionsView render', () => {
|
||||||
it('should navigete to RoomActionsView', async () => {
|
it('should navigate to RoomActionsView', async () => {
|
||||||
await waitFor(element(by.id('room-header')))
|
await waitFor(element(by.id('room-header')))
|
||||||
.toBeVisible()
|
.toBeVisible()
|
||||||
.withTimeout(5000);
|
.withTimeout(5000);
|
||||||
|
@ -173,4 +173,51 @@ describe('Discussion', () => {
|
||||||
await expect(element(by.id('room-info-view-edit-button'))).toBeVisible();
|
await expect(element(by.id('room-info-view-edit-button'))).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Open Discussion from DiscussionsView', () => {
|
||||||
|
const discussionName = `${data.random}message`;
|
||||||
|
it('should go back to main room', async () => {
|
||||||
|
await tapBack();
|
||||||
|
await waitFor(element(by.id('room-actions-view')))
|
||||||
|
.toBeVisible()
|
||||||
|
.withTimeout(5000);
|
||||||
|
await tapBack();
|
||||||
|
await waitFor(element(by.id(`room-view-title-${discussionName}`)))
|
||||||
|
.toExist()
|
||||||
|
.withTimeout(5000);
|
||||||
|
await tapBack();
|
||||||
|
await navigateToRoom();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should navigate to DiscussionsView', async () => {
|
||||||
|
await waitFor(element(by.id(`room-view-title-${channel}`)))
|
||||||
|
.toExist()
|
||||||
|
.withTimeout(5000);
|
||||||
|
await waitFor(element(by.id('room-header')))
|
||||||
|
.toBeVisible()
|
||||||
|
.withTimeout(5000);
|
||||||
|
await element(by.id('room-header')).tap();
|
||||||
|
await waitFor(element(by.id('room-actions-discussions')))
|
||||||
|
.toBeVisible()
|
||||||
|
.withTimeout(5000);
|
||||||
|
await element(by.id('room-actions-discussions')).tap();
|
||||||
|
await waitFor(element(by.id('discussions-view')))
|
||||||
|
.toBeVisible()
|
||||||
|
.withTimeout(5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should navigate to discussion', async () => {
|
||||||
|
const discussionName = `${data.random} Discussion NewMessageView`;
|
||||||
|
await waitFor(element(by.label(discussionName)).atIndex(0))
|
||||||
|
.toExist()
|
||||||
|
.withTimeout(5000);
|
||||||
|
await element(by.label(discussionName)).atIndex(0).tap();
|
||||||
|
await waitFor(element(by.id(`room-view-title-${discussionName}`)))
|
||||||
|
.toExist()
|
||||||
|
.withTimeout(5000);
|
||||||
|
await waitFor(element(by.id('messagebox')))
|
||||||
|
.toBeVisible()
|
||||||
|
.withTimeout(60000);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -11,6 +11,7 @@ import './Markdown';
|
||||||
import './HeaderButtons';
|
import './HeaderButtons';
|
||||||
import './UnreadBadge';
|
import './UnreadBadge';
|
||||||
import '../../app/views/ThreadMessagesView/Item.stories.js';
|
import '../../app/views/ThreadMessagesView/Item.stories.js';
|
||||||
|
import '../../app/views/DiscussionsView/Item.stories.js';
|
||||||
import './Avatar';
|
import './Avatar';
|
||||||
import './NewMarkdown';
|
import './NewMarkdown';
|
||||||
import '../../app/containers/BackgroundContainer/index.stories.js';
|
import '../../app/containers/BackgroundContainer/index.stories.js';
|
||||||
|
|
Loading…
Reference in New Issue