forked from Ivasoft/mattermost-mobile
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afbcc2a203 | ||
|
|
94dec5e443 | ||
|
|
f6e23a9e0c | ||
|
|
fe1fc259ad | ||
|
|
e480dcfacb | ||
|
|
62026e375a | ||
|
|
9c22be4465 | ||
|
|
b5b948e58f | ||
|
|
5d9b8a7e06 | ||
|
|
2b495f4c51 | ||
|
|
49a284f46d | ||
|
|
61a33b3025 | ||
|
|
01d7d08b25 | ||
|
|
4bd364df42 | ||
|
|
ef808262d7 | ||
|
|
95f8e72c11 | ||
|
|
3ea3fe7e2f | ||
|
|
2e5f7fd60c | ||
|
|
cb98efc6da | ||
|
|
09a49302f2 | ||
|
|
68fe3653a8 | ||
|
|
2cf74c07cb | ||
|
|
a70688fe44 | ||
|
|
b8f4757300 | ||
|
|
b3ec19fe17 | ||
|
|
faa3a55b3d | ||
|
|
dff9628b6e | ||
|
|
82d9995f2b | ||
|
|
112fd06dfd | ||
|
|
92541025ef | ||
|
|
d927642cb9 | ||
|
|
696b2e7c5e | ||
|
|
bfac3546c9 | ||
|
|
e170bc1177 | ||
|
|
b41777a7e0 | ||
|
|
783dc66b2a | ||
|
|
1c093f70a4 | ||
|
|
63bcdc42fb | ||
|
|
793aea617c | ||
|
|
ed3e822b63 | ||
|
|
7bbe6ccd9f | ||
|
|
bc159153d6 | ||
|
|
167b91d7fd | ||
|
|
87b3e3e724 | ||
|
|
cb1e286e4f | ||
|
|
fb2f711359 | ||
|
|
07ff66726c | ||
|
|
2bb69a9b7e | ||
|
|
a25423eb4b | ||
|
|
dfb0557de6 | ||
|
|
093484eab3 |
39
Makefile
39
Makefile
@@ -2,7 +2,8 @@
|
||||
.PHONY: check-style
|
||||
.PHONY: start stop
|
||||
.PHONY: run run-ios run-android
|
||||
.PHONY: build-ios build-android unsigned-ios unsigned-android
|
||||
.PHONY: build build-ios build-android unsigned-ios unsigned-android
|
||||
.PHONY: build-pr can-build-pr prepare-pr
|
||||
.PHONY: test help
|
||||
|
||||
POD := $(shell which pod 2> /dev/null)
|
||||
@@ -60,9 +61,6 @@ clean: ## Cleans dependencies, previous builds and temp files
|
||||
@echo Cleanup finished
|
||||
|
||||
post-install:
|
||||
@# Need to copy custom ImagePickerModule.java that implements correct permission checks for android
|
||||
@rm node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java
|
||||
@cp ./native_modules/ImagePickerModule.java node_modules/react-native-image-picker/android/src/main/java/com/imagepicker
|
||||
@# Need to copy custom RNDocumentPicker.m that implements direct access to the document picker in iOS
|
||||
@cp ./native_modules/RNDocumentPicker.m node_modules/react-native-document-picker/ios/RNDocumentPicker/RNDocumentPicker.m
|
||||
|
||||
@@ -170,7 +168,17 @@ run-android: | check-device-android pre-run prepare-android-build ## Runs the ap
|
||||
fi; \
|
||||
fi
|
||||
|
||||
build-ios: | pre-run check-style ## Creates an iOS build
|
||||
build: | stop pre-run check-style ## Builds the app for Android & iOS
|
||||
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
npm start & echo; \
|
||||
fi
|
||||
@echo "Building App"
|
||||
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane build
|
||||
@ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9
|
||||
|
||||
|
||||
build-ios: | stop pre-run check-style ## Builds the iOS app
|
||||
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
npm start & echo; \
|
||||
@@ -179,7 +187,7 @@ build-ios: | pre-run check-style ## Creates an iOS build
|
||||
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane ios build
|
||||
@ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9
|
||||
|
||||
build-android: | pre-run check-style prepare-android-build ## Creates an Android build
|
||||
build-android: | stop pre-run check-style prepare-android-build ## Build the Android app
|
||||
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
npm start & echo; \
|
||||
@@ -188,7 +196,7 @@ build-android: | pre-run check-style prepare-android-build ## Creates an Android
|
||||
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane android build
|
||||
@ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9
|
||||
|
||||
unsigned-ios: pre-run check-style
|
||||
unsigned-ios: stop pre-run check-style ## Build an unsigned version of the iOS app
|
||||
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
npm start & echo; \
|
||||
@@ -202,7 +210,7 @@ unsigned-ios: pre-run check-style
|
||||
@rm -rf build-ios/
|
||||
@ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9
|
||||
|
||||
unsigned-android: pre-run check-style prepare-android-build
|
||||
unsigned-android: stop pre-run check-style prepare-android-build ## Build an unsigned version of the Android app
|
||||
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
npm start & echo; \
|
||||
@@ -215,6 +223,21 @@ unsigned-android: pre-run check-style prepare-android-build
|
||||
test: | pre-run check-style ## Runs tests
|
||||
@npm test
|
||||
|
||||
build-pr: | can-build-pr stop pre-run check-style ## Build a PR from the mattermost-mobile repo
|
||||
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
npm start & echo; \
|
||||
fi
|
||||
@echo "Building App from PR ${PR_ID}"
|
||||
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane build_pr pr:PR-${PR_ID}
|
||||
@ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9
|
||||
|
||||
can-build-pr:
|
||||
@if [ -z ${PR_ID} ]; then \
|
||||
echo a PR number needs to be specified; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
## Help documentation https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
|
||||
help:
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
@@ -113,7 +113,7 @@ android {
|
||||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 144
|
||||
versionCode 149
|
||||
versionName "1.13.0"
|
||||
multiDexEnabled = true
|
||||
ndk {
|
||||
@@ -214,11 +214,11 @@ dependencies {
|
||||
implementation project(':react-native-recyclerview-list')
|
||||
|
||||
// For animated GIF support
|
||||
implementation 'com.facebook.fresco:animated-base-support:1.3.0'
|
||||
implementation 'com.facebook.fresco:fresco:1.10.0'
|
||||
implementation 'com.facebook.fresco:animated-gif:1.10.0'
|
||||
// For WebP support, including animated WebP
|
||||
implementation 'com.facebook.fresco:animated-gif:1.3.0'
|
||||
implementation 'com.facebook.fresco:animated-webp:1.3.0'
|
||||
implementation 'com.facebook.fresco:webpsupport:1.3.0'
|
||||
implementation 'com.facebook.fresco:animated-webp:1.10.0'
|
||||
implementation 'com.facebook.fresco:webpsupport:1.10.0'
|
||||
}
|
||||
|
||||
// Run this once to be able to run the application with BUCK
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.util.ArraySet;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.content.res.Configuration;
|
||||
|
||||
@@ -69,9 +70,13 @@ public class NotificationsLifecycleFacade extends ActivityCallbacks implements A
|
||||
activity.getWindow().setFlags(LayoutParams.FLAG_SECURE,
|
||||
LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
if (managedConfig!= null && managedConfig.size() > 0 && activity != null) {
|
||||
if (managedConfig != null && managedConfig.size() > 0 && activity != null) {
|
||||
activity.registerReceiver(restrictionsReceiver, restrictionsFilter);
|
||||
}
|
||||
|
||||
if (activity != null) {
|
||||
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import {Posts} from 'mattermost-redux/constants';
|
||||
import {PostTypes} from 'mattermost-redux/action_types';
|
||||
import {doPostAction} from 'mattermost-redux/actions/posts';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
@@ -50,3 +51,15 @@ export function setMenuActionSelector(dataSource, onSelect, options) {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function selectAttachmentMenuAction(postId, actionId, dataSource, displayText, value) {
|
||||
return (dispatch) => {
|
||||
dispatch({
|
||||
type: ViewTypes.SUBMIT_ATTACHMENT_MENU_ACTION,
|
||||
postId,
|
||||
data: {displayText, value},
|
||||
});
|
||||
|
||||
dispatch(doPostAction(postId, actionId, value));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import AnnouncementBanner from './announcement_banner.js';
|
||||
|
||||
jest.useFakeTimers();
|
||||
@@ -16,7 +18,7 @@ describe('AnnouncementBanner', () => {
|
||||
bannerText: 'Banner Text',
|
||||
bannerTextColor: '#fff',
|
||||
navigator: {},
|
||||
theme: {},
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
import {DocumentPicker} from 'react-native-document-picker';
|
||||
import ImagePicker from 'react-native-image-picker';
|
||||
@@ -27,8 +29,10 @@ export default class AttachmentButton extends PureComponent {
|
||||
children: PropTypes.node,
|
||||
fileCount: PropTypes.number,
|
||||
maxFileCount: PropTypes.number.isRequired,
|
||||
maxFileSize: PropTypes.number.isRequired,
|
||||
navigator: PropTypes.object.isRequired,
|
||||
onShowFileMaxWarning: PropTypes.func,
|
||||
onShowFileSizeWarning: PropTypes.func,
|
||||
theme: PropTypes.object.isRequired,
|
||||
uploadFiles: PropTypes.func.isRequired,
|
||||
wrapper: PropTypes.bool,
|
||||
@@ -42,11 +46,17 @@ export default class AttachmentButton extends PureComponent {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
attachFileFromCamera = async () => {
|
||||
attachPhotoFromCamera = () => {
|
||||
return this.attachFileFromCamera('photo');
|
||||
};
|
||||
|
||||
attachFileFromCamera = async (mediaType) => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const options = {
|
||||
quality: 1.0,
|
||||
quality: 1,
|
||||
videoQuality: 'high',
|
||||
noData: true,
|
||||
mediaType,
|
||||
storageOptions: {
|
||||
cameraRoll: true,
|
||||
waitUntilSaved: true,
|
||||
@@ -84,7 +94,7 @@ export default class AttachmentButton extends PureComponent {
|
||||
attachFileFromLibrary = () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const options = {
|
||||
quality: 1.0,
|
||||
quality: 1,
|
||||
noData: true,
|
||||
permissionDenied: {
|
||||
title: formatMessage({
|
||||
@@ -116,10 +126,14 @@ export default class AttachmentButton extends PureComponent {
|
||||
});
|
||||
};
|
||||
|
||||
attachVideoFromCamera = () => {
|
||||
return this.attachFileFromCamera('video');
|
||||
};
|
||||
|
||||
attachVideoFromLibraryAndroid = () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const options = {
|
||||
quality: 1.0,
|
||||
videoQuality: 'high',
|
||||
mediaType: 'video',
|
||||
noData: true,
|
||||
permissionDenied: {
|
||||
@@ -283,8 +297,20 @@ export default class AttachmentButton extends PureComponent {
|
||||
return true;
|
||||
};
|
||||
|
||||
uploadFiles = (images) => {
|
||||
this.props.uploadFiles(images);
|
||||
uploadFiles = async (files) => {
|
||||
const file = files[0];
|
||||
if (!file.fileSize | !file.fileName) {
|
||||
const path = (file.path || file.uri).replace('file://', '');
|
||||
const fileInfo = await RNFetchBlob.fs.stat(path);
|
||||
file.fileSize = fileInfo.size;
|
||||
file.fileName = fileInfo.filename;
|
||||
}
|
||||
|
||||
if (file.fileSize > this.props.maxFileSize) {
|
||||
this.props.onShowFileSizeWarning(file.fileName);
|
||||
} else {
|
||||
this.props.uploadFiles(files);
|
||||
}
|
||||
};
|
||||
|
||||
handleFileAttachmentOption = (action) => {
|
||||
@@ -313,12 +339,19 @@ export default class AttachmentButton extends PureComponent {
|
||||
this.props.blurTextBox();
|
||||
const options = {
|
||||
items: [{
|
||||
action: () => this.handleFileAttachmentOption(this.attachFileFromCamera),
|
||||
action: () => this.handleFileAttachmentOption(this.attachPhotoFromCamera),
|
||||
text: {
|
||||
id: t('mobile.file_upload.camera'),
|
||||
defaultMessage: 'Take Photo or Video',
|
||||
id: t('mobile.file_upload.camera_photo'),
|
||||
defaultMessage: 'Take Photo',
|
||||
},
|
||||
icon: 'camera',
|
||||
}, {
|
||||
action: () => this.handleFileAttachmentOption(this.attachVideoFromCamera),
|
||||
text: {
|
||||
id: t('mobile.file_upload.camera_video'),
|
||||
defaultMessage: 'Take Video',
|
||||
},
|
||||
icon: 'video-camera',
|
||||
}, {
|
||||
action: () => this.handleFileAttachmentOption(this.attachFileFromLibrary),
|
||||
text: {
|
||||
|
||||
@@ -26,8 +26,8 @@ export default class AtMention extends PureComponent {
|
||||
defaultChannel: PropTypes.object,
|
||||
inChannel: PropTypes.array,
|
||||
isSearch: PropTypes.bool,
|
||||
listHeight: PropTypes.number,
|
||||
matchTerm: PropTypes.string,
|
||||
maxListHeight: PropTypes.number,
|
||||
onChangeText: PropTypes.func.isRequired,
|
||||
onResultCountChange: PropTypes.func.isRequired,
|
||||
outChannel: PropTypes.array,
|
||||
@@ -204,7 +204,7 @@ export default class AtMention extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {isSearch, listHeight, theme} = this.props;
|
||||
const {maxListHeight, theme} = this.props;
|
||||
const {mentionComplete, sections} = this.state;
|
||||
|
||||
if (sections.length === 0 || mentionComplete) {
|
||||
@@ -219,7 +219,7 @@ export default class AtMention extends PureComponent {
|
||||
<SectionList
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyExtractor={this.keyExtractor}
|
||||
style={[style.listView, isSearch ? [style.search, {height: listHeight}] : null]}
|
||||
style={[style.listView, {maxHeight: maxListHeight}]}
|
||||
sections={sections}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
@@ -235,8 +235,5 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
listView: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
search: {
|
||||
minHeight: 125,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ export default class Autocomplete extends PureComponent {
|
||||
cursorPosition: PropTypes.number.isRequired,
|
||||
deviceHeight: PropTypes.number,
|
||||
onChangeText: PropTypes.func.isRequired,
|
||||
maxHeight: PropTypes.number,
|
||||
rootId: PropTypes.string,
|
||||
isSearch: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
@@ -84,12 +85,21 @@ export default class Autocomplete extends PureComponent {
|
||||
this.setState({keyboardOffset: 0});
|
||||
};
|
||||
|
||||
listHeight() {
|
||||
let offset = Platform.select({ios: 65, android: 75});
|
||||
if (DeviceInfo.getModel().includes('iPhone X')) {
|
||||
offset = 90;
|
||||
maxListHeight() {
|
||||
let maxHeight;
|
||||
if (this.props.maxHeight) {
|
||||
maxHeight = this.props.maxHeight;
|
||||
} else {
|
||||
// List is expanding downwards, likely from the search box
|
||||
let offset = Platform.select({ios: 65, android: 75});
|
||||
if (DeviceInfo.getModel().includes('iPhone X')) {
|
||||
offset = 90;
|
||||
}
|
||||
|
||||
maxHeight = this.props.deviceHeight - offset - this.state.keyboardOffset;
|
||||
}
|
||||
return this.props.deviceHeight - offset - this.state.keyboardOffset;
|
||||
|
||||
return maxHeight;
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -113,26 +123,29 @@ export default class Autocomplete extends PureComponent {
|
||||
containerStyle.push(style.borders);
|
||||
}
|
||||
}
|
||||
const listHeight = this.listHeight();
|
||||
|
||||
const maxListHeight = this.maxListHeight();
|
||||
|
||||
return (
|
||||
<View style={wrapperStyle}>
|
||||
<View style={containerStyle}>
|
||||
<AtMention
|
||||
listHeight={listHeight}
|
||||
maxListHeight={maxListHeight}
|
||||
onResultCountChange={this.handleAtMentionCountChange}
|
||||
{...this.props}
|
||||
/>
|
||||
<ChannelMention
|
||||
listHeight={listHeight}
|
||||
maxListHeight={maxListHeight}
|
||||
onResultCountChange={this.handleChannelMentionCountChange}
|
||||
{...this.props}
|
||||
/>
|
||||
<EmojiSuggestion
|
||||
maxListHeight={maxListHeight}
|
||||
onResultCountChange={this.handleEmojiCountChange}
|
||||
{...this.props}
|
||||
/>
|
||||
<SlashSuggestion
|
||||
maxListHeight={maxListHeight}
|
||||
onResultCountChange={this.handleCommandCountChange}
|
||||
{...this.props}
|
||||
/>
|
||||
@@ -167,7 +180,6 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
},
|
||||
container: {
|
||||
bottom: 0,
|
||||
maxHeight: 200,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
|
||||
@@ -25,8 +25,8 @@ export default class ChannelMention extends PureComponent {
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
cursorPosition: PropTypes.number.isRequired,
|
||||
isSearch: PropTypes.bool,
|
||||
listHeight: PropTypes.number,
|
||||
matchTerm: PropTypes.string,
|
||||
maxListHeight: PropTypes.number,
|
||||
myChannels: PropTypes.array,
|
||||
myMembers: PropTypes.object,
|
||||
otherChannels: PropTypes.array,
|
||||
@@ -194,7 +194,7 @@ export default class ChannelMention extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {isSearch, listHeight, theme} = this.props;
|
||||
const {maxListHeight, theme} = this.props;
|
||||
const {mentionComplete, sections} = this.state;
|
||||
|
||||
if (sections.length === 0 || mentionComplete) {
|
||||
@@ -209,7 +209,7 @@ export default class ChannelMention extends PureComponent {
|
||||
<SectionList
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyExtractor={this.keyExtractor}
|
||||
style={[style.listView, isSearch ? [style.search, {height: listHeight}] : null]}
|
||||
style={[style.listView, {maxHeight: maxListHeight}]}
|
||||
sections={sections}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
@@ -225,8 +225,5 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
listView: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
search: {
|
||||
minHeight: 125,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -15,7 +15,6 @@ import {changeOpacity} from 'app/utils/theme';
|
||||
export default class DateSuggestion extends PureComponent {
|
||||
static propTypes = {
|
||||
cursorPosition: PropTypes.number.isRequired,
|
||||
listHeight: PropTypes.number,
|
||||
locale: PropTypes.string.isRequired,
|
||||
matchTerm: PropTypes.string,
|
||||
onChangeText: PropTypes.func.isRequired,
|
||||
|
||||
@@ -29,6 +29,7 @@ export default class EmojiSuggestion extends Component {
|
||||
emojis: PropTypes.array.isRequired,
|
||||
isSearch: PropTypes.bool,
|
||||
fuse: PropTypes.object.isRequired,
|
||||
maxListHeight: PropTypes.number,
|
||||
theme: PropTypes.object.isRequired,
|
||||
onChangeText: PropTypes.func.isRequired,
|
||||
onResultCountChange: PropTypes.func.isRequired,
|
||||
@@ -171,18 +172,20 @@ export default class EmojiSuggestion extends Component {
|
||||
getItemLayout = ({index}) => ({length: 40, offset: 40 * index, index})
|
||||
|
||||
render() {
|
||||
const {maxListHeight, theme} = this.props;
|
||||
|
||||
if (!this.state.active) {
|
||||
// If we are not in an active state return null so nothing is rendered
|
||||
// other components are not blocked.
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = getStyleFromTheme(this.props.theme);
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
keyboardShouldPersistTaps='always'
|
||||
style={style.listView}
|
||||
style={[style.listView, {maxHeight: maxListHeight}]}
|
||||
extraData={this.state}
|
||||
data={this.state.dataSource}
|
||||
keyExtractor={this.keyExtractor}
|
||||
|
||||
@@ -25,6 +25,7 @@ export default class SlashSuggestion extends Component {
|
||||
commands: PropTypes.array,
|
||||
commandsRequest: PropTypes.object.isRequired,
|
||||
isSearch: PropTypes.bool,
|
||||
maxListHeight: PropTypes.number,
|
||||
theme: PropTypes.object.isRequired,
|
||||
onChangeText: PropTypes.func.isRequired,
|
||||
onResultCountChange: PropTypes.func.isRequired,
|
||||
@@ -133,18 +134,20 @@ export default class SlashSuggestion extends Component {
|
||||
)
|
||||
|
||||
render() {
|
||||
const {maxListHeight, theme} = this.props;
|
||||
|
||||
if (!this.state.active) {
|
||||
// If we are not in an active state return null so nothing is rendered
|
||||
// other components are not blocked.
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = getStyleFromTheme(this.props.theme);
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
keyboardShouldPersistTaps='always'
|
||||
style={style.listView}
|
||||
style={[style.listView, {maxHeight: maxListHeight}]}
|
||||
extraData={this.state}
|
||||
data={this.state.dataSource}
|
||||
keyExtractor={this.keyExtractor}
|
||||
|
||||
@@ -23,7 +23,6 @@ export default class ChannelIcon extends React.PureComponent {
|
||||
membersCount: PropTypes.number,
|
||||
size: PropTypes.number,
|
||||
status: PropTypes.string,
|
||||
teammateDeletedAt: PropTypes.number,
|
||||
theme: PropTypes.object.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
isArchived: PropTypes.bool.isRequired,
|
||||
@@ -46,7 +45,6 @@ export default class ChannelIcon extends React.PureComponent {
|
||||
size,
|
||||
status,
|
||||
theme,
|
||||
teammateDeletedAt,
|
||||
type,
|
||||
isArchived,
|
||||
} = this.props;
|
||||
@@ -116,13 +114,6 @@ export default class ChannelIcon extends React.PureComponent {
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
} else if (type === General.DM_CHANNEL && teammateDeletedAt) {
|
||||
icon = (
|
||||
<Image
|
||||
source={require('assets/images/status/archive_avatar.png')}
|
||||
style={{width: size, height: size, tintColor: offlineColor}}
|
||||
/>
|
||||
);
|
||||
} else if (type === General.DM_CHANNEL) {
|
||||
switch (status) {
|
||||
case General.AWAY:
|
||||
|
||||
@@ -27,7 +27,7 @@ exports[`CustomList should match snapshot 1`] = `
|
||||
scrollRenderAheadDistance={0}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#aaa",
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ exports[`CustomList should match snapshot, renderFooter 2`] = `
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#aaa",
|
||||
"backgroundColor": "#ffffff",
|
||||
"height": 70,
|
||||
"justifyContent": "center",
|
||||
}
|
||||
@@ -53,7 +53,7 @@ exports[`CustomList should match snapshot, renderFooter 2`] = `
|
||||
id="mobile.loading_members"
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(170,170,170,0.6)",
|
||||
"color": "rgba(61,60,64,0.6)",
|
||||
}
|
||||
}
|
||||
/>
|
||||
@@ -64,14 +64,14 @@ exports[`CustomList should match snapshot, renderSectionHeader 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#aaa",
|
||||
"backgroundColor": "#ffffff",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(170,170,170,0.07)",
|
||||
"backgroundColor": "rgba(61,60,64,0.07)",
|
||||
"paddingLeft": 10,
|
||||
"paddingVertical": 2,
|
||||
}
|
||||
@@ -80,7 +80,7 @@ exports[`CustomList should match snapshot, renderSectionHeader 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"color": "#aaa",
|
||||
"color": "#3d3c40",
|
||||
"fontWeight": "600",
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ exports[`CustomList should match snapshot, renderSeparator 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(170,170,170,0.1)",
|
||||
"backgroundColor": "rgba(61,60,64,0.1)",
|
||||
"flex": 1,
|
||||
"height": 1,
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ export default class ChannelListRow extends React.PureComponent {
|
||||
return (
|
||||
<CustomListRow
|
||||
id={this.props.id}
|
||||
theme={this.props.theme}
|
||||
onPress={this.props.onPress ? this.onPress : null}
|
||||
enabled={this.props.enabled}
|
||||
selectable={this.props.selectable}
|
||||
@@ -84,6 +83,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
paddingHorizontal: 15,
|
||||
},
|
||||
purpose: {
|
||||
marginTop: 7,
|
||||
|
||||
@@ -4,17 +4,16 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import ConditionalTouchable from 'app/components/conditional_touchable';
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
export default class CustomListRow extends React.PureComponent {
|
||||
static propTypes = {
|
||||
theme: PropTypes.object.isRequired,
|
||||
onPress: PropTypes.func,
|
||||
enabled: PropTypes.bool,
|
||||
selectable: PropTypes.bool,
|
||||
@@ -28,12 +27,11 @@ export default class CustomListRow extends React.PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const style = getStyleFromTheme(this.props.theme);
|
||||
|
||||
return (
|
||||
<ConditionalTouchable
|
||||
touchable={Boolean(this.props.enabled && this.props.onPress)}
|
||||
onPress={this.props.onPress}
|
||||
style={style.touchable}
|
||||
>
|
||||
<View style={style.container}>
|
||||
{this.props.selectable &&
|
||||
@@ -58,40 +56,40 @@ export default class CustomListRow extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
height: 65,
|
||||
paddingHorizontal: 15,
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
children: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
selector: {
|
||||
height: 28,
|
||||
width: 28,
|
||||
borderRadius: 14,
|
||||
borderWidth: 1,
|
||||
borderColor: '#888',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
selectorContainer: {
|
||||
flex: 1,
|
||||
height: 50,
|
||||
paddingRight: 15,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
selectorDisabled: {
|
||||
backgroundColor: '#888',
|
||||
},
|
||||
selectorFilled: {
|
||||
backgroundColor: '#378FD2',
|
||||
borderWidth: 0,
|
||||
},
|
||||
};
|
||||
const style = StyleSheet.create({
|
||||
touchable: {
|
||||
flex: 1,
|
||||
},
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
height: 65,
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
},
|
||||
children: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
selector: {
|
||||
height: 28,
|
||||
width: 28,
|
||||
borderRadius: 14,
|
||||
borderWidth: 1,
|
||||
borderColor: '#888',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
selectorContainer: {
|
||||
height: 50,
|
||||
paddingRight: 10,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
selectorDisabled: {
|
||||
backgroundColor: '#888',
|
||||
},
|
||||
selectorFilled: {
|
||||
backgroundColor: '#378FD2',
|
||||
borderWidth: 0,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import {createMembersSections, loadingText} from 'app/utils/member_list';
|
||||
|
||||
import CustomList from './index';
|
||||
@@ -13,7 +15,7 @@ describe('CustomList', () => {
|
||||
|
||||
const baseProps = {
|
||||
data: [{username: 'username_1'}, {username: 'username_2'}],
|
||||
theme: {centerChannelBg: '#aaa', centerChannelColor: '#aaa'},
|
||||
theme: Preferences.THEMES.default,
|
||||
searching: false,
|
||||
onListEndReached: emptyFunc,
|
||||
onListEndReachedThreshold: 0,
|
||||
|
||||
@@ -44,7 +44,6 @@ export default class OptionListRow extends React.PureComponent {
|
||||
return (
|
||||
<CustomListRow
|
||||
id={value}
|
||||
theme={theme}
|
||||
onPress={this.onPress}
|
||||
enabled={enabled}
|
||||
selectable={selectable}
|
||||
|
||||
@@ -6,7 +6,7 @@ exports[`UserListRow should match snapshot 1`] = `
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"marginLeft": 10,
|
||||
"marginHorizontal": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -14,41 +14,13 @@ exports[`UserListRow should match snapshot 1`] = `
|
||||
enabled={true}
|
||||
id="21345"
|
||||
onPress={[Function]}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"color": "#3d3c40",
|
||||
"flexDirection": "row",
|
||||
"marginLeft": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -59,14 +31,12 @@ exports[`UserListRow should match snapshot 1`] = `
|
||||
</Component>
|
||||
<Component
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"marginLeft": 5,
|
||||
},
|
||||
Object {
|
||||
"justifyContent": "center",
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column",
|
||||
"justifyContent": "center",
|
||||
"marginLeft": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component>
|
||||
@@ -84,13 +54,6 @@ exports[`UserListRow should match snapshot 1`] = `
|
||||
</Component>
|
||||
</Component>
|
||||
</Component>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"width": 25,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</CustomListRow>
|
||||
</Component>
|
||||
`;
|
||||
@@ -101,7 +64,7 @@ exports[`UserListRow should match snapshot for currentUser with (you) populated
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"marginLeft": 10,
|
||||
"marginHorizontal": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -109,41 +72,13 @@ exports[`UserListRow should match snapshot for currentUser with (you) populated
|
||||
enabled={true}
|
||||
id="21345"
|
||||
onPress={[Function]}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"color": "#3d3c40",
|
||||
"flexDirection": "row",
|
||||
"marginLeft": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -154,14 +89,12 @@ exports[`UserListRow should match snapshot for currentUser with (you) populated
|
||||
</Component>
|
||||
<Component
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"marginLeft": 5,
|
||||
},
|
||||
Object {
|
||||
"justifyContent": "center",
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column",
|
||||
"justifyContent": "center",
|
||||
"marginLeft": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component>
|
||||
@@ -177,13 +110,6 @@ exports[`UserListRow should match snapshot for currentUser with (you) populated
|
||||
/>
|
||||
</Component>
|
||||
</Component>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"width": 25,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</CustomListRow>
|
||||
</Component>
|
||||
`;
|
||||
@@ -194,7 +120,7 @@ exports[`UserListRow should match snapshot for deactivated user 1`] = `
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"marginLeft": 10,
|
||||
"marginHorizontal": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -202,41 +128,13 @@ exports[`UserListRow should match snapshot for deactivated user 1`] = `
|
||||
enabled={true}
|
||||
id="21345"
|
||||
onPress={[Function]}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"color": "#3d3c40",
|
||||
"flexDirection": "row",
|
||||
"marginLeft": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -247,14 +145,12 @@ exports[`UserListRow should match snapshot for deactivated user 1`] = `
|
||||
</Component>
|
||||
<Component
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"marginLeft": 5,
|
||||
},
|
||||
Object {
|
||||
"justifyContent": "center",
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column",
|
||||
"justifyContent": "center",
|
||||
"marginLeft": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component>
|
||||
@@ -267,16 +163,22 @@ exports[`UserListRow should match snapshot for deactivated user 1`] = `
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
@user
|
||||
</Component>
|
||||
</Component>
|
||||
<Component>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"fontSize": 12,
|
||||
"marginTop": 2,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
</Component>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"width": 25,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</CustomListRow>
|
||||
</Component>
|
||||
`;
|
||||
|
||||
@@ -58,13 +58,6 @@ export default class UserListRow extends React.PureComponent {
|
||||
}, {username});
|
||||
}
|
||||
|
||||
if (user.delete_at > 0) {
|
||||
usernameDisplay = formatMessage({
|
||||
id: 'more_direct_channels.directchannel.deactivated',
|
||||
defaultMessage: '{displayname} - Deactivated',
|
||||
}, {displayname: usernameDisplay});
|
||||
}
|
||||
|
||||
const teammateDisplay = displayUsername(user, teammateNameDisplay);
|
||||
const showTeammateDisplay = teammateDisplay !== username;
|
||||
|
||||
@@ -72,7 +65,6 @@ export default class UserListRow extends React.PureComponent {
|
||||
<View style={style.container}>
|
||||
<CustomListRow
|
||||
id={id}
|
||||
theme={theme}
|
||||
onPress={this.onPress}
|
||||
enabled={enabled}
|
||||
selectable={selectable}
|
||||
@@ -84,7 +76,7 @@ export default class UserListRow extends React.PureComponent {
|
||||
size={32}
|
||||
/>
|
||||
</View>
|
||||
<View style={[style.textContainer, (showTeammateDisplay ? style.showTeammateDisplay : style.hideTeammateDisplay)]}>
|
||||
<View style={style.textContainer}>
|
||||
<View>
|
||||
<Text
|
||||
style={style.username}
|
||||
@@ -105,8 +97,16 @@ export default class UserListRow extends React.PureComponent {
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
{user.delete_at > 0 &&
|
||||
<View>
|
||||
<Text
|
||||
style={style.deactivated}
|
||||
>
|
||||
{formatMessage({id: 'mobile.user_list.deactivated', defaultMessage: 'Deactivated'})}
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
</View>
|
||||
<View style={style.rightFiller}/>
|
||||
</CustomListRow>
|
||||
</View>
|
||||
);
|
||||
@@ -118,23 +118,19 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
marginLeft: 10,
|
||||
marginHorizontal: 10,
|
||||
},
|
||||
profileContainer: {
|
||||
flexDirection: 'row',
|
||||
marginLeft: 10,
|
||||
alignItems: 'center',
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
textContainer: {
|
||||
marginLeft: 5,
|
||||
},
|
||||
showTeammateDisplay: {
|
||||
marginLeft: 10,
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
},
|
||||
hideTeammateDisplay: {
|
||||
justifyContent: 'center',
|
||||
},
|
||||
displayName: {
|
||||
fontSize: 15,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
@@ -143,8 +139,10 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
rightFiller: {
|
||||
width: 25,
|
||||
deactivated: {
|
||||
marginTop: 2,
|
||||
fontSize: 12,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {configure, shallow} from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import UserListRow from './user_list_row';
|
||||
|
||||
configure({adapter: new Adapter()});
|
||||
|
||||
jest.mock('react-intl');
|
||||
jest.mock('app/utils/theme', () => {
|
||||
const original = require.requireActual('app/utils/theme');
|
||||
|
||||
@@ -254,8 +254,6 @@ export default class CustomSectionList extends React.PureComponent {
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
listView: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
...Platform.select({
|
||||
android: {
|
||||
marginBottom: 20,
|
||||
|
||||
@@ -457,8 +457,9 @@ export default class EmojiPicker extends PureComponent {
|
||||
<SafeAreaView excludeHeader={true}>
|
||||
<KeyboardAvoidingView
|
||||
behavior='padding'
|
||||
style={{flex: 1}}
|
||||
style={styles.flex}
|
||||
keyboardVerticalOffset={keyboardOffset}
|
||||
enabled={Platform.OS === 'ios'}
|
||||
>
|
||||
<View style={styles.searchBar}>
|
||||
<SearchBar
|
||||
@@ -496,6 +497,9 @@ export default class EmojiPicker extends PureComponent {
|
||||
|
||||
const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
bottomContent: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.3),
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ErrorText should match snapshot 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "red",
|
||||
"fontSize": 12,
|
||||
"marginBottom": 15,
|
||||
"marginTop": 15,
|
||||
"textAlign": "left",
|
||||
},
|
||||
Object {
|
||||
"color": "#fd5960",
|
||||
},
|
||||
Object {
|
||||
"fontSize": 14,
|
||||
"marginHorizontal": 15,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
Username must begin with a letter and contain between 3 and 22 characters including numbers, lowercase letters, and the symbols
|
||||
</Component>
|
||||
`;
|
||||
@@ -1,18 +1,16 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {GlobalStyles} from 'app/styles';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
class ErrorText extends PureComponent {
|
||||
export default class ErrorText extends PureComponent {
|
||||
static propTypes = {
|
||||
error: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
textStyle: CustomPropTypes.Style,
|
||||
@@ -54,12 +52,3 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ErrorText);
|
||||
29
app/components/error_text/error_text.test.js
Normal file
29
app/components/error_text/error_text.test.js
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import ErrorText from './error_text.js';
|
||||
|
||||
describe('ErrorText', () => {
|
||||
const baseProps = {
|
||||
textStyle: {
|
||||
fontSize: 14,
|
||||
marginHorizontal: 15,
|
||||
},
|
||||
theme: Preferences.THEMES.default,
|
||||
error: {
|
||||
message: 'Username must begin with a letter and contain between 3 and 22 characters including numbers, lowercase letters, and the symbols',
|
||||
},
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<ErrorText {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
15
app/components/error_text/index.js
Normal file
15
app/components/error_text/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import ErrorText from './error_text.js';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ErrorText);
|
||||
@@ -86,6 +86,7 @@ export default class FileAttachmentIcon extends PureComponent {
|
||||
const styles = StyleSheet.create({
|
||||
fileIconWrapper: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#fff',
|
||||
justifyContent: 'center',
|
||||
borderTopLeftRadius: 2,
|
||||
borderBottomLeftRadius: 2,
|
||||
|
||||
@@ -107,8 +107,8 @@ export default class FileAttachmentImage extends PureComponent {
|
||||
let width = imageWidth;
|
||||
let imageStyle = {height, width};
|
||||
if (imageSize === IMAGE_SIZE.Preview) {
|
||||
height = 100;
|
||||
width = this.calculateNeededWidth(file.height, file.width, height) || 100;
|
||||
height = 80;
|
||||
width = this.calculateNeededWidth(file.height, file.width, height) || 80;
|
||||
imageStyle = {height, width, position: 'absolute', top: 0, left: 0, borderBottomLeftRadius: 2, borderTopLeftRadius: 2};
|
||||
}
|
||||
|
||||
|
||||
@@ -175,6 +175,7 @@ export default class FileUploadItem extends PureComponent {
|
||||
filePreviewComponent = (
|
||||
<FileAttachmentImage
|
||||
file={file}
|
||||
imageSize='fullsize'
|
||||
imageHeight={100}
|
||||
imageWidth={100}
|
||||
wrapperHeight={100}
|
||||
|
||||
@@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
|
||||
import {
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
@@ -17,11 +18,10 @@ export default class FileUploadPreview extends PureComponent {
|
||||
static propTypes = {
|
||||
channelId: PropTypes.string.isRequired,
|
||||
channelIsLoading: PropTypes.bool,
|
||||
createPostRequestStatus: PropTypes.string.isRequired,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
files: PropTypes.array.isRequired,
|
||||
filesUploadingForCurrentChannel: PropTypes.bool.isRequired,
|
||||
inputHeight: PropTypes.number.isRequired,
|
||||
fileSizeWarning: PropTypes.string,
|
||||
rootId: PropTypes.string,
|
||||
showFileMaxWarning: PropTypes.bool.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
@@ -44,12 +44,17 @@ export default class FileUploadPreview extends PureComponent {
|
||||
render() {
|
||||
const {
|
||||
showFileMaxWarning,
|
||||
fileSizeWarning,
|
||||
channelIsLoading,
|
||||
filesUploadingForCurrentChannel,
|
||||
deviceHeight,
|
||||
files,
|
||||
} = this.props;
|
||||
if (channelIsLoading || (!files.length && !filesUploadingForCurrentChannel)) {
|
||||
|
||||
if (
|
||||
!fileSizeWarning && !showFileMaxWarning &&
|
||||
(channelIsLoading || (!files.length && !filesUploadingForCurrentChannel))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -70,7 +75,11 @@ export default class FileUploadPreview extends PureComponent {
|
||||
defaultMessage='Uploads limited to 5 files maximum.'
|
||||
/>
|
||||
)}
|
||||
|
||||
{Boolean(fileSizeWarning) &&
|
||||
<Text style={style.warning}>
|
||||
{fileSizeWarning}
|
||||
</Text>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,6 @@ function mapStateToProps(state, ownProps) {
|
||||
|
||||
return {
|
||||
channelIsLoading: state.views.channel.loading,
|
||||
createPostRequestStatus: state.requests.posts.createPost.status,
|
||||
deviceHeight,
|
||||
filesUploadingForCurrentChannel: checkForFileUploadingInChannel(state, ownProps.channelId, ownProps.rootId),
|
||||
theme: getTheme(state),
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {getStatusBarHeight} from 'app/selectors/device';
|
||||
|
||||
import KeyboardLayout from './keyboard_layout';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
statusBarHeight: getStatusBarHeight(state),
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(KeyboardLayout);
|
||||
export default KeyboardLayout;
|
||||
|
||||
@@ -3,34 +3,33 @@
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Keyboard, Platform, View} from 'react-native';
|
||||
import {
|
||||
Keyboard,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import * as CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
|
||||
export default class KeyboardLayout extends PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
statusBarHeight: PropTypes.number,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
keyboardVerticalOffset: 0,
|
||||
style: CustomPropTypes.Style,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.subscriptions = [];
|
||||
this.count = 0;
|
||||
this.state = {
|
||||
bottom: 0,
|
||||
keyboardHeight: 0,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
if (Platform.OS === 'ios') {
|
||||
this.subscriptions = [
|
||||
Keyboard.addListener('keyboardWillChangeFrame', this.onKeyboardChange),
|
||||
Keyboard.addListener('keyboardWillShow', this.onKeyboardWillShow),
|
||||
Keyboard.addListener('keyboardWillHide', this.onKeyboardWillHide),
|
||||
];
|
||||
}
|
||||
@@ -41,52 +40,36 @@ export default class KeyboardLayout extends PureComponent {
|
||||
}
|
||||
|
||||
onKeyboardWillHide = () => {
|
||||
this.setState({bottom: 0});
|
||||
this.setState({
|
||||
keyboardHeight: 0,
|
||||
});
|
||||
};
|
||||
|
||||
onKeyboardChange = (e) => {
|
||||
if (!e) {
|
||||
this.setState({bottom: 0});
|
||||
return;
|
||||
}
|
||||
|
||||
const {endCoordinates} = e;
|
||||
const {height} = endCoordinates;
|
||||
|
||||
this.setState({bottom: height});
|
||||
onKeyboardWillShow = (e) => {
|
||||
this.setState({
|
||||
keyboardHeight: e?.endCoordinates?.height || 0,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {children, theme, ...otherProps} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
const layoutStyle = [this.props.style, style.keyboardLayout];
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
return (
|
||||
<View
|
||||
style={style.keyboardLayout}
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
if (Platform.OS === 'ios') {
|
||||
// iOS doesn't resize the app automatically
|
||||
layoutStyle.push({paddingBottom: this.state.keyboardHeight});
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[style.keyboardLayout, {marginBottom: this.state.bottom}]}
|
||||
>
|
||||
{children}
|
||||
<View style={layoutStyle}>
|
||||
{this.props.children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
keyboardLayout: {
|
||||
position: 'relative',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
flex: 1,
|
||||
},
|
||||
};
|
||||
const style = StyleSheet.create({
|
||||
keyboardLayout: {
|
||||
position: 'relative',
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import {PropTypes} from 'prop-types';
|
||||
import React from 'react';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {
|
||||
Clipboard,
|
||||
StyleSheet,
|
||||
@@ -21,9 +21,8 @@ import mattermostManaged from 'app/mattermost_managed';
|
||||
|
||||
const MAX_LINES = 4;
|
||||
|
||||
class MarkdownCodeBlock extends React.PureComponent {
|
||||
export default class MarkdownCodeBlock extends React.PureComponent {
|
||||
static propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
navigator: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
language: PropTypes.string,
|
||||
@@ -36,8 +35,13 @@ class MarkdownCodeBlock extends React.PureComponent {
|
||||
language: '',
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape,
|
||||
};
|
||||
|
||||
handlePress = preventDoubleTap(() => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
const {navigator, theme} = this.props;
|
||||
const {intl} = this.context;
|
||||
|
||||
const languageDisplayName = getDisplayNameForLanguage(this.props.language);
|
||||
let title;
|
||||
@@ -76,7 +80,7 @@ class MarkdownCodeBlock extends React.PureComponent {
|
||||
});
|
||||
|
||||
handleLongPress = async () => {
|
||||
const {formatMessage} = this.props.intl;
|
||||
const {formatMessage} = this.context.intl;
|
||||
|
||||
const config = await mattermostManaged.getLocalConfig();
|
||||
|
||||
@@ -238,5 +242,3 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default injectIntl(MarkdownCodeBlock);
|
||||
|
||||
@@ -15,7 +15,7 @@ import {ViewTypes} from 'app/constants';
|
||||
export default class ActionMenu extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
doPostAction: PropTypes.func.isRequired,
|
||||
selectAttachmentMenuAction: PropTypes.func.isRequired,
|
||||
setMenuActionSelector: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
@@ -23,6 +23,7 @@ export default class ActionMenu extends PureComponent {
|
||||
dataSource: PropTypes.string,
|
||||
options: PropTypes.arrayOf(PropTypes.object),
|
||||
postId: PropTypes.string.isRequired,
|
||||
selected: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
};
|
||||
@@ -39,6 +40,17 @@ export default class ActionMenu extends PureComponent {
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
if (props.selected && props.selected !== state.selected) {
|
||||
return {
|
||||
selectedText: props.selected.displayText,
|
||||
selected: props.selected,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
handleSelect = (selected) => {
|
||||
if (!selected) {
|
||||
return;
|
||||
@@ -61,7 +73,7 @@ export default class ActionMenu extends PureComponent {
|
||||
|
||||
this.setState({selectedText});
|
||||
|
||||
actions.doPostAction(postId, id, selectedValue);
|
||||
actions.selectAttachmentMenuAction(postId, id, dataSource, selectedText, selectedValue);
|
||||
}
|
||||
|
||||
goToMenuActionSelector = preventDoubleTap(() => {
|
||||
|
||||
@@ -4,23 +4,23 @@
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {doPostAction} from 'mattermost-redux/actions/posts';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {setMenuActionSelector} from 'app/actions/views/post';
|
||||
import {setMenuActionSelector, selectAttachmentMenuAction} from 'app/actions/views/post';
|
||||
|
||||
import ActionMenu from './action_menu';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
selected: state.views.post.submittedMenuActions[ownProps.postId],
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
doPostAction,
|
||||
selectAttachmentMenuAction,
|
||||
setMenuActionSelector,
|
||||
}, dispatch),
|
||||
};
|
||||
|
||||
@@ -6,7 +6,8 @@ exports[`PostAttachmentOpenGraph should match snapshot, without image and descri
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"borderColor": "rgba(170,170,170,0.2)",
|
||||
"borderColor": "rgba(61,60,64,0.2)",
|
||||
"borderRadius": 3,
|
||||
"borderWidth": 1,
|
||||
"flex": 1,
|
||||
"marginTop": 10,
|
||||
@@ -26,7 +27,7 @@ exports[`PostAttachmentOpenGraph should match snapshot, without image and descri
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(170,170,170,0.5)",
|
||||
"color": "rgba(61,60,64,0.5)",
|
||||
"fontSize": 12,
|
||||
"marginBottom": 10,
|
||||
}
|
||||
@@ -58,7 +59,7 @@ exports[`PostAttachmentOpenGraph should match snapshot, without image and descri
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": undefined,
|
||||
"color": "#2389d7",
|
||||
"fontSize": 14,
|
||||
"marginBottom": 10,
|
||||
},
|
||||
@@ -75,6 +76,112 @@ exports[`PostAttachmentOpenGraph should match snapshot, without image and descri
|
||||
</Component>
|
||||
`;
|
||||
|
||||
exports[`PostAttachmentOpenGraph should match snapshot, without site_name 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"borderColor": "rgba(61,60,64,0.2)",
|
||||
"borderRadius": 3,
|
||||
"borderWidth": 1,
|
||||
"flex": 1,
|
||||
"marginTop": 10,
|
||||
"padding": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.2}
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={3}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#2389d7",
|
||||
"fontSize": 14,
|
||||
"marginBottom": 10,
|
||||
},
|
||||
Object {
|
||||
"marginRight": 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
Title
|
||||
</Component>
|
||||
</TouchableOpacity>
|
||||
</Component>
|
||||
</Component>
|
||||
`;
|
||||
|
||||
exports[`PostAttachmentOpenGraph should match snapshot, without title and url 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"borderColor": "rgba(61,60,64,0.2)",
|
||||
"borderRadius": 3,
|
||||
"borderWidth": 1,
|
||||
"flex": 1,
|
||||
"marginTop": 10,
|
||||
"padding": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.2}
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={3}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#2389d7",
|
||||
"fontSize": 14,
|
||||
"marginBottom": 10,
|
||||
},
|
||||
Object {
|
||||
"marginRight": 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
https://mattermost.com/
|
||||
</Component>
|
||||
</TouchableOpacity>
|
||||
</Component>
|
||||
</Component>
|
||||
`;
|
||||
|
||||
exports[`PostAttachmentOpenGraph should match state and snapshot, on renderDescription 1`] = `null`;
|
||||
|
||||
exports[`PostAttachmentOpenGraph should match state and snapshot, on renderDescription 2`] = `
|
||||
@@ -90,7 +197,7 @@ exports[`PostAttachmentOpenGraph should match state and snapshot, on renderDescr
|
||||
numberOfLines={5}
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(170,170,170,0.7)",
|
||||
"color": "rgba(61,60,64,0.7)",
|
||||
"fontSize": 13,
|
||||
"marginBottom": 10,
|
||||
}
|
||||
@@ -108,6 +215,10 @@ exports[`PostAttachmentOpenGraph should match state and snapshot, on renderImage
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"borderColor": "rgba(61,60,64,0.2)",
|
||||
"borderRadius": 3,
|
||||
"borderWidth": 1,
|
||||
"marginTop": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -116,7 +227,7 @@ exports[`PostAttachmentOpenGraph should match state and snapshot, on renderImage
|
||||
style={
|
||||
Object {
|
||||
"height": 150,
|
||||
"width": 312,
|
||||
"width": 307,
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -129,7 +240,7 @@ exports[`PostAttachmentOpenGraph should match state and snapshot, on renderImage
|
||||
},
|
||||
Object {
|
||||
"height": 150,
|
||||
"width": 312,
|
||||
"width": 307,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import {getNearestPoint} from 'app/utils/opengraph';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
const MAX_IMAGE_HEIGHT = 150;
|
||||
const VIEWPORT_IMAGE_OFFSET = 88;
|
||||
const VIEWPORT_IMAGE_OFFSET = 93;
|
||||
const VIEWPORT_IMAGE_REPLY_OFFSET = 13;
|
||||
|
||||
export default class PostAttachmentOpenGraph extends PureComponent {
|
||||
@@ -216,7 +216,6 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
||||
style={{width, height}}
|
||||
>
|
||||
<Image
|
||||
ref='image'
|
||||
style={[style.image, {width, height}]}
|
||||
source={source}
|
||||
resizeMode='contain'
|
||||
@@ -227,7 +226,12 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {isReplyPost, openGraphData, theme} = this.props;
|
||||
const {
|
||||
isReplyPost,
|
||||
link,
|
||||
openGraphData,
|
||||
theme,
|
||||
} = this.props;
|
||||
|
||||
if (!openGraphData) {
|
||||
return null;
|
||||
@@ -235,8 +239,9 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
let siteName;
|
||||
if (openGraphData.site_name) {
|
||||
siteName = (
|
||||
<View style={style.flex}>
|
||||
<Text
|
||||
style={style.siteTitle}
|
||||
@@ -246,6 +251,13 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
||||
{openGraphData.site_name}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const title = openGraphData.title || openGraphData.url || link;
|
||||
let siteTitle;
|
||||
if (title) {
|
||||
siteTitle = (
|
||||
<View style={style.wrapper}>
|
||||
<TouchableOpacity
|
||||
style={style.flex}
|
||||
@@ -256,10 +268,17 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
||||
numberOfLines={3}
|
||||
ellipsizeMode='tail'
|
||||
>
|
||||
{openGraphData.title || openGraphData.url}
|
||||
{title}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
{siteName}
|
||||
{siteTitle}
|
||||
{this.renderDescription()}
|
||||
{this.renderImage()}
|
||||
</View>
|
||||
@@ -273,6 +292,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
flex: 1,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderWidth: 1,
|
||||
borderRadius: 3,
|
||||
marginTop: 10,
|
||||
padding: 10,
|
||||
},
|
||||
@@ -300,6 +320,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
},
|
||||
imageContainer: {
|
||||
alignItems: 'center',
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderWidth: 1,
|
||||
borderRadius: 3,
|
||||
marginTop: 5,
|
||||
},
|
||||
image: {
|
||||
borderRadius: 3,
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import PostAttachmentOpenGraph from './post_attachment_opengraph';
|
||||
|
||||
describe('PostAttachmentOpenGraph', () => {
|
||||
@@ -26,9 +28,7 @@ describe('PostAttachmentOpenGraph', () => {
|
||||
isReplyPost: false,
|
||||
link: 'https://mattermost.com/',
|
||||
navigator: {},
|
||||
theme: {
|
||||
centerChannelColor: '#aaa',
|
||||
},
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
test('should match snapshot, without image and description', () => {
|
||||
@@ -44,6 +44,30 @@ describe('PostAttachmentOpenGraph', () => {
|
||||
expect(wrapper.find(TouchableOpacity).exists()).toEqual(true);
|
||||
});
|
||||
|
||||
test('should match snapshot, without site_name', () => {
|
||||
const newOpenGraphData = {
|
||||
title: 'Title',
|
||||
url: 'https://mattermost.com/',
|
||||
};
|
||||
const wrapper = shallow(
|
||||
<PostAttachmentOpenGraph
|
||||
{...baseProps}
|
||||
openGraphData={newOpenGraphData}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, without title and url', () => {
|
||||
const wrapper = shallow(
|
||||
<PostAttachmentOpenGraph
|
||||
{...baseProps}
|
||||
openGraphData={{}}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match state and snapshot, on renderImage', () => {
|
||||
const wrapper = shallow(
|
||||
<PostAttachmentOpenGraph {...baseProps}/>
|
||||
|
||||
@@ -17,7 +17,7 @@ exports[`DateHeader component should match snapshot with suffix 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#aaa",
|
||||
"backgroundColor": "#3d3c40",
|
||||
"flex": 1,
|
||||
"height": 1,
|
||||
"opacity": 0.2,
|
||||
@@ -36,7 +36,7 @@ exports[`DateHeader component should match snapshot with suffix 1`] = `
|
||||
month="short"
|
||||
style={
|
||||
Object {
|
||||
"color": "#aaa",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 14,
|
||||
"fontWeight": "600",
|
||||
}
|
||||
@@ -49,7 +49,7 @@ exports[`DateHeader component should match snapshot with suffix 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#aaa",
|
||||
"backgroundColor": "#3d3c40",
|
||||
"flex": 1,
|
||||
"height": 1,
|
||||
"opacity": 0.2,
|
||||
@@ -76,7 +76,7 @@ exports[`DateHeader component should match snapshot without suffix 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#aaa",
|
||||
"backgroundColor": "#3d3c40",
|
||||
"flex": 1,
|
||||
"height": 1,
|
||||
"opacity": 0.2,
|
||||
@@ -95,7 +95,7 @@ exports[`DateHeader component should match snapshot without suffix 1`] = `
|
||||
month="short"
|
||||
style={
|
||||
Object {
|
||||
"color": "#aaa",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 14,
|
||||
"fontWeight": "600",
|
||||
}
|
||||
@@ -108,7 +108,7 @@ exports[`DateHeader component should match snapshot without suffix 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#aaa",
|
||||
"backgroundColor": "#3d3c40",
|
||||
"flex": 1,
|
||||
"height": 1,
|
||||
"opacity": 0.2,
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import DateHeader from './date_header.js';
|
||||
|
||||
describe('DateHeader', () => {
|
||||
const baseProps = {
|
||||
theme: {centerChannelBg: '#aaa', centerChannelColor: '#aaa'},
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
describe('component should match snapshot', () => {
|
||||
|
||||
@@ -79,10 +79,11 @@ export default class PostList extends PostListBase {
|
||||
} = this.props;
|
||||
|
||||
const otherProps = {};
|
||||
const footer = typeof this.props.renderFooter === 'object' ? this.props.renderFooter : this.props.renderFooter();
|
||||
if (postIds.length) {
|
||||
otherProps.ListFooterComponent = this.props.renderFooter();
|
||||
otherProps.ListFooterComponent = footer;
|
||||
} else {
|
||||
otherProps.ListEmptyComponent = this.props.renderFooter();
|
||||
otherProps.ListEmptyComponent = footer;
|
||||
}
|
||||
|
||||
const hasPostsKey = postIds.length ? 'true' : 'false';
|
||||
|
||||
@@ -35,7 +35,7 @@ export default class PostListBase extends PureComponent {
|
||||
onPostPress: PropTypes.func,
|
||||
onRefresh: PropTypes.func,
|
||||
postIds: PropTypes.array.isRequired,
|
||||
renderFooter: PropTypes.func,
|
||||
renderFooter: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
|
||||
renderReplies: PropTypes.bool,
|
||||
serverURL: PropTypes.string.isRequired,
|
||||
shouldRenderReplyButton: PropTypes.bool,
|
||||
|
||||
@@ -20,6 +20,7 @@ import {handleCommentDraftChanged, handleCommentDraftSelectionChanged} from 'app
|
||||
import {userTyping} from 'app/actions/views/typing';
|
||||
import {getCurrentChannelDraft, getThreadDraft} from 'app/selectors/views';
|
||||
import {getChannelMembersForDm} from 'app/selectors/channel';
|
||||
import {getAllowedServerMaxFileSize} from 'app/utils/file';
|
||||
|
||||
import PostTextbox from './post_textbox';
|
||||
|
||||
@@ -53,6 +54,7 @@ function mapStateToProps(state, ownProps) {
|
||||
userIsOutOfOffice,
|
||||
deactivatedChannel,
|
||||
files: currentDraft.files,
|
||||
maxFileSize: getAllowedServerMaxFileSize(config),
|
||||
maxMessageLength: (config && parseInt(config.MaxPostSize || 0, 10)) || MAX_MESSAGE_LENGTH,
|
||||
theme: getTheme(state),
|
||||
uploadFileRequestStatus: state.requests.files.uploadFiles.status,
|
||||
|
||||
@@ -8,6 +8,7 @@ import {intlShape} from 'react-intl';
|
||||
import Button from 'react-native-button';
|
||||
import {General, RequestStatus} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
import {getFormattedFileSize} from 'mattermost-redux/utils/file_utils';
|
||||
|
||||
import AttachmentButton from 'app/components/attachment_button';
|
||||
import Autocomplete from 'app/components/autocomplete';
|
||||
@@ -21,6 +22,9 @@ import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
import Typing from './components/typing';
|
||||
|
||||
const AUTOCOMPLETE_MARGIN = 20;
|
||||
const AUTOCOMPLETE_MAX_HEIGHT = 200;
|
||||
|
||||
let PaperPlane = null;
|
||||
|
||||
export default class PostTextbox extends PureComponent {
|
||||
@@ -48,6 +52,7 @@ export default class PostTextbox extends PureComponent {
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
deactivatedChannel: PropTypes.bool.isRequired,
|
||||
files: PropTypes.array,
|
||||
maxFileSize: PropTypes.number.isRequired,
|
||||
maxMessageLength: PropTypes.number.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
rootId: PropTypes.string,
|
||||
@@ -76,6 +81,8 @@ export default class PostTextbox extends PureComponent {
|
||||
contentHeight: INITIAL_HEIGHT,
|
||||
cursorPosition: 0,
|
||||
keyboardType: 'default',
|
||||
fileSizeWarning: null,
|
||||
top: 0,
|
||||
value: props.value,
|
||||
showFileMaxWarning: false,
|
||||
};
|
||||
@@ -105,10 +112,6 @@ export default class PostTextbox extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
attachAutocomplete = (c) => {
|
||||
this.autocomplete = c;
|
||||
};
|
||||
|
||||
blur = () => {
|
||||
if (this.refs.input) {
|
||||
this.refs.input.blur();
|
||||
@@ -458,6 +461,23 @@ export default class PostTextbox extends PureComponent {
|
||||
});
|
||||
};
|
||||
|
||||
onShowFileSizeWarning = (filename) => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const fileSizeWarning = formatMessage({
|
||||
id: 'file_upload.fileAbove',
|
||||
defaultMessage: 'File above {max}MB cannot be uploaded: {filename}',
|
||||
}, {
|
||||
max: getFormattedFileSize({size: this.props.maxFileSize}),
|
||||
filename,
|
||||
});
|
||||
|
||||
this.setState({fileSizeWarning}, () => {
|
||||
setTimeout(() => {
|
||||
this.setState({fileSizeWarning: null});
|
||||
}, 3000);
|
||||
});
|
||||
};
|
||||
|
||||
onCloseChannelPress = () => {
|
||||
const {onCloseChannel, channelTeamId} = this.props;
|
||||
this.props.actions.selectPenultimateChannel(channelTeamId);
|
||||
@@ -487,6 +507,12 @@ export default class PostTextbox extends PureComponent {
|
||||
</View>);
|
||||
};
|
||||
|
||||
handleLayout = (e) => {
|
||||
this.setState({
|
||||
top: e.nativeEvent.layout.y,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {intl} = this.context;
|
||||
const {
|
||||
@@ -496,6 +522,7 @@ export default class PostTextbox extends PureComponent {
|
||||
channelIsReadOnly,
|
||||
deactivatedChannel,
|
||||
files,
|
||||
maxFileSize,
|
||||
navigator,
|
||||
rootId,
|
||||
theme,
|
||||
@@ -514,7 +541,14 @@ export default class PostTextbox extends PureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
const {contentHeight, cursorPosition, showFileMaxWarning, value} = this.state;
|
||||
const {
|
||||
contentHeight,
|
||||
cursorPosition,
|
||||
fileSizeWarning,
|
||||
showFileMaxWarning,
|
||||
top,
|
||||
value,
|
||||
} = this.state;
|
||||
|
||||
const textInputHeight = Math.min(contentHeight, MAX_CONTENT_HEIGHT);
|
||||
const textValue = channelIsLoading ? '' : value;
|
||||
@@ -537,8 +571,10 @@ export default class PostTextbox extends PureComponent {
|
||||
theme={theme}
|
||||
navigator={navigator}
|
||||
fileCount={files.length}
|
||||
maxFileSize={maxFileSize}
|
||||
maxFileCount={MAX_FILE_COUNT}
|
||||
onShowFileMaxWarning={this.onShowFileMaxWarning}
|
||||
onShowFileSizeWarning={this.onShowFileSizeWarning}
|
||||
uploadFiles={this.handleUploadFiles}
|
||||
/>
|
||||
);
|
||||
@@ -547,48 +583,53 @@ export default class PostTextbox extends PureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<React.Fragment>
|
||||
<Typing/>
|
||||
<FileUploadPreview
|
||||
channelId={channelId}
|
||||
files={files}
|
||||
inputHeight={textInputHeight}
|
||||
fileSizeWarning={fileSizeWarning}
|
||||
rootId={rootId}
|
||||
showFileMaxWarning={showFileMaxWarning}
|
||||
/>
|
||||
<Autocomplete
|
||||
ref={this.attachAutocomplete}
|
||||
cursorPosition={cursorPosition}
|
||||
maxHeight={Math.min(top - AUTOCOMPLETE_MARGIN, AUTOCOMPLETE_MAX_HEIGHT)}
|
||||
onChangeText={this.handleTextChange}
|
||||
value={this.state.value}
|
||||
rootId={rootId}
|
||||
/>
|
||||
{!channelIsArchived && <View style={style.inputWrapper}>
|
||||
{!channelIsReadOnly && attachmentButton}
|
||||
<View style={[inputContainerStyle, (channelIsReadOnly && {marginLeft: 10})]}>
|
||||
<TextInput
|
||||
ref='input'
|
||||
value={textValue}
|
||||
onChangeText={this.handleTextChange}
|
||||
onSelectionChange={this.handlePostDraftSelectionChanged}
|
||||
placeholder={intl.formatMessage(placeholder)}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
multiline={true}
|
||||
numberOfLines={5}
|
||||
blurOnSubmit={false}
|
||||
underlineColorAndroid='transparent'
|
||||
style={[style.input, Platform.OS === 'android' ? {height: textInputHeight} : {maxHeight: MAX_CONTENT_HEIGHT}]}
|
||||
onContentSizeChange={this.handleContentSizeChange}
|
||||
keyboardType={this.state.keyboardType}
|
||||
onEndEditing={this.handleEndEditing}
|
||||
disableFullscreenUI={true}
|
||||
editable={!channelIsReadOnly}
|
||||
/>
|
||||
{this.renderSendButton()}
|
||||
{!channelIsArchived && (
|
||||
<View
|
||||
style={style.inputWrapper}
|
||||
onLayout={this.handleLayout}
|
||||
>
|
||||
{!channelIsReadOnly && attachmentButton}
|
||||
<View style={[inputContainerStyle, (channelIsReadOnly && {marginLeft: 10})]}>
|
||||
<TextInput
|
||||
ref='input'
|
||||
value={textValue}
|
||||
onChangeText={this.handleTextChange}
|
||||
onSelectionChange={this.handlePostDraftSelectionChanged}
|
||||
placeholder={intl.formatMessage(placeholder)}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
multiline={true}
|
||||
numberOfLines={5}
|
||||
blurOnSubmit={false}
|
||||
underlineColorAndroid='transparent'
|
||||
style={[style.input, Platform.OS === 'android' ? {height: textInputHeight} : {maxHeight: MAX_CONTENT_HEIGHT}]}
|
||||
onContentSizeChange={this.handleContentSizeChange}
|
||||
keyboardType={this.state.keyboardType}
|
||||
onEndEditing={this.handleEndEditing}
|
||||
disableFullscreenUI={true}
|
||||
editable={!channelIsReadOnly}
|
||||
/>
|
||||
{this.renderSendButton()}
|
||||
</View>
|
||||
</View>
|
||||
</View>}
|
||||
)}
|
||||
{channelIsArchived && this.archivedView(theme, style)}
|
||||
</View>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,9 +39,9 @@ exports[`ShowMoreButton should match, full snapshot 1`] = `
|
||||
<LinearGradient
|
||||
colors={
|
||||
Array [
|
||||
"rgba(47,62,78,0)",
|
||||
"rgba(47,62,78,0.75)",
|
||||
"#2f3e4e",
|
||||
"rgba(255,255,255,0)",
|
||||
"rgba(255,255,255,0.75)",
|
||||
"#ffffff",
|
||||
]
|
||||
}
|
||||
end={
|
||||
@@ -89,7 +89,7 @@ exports[`ShowMoreButton should match, full snapshot 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(221,221,221,0.2)",
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"flex": 1,
|
||||
"height": 1,
|
||||
"marginRight": 10,
|
||||
@@ -101,8 +101,8 @@ exports[`ShowMoreButton should match, full snapshot 1`] = `
|
||||
onPress={[MockFunction]}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#2f3e4e",
|
||||
"borderColor": "rgba(221,221,221,0.2)",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderColor": "rgba(61,60,64,0.2)",
|
||||
"borderRadius": 4,
|
||||
"borderWidth": 1,
|
||||
"height": 37,
|
||||
@@ -122,7 +122,7 @@ exports[`ShowMoreButton should match, full snapshot 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"color": "#2389d7",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "600",
|
||||
"marginRight": 8,
|
||||
@@ -136,7 +136,7 @@ exports[`ShowMoreButton should match, full snapshot 1`] = `
|
||||
id="post_info.message.show_more"
|
||||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"color": "#2389d7",
|
||||
"fontSize": 13,
|
||||
"fontWeight": "600",
|
||||
}
|
||||
@@ -147,7 +147,7 @@ exports[`ShowMoreButton should match, full snapshot 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(221,221,221,0.2)",
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"flex": 1,
|
||||
"height": 1,
|
||||
"marginLeft": 10,
|
||||
|
||||
@@ -5,6 +5,8 @@ import React from 'react';
|
||||
import {TouchableOpacity} from 'react-native';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
|
||||
import ShowMoreButton from './show_more_button';
|
||||
@@ -14,10 +16,7 @@ describe('ShowMoreButton', () => {
|
||||
highlight: false,
|
||||
onPress: jest.fn(),
|
||||
showMore: true,
|
||||
theme: {
|
||||
centerChannelBg: '#2f3e4e',
|
||||
centerChannelColor: '#dddddd',
|
||||
},
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
test('should match, full snapshot', () => {
|
||||
|
||||
@@ -44,7 +44,6 @@ exports[`ChannelItem should match snapshot 1`] = `
|
||||
membersCount={1}
|
||||
size={16}
|
||||
status="online"
|
||||
teammateDeletedAt={0}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
@@ -102,7 +101,9 @@ exports[`ChannelItem should match snapshot 1`] = `
|
||||
</AnimatedComponent>
|
||||
`;
|
||||
|
||||
exports[`ChannelItem should match snapshot for deactivated user 1`] = `
|
||||
exports[`ChannelItem should match snapshot for deactivated user 1`] = `null`;
|
||||
|
||||
exports[`ChannelItem should match snapshot for deactivated user and is searchResult 1`] = `
|
||||
<AnimatedComponent>
|
||||
<TouchableHighlight
|
||||
activeOpacity={0.85}
|
||||
@@ -140,13 +141,12 @@ exports[`ChannelItem should match snapshot for deactivated user 1`] = `
|
||||
channelId="channel_id"
|
||||
hasDraft={false}
|
||||
isActive={false}
|
||||
isArchived={false}
|
||||
isArchived={true}
|
||||
isInfo={false}
|
||||
isUnread={true}
|
||||
membersCount={1}
|
||||
size={16}
|
||||
status="online"
|
||||
teammateDeletedAt={100}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
@@ -204,6 +204,120 @@ exports[`ChannelItem should match snapshot for deactivated user 1`] = `
|
||||
</AnimatedComponent>
|
||||
`;
|
||||
|
||||
exports[`ChannelItem should match snapshot if channel is archived 1`] = `null`;
|
||||
|
||||
exports[`ChannelItem should match snapshot if channel is archived and is currentChannel 1`] = `
|
||||
<AnimatedComponent>
|
||||
<TouchableHighlight
|
||||
activeOpacity={0.85}
|
||||
delayPressOut={100}
|
||||
onLongPress={[Function]}
|
||||
onPress={[Function]}
|
||||
underlayColor="rgba(69,120,191,0.5)"
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"height": 44,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#579eff",
|
||||
"width": 5,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Component
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"paddingLeft": 16,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.1)",
|
||||
"paddingLeft": 11,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<ChannelIcon
|
||||
channelId="channel_id"
|
||||
hasDraft={false}
|
||||
isActive={true}
|
||||
isArchived={true}
|
||||
isInfo={false}
|
||||
isUnread={true}
|
||||
membersCount={1}
|
||||
size={16}
|
||||
status="online"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
type="O"
|
||||
/>
|
||||
<Component
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
"flex": 1,
|
||||
"fontSize": 14,
|
||||
"fontWeight": "600",
|
||||
"height": "100%",
|
||||
"lineHeight": 44,
|
||||
"paddingRight": 40,
|
||||
"textAlignVertical": "center",
|
||||
},
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
</Component>
|
||||
</TouchableHighlight>
|
||||
</AnimatedComponent>
|
||||
`;
|
||||
|
||||
exports[`ChannelItem should match snapshot with draft 1`] = `
|
||||
<AnimatedComponent>
|
||||
<TouchableHighlight
|
||||
@@ -248,7 +362,6 @@ exports[`ChannelItem should match snapshot with draft 1`] = `
|
||||
membersCount={1}
|
||||
size={16}
|
||||
status="online"
|
||||
teammateDeletedAt={0}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
|
||||
@@ -35,11 +35,11 @@ export default class ChannelItem extends PureComponent {
|
||||
shouldHideChannel: PropTypes.bool,
|
||||
showUnreadForMsgs: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string,
|
||||
teammateDeletedAt: PropTypes.number,
|
||||
type: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
unreadMsgs: PropTypes.number.isRequired,
|
||||
isArchived: PropTypes.bool.isRequired,
|
||||
isSearchResult: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -97,15 +97,15 @@ export default class ChannelItem extends PureComponent {
|
||||
mentions,
|
||||
shouldHideChannel,
|
||||
status,
|
||||
teammateDeletedAt,
|
||||
theme,
|
||||
type,
|
||||
isArchived,
|
||||
isSearchResult,
|
||||
} = this.props;
|
||||
|
||||
// Only ever show an archived channel if it's the currently viewed channel.
|
||||
// It should disappear as soon as one navigates to another channel.
|
||||
if (isArchived && (currentChannelId !== channelId)) {
|
||||
if (isArchived && (currentChannelId !== channelId) && !isSearchResult) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -173,7 +173,6 @@ export default class ChannelItem extends PureComponent {
|
||||
membersCount={displayName.split(',').length}
|
||||
size={16}
|
||||
status={status}
|
||||
teammateDeletedAt={teammateDeletedAt}
|
||||
theme={theme}
|
||||
type={type}
|
||||
isArchived={isArchived}
|
||||
|
||||
@@ -26,7 +26,6 @@ describe('ChannelItem', () => {
|
||||
shouldHideChannel: false,
|
||||
showUnreadForMsgs: true,
|
||||
status: 'online',
|
||||
teammateDeletedAt: 0,
|
||||
type: 'O',
|
||||
theme: Preferences.THEMES.default,
|
||||
unreadMsgs: 1,
|
||||
@@ -45,8 +44,22 @@ describe('ChannelItem', () => {
|
||||
test('should match snapshot for deactivated user', () => {
|
||||
const newProps = {
|
||||
...baseProps,
|
||||
teammateDeletedAt: 100,
|
||||
type: 'D',
|
||||
isArchived: true,
|
||||
};
|
||||
const wrapper = shallow(
|
||||
<ChannelItem {...newProps}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot for deactivated user and is searchResult', () => {
|
||||
const newProps = {
|
||||
...baseProps,
|
||||
type: 'D',
|
||||
isArchived: true,
|
||||
isSearchResult: true,
|
||||
};
|
||||
const wrapper = shallow(
|
||||
<ChannelItem {...newProps}/>,
|
||||
@@ -66,4 +79,29 @@ describe('ChannelItem', () => {
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot if channel is archived', () => {
|
||||
const wrapper = shallow(
|
||||
<ChannelItem
|
||||
{...baseProps}
|
||||
isArchived={true}
|
||||
/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot if channel is archived and is currentChannel', () => {
|
||||
const wrapper = shallow(
|
||||
<ChannelItem
|
||||
{...baseProps}
|
||||
isArchived={true}
|
||||
currentChannelId={'channel_id'}
|
||||
/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,22 +29,16 @@ function makeMapStateToProps() {
|
||||
const channelDraft = getDraftForChannel(state, channel.id);
|
||||
|
||||
let isMyUser = false;
|
||||
let teammateDeletedAt = 0;
|
||||
let displayName = channel.display_name;
|
||||
let isArchived = false;
|
||||
const isArchived = channel.delete_at > 0;
|
||||
|
||||
if (channel.type === General.DM_CHANNEL) {
|
||||
if (ownProps.isSearchResult) {
|
||||
isMyUser = channel.id === currentUserId;
|
||||
teammateDeletedAt = channel.delete_at;
|
||||
} else {
|
||||
isMyUser = channel.teammate_id === currentUserId;
|
||||
isMyUser = channel.id === currentUserId;
|
||||
|
||||
if (!ownProps.isSearchResult) {
|
||||
const teammate = getUser(state, channel.teammate_id);
|
||||
if (teammate && teammate.delete_at) {
|
||||
teammateDeletedAt = teammate.delete_at;
|
||||
}
|
||||
const teammateNameDisplay = getTeammateNameDisplaySetting(state);
|
||||
displayName = displayUsername(teammate, teammateNameDisplay, false);
|
||||
isArchived = channel.delete_at > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,12 +72,11 @@ function makeMapStateToProps() {
|
||||
fake: channel.fake,
|
||||
isChannelMuted: isChannelMuted(member),
|
||||
isMyUser,
|
||||
hasDraft: Boolean(channelDraft.draft || channelDraft.files.length),
|
||||
hasDraft: Boolean(channelDraft.draft.trim() || channelDraft.files.length),
|
||||
mentions: member ? member.mention_count : 0,
|
||||
shouldHideChannel,
|
||||
showUnreadForMsgs,
|
||||
status: channel.status,
|
||||
teammateDeletedAt,
|
||||
theme: getTheme(state),
|
||||
type: channel.type,
|
||||
unreadMsgs,
|
||||
|
||||
@@ -217,6 +217,9 @@ class FilteredList extends Component {
|
||||
nickname: u.nickname,
|
||||
fullname: `${u.first_name} ${u.last_name}`,
|
||||
delete_at: u.delete_at,
|
||||
|
||||
// need name key for DM's as we use it for sortChannelsByDisplayName with same display_name
|
||||
name: displayName,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`UserStatus should match snapshot, away status 1`] = `
|
||||
<Component
|
||||
source={
|
||||
Object {
|
||||
"testUri": "../../../dist/assets/images/status/away.png",
|
||||
}
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"height": 32,
|
||||
"tintColor": "#ffbc42",
|
||||
"width": 32,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`UserStatus should match snapshot, dnd status 1`] = `
|
||||
<Component
|
||||
source={
|
||||
Object {
|
||||
"testUri": "../../../dist/assets/images/status/dnd.png",
|
||||
}
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"height": 32,
|
||||
"tintColor": "#f74343",
|
||||
"width": 32,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`UserStatus should match snapshot, online status 1`] = `
|
||||
<Component
|
||||
source={
|
||||
Object {
|
||||
"testUri": "../../../dist/assets/images/status/online.png",
|
||||
}
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"height": 32,
|
||||
"tintColor": "#06d6a0",
|
||||
"width": 32,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`UserStatus should match snapshot, should default to offline status 1`] = `
|
||||
<Component
|
||||
source={
|
||||
Object {
|
||||
"testUri": "../../../dist/assets/images/status/offline.png",
|
||||
}
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"height": 32,
|
||||
"tintColor": "rgba(61,60,64,0.3)",
|
||||
"width": 32,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
@@ -6,6 +6,9 @@ import {Image} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
|
||||
import away from 'assets/images/status/away.png';
|
||||
import dnd from 'assets/images/status/dnd.png';
|
||||
import offline from 'assets/images/status/offline.png';
|
||||
@@ -47,7 +50,7 @@ export default class UserStatus extends PureComponent {
|
||||
iconColor = theme.onlineIndicator;
|
||||
break;
|
||||
default:
|
||||
iconColor = theme.centerChannelColor;
|
||||
iconColor = changeOpacity(theme.centerChannelColor, 0.3);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
58
app/components/user_status/user_status.test.js
Normal file
58
app/components/user_status/user_status.test.js
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import UserStatus from './user_status';
|
||||
|
||||
describe('UserStatus', () => {
|
||||
const baseProps = {
|
||||
size: 32,
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
test('should match snapshot, should default to offline status', () => {
|
||||
const wrapper = shallow(
|
||||
<UserStatus {...baseProps}/>
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, away status', () => {
|
||||
const wrapper = shallow(
|
||||
<UserStatus
|
||||
{...baseProps}
|
||||
status={General.AWAY}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, dnd status', () => {
|
||||
const wrapper = shallow(
|
||||
<UserStatus
|
||||
{...baseProps}
|
||||
status={General.DND}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, online status', () => {
|
||||
const wrapper = shallow(
|
||||
<UserStatus
|
||||
{...baseProps}
|
||||
status={General.ONLINE}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -8,4 +8,6 @@ const VISIBILITY_CONFIG_DEFAULTS = {
|
||||
|
||||
export default {
|
||||
VISIBILITY_CONFIG_DEFAULTS,
|
||||
};
|
||||
VISIBILITY_SCROLL_DOWN: 'down',
|
||||
VISIBILITY_SCROLL_UP: 'up',
|
||||
};
|
||||
|
||||
@@ -73,6 +73,7 @@ const ViewTypes = keyMirror({
|
||||
SET_PROFILE_IMAGE_URI: null,
|
||||
|
||||
SELECTED_ACTION_MENU: null,
|
||||
SUBMIT_ATTACHMENT_MENU_ACTION: null,
|
||||
});
|
||||
|
||||
export default {
|
||||
|
||||
@@ -61,6 +61,7 @@ Client4.doFetchWithResponse = async (url, options) => {
|
||||
id: t('mobile.request.invalid_response'),
|
||||
defaultMessage: 'Received invalid response from the server.',
|
||||
},
|
||||
url,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {combineReducers} from 'redux';
|
||||
import {UserTypes} from 'mattermost-redux/action_types';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
function menuAction(state = {}, action) {
|
||||
function selectedMenuAction(state = {}, action) {
|
||||
switch (action.type) {
|
||||
case ViewTypes.SELECTED_ACTION_MENU:
|
||||
return action.data;
|
||||
@@ -15,6 +16,25 @@ function menuAction(state = {}, action) {
|
||||
}
|
||||
}
|
||||
|
||||
function submittedMenuActions(state = {}, action) {
|
||||
switch (action.type) {
|
||||
case ViewTypes.SUBMIT_ATTACHMENT_MENU_ACTION: {
|
||||
const nextState = {...state};
|
||||
nextState[action.postId] = action.data;
|
||||
return nextState;
|
||||
}
|
||||
case UserTypes.LOGOUT_SUCCESS:
|
||||
return {};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
menuAction,
|
||||
|
||||
// Currently selected menu action
|
||||
selectedMenuAction,
|
||||
|
||||
// Submitted menu actions per post
|
||||
submittedMenuActions,
|
||||
});
|
||||
|
||||
@@ -395,6 +395,7 @@ export default class Channel extends PureComponent {
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
postList: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
flex: 1,
|
||||
},
|
||||
loading: {
|
||||
|
||||
@@ -62,6 +62,10 @@ export default class ChannelPostList extends PureComponent {
|
||||
visiblePostIds = this.getVisiblePostIds(nextProps);
|
||||
}
|
||||
|
||||
if (this.props.channelId !== nextProps.channelId) {
|
||||
this.isLoadingMoreTop = false;
|
||||
}
|
||||
|
||||
this.setState({visiblePostIds});
|
||||
}
|
||||
|
||||
|
||||
@@ -474,6 +474,7 @@ export default class ChannelInfo extends PureComponent {
|
||||
status={status}
|
||||
theme={theme}
|
||||
type={currentChannel.type}
|
||||
isArchived={currentChannel.delete_at !== 0}
|
||||
/>
|
||||
}
|
||||
<View style={style.rowsContainer}>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import ChanneIcon from 'app/components/channel_icon';
|
||||
import ChannelIcon from 'app/components/channel_icon';
|
||||
import FormattedDate from 'app/components/formatted_date';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import Markdown from 'app/components/markdown';
|
||||
@@ -28,6 +28,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
||||
status: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
isArchived: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -43,6 +44,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
||||
status,
|
||||
theme,
|
||||
type,
|
||||
isArchived,
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
@@ -52,13 +54,14 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<View style={style.channelNameContainer}>
|
||||
<ChanneIcon
|
||||
<ChannelIcon
|
||||
isInfo={true}
|
||||
membersCount={memberCount - 1}
|
||||
size={16}
|
||||
status={status}
|
||||
theme={theme}
|
||||
type={type}
|
||||
isArchived={isArchived}
|
||||
/>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
|
||||
@@ -550,6 +550,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
},
|
||||
errorText: {
|
||||
fontSize: 14,
|
||||
marginHorizontal: 15,
|
||||
},
|
||||
separator: {
|
||||
height: 15,
|
||||
@@ -560,4 +561,3 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ exports[`ErrorTeamsList should match snapshot 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": undefined,
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,34 @@ exports[`ErrorTeamsList should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
onRetry={[Function]}
|
||||
theme={Object {}}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
`;
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {configure, shallow} from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import FailedNetworkAction from 'app/components/failed_network_action';
|
||||
import ErrorTeamsList from './error_teams_list';
|
||||
|
||||
configure({adapter: new Adapter()});
|
||||
|
||||
describe('ErrorTeamsList', () => {
|
||||
const navigator = {
|
||||
setOnNavigatorEvent: () => {}, // eslint-disable-line no-empty-function
|
||||
@@ -27,7 +27,7 @@ describe('ErrorTeamsList', () => {
|
||||
logout: () => {}, // eslint-disable-line no-empty-function
|
||||
selectDefaultTeam: () => {}, // eslint-disable-line no-empty-function
|
||||
},
|
||||
theme: {},
|
||||
theme: Preferences.THEMES.default,
|
||||
navigator,
|
||||
};
|
||||
|
||||
|
||||
@@ -143,6 +143,14 @@ export default class Login extends PureComponent {
|
||||
Keyboard.dismiss();
|
||||
InteractionManager.runAfterInteractions(async () => {
|
||||
if (!this.props.loginId) {
|
||||
t('login.noEmail');
|
||||
t('login.noEmailLdapUsername');
|
||||
t('login.noEmailUsername');
|
||||
t('login.noEmailUsernameLdapUsername');
|
||||
t('login.noLdapUsername');
|
||||
t('login.noUsername');
|
||||
t('login.noUsernameLdapUsername');
|
||||
|
||||
// it's slightly weird to be constructing the message ID, but it's a bit nicer than triply nested if statements
|
||||
let msgId = 'login.no';
|
||||
if (this.props.config.EnableSignInWithEmail === 'true') {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ import {ViewTypes} from 'app/constants';
|
||||
import MenuActionSelector from './menu_action_selector';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const menuAction = state.views.post.menuAction || {};
|
||||
const menuAction = state.views.post.selectedMenuAction || {};
|
||||
|
||||
let data;
|
||||
let loadMoreRequestStatus;
|
||||
|
||||
@@ -4,6 +4,8 @@ import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
import {IntlProvider} from 'react-intl';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import MenuActionSelector from './menu_action_selector.js';
|
||||
|
||||
jest.mock('rn-fetch-blob', () => ({
|
||||
@@ -76,7 +78,7 @@ describe('MenuActionSelector', () => {
|
||||
onSelect: jest.fn(),
|
||||
data: [{text: 'text', value: 'value'}],
|
||||
dataSource: null,
|
||||
theme: {},
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
test('should match snapshot for explicit options', async () => {
|
||||
@@ -84,7 +86,7 @@ describe('MenuActionSelector', () => {
|
||||
<MenuActionSelector {...baseProps}/>,
|
||||
{context: {intl}},
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot for users', async () => {
|
||||
@@ -98,10 +100,10 @@ describe('MenuActionSelector', () => {
|
||||
<MenuActionSelector {...props}/>,
|
||||
{context: {intl}},
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
wrapper.setState({isLoading: false});
|
||||
wrapper.update();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot for channels', async () => {
|
||||
@@ -115,10 +117,10 @@ describe('MenuActionSelector', () => {
|
||||
<MenuActionSelector {...props}/>,
|
||||
{context: {intl}},
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
wrapper.setState({isLoading: false});
|
||||
wrapper.update();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot for searching', async () => {
|
||||
@@ -134,6 +136,6 @@ describe('MenuActionSelector', () => {
|
||||
);
|
||||
wrapper.setState({isLoading: false, searching: true, term: 'name2'});
|
||||
wrapper.update();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Keyboard,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
} from 'react-native';
|
||||
@@ -130,8 +131,9 @@ export default class Mfa extends PureComponent {
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior='padding'
|
||||
style={{flex: 1}}
|
||||
style={style.flex}
|
||||
keyboardVerticalOffset={5}
|
||||
enabled={Platform.OS === 'ios'}
|
||||
>
|
||||
<StatusBar/>
|
||||
<TouchableWithoutFeedback onPress={this.blur}>
|
||||
@@ -168,3 +170,9 @@ export default class Mfa extends PureComponent {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`MoreChannels should match snapshot 1`] = `
|
||||
<Connect(KeyboardLayout)>
|
||||
<KeyboardLayout>
|
||||
<Connect(StatusBar) />
|
||||
<React.Fragment>
|
||||
<Component
|
||||
@@ -18,8 +18,8 @@ exports[`MoreChannels should match snapshot 1`] = `
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "rgba(170,170,170,0.2)",
|
||||
"color": "#aaa",
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
@@ -29,10 +29,10 @@ exports[`MoreChannels should match snapshot 1`] = `
|
||||
onFocus={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholderTextColor="rgba(170,170,170,0.5)"
|
||||
tintColorDelete="rgba(170,170,170,0.5)"
|
||||
tintColorSearch="rgba(170,170,170,0.5)"
|
||||
titleCancelColor="#aaa"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.5)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
</Component>
|
||||
@@ -67,13 +67,33 @@ exports[`MoreChannels should match snapshot 1`] = `
|
||||
showSections={false}
|
||||
theme={
|
||||
Object {
|
||||
"centerChannelBg": "#aaa",
|
||||
"centerChannelColor": "#aaa",
|
||||
"sidebarHeaderBg": "#aaa",
|
||||
"sidebarHeaderTextColor": "#aaa",
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
</Connect(KeyboardLayout)>
|
||||
</KeyboardLayout>
|
||||
`;
|
||||
|
||||
@@ -7,6 +7,7 @@ import {intlShape} from 'react-intl';
|
||||
import {
|
||||
Platform,
|
||||
InteractionManager,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
@@ -20,7 +21,7 @@ import Loading from 'app/components/loading';
|
||||
import SearchBar from 'app/components/search_bar';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import {alertErrorWithFallback} from 'app/utils/general';
|
||||
import {changeOpacity, makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme';
|
||||
import {changeOpacity, setNavigatorStyles} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
|
||||
export default class MoreChannels extends PureComponent {
|
||||
@@ -300,7 +301,6 @@ export default class MoreChannels extends PureComponent {
|
||||
const {adding, channels, searching, term} = this.state;
|
||||
const {formatMessage} = intl;
|
||||
const isLoading = requestStatus.status === RequestStatus.STARTED || requestStatus.status === RequestStatus.NOT_STARTED;
|
||||
const style = getStyleFromTheme(theme);
|
||||
const more = searching ? () => true : this.loadMoreChannels;
|
||||
|
||||
let content;
|
||||
@@ -320,7 +320,7 @@ export default class MoreChannels extends PureComponent {
|
||||
|
||||
content = (
|
||||
<React.Fragment>
|
||||
<View style={style.wrapper}>
|
||||
<View style={style.searchbar}>
|
||||
<SearchBar
|
||||
ref='search_bar'
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
@@ -365,25 +365,8 @@ export default class MoreChannels extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
wrapper: {
|
||||
marginVertical: 5,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
navTitle: {
|
||||
...Platform.select({
|
||||
android: {
|
||||
fontSize: 18,
|
||||
},
|
||||
ios: {
|
||||
fontSize: 15,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
const style = StyleSheet.create({
|
||||
searchbar: {
|
||||
marginVertical: 5,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import MoreChannels from './more_channels.js';
|
||||
|
||||
jest.mock('react-intl');
|
||||
@@ -29,12 +31,7 @@ describe('MoreChannels', () => {
|
||||
currentUserId: 'current_user_id',
|
||||
currentTeamId: 'current_team_id',
|
||||
navigator,
|
||||
theme: {
|
||||
centerChannelBg: '#aaa',
|
||||
centerChannelColor: '#aaa',
|
||||
sidebarHeaderBg: '#aaa',
|
||||
sidebarHeaderTextColor: '#aaa',
|
||||
},
|
||||
theme: Preferences.THEMES.default,
|
||||
canCreateChannels: true,
|
||||
channels: [{id: 'id', name: 'name', display_name: 'display_name'}],
|
||||
closeButton: {},
|
||||
|
||||
152
app/screens/permalink/__snapshots__/permalink.test.js.snap
Normal file
152
app/screens/permalink/__snapshots__/permalink.test.js.snap
Normal file
@@ -0,0 +1,152 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Permalink should match snapshot 1`] = `
|
||||
<Connect(SafeAreaIos)
|
||||
backgroundColor="transparent"
|
||||
excludeHeader={true}
|
||||
footerColor="transparent"
|
||||
forceTop={44}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"marginTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<withAnimatable(Component)
|
||||
animation="zoomIn"
|
||||
delay={0}
|
||||
direction="normal"
|
||||
duration={200}
|
||||
iterationCount={1}
|
||||
iterationDelay={0}
|
||||
onAnimationBegin={[Function]}
|
||||
onAnimationEnd={[Function]}
|
||||
onTransitionBegin={[Function]}
|
||||
onTransitionEnd={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": 6,
|
||||
"flex": 1,
|
||||
"margin": 10,
|
||||
"opacity": 0,
|
||||
}
|
||||
}
|
||||
useNativeDriver={true}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderTopLeftRadius": 6,
|
||||
"borderTopRightRadius": 6,
|
||||
"flexDirection": "row",
|
||||
"height": 44,
|
||||
"paddingRight": 16,
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.2}
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"height": 44,
|
||||
"justifyContent": "center",
|
||||
"paddingLeft": 7,
|
||||
"width": 40,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
color="#3d3c40"
|
||||
name="close"
|
||||
size={20}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"paddingRight": 40,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 17,
|
||||
"fontWeight": "600",
|
||||
}
|
||||
}
|
||||
>
|
||||
channel_name
|
||||
</Component>
|
||||
</Component>
|
||||
</Component>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"height": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
<Component
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
},
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Loading
|
||||
color="grey"
|
||||
size="large"
|
||||
style={Object {}}
|
||||
/>
|
||||
</Component>
|
||||
</withAnimatable(Component)>
|
||||
</Component>
|
||||
</Connect(SafeAreaIos)>
|
||||
`;
|
||||
|
||||
exports[`Permalink should match snapshot 2`] = `
|
||||
<Component>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
name="archive"
|
||||
size={12}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 16,
|
||||
"paddingRight": 20,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
|
||||
</Component>
|
||||
`;
|
||||
@@ -86,10 +86,49 @@ export default class Permalink extends PureComponent {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
const newState = {};
|
||||
if (nextProps.focusedPostId !== prevState.focusedPostIdState) {
|
||||
newState.focusedPostIdState = nextProps.focusedPostId;
|
||||
}
|
||||
|
||||
if (nextProps.channelId && nextProps.channelId !== prevState.channelIdState) {
|
||||
newState.channelIdState = nextProps.channelId;
|
||||
}
|
||||
|
||||
if (nextProps.channelName && nextProps.channelName !== prevState.channelNameState) {
|
||||
newState.channelNameState = nextProps.channelName;
|
||||
}
|
||||
|
||||
if (nextProps.postIds && nextProps.postIds.length > 0 && nextProps.postIds !== prevState.postIdsState) {
|
||||
newState.postIdsState = nextProps.postIds;
|
||||
}
|
||||
|
||||
if (nextProps.focusedPostId !== prevState.focusedPostIdState) {
|
||||
let loading = true;
|
||||
if (nextProps.postIds && nextProps.postIds.length >= 10) {
|
||||
loading = false;
|
||||
}
|
||||
|
||||
newState.loading = loading;
|
||||
}
|
||||
|
||||
if (Object.keys(newState).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const {postIds, channelName} = props;
|
||||
const {
|
||||
postIds,
|
||||
channelId,
|
||||
channelName,
|
||||
focusedPostId,
|
||||
} = props;
|
||||
let loading = true;
|
||||
|
||||
if (postIds && postIds.length >= 10) {
|
||||
@@ -103,10 +142,14 @@ export default class Permalink extends PureComponent {
|
||||
loading,
|
||||
error: '',
|
||||
retry: false,
|
||||
channelIdState: channelId,
|
||||
channelNameState: channelName,
|
||||
focusedPostIdState: focusedPostId,
|
||||
postIdsState: postIds,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
|
||||
if (this.state.loading) {
|
||||
@@ -114,18 +157,9 @@ export default class Permalink extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.channelName !== nextProps.channelName && this.mounted) {
|
||||
this.setState({title: nextProps.channelName});
|
||||
}
|
||||
|
||||
if (this.props.focusedPostId !== nextProps.focusedPostId && this.mounted) {
|
||||
this.setState({loading: true});
|
||||
if (nextProps.postIds && nextProps.postIds.length < 10) {
|
||||
this.loadPosts(nextProps);
|
||||
} else {
|
||||
this.setState({loading: false});
|
||||
}
|
||||
componentDidUpdate() {
|
||||
if (this.state.loading) {
|
||||
this.loadPosts(this.props);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,11 +210,11 @@ export default class Permalink extends PureComponent {
|
||||
};
|
||||
|
||||
handlePress = () => {
|
||||
const {channelId, channelName} = this.props;
|
||||
const {channelIdState, channelNameState} = this.state;
|
||||
|
||||
if (this.refs.view) {
|
||||
this.refs.view.growOut().then(() => {
|
||||
this.jumpToChannel(channelId, channelName);
|
||||
this.jumpToChannel(channelIdState, channelNameState);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -303,18 +337,21 @@ export default class Permalink extends PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
archivedIcon = (style) => {
|
||||
let ico = null;
|
||||
archivedIcon = () => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
let icon = null;
|
||||
if (this.props.channelIsArchived) {
|
||||
ico = (<Text>
|
||||
<AwesomeIcon
|
||||
name='archive'
|
||||
style={[style.archiveIcon]}
|
||||
/>
|
||||
{' '}
|
||||
</Text>);
|
||||
icon = (
|
||||
<Text>
|
||||
<AwesomeIcon
|
||||
name='archive'
|
||||
style={[style.archiveIcon]}
|
||||
/>
|
||||
{' '}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
return ico;
|
||||
return icon;
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -324,10 +361,15 @@ export default class Permalink extends PureComponent {
|
||||
navigator,
|
||||
onHashtagPress,
|
||||
onPermalinkPress,
|
||||
postIds,
|
||||
theme,
|
||||
} = this.props;
|
||||
const {error, retry, loading, title} = this.state;
|
||||
const {
|
||||
error,
|
||||
retry,
|
||||
loading,
|
||||
postIdsState,
|
||||
title,
|
||||
} = this.state;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
let postList;
|
||||
@@ -359,7 +401,7 @@ export default class Permalink extends PureComponent {
|
||||
onHashtagPress={onHashtagPress}
|
||||
onPermalinkPress={onPermalinkPress}
|
||||
onPostPress={this.goToThread}
|
||||
postIds={postIds}
|
||||
postIds={postIdsState}
|
||||
currentUserId={currentUserId}
|
||||
lastViewedAt={0}
|
||||
navigator={navigator}
|
||||
@@ -404,7 +446,7 @@ export default class Permalink extends PureComponent {
|
||||
numberOfLines={1}
|
||||
style={style.title}
|
||||
>
|
||||
{this.archivedIcon(style)}
|
||||
{this.archivedIcon()}
|
||||
{title}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
132
app/screens/permalink/permalink.test.js
Normal file
132
app/screens/permalink/permalink.test.js
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import Permalink from './permalink.js';
|
||||
|
||||
jest.mock('react-intl');
|
||||
|
||||
describe('Permalink', () => {
|
||||
const navigator = {
|
||||
dismissAllModals: jest.fn(),
|
||||
dismissModal: jest.fn(),
|
||||
push: jest.fn(),
|
||||
resetTo: jest.fn(),
|
||||
setOnNavigatorEvent: jest.fn(),
|
||||
};
|
||||
|
||||
const actions = {
|
||||
getPostsAfter: jest.fn(),
|
||||
getPostsBefore: jest.fn(),
|
||||
getPostThread: jest.fn(),
|
||||
getChannel: jest.fn(),
|
||||
handleSelectChannel: jest.fn(),
|
||||
handleTeamChange: jest.fn(),
|
||||
joinChannel: jest.fn(),
|
||||
loadThreadIfNecessary: jest.fn(),
|
||||
markChannelAsRead: jest.fn(),
|
||||
markChannelAsViewed: jest.fn(),
|
||||
selectPost: jest.fn(),
|
||||
setChannelDisplayName: jest.fn(),
|
||||
setChannelLoading: jest.fn(),
|
||||
};
|
||||
|
||||
const baseProps = {
|
||||
actions,
|
||||
channelId: 'channel_id',
|
||||
channelIsArchived: false,
|
||||
channelName: 'channel_name',
|
||||
channelTeamId: 'team_id',
|
||||
currentTeamId: 'current_team_id',
|
||||
currentUserId: 'current_user_id',
|
||||
focusedPostId: 'focused_post_id',
|
||||
isPermalink: true,
|
||||
myMembers: {},
|
||||
navigator,
|
||||
onClose: jest.fn(),
|
||||
onHashtagPress: jest.fn(),
|
||||
onPermalinkPress: jest.fn(),
|
||||
onPress: jest.fn(),
|
||||
postIds: ['post_id_1', 'focused_post_id', 'post_id_3'],
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<Permalink {...baseProps}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
|
||||
// match archived icon
|
||||
wrapper.setProps({channelIsArchived: true});
|
||||
expect(wrapper.instance().archivedIcon()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match state and call loadPosts on retry', () => {
|
||||
const wrapper = shallow(
|
||||
<Permalink {...baseProps}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
wrapper.instance().loadPosts = jest.fn();
|
||||
wrapper.instance().retry();
|
||||
expect(wrapper.instance().loadPosts).toHaveBeenCalledTimes(2);
|
||||
expect(wrapper.instance().loadPosts).toBeCalledWith(baseProps);
|
||||
});
|
||||
|
||||
test('should call handleClose on onNavigatorEvent(backPress)', () => {
|
||||
const wrapper = shallow(
|
||||
<Permalink {...baseProps}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
wrapper.instance().handleClose = jest.fn();
|
||||
wrapper.instance().onNavigatorEvent({id: 'backPress'});
|
||||
expect(wrapper.instance().handleClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should match state', () => {
|
||||
const wrapper = shallow(
|
||||
<Permalink {...baseProps}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
expect(wrapper.state('channelIdState')).toEqual(baseProps.channelId);
|
||||
expect(wrapper.state('channelNameState')).toEqual(baseProps.channelName);
|
||||
expect(wrapper.state('focusedPostIdState')).toEqual(baseProps.focusedPostId);
|
||||
expect(wrapper.state('postIdsState')).toEqual(baseProps.postIds);
|
||||
|
||||
wrapper.setProps({channelId: ''});
|
||||
expect(wrapper.state('channelIdState')).toEqual(baseProps.channelId);
|
||||
wrapper.setProps({channelId: null});
|
||||
expect(wrapper.state('channelIdState')).toEqual(baseProps.channelId);
|
||||
wrapper.setProps({channelId: 'new_channel_id'});
|
||||
expect(wrapper.state('channelIdState')).toEqual('new_channel_id');
|
||||
|
||||
wrapper.setProps({channelName: ''});
|
||||
expect(wrapper.state('channelNameState')).toEqual(baseProps.channelName);
|
||||
wrapper.setProps({channelName: null});
|
||||
expect(wrapper.state('channelNameState')).toEqual(baseProps.channelName);
|
||||
wrapper.setProps({channelName: 'new_channel_name'});
|
||||
expect(wrapper.state('channelNameState')).toEqual('new_channel_name');
|
||||
|
||||
wrapper.setProps({focusedPostId: 'new_focused_post_id'});
|
||||
expect(wrapper.state('focusedPostIdState')).toEqual('new_focused_post_id');
|
||||
|
||||
wrapper.setProps({postIds: []});
|
||||
expect(wrapper.state('postIdsState')).toEqual(baseProps.postIds);
|
||||
wrapper.setProps({postIds: ['post_id_1', 'focused_post_id']});
|
||||
expect(wrapper.state('postIdsState')).toEqual(['post_id_1', 'focused_post_id']);
|
||||
|
||||
wrapper.setProps({postIds: baseProps.postIds, focusedPostId: baseProps.focusedPostId});
|
||||
expect(wrapper.state('loading')).toEqual(true);
|
||||
wrapper.setProps({postIds: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'], focusedPostId: 'new_focused_post_id'});
|
||||
expect(wrapper.state('loading')).toEqual(false);
|
||||
});
|
||||
});
|
||||
@@ -66,6 +66,7 @@ function makeMapStateToProps() {
|
||||
theme: getTheme(state),
|
||||
enableDateSuggestion,
|
||||
timezoneOffsetInSeconds,
|
||||
viewArchivedChannels,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import PostSeparator from 'app/components/post_separator';
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import SearchBar from 'app/components/search_bar';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import {ListTypes} from 'app/constants';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
@@ -43,6 +44,7 @@ const SECTION_HEIGHT = 20;
|
||||
const RECENT_LABEL_HEIGHT = 42;
|
||||
const RECENT_SEPARATOR_HEIGHT = 3;
|
||||
const MODIFIER_LABEL_HEIGHT = 58;
|
||||
const SCROLL_UP_MULTIPLIER = 6;
|
||||
const SEARCHING = 'searching';
|
||||
const NO_RESULTS = 'no results';
|
||||
|
||||
@@ -71,6 +73,7 @@ export default class Search extends PureComponent {
|
||||
theme: PropTypes.object.isRequired,
|
||||
enableDateSuggestion: PropTypes.bool,
|
||||
timezoneOffsetInSeconds: PropTypes.number.isRequired,
|
||||
viewArchivedChannels: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -89,6 +92,7 @@ export default class Search extends PureComponent {
|
||||
|
||||
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
|
||||
this.isX = DeviceInfo.getModel().includes('iPhone X');
|
||||
this.contentOffsetY = 0;
|
||||
this.state = {
|
||||
channelName: '',
|
||||
cursorPosition: 0,
|
||||
@@ -119,7 +123,9 @@ export default class Search extends PureComponent {
|
||||
const {searchingStatus: status, recent, enableDateSuggestion} = this.props;
|
||||
const {searchingStatus: prevStatus} = prevProps;
|
||||
const recentLength = recent.length;
|
||||
const shouldScroll = prevStatus !== status && (status === RequestStatus.SUCCESS || status === RequestStatus.STARTED) && !this.props.isSearchGettingMore && !prevProps.isSearchGettingMore;
|
||||
const shouldScroll = prevStatus !== status &&
|
||||
(status === RequestStatus.SUCCESS || status === RequestStatus.STARTED) &&
|
||||
!this.props.isSearchGettingMore && !prevProps.isSearchGettingMore;
|
||||
|
||||
if (this.props.isLandscape !== prevProps.isLandscape) {
|
||||
this.refs.searchBar.blur();
|
||||
@@ -142,6 +148,30 @@ export default class Search extends PureComponent {
|
||||
mattermostManaged.removeEventListener(this.listenerId);
|
||||
}
|
||||
|
||||
archivedIndicator = (postID, style) => {
|
||||
const channelIsArchived = this.props.archivedPostIds.includes(postID);
|
||||
let archivedIndicator = null;
|
||||
if (channelIsArchived) {
|
||||
archivedIndicator = (
|
||||
<View style={style.archivedIndicator}>
|
||||
<Text>
|
||||
<AwesomeIcon
|
||||
name='archive'
|
||||
style={style.archivedText}
|
||||
/>
|
||||
{' '}
|
||||
<FormattedText
|
||||
style={style.archivedText}
|
||||
id='search_item.channelArchived'
|
||||
defaultMessage='Archived'
|
||||
/>
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return archivedIndicator;
|
||||
};
|
||||
|
||||
cancelSearch = preventDoubleTap(() => {
|
||||
const {navigator} = this.props;
|
||||
this.handleTextChanged('', true);
|
||||
@@ -196,11 +226,34 @@ export default class Search extends PureComponent {
|
||||
this.showingPermalink = false;
|
||||
};
|
||||
|
||||
handleLayout = (event) => {
|
||||
const {height} = event.nativeEvent.layout;
|
||||
this.setState({searchListHeight: height});
|
||||
};
|
||||
|
||||
handlePermalinkPress = (postId, teamName) => {
|
||||
this.props.actions.loadChannelsByTeamName(teamName);
|
||||
this.showPermalinkView(postId, true);
|
||||
};
|
||||
|
||||
handleScroll = (event) => {
|
||||
const pageOffsetY = event.nativeEvent.contentOffset.y;
|
||||
if (pageOffsetY > 0) {
|
||||
const contentHeight = event.nativeEvent.contentSize.height;
|
||||
const direction = (this.contentOffsetY < pageOffsetY) ?
|
||||
ListTypes.VISIBILITY_SCROLL_UP :
|
||||
ListTypes.VISIBILITY_SCROLL_DOWN;
|
||||
|
||||
this.contentOffsetY = pageOffsetY;
|
||||
if (
|
||||
direction === ListTypes.VISIBILITY_SCROLL_UP &&
|
||||
(contentHeight - pageOffsetY) < (this.state.searchListHeight * SCROLL_UP_MULTIPLIER)
|
||||
) {
|
||||
this.getMoreSearchResults();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleSelectionChange = (event) => {
|
||||
const cursorPosition = event.nativeEvent.selection.end;
|
||||
this.setState({
|
||||
@@ -208,6 +261,10 @@ export default class Search extends PureComponent {
|
||||
});
|
||||
};
|
||||
|
||||
handleSearchButtonPress = preventDoubleTap((text) => {
|
||||
this.search(text);
|
||||
});
|
||||
|
||||
handleTextChanged = (value, selectionChanged) => {
|
||||
const {actions, searchingStatus, isSearchGettingMore} = this.props;
|
||||
this.setState({value});
|
||||
@@ -243,6 +300,12 @@ export default class Search extends PureComponent {
|
||||
return item.id || item;
|
||||
};
|
||||
|
||||
getMoreSearchResults = debounce(() => {
|
||||
if (this.state.value && this.props.postIds.length) {
|
||||
this.props.actions.getMorePostsForSearch();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
onNavigatorEvent = (event) => {
|
||||
if (event.id === 'backPress') {
|
||||
if (this.state.preview) {
|
||||
@@ -259,40 +322,24 @@ export default class Search extends PureComponent {
|
||||
this.showPermalinkView(post.id, false);
|
||||
};
|
||||
|
||||
showPermalinkView = (postId, isPermalink) => {
|
||||
const {actions, navigator} = this.props;
|
||||
|
||||
actions.selectFocusedPostId(postId);
|
||||
|
||||
if (!this.showingPermalink) {
|
||||
const options = {
|
||||
screen: 'Permalink',
|
||||
animationType: 'none',
|
||||
backButtonTitle: '',
|
||||
overrideBackPress: true,
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
screenBackgroundColor: changeOpacity('#000', 0.2),
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
},
|
||||
passProps: {
|
||||
isPermalink,
|
||||
onClose: this.handleClosePermalink,
|
||||
onHashtagPress: this.handleHashtagPress,
|
||||
onPermalinkPress: this.handlePermalinkPress,
|
||||
},
|
||||
};
|
||||
|
||||
this.showingPermalink = true;
|
||||
navigator.showModal(options);
|
||||
}
|
||||
};
|
||||
|
||||
removeSearchTerms = preventDoubleTap((item) => {
|
||||
const {actions, currentTeamId} = this.props;
|
||||
actions.removeSearchTerms(currentTeamId, item.terms);
|
||||
});
|
||||
|
||||
renderFooter = () => {
|
||||
if (this.props.isSearchGettingMore) {
|
||||
const style = getStyleFromTheme(this.props.theme);
|
||||
return (
|
||||
<View style={style.loadingMore}>
|
||||
<Loading/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
renderModifiers = ({item}) => {
|
||||
const {theme} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
@@ -327,30 +374,6 @@ export default class Search extends PureComponent {
|
||||
);
|
||||
};
|
||||
|
||||
archivedIndicator = (postID, style) => {
|
||||
const channelIsArchived = this.props.archivedPostIds.includes(postID);
|
||||
let archivedIndicator = null;
|
||||
if (channelIsArchived) {
|
||||
archivedIndicator = (
|
||||
<View style={style.archivedIndicator}>
|
||||
<Text>
|
||||
<AwesomeIcon
|
||||
name='archive'
|
||||
style={style.archivedText}
|
||||
/>
|
||||
{' '}
|
||||
<FormattedText
|
||||
style={style.archivedText}
|
||||
id='search_item.channelArchived'
|
||||
defaultMessage='Archived'
|
||||
/>
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return archivedIndicator;
|
||||
};
|
||||
|
||||
renderPost = ({item, index}) => {
|
||||
const {postIds, theme} = this.props;
|
||||
const {managedConfig} = this.state;
|
||||
@@ -380,7 +403,7 @@ export default class Search extends PureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={style.postResult}>
|
||||
<ChannelDisplayName postId={item}/>
|
||||
{this.archivedIndicator(postIds[index], style)}
|
||||
<SearchResultPost
|
||||
@@ -476,6 +499,35 @@ export default class Search extends PureComponent {
|
||||
});
|
||||
};
|
||||
|
||||
showPermalinkView = (postId, isPermalink) => {
|
||||
const {actions, navigator} = this.props;
|
||||
|
||||
actions.selectFocusedPostId(postId);
|
||||
|
||||
if (!this.showingPermalink) {
|
||||
const options = {
|
||||
screen: 'Permalink',
|
||||
animationType: 'none',
|
||||
backButtonTitle: '',
|
||||
overrideBackPress: true,
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
screenBackgroundColor: changeOpacity('#000', 0.2),
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
},
|
||||
passProps: {
|
||||
isPermalink,
|
||||
onClose: this.handleClosePermalink,
|
||||
onHashtagPress: this.handleHashtagPress,
|
||||
onPermalinkPress: this.handlePermalinkPress,
|
||||
},
|
||||
};
|
||||
|
||||
this.showingPermalink = true;
|
||||
navigator.showModal(options);
|
||||
}
|
||||
};
|
||||
|
||||
scrollToTop = () => {
|
||||
if (this.refs.list) {
|
||||
this.refs.list._wrapperListRef.getListRef().scrollToOffset({ //eslint-disable-line no-underscore-dangle
|
||||
@@ -486,7 +538,7 @@ export default class Search extends PureComponent {
|
||||
};
|
||||
|
||||
search = (terms, isOrSearch) => {
|
||||
const {actions, currentTeamId} = this.props;
|
||||
const {actions, currentTeamId, viewArchivedChannels} = this.props;
|
||||
|
||||
this.handleTextChanged(`${terms.trim()} `);
|
||||
|
||||
@@ -500,13 +552,17 @@ export default class Search extends PureComponent {
|
||||
});
|
||||
|
||||
// timezone offset in seconds
|
||||
actions.searchPostsWithParams(currentTeamId, {terms: terms.trim(), is_or_search: isOrSearch, time_zone_offset: this.props.timezoneOffsetInSeconds, page: 0, per_page: 20}, true);
|
||||
const params = {
|
||||
terms: terms.trim(),
|
||||
is_or_search: isOrSearch,
|
||||
time_zone_offset: this.props.timezoneOffsetInSeconds,
|
||||
page: 0,
|
||||
per_page: 20,
|
||||
include_deleted_channels: viewArchivedChannels,
|
||||
};
|
||||
actions.searchPostsWithParams(currentTeamId, params, true);
|
||||
};
|
||||
|
||||
handleSearchButtonPress = preventDoubleTap((text) => {
|
||||
this.search(text);
|
||||
});
|
||||
|
||||
setModifierValue = preventDoubleTap((modifier) => {
|
||||
const {value} = this.state;
|
||||
let newValue = '';
|
||||
@@ -533,12 +589,6 @@ export default class Search extends PureComponent {
|
||||
Keyboard.dismiss();
|
||||
});
|
||||
|
||||
onEndReached = debounce(() => {
|
||||
if (this.state.value) {
|
||||
this.props.actions.getMorePostsForSearch();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
render() {
|
||||
const {
|
||||
isLandscape,
|
||||
@@ -721,8 +771,10 @@ export default class Search extends PureComponent {
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyboardDismissMode='interactive'
|
||||
stickySectionHeadersEnabled={Platform.OS === 'ios'}
|
||||
onEndReached={this.onEndReached}
|
||||
onEndReachedThreshold={Platform.OS === 'ios' ? 0 : 1}
|
||||
onLayout={this.handleLayout}
|
||||
onScroll={this.handleScroll}
|
||||
scrollEventThrottle={60}
|
||||
ListFooterComponent={this.renderFooter}
|
||||
/>
|
||||
<Autocomplete
|
||||
cursorPosition={cursorPosition}
|
||||
@@ -861,6 +913,12 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
archivedText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.4),
|
||||
},
|
||||
postResult: {
|
||||
overflow: 'hidden',
|
||||
},
|
||||
loadingMore: {
|
||||
height: 60,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -443,6 +443,7 @@ export default class SelectServer extends PureComponent {
|
||||
behavior='padding'
|
||||
style={style.container}
|
||||
keyboardVerticalOffset={0}
|
||||
enabled={Platform.OS === 'ios'}
|
||||
>
|
||||
<StatusBar barStyle={statusStyle}/>
|
||||
<TouchableWithoutFeedback onPress={this.blur}>
|
||||
|
||||
@@ -15,7 +15,34 @@ exports[`SelectTeam should match snapshot for fail of teams 1`] = `
|
||||
}
|
||||
}
|
||||
onRetry={[Function]}
|
||||
theme={Object {}}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
@@ -23,7 +50,7 @@ exports[`SelectTeam should match snapshot for teams 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": undefined,
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
|
||||
import SelectTeam from './select_team.js';
|
||||
@@ -40,7 +42,7 @@ describe('SelectTeam', () => {
|
||||
},
|
||||
userWithoutTeams: false,
|
||||
teams: [],
|
||||
theme: {},
|
||||
theme: Preferences.THEMES.default,
|
||||
teamsRequest: {
|
||||
status: RequestStatus.FAILURE,
|
||||
},
|
||||
|
||||
@@ -18,8 +18,30 @@ exports[`NotificationSettingsEmailAndroid should match snapshot 1`] = `
|
||||
}
|
||||
theme={
|
||||
Object {
|
||||
"centerChannelBg": "#aaa",
|
||||
"centerChannelColor": "#aaa",
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -14,8 +14,30 @@ exports[`NotificationSettingsEmailIos should match snapshot, renderEmailSection
|
||||
headerId="mobile.notification_settings.email.send"
|
||||
theme={
|
||||
Object {
|
||||
"centerChannelBg": "#aaa",
|
||||
"centerChannelColor": "#aaa",
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -33,8 +55,30 @@ exports[`NotificationSettingsEmailIos should match snapshot, renderEmailSection
|
||||
selected={true}
|
||||
theme={
|
||||
Object {
|
||||
"centerChannelBg": "#aaa",
|
||||
"centerChannelColor": "#aaa",
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
@@ -61,8 +105,30 @@ exports[`NotificationSettingsEmailIos should match snapshot, renderEmailSection
|
||||
selected={false}
|
||||
theme={
|
||||
Object {
|
||||
"centerChannelBg": "#aaa",
|
||||
"centerChannelColor": "#aaa",
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
|
||||
@@ -22,10 +24,7 @@ describe('NotificationSettingsEmailAndroid', () => {
|
||||
},
|
||||
sendEmailNotifications: true,
|
||||
siteName: 'Mattermost',
|
||||
theme: {
|
||||
centerChannelBg: '#aaa',
|
||||
centerChannelColor: '#aaa',
|
||||
},
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
|
||||
import SectionItem from 'app/screens/settings/section_item';
|
||||
@@ -30,10 +32,7 @@ describe('NotificationSettingsEmailIos', () => {
|
||||
},
|
||||
sendEmailNotifications: true,
|
||||
siteName: 'Mattermost',
|
||||
theme: {
|
||||
centerChannelBg: '#aaa',
|
||||
centerChannelColor: '#aaa',
|
||||
},
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
test('should match snapshot, renderEmailSection', () => {
|
||||
|
||||
@@ -53,19 +53,19 @@ export default class NotificationSettingsMentionsBase extends PureComponent {
|
||||
}
|
||||
|
||||
const comments = notifyProps.comments || 'any';
|
||||
const mentionKeysString = mentionKeys.join(',');
|
||||
|
||||
const newState = {
|
||||
...notifyProps,
|
||||
comments,
|
||||
newReplyValue: comments,
|
||||
usernameMention: usernameMentionIndex > -1,
|
||||
mention_keys: mentionKeys.join(','),
|
||||
mention_keys: mentionKeysString,
|
||||
androidKeywords: mentionKeysString,
|
||||
showKeywordsModal: false,
|
||||
showReplyModal: false,
|
||||
};
|
||||
|
||||
this.keywords = newState.mention_keys;
|
||||
|
||||
return newState;
|
||||
};
|
||||
|
||||
|
||||
@@ -23,8 +23,10 @@ import NotificationSettingsMentionsBase from './notification_settings_mention_ba
|
||||
|
||||
class NotificationSettingsMentionsAndroid extends NotificationSettingsMentionsBase {
|
||||
cancelMentionKeys = () => {
|
||||
this.setState({showKeywordsModal: false});
|
||||
this.keywords = this.state.mention_keys;
|
||||
this.setState({
|
||||
androidKeywords: this.state.mention_keys,
|
||||
showKeywordsModal: false,
|
||||
});
|
||||
};
|
||||
|
||||
cancelReplyNotification = () => {
|
||||
@@ -34,8 +36,8 @@ class NotificationSettingsMentionsAndroid extends NotificationSettingsMentionsBa
|
||||
});
|
||||
};
|
||||
|
||||
onKeywordsChangeText = (value) => {
|
||||
this.keywords = value;
|
||||
onKeywordsChangeText = (androidKeywords) => {
|
||||
this.setState({androidKeywords});
|
||||
};
|
||||
|
||||
onReplyChanged = (value) => {
|
||||
@@ -64,7 +66,7 @@ class NotificationSettingsMentionsAndroid extends NotificationSettingsMentionsBa
|
||||
</View>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
autoFocus={true}
|
||||
value={this.keywords}
|
||||
value={this.state.androidKeywords}
|
||||
blurOnSubmit={true}
|
||||
onChangeText={this.onKeywordsChangeText}
|
||||
onSubmitEditing={this.saveMentionKeys}
|
||||
@@ -243,8 +245,7 @@ class NotificationSettingsMentionsAndroid extends NotificationSettingsMentionsBa
|
||||
}
|
||||
|
||||
saveMentionKeys = () => {
|
||||
this.setState({showKeywordsModal: false});
|
||||
this.updateMentionKeys(this.keywords);
|
||||
this.updateMentionKeys(this.state.androidKeywords);
|
||||
};
|
||||
|
||||
saveReplyNotification = () => {
|
||||
|
||||
214
app/screens/thread/__snapshots__/thread.test.js.snap
Normal file
214
app/screens/thread/__snapshots__/thread.test.js.snap
Normal file
@@ -0,0 +1,214 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`thread should match snapshot, has root post 1`] = `
|
||||
<Connect(SafeAreaIos)
|
||||
excludeHeader={true}
|
||||
keyboardOffset={20}
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<KeyboardLayout
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(PostList)
|
||||
currentUserId="member_user_id"
|
||||
indicateNewMessages={true}
|
||||
navigator={
|
||||
Object {
|
||||
"dismissModal": [MockFunction],
|
||||
"pop": [MockFunction],
|
||||
"resetTo": [MockFunction],
|
||||
"setTitle": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
Object {
|
||||
"title": undefined,
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"isThrow": false,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
postIds={
|
||||
Array [
|
||||
"root_id",
|
||||
"post_id_1",
|
||||
"post_id_2",
|
||||
]
|
||||
}
|
||||
renderFooter={
|
||||
<Loading
|
||||
color="grey"
|
||||
size="large"
|
||||
style={Object {}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Connect(PostTextbox)
|
||||
channelId="channel_id"
|
||||
channelIsArchived={false}
|
||||
navigator={
|
||||
Object {
|
||||
"dismissModal": [MockFunction],
|
||||
"pop": [MockFunction],
|
||||
"resetTo": [MockFunction],
|
||||
"setTitle": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
Object {
|
||||
"title": undefined,
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"isThrow": false,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
onCloseChannel={[Function]}
|
||||
rootId="root_id"
|
||||
/>
|
||||
</KeyboardLayout>
|
||||
</Connect(SafeAreaIos)>
|
||||
`;
|
||||
|
||||
exports[`thread should match snapshot, no root post, loading 1`] = `
|
||||
<Connect(SafeAreaIos)
|
||||
excludeHeader={true}
|
||||
keyboardOffset={20}
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<KeyboardLayout
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Loading
|
||||
color="grey"
|
||||
size="large"
|
||||
style={Object {}}
|
||||
/>
|
||||
</KeyboardLayout>
|
||||
</Connect(SafeAreaIos)>
|
||||
`;
|
||||
|
||||
exports[`thread should match snapshot, render footer 1`] = `
|
||||
<Connect(PostList)
|
||||
currentUserId="member_user_id"
|
||||
indicateNewMessages={true}
|
||||
navigator={
|
||||
Object {
|
||||
"dismissModal": [MockFunction],
|
||||
"pop": [MockFunction],
|
||||
"resetTo": [MockFunction],
|
||||
"setTitle": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
Object {
|
||||
"title": undefined,
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"isThrow": false,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
postIds={
|
||||
Array [
|
||||
"root_id",
|
||||
"post_id_1",
|
||||
"post_id_2",
|
||||
]
|
||||
}
|
||||
renderFooter={
|
||||
<Loading
|
||||
color="grey"
|
||||
size="large"
|
||||
style={Object {}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`thread should match snapshot, render footer 2`] = `
|
||||
<Connect(PostList)
|
||||
currentUserId="member_user_id"
|
||||
indicateNewMessages={true}
|
||||
lastViewedAt={0}
|
||||
navigator={
|
||||
Object {
|
||||
"dismissModal": [MockFunction],
|
||||
"pop": [MockFunction],
|
||||
"resetTo": [MockFunction],
|
||||
"setTitle": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
Object {
|
||||
"title": undefined,
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"isThrow": false,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
postIds={
|
||||
Array [
|
||||
"root_id",
|
||||
"post_id_1",
|
||||
"post_id_2",
|
||||
]
|
||||
}
|
||||
renderFooter={null}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`thread should match snapshot, render footer 3`] = `
|
||||
<Connect(SafeAreaIos)
|
||||
excludeHeader={true}
|
||||
keyboardOffset={20}
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<KeyboardLayout
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Loading
|
||||
color="grey"
|
||||
size="large"
|
||||
style={Object {}}
|
||||
/>
|
||||
</KeyboardLayout>
|
||||
</Connect(SafeAreaIos)>
|
||||
`;
|
||||
@@ -4,7 +4,7 @@
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Platform} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {General, RequestStatus} from 'mattermost-redux/constants';
|
||||
|
||||
import Loading from 'app/components/loading';
|
||||
@@ -16,7 +16,7 @@ import StatusBar from 'app/components/status_bar';
|
||||
import {makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme';
|
||||
import DeletedPost from 'app/components/deleted_post';
|
||||
|
||||
class Thread extends PureComponent {
|
||||
export default class Thread extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
selectPost: PropTypes.func.isRequired,
|
||||
@@ -24,7 +24,6 @@ class Thread extends PureComponent {
|
||||
channelId: PropTypes.string.isRequired,
|
||||
channelType: PropTypes.string,
|
||||
displayName: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
myMember: PropTypes.object.isRequired,
|
||||
rootId: PropTypes.string.isRequired,
|
||||
@@ -36,8 +35,13 @@ class Thread extends PureComponent {
|
||||
|
||||
state = {};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape,
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
const {channelType, displayName, intl} = this.props;
|
||||
const {channelType, displayName} = this.props;
|
||||
const {intl} = this.context;
|
||||
let title;
|
||||
|
||||
if (channelType === General.DM_CHANNEL) {
|
||||
@@ -86,16 +90,21 @@ class Thread extends PureComponent {
|
||||
|
||||
hasRootPost = () => {
|
||||
return this.props.postIds.includes(this.props.rootId);
|
||||
}
|
||||
};
|
||||
|
||||
renderFooter = () => {
|
||||
if (!this.hasRootPost() && this.props.threadLoadingStatus.status !== RequestStatus.STARTED) {
|
||||
return (
|
||||
<DeletedPost theme={this.props.theme}/>
|
||||
);
|
||||
} else if (this.props.threadLoadingStatus.status === RequestStatus.STARTED) {
|
||||
return (
|
||||
<Loading/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
onCloseChannel = () => {
|
||||
this.props.navigator.resetTo({
|
||||
@@ -112,7 +121,7 @@ class Thread extends PureComponent {
|
||||
screenBackgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
@@ -126,14 +135,11 @@ class Thread extends PureComponent {
|
||||
} = this.props;
|
||||
const style = getStyle(theme);
|
||||
let content;
|
||||
if (this.props.threadLoadingStatus.status === RequestStatus.STARTED) {
|
||||
content = (
|
||||
<Loading/>
|
||||
);
|
||||
} else {
|
||||
let postTextBox;
|
||||
if (this.hasRootPost()) {
|
||||
content = (
|
||||
<PostList
|
||||
renderFooter={this.renderFooter}
|
||||
renderFooter={this.renderFooter()}
|
||||
indicateNewMessages={true}
|
||||
postIds={postIds}
|
||||
currentUserId={myMember.user_id}
|
||||
@@ -141,9 +147,7 @@ class Thread extends PureComponent {
|
||||
navigator={navigator}
|
||||
/>
|
||||
);
|
||||
}
|
||||
let postTextBox;
|
||||
if (this.hasRootPost() && this.props.threadLoadingStatus.status !== RequestStatus.STARTED) {
|
||||
|
||||
postTextBox = (
|
||||
<PostTextbox
|
||||
channelIsArchived={channelIsArchived}
|
||||
@@ -153,6 +157,10 @@ class Thread extends PureComponent {
|
||||
onCloseChannel={this.onCloseChannel}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<Loading/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -161,11 +169,7 @@ class Thread extends PureComponent {
|
||||
keyboardOffset={20}
|
||||
>
|
||||
<StatusBar/>
|
||||
<KeyboardLayout
|
||||
behavior='padding'
|
||||
style={style.container}
|
||||
keyboardVerticalOffset={65}
|
||||
>
|
||||
<KeyboardLayout style={style.container}>
|
||||
{content}
|
||||
{postTextBox}
|
||||
</KeyboardLayout>
|
||||
@@ -182,5 +186,3 @@ const getStyle = makeStyleSheetFromTheme((theme) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default injectIntl(Thread);
|
||||
|
||||
104
app/screens/thread/thread.test.js
Normal file
104
app/screens/thread/thread.test.js
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
import {General, RequestStatus} from 'mattermost-redux/constants';
|
||||
import PostList from 'app/components/post_list';
|
||||
|
||||
import Thread from './thread.js';
|
||||
|
||||
jest.mock('react-intl');
|
||||
|
||||
describe('thread', () => {
|
||||
const navigator = {
|
||||
dismissModal: jest.fn(),
|
||||
pop: jest.fn(),
|
||||
resetTo: jest.fn(),
|
||||
setTitle: jest.fn(),
|
||||
};
|
||||
const baseProps = {
|
||||
actions: {
|
||||
selectPost: jest.fn(),
|
||||
},
|
||||
channelId: 'channel_id',
|
||||
channelType: General.OPEN_CHANNEL,
|
||||
displayName: 'channel_display_name',
|
||||
navigator,
|
||||
myMember: {last_viewed_at: 0, user_id: 'member_user_id'},
|
||||
rootId: 'root_id',
|
||||
theme: Preferences.THEMES.default,
|
||||
postIds: ['root_id', 'post_id_1', 'post_id_2'],
|
||||
channelIsArchived: false,
|
||||
threadLoadingStatus: {status: RequestStatus.STARTED},
|
||||
};
|
||||
|
||||
test('should match snapshot, has root post', () => {
|
||||
const wrapper = shallow(
|
||||
<Thread {...baseProps}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, no root post, loading', () => {
|
||||
const newPostIds = ['post_id_1', 'post_id_2'];
|
||||
const wrapper = shallow(
|
||||
<Thread
|
||||
{...baseProps}
|
||||
postIds={newPostIds}
|
||||
/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should call props.navigator on onCloseChannel', () => {
|
||||
const channelScreen = {
|
||||
screen: 'Channel',
|
||||
title: '',
|
||||
animated: false,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
animated: true,
|
||||
animationType: 'fade',
|
||||
navBarHidden: true,
|
||||
statusBarHidden: false,
|
||||
statusBarHideWithNavBar: false,
|
||||
screenBackgroundColor: 'transparent',
|
||||
},
|
||||
};
|
||||
const newNavigator = {...navigator};
|
||||
const wrapper = shallow(
|
||||
<Thread
|
||||
{...baseProps}
|
||||
navigator={newNavigator}
|
||||
/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
wrapper.instance().onCloseChannel();
|
||||
expect(newNavigator.resetTo).toHaveBeenCalledTimes(1);
|
||||
expect(newNavigator.resetTo).toBeCalledWith(channelScreen);
|
||||
});
|
||||
|
||||
test('should match snapshot, render footer', () => {
|
||||
const wrapper = shallow(
|
||||
<Thread {...baseProps}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
// return loading
|
||||
expect(wrapper.find(PostList).getElement()).toMatchSnapshot();
|
||||
|
||||
// return null
|
||||
wrapper.setProps({threadLoadingStatus: {status: RequestStatus.SUCCESS}});
|
||||
expect(wrapper.find(PostList).getElement()).toMatchSnapshot();
|
||||
|
||||
// return deleted post
|
||||
const newPostIds = ['post_id_1', 'post_id_2'];
|
||||
wrapper.setProps({postIds: newPostIds});
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -12,7 +12,7 @@ exports[`user_profile should match snapshot 1`] = `
|
||||
<ScrollView
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#aaa",
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ exports[`user_profile should match snapshot 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"color": "#aaa",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
"marginTop": 15,
|
||||
}
|
||||
@@ -69,7 +69,7 @@ exports[`user_profile should match snapshot 1`] = `
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"color": "#aaa",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
@@ -89,9 +89,30 @@ exports[`user_profile should match snapshot 1`] = `
|
||||
textId="mobile.routes.user_profile.send_message"
|
||||
theme={
|
||||
Object {
|
||||
"centerChannelBg": "#aaa",
|
||||
"centerChannelColor": "#aaa",
|
||||
"color": "#aaa",
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
jest.mock('react-intl');
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import UserProfile from './user_profile.js';
|
||||
|
||||
jest.mock('react-intl');
|
||||
jest.mock('app/utils/theme', () => {
|
||||
const original = require.requireActual('app/utils/theme');
|
||||
return {
|
||||
@@ -29,11 +31,7 @@ describe('user_profile', () => {
|
||||
resetTo: jest.fn(),
|
||||
},
|
||||
teams: [],
|
||||
theme: {
|
||||
centerChannelBg: '#aaa',
|
||||
centerChannelColor: '#aaa',
|
||||
color: '#aaa',
|
||||
},
|
||||
theme: Preferences.THEMES.default,
|
||||
enableTimezone: false,
|
||||
user: {
|
||||
email: 'test@test.com',
|
||||
|
||||
@@ -44,7 +44,10 @@ export function makePreparePostIdsForPostList() {
|
||||
for (let i = posts.length - 1; i >= 0; i--) {
|
||||
const post = posts[i];
|
||||
|
||||
if (post.type === Posts.POST_TYPES.EPHEMERAL_ADD_TO_CHANNEL && !selectedPostId) {
|
||||
if (
|
||||
!post ||
|
||||
(post.type === Posts.POST_TYPES.EPHEMERAL_ADD_TO_CHANNEL && !selectedPostId)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import mattermostBucket from 'app/mattermost_bucket';
|
||||
import Config from 'assets/config';
|
||||
|
||||
import {messageRetention} from './middleware';
|
||||
import {createThunkMiddleware} from './thunk';
|
||||
import {transformSet} from './utils';
|
||||
|
||||
function getAppReducer() {
|
||||
@@ -276,8 +277,14 @@ export default function configureAppStore(initialState) {
|
||||
},
|
||||
};
|
||||
|
||||
const additionalMiddleware = [createSentryMiddleware(), messageRetention];
|
||||
return configureStore(initialState, appReducer, offlineOptions, getAppReducer, {
|
||||
additionalMiddleware,
|
||||
});
|
||||
const clientOptions = {
|
||||
additionalMiddleware: [
|
||||
createThunkMiddleware(),
|
||||
createSentryMiddleware(),
|
||||
messageRetention,
|
||||
],
|
||||
enableThunk: false, // We override the default thunk middleware
|
||||
};
|
||||
|
||||
return configureStore(initialState, appReducer, offlineOptions, getAppReducer, clientOptions);
|
||||
}
|
||||
|
||||
41
app/store/thunk.js
Normal file
41
app/store/thunk.js
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {
|
||||
captureMessage,
|
||||
cleanUrlForLogging,
|
||||
LOGGER_JAVASCRIPT_WARNING,
|
||||
} from 'app/utils/sentry';
|
||||
|
||||
// Creates middleware that mimics thunk while catching network errors thrown by Client4 that haven't
|
||||
// been otherwise handled.
|
||||
export function createThunkMiddleware() {
|
||||
return (store) => (next) => (action) => {
|
||||
if (typeof action === 'function') {
|
||||
const result = action(store.dispatch, store.getState);
|
||||
|
||||
if (result instanceof Promise) {
|
||||
return result.catch((error) => {
|
||||
if (error.url) {
|
||||
// This is a connection error from mattermost-redux. This should've been handled
|
||||
// within the action itself, so we'll log to Sentry enough to identify where
|
||||
// that handling is missing.
|
||||
captureMessage(
|
||||
`Caught Client4 error "${error.message}" from "${cleanUrlForLogging(error.url)}"`,
|
||||
LOGGER_JAVASCRIPT_WARNING,
|
||||
store
|
||||
);
|
||||
|
||||
return {error};
|
||||
}
|
||||
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return next(action);
|
||||
};
|
||||
}
|
||||
@@ -28,7 +28,10 @@ export const calculateDimensions = (height, width, viewPortWidth = 0, viewPortHe
|
||||
) {
|
||||
imageHeight = viewPortHeight || IMAGE_MAX_HEIGHT;
|
||||
imageWidth = imageHeight * heightRatio;
|
||||
} else if (imageHeight < IMAGE_MIN_DIMENSION) {
|
||||
} else if (
|
||||
imageHeight < IMAGE_MIN_DIMENSION &&
|
||||
IMAGE_MIN_DIMENSION * heightRatio <= viewPortWidth
|
||||
) {
|
||||
imageHeight = IMAGE_MIN_DIMENSION;
|
||||
imageWidth = imageHeight * heightRatio;
|
||||
}
|
||||
|
||||
@@ -56,4 +56,10 @@ describe('Images calculateDimensions', () => {
|
||||
const {height} = calculateDimensions(1920, 1080, PORTRAIT_VIEWPORT, 340);
|
||||
expect(height).toEqual(340);
|
||||
});
|
||||
|
||||
it('images with height below 50 but setting to 50 will make the width exceed the view port width should remain as is', () => {
|
||||
const {height, width} = calculateDimensions(45, 310, PORTRAIT_VIEWPORT);
|
||||
expect(height).toEqual(45);
|
||||
expect(width).toEqual(310);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ export function generateUserProfilesById(userProfiles = []) {
|
||||
|
||||
export function getMissingUserIds(userProfilesById = {}, allUserIds = []) {
|
||||
return allUserIds.reduce((acc, userId) => {
|
||||
if (userProfilesById[userId]) {
|
||||
if (!userProfilesById[userId]) {
|
||||
acc.push(userId);
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user