Commit e32a6606 authored by James Addison's avatar James Addison
Browse files

Merge branch 'master' into collabora

parents fc8c92e7 b0a0b054
Pipeline #28468 passed with stage
in 16 minutes and 23 seconds
......@@ -27,3 +27,6 @@ indent_style = tab
[*.scss]
indent_style = space
indent_size = 4
[.npm-upgrade.json]
insert_final_newline = false
{
"ignore": {
"@formatjs/intl-pluralrules": {
"versions": "",
"reason": "MM-34912 - Breaking changes to how locale data is loaded."
},
"@formatjs/intl-relativetimeformat": {
"versions": "",
"reason": "MM-34912 - Breaking changes to how locale data is loaded."
},
"@stripe/react-stripe-js": {
"versions": "",
"reason": "MM-34916 - Upgrades involve sensitive parts of the code."
},
"@stripe/stripe-js": {
"versions": "",
"reason": "MM-34916 - Upgrades involve sensitive parts of the code."
},
"bootstrap": {
"versions": "",
"reason": "MM-10786 - Bootstrap is heavily integrated in the code base and signficantly out of date."
},
"@types/bootstrap": {
"versions": "",
"reason": "MM-10786 - Bootstrap is heavily integrated in the code base and signficantly out of date."
},
"chart.js": {
"versions": "",
"reason": "MM-34915 - Breaking changes to how components are loaded."
},
"emoji-datasource": {
"versions": "",
"reason": "MM-36772 - Emoji updates are handled separately from regular dependencies."
},
"emoji-datasource-apple": {
"versions": "",
"reason": "MM-36772 - Emoji updates are handled separately from regular dependencies."
},
"jasny-bootstrap": {
"versions": "",
"reason": "MM-18032 - Breaking changes that may involve upgrading Bootstrap."
},
"p-queue": {
"versions": "",
"reason": "MM-34914 - Breaking changes to how modules are loaded."
},
"pdfjs-dist": {
"versions": "",
"reason": "MM-32929 - Breaking changes to how library is loaded and used."
},
"react": {
"versions": "",
"reason": "MM-34187 - React updates are handled separately from regular dependencies."
},
"@types/react": {
"versions": "",
"reason": "MM-34187 - React updates are handled separately from regular dependencies."
},
"react-dom": {
"versions": "",
"reason": "MM-34187 - React updates are handled separately from regular dependencies."
},
"@types/react-dom": {
"versions": "",
"reason": "MM-34187 - React updates are handled separately from regular dependencies."
},
"react-is": {
"versions": "",
"reason": "MM-34187 - React updates are handled separately from regular dependencies."
},
"@types/react-is": {
"versions": "",
"reason": "MM-34187 - React updates are handled separately from regular dependencies."
},
"react-overlays": {
"versions": "",
"reason": "MM-10786 - Bootstrap is heavily integrated in the code base and signficantly out of date."
},
"redux-persist": {
"versions": "",
"reason": "MM-34911 - Library is no longer supported and we're significantly out of date on it."
},
"serialize-error": {
"versions": "",
"reason": "MM-34914 - Breaking changes to how modules are loaded."
},
"@hot-loader/react-dom": {
"versions": "",
"reason": "MM-34187 - React updates are handled separately from other dependencies."
},
"@types/marked": {
"versions": "",
"reason": "Ignored since our forked version is wildly different from upstream."
},
"@types/react-bootstrap": {
"versions": "",
"reason": "MM-10786 - Bootstrap is heavily integrated in the code base and signficantly out of date."
},
"react-select": {
"versions": "",
"reason": "MM-18031 - Breaking changes."
},
"@types/react-select": {
"versions": "",
"reason": "MM-18031 - Breaking changes."
},
"flexsearch": {
"versions": "",
"reason": "MM-37116 - Breaking changes in API"
}
}
}
\ No newline at end of file
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {StorageTypes} from 'utils/constants';
import {StoragePrefixes, StorageTypes} from 'utils/constants';
import {getPrefix} from 'utils/storage_utils';
import {batchActions} from 'mattermost-redux/types/actions';
export function setItem(name, value) {
return (dispatch, getState) => {
const state = getState();
......@@ -80,6 +82,27 @@ export function actionOnItemsWithPrefix(prefix, action) {
};
}
// Temporary action to manually rehydrate drafts from localStorage.
function rehydrateDrafts() {
return (dispatch) => {
const actions = [];
Object.entries(localStorage).forEach((entry) => {
const key = entry[0];
const value = entry[1];
if (key.indexOf(StoragePrefixes.DRAFT) === 0 || key.indexOf(StoragePrefixes.COMMENT_DRAFT) === 0) {
actions.push({
type: StorageTypes.SET_GLOBAL_ITEM,
data: {name: key, value: JSON.parse(value), timestamp: new Date()},
});
}
});
dispatch(batchActions(actions));
return {data: true};
};
}
export function storageRehydrate(incoming, persistor) {
return (dispatch, getState) => {
const state = getState();
......@@ -115,6 +138,7 @@ export function storageRehydrate(incoming, persistor) {
data: storage,
});
});
dispatch(rehydrateDrafts());
persistor.resume();
return {data: true};
};
......
......@@ -39,8 +39,16 @@ export function clearCommentDraftUploads() {
});
}
// Temporarily store draft manually in localStorage since the current version of redux-persist
// we're on will not save the draft quickly enough on page unload.
export function updateCommentDraft(rootId, draft) {
return setGlobalItem(`${StoragePrefixes.COMMENT_DRAFT}${rootId}`, draft);
const key = `${StoragePrefixes.COMMENT_DRAFT}${rootId}`;
if (draft) {
localStorage.setItem(key, JSON.stringify(draft));
} else {
localStorage.removeItem(key);
}
return setGlobalItem(key, draft);
}
export function makeOnMoveHistoryIndex(rootId, direction) {
......@@ -146,8 +154,7 @@ export function submitCommand(channelId, rootId, draft) {
}
export function makeOnSubmit(channelId, rootId, latestPostId) {
return (options = {}) => async (dispatch, getState) => {
const draft = getPostDraft(getState(), StoragePrefixes.COMMENT_DRAFT, rootId);
return (draft, options = {}) => async (dispatch, getState) => {
const {message} = draft;
dispatch(addMessageIntoHistory(message));
......
......@@ -332,9 +332,14 @@ describe('rhs view actions', () => {
describe('makeOnSubmit', () => {
const onSubmit = makeOnSubmit(channelId, rootId, latestPostId);
const draft = {
message: '',
fileInfos: [],
uploadsInProgress: [],
};
test('it adds message into history', () => {
store.dispatch(onSubmit());
store.dispatch(onSubmit(draft));
const testStore = mockStore(initialState);
testStore.dispatch(addMessageIntoHistory(''));
......@@ -345,7 +350,7 @@ describe('rhs view actions', () => {
});
test('it clears comment draft', () => {
store.dispatch(onSubmit());
store.dispatch(onSubmit(draft));
const testStore = mockStore(initialState);
testStore.dispatch(updateCommentDraft(rootId, null));
......@@ -356,23 +361,11 @@ describe('rhs view actions', () => {
});
test('it submits a reaction when message is +:smile:', () => {
store = mockStore({
...initialState,
storage: {
storage: {
[`${StoragePrefixes.COMMENT_DRAFT}${rootId}`]: {
value: {
message: '+:smile:',
fileInfos: [],
uploadsInProgress: [],
},
timestamp: new Date(),
},
},
},
});
store.dispatch(onSubmit());
store.dispatch(onSubmit({
message: '+:smile:',
fileInfos: [],
uploadsInProgress: [],
}));
const testStore = mockStore(initialState);
testStore.dispatch(submitReaction(latestPostId, '+', 'smile'));
......@@ -383,23 +376,11 @@ describe('rhs view actions', () => {
});
test('it submits a command when message is /away', () => {
store = mockStore({
...initialState,
storage: {
storage: {
[`${StoragePrefixes.COMMENT_DRAFT}${latestPostId}`]: {
value: {
message: '/away',
fileInfos: [],
uploadsInProgress: [],
},
timestamp: new Date(),
},
},
},
});
store.dispatch(onSubmit());
store.dispatch(onSubmit({
message: '/away',
fileInfos: [],
uploadsInProgress: [],
}));
const testStore = mockStore(initialState);
testStore.dispatch(submitCommand(channelId, rootId, {message: '/away', fileInfos: [], uploadsInProgress: []}));
......@@ -412,23 +393,11 @@ describe('rhs view actions', () => {
});
test('it submits a regular post when options.ignoreSlash is true', () => {
store = mockStore({
...initialState,
storage: {
storage: {
[`${StoragePrefixes.COMMENT_DRAFT}${latestPostId}`]: {
value: {
message: '/fakecommand',
fileInfos: [],
uploadsInProgress: [],
},
timestamp: new Date(),
},
},
},
});
store.dispatch(onSubmit({ignoreSlash: true}));
store.dispatch(onSubmit({
message: '/fakecommand',
fileInfos: [],
uploadsInProgress: [],
}, {ignoreSlash: true}));
const testStore = mockStore(initialState);
testStore.dispatch(submitPost(channelId, rootId, {message: '/fakecommand', fileInfos: [], uploadsInProgress: []}));
......@@ -440,23 +409,11 @@ describe('rhs view actions', () => {
});
test('it submits a regular post when message is something else', () => {
store = mockStore({
...initialState,
storage: {
storage: {
[`${StoragePrefixes.COMMENT_DRAFT}${latestPostId}`]: {
value: {
message: 'test msg',
fileInfos: [],
uploadsInProgress: [],
},
timestamp: new Date(),
},
},
},
});
store.dispatch(onSubmit());
store.dispatch(onSubmit({
message: 'test msg',
fileInfos: [],
uploadsInProgress: [],
}));
const testStore = mockStore(initialState);
testStore.dispatch(submitPost(channelId, rootId, {message: 'test msg', fileInfos: [], uploadsInProgress: []}));
......
......@@ -808,6 +808,10 @@ function handleDeleteTeamEvent(msg) {
{type: TeamTypes.UPDATED_TEAM, data: deletedTeam},
]));
if (browserHistory.location?.pathname === `/admin_console/user_management/teams/${deletedTeam.id}`) {
return;
}
if (newTeamId) {
dispatch({type: TeamTypes.SELECT_TEAM, data: newTeamId});
const globalState = getState();
......@@ -1471,6 +1475,14 @@ function handleThreadUpdated(msg) {
if (isThreadOpen(state, threadData.id) && !isThreadManuallyUnread(state, threadData.id)) {
lastViewedAt = Date.now();
// Sometimes `Date.now()` was generating a timestamp before the
// last_reply_at of the thread, thus marking the thread as unread
// instead of read. Here we set the timestamp to after the
// last_reply_at if this happens.
if (lastViewedAt < threadData.last_reply_at) {
lastViewedAt = threadData.last_reply_at + 1;
}
// prematurely update thread data as read
// so we won't flash the indicators when
// we mark the thread as read on the server
......
......@@ -30,6 +30,13 @@ const config = {
'@babel/plugin-proposal-object-rest-spread',
'react-hot-loader/babel',
'babel-plugin-typescript-to-proptypes',
[
'babel-plugin-styled-components',
{
ssr: false,
fileName: false,
},
],
],
};
......
......@@ -16,13 +16,13 @@ exports[`components/FileUploadOverlay should match snapshot when file upload is
src={null}
/>
<span>
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage="Upload Icon"
id="generic_icons.upload"
>
<Component />
</FormattedMessage>
<FormattedMessage
</MemoizedFormattedMessage>
<MemoizedFormattedMessage
defaultMessage="Drop a file to upload it."
id="upload_overlay.info"
/>
......@@ -54,13 +54,13 @@ exports[`components/FileUploadOverlay should match snapshot when file upload is
src={null}
/>
<span>
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage="Upload Icon"
id="generic_icons.upload"
>
<Component />
</FormattedMessage>
<FormattedMessage
</MemoizedFormattedMessage>
<MemoizedFormattedMessage
defaultMessage="Drop a file to upload it."
id="upload_overlay.info"
/>
......@@ -92,13 +92,13 @@ exports[`components/FileUploadOverlay should match snapshot when file upload is
src={null}
/>
<span>
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage="Upload Icon"
id="generic_icons.upload"
>
<Component />
</FormattedMessage>
<FormattedMessage
</MemoizedFormattedMessage>
<MemoizedFormattedMessage
defaultMessage="Drop a file to upload it."
id="upload_overlay.info"
/>
......
......@@ -34,10 +34,13 @@ exports[`components/FormattedMarkdownMessage should allow to disable links 1`] =
Object {
"defaultFormats": Object {},
"defaultLocale": "en",
"defaultRichTextElements": undefined,
"formatDate": [Function],
"formatDateTimeRange": [Function],
"formatDateToParts": [Function],
"formatDisplayName": [Function],
"formatList": [Function],
"formatListToParts": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatNumberToParts": [Function],
......@@ -113,10 +116,13 @@ exports[`components/FormattedMarkdownMessage should backup to default 1`] = `
Object {
"defaultFormats": Object {},
"defaultLocale": "en",
"defaultRichTextElements": undefined,
"formatDate": [Function],
"formatDateTimeRange": [Function],
"formatDateToParts": [Function],
"formatDisplayName": [Function],
"formatList": [Function],
"formatListToParts": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatNumberToParts": [Function],
......@@ -193,10 +199,13 @@ exports[`components/FormattedMarkdownMessage should escape non-BR 1`] = `
Object {
"defaultFormats": Object {},
"defaultLocale": "en",
"defaultRichTextElements": undefined,
"formatDate": [Function],
"formatDateTimeRange": [Function],
"formatDateToParts": [Function],
"formatDisplayName": [Function],
"formatList": [Function],
"formatListToParts": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatNumberToParts": [Function],
......@@ -273,10 +282,13 @@ exports[`components/FormattedMarkdownMessage should render message 1`] = `
Object {
"defaultFormats": Object {},
"defaultLocale": "en",
"defaultRichTextElements": undefined,
"formatDate": [Function],
"formatDateTimeRange": [Function],
"formatDateToParts": [Function],
"formatDisplayName": [Function],
"formatList": [Function],
"formatListToParts": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatNumberToParts": [Function],
......@@ -352,10 +364,13 @@ exports[`components/FormattedMarkdownMessage values should work 1`] = `
Object {
"defaultFormats": Object {},
"defaultLocale": "en",
"defaultRichTextElements": undefined,
"formatDate": [Function],
"formatDateTimeRange": [Function],
"formatDateToParts": [Function],
"formatDisplayName": [Function],
"formatList": [Function],
"formatListToParts": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatNumberToParts": [Function],
......
......@@ -125,7 +125,7 @@ exports[`components/GenericModal should match snapshot with both buttons 1`] = `
onClick={[Function]}
type="button"
>
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage="Cancel"
id="generic_modal.cancel"
/>
......@@ -135,7 +135,7 @@ exports[`components/GenericModal should match snapshot with both buttons 1`] = `
onClick={[Function]}
type="submit"
>
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage="Confirm"
id="generic_modal.confirm"
/>
......@@ -209,7 +209,7 @@ exports[`components/GenericModal should match snapshot with disabled confirm but
onClick={[Function]}
type="submit"
>
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage="Confirm"
id="generic_modal.confirm"
/>
......
......@@ -64,7 +64,7 @@ exports[`components/GetLinkModal should have called onHide 1`] = `
onClick={[Function]}
type="button"
>
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage="Close"
id="get_link.close"
/>
......@@ -76,7 +76,7 @@ exports[`components/GetLinkModal should have called onHide 1`] = `
onClick={[Function]}
type="button"
>
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage="Copy Link"
id="get_link.copy"
/>
......@@ -85,7 +85,7 @@ exports[`components/GetLinkModal should have called onHide 1`] = `
className="alert alert-success alert--confirm"
>
<SuccessIcon />
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage=" Link copied"
id="get_link.clipboard"
/>
......@@ -158,7 +158,7 @@ exports[`components/GetLinkModal should have called onHide 2`] = `
onClick={[Function]}
type="button"
>
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage="Close"
id="get_link.close"
/>
......@@ -170,7 +170,7 @@ exports[`components/GetLinkModal should have called onHide 2`] = `
onClick={[Function]}
type="button"
>
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage="Copy Link"
id="get_link.copy"
/>
......@@ -248,7 +248,7 @@ exports[`components/GetLinkModal should match snapshot when all props is set 1`]
onClick={[Function]}
type="button"
>
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage="Close"
id="get_link.close"
/>
......@@ -260,7 +260,7 @@ exports[`components/GetLinkModal should match snapshot when all props is set 1`]
onClick={[Function]}
type="button"
>
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage="Copy Link"
id="get_link.copy"
/>
......@@ -333,7 +333,7 @@ exports[`components/GetLinkModal should match snapshot when helpText is not set
onClick={[Function]}
type="button"
>
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage="Close"
id="get_link.close"
/>
......@@ -345,7 +345,7 @@ exports[`components/GetLinkModal should match snapshot when helpText is not set
onClick={[Function]}
type="button"
>
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage="Copy Link"
id="get_link.copy"
/>
......
......@@ -77,7 +77,7 @@ exports[`components/ListModal should match snapshot 1`] = `
<span
className="member-count pull-left"
>
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage="{startCount, number} - {endCount, number} of {total, number} total"
id="list_modal.paginatorCount"
values={
......@@ -190,7 +190,7 @@ exports[`components/ListModal should match snapshot with title bar button 1`] =
<span
className="member-count pull-left"
>
<FormattedMessage
<MemoizedFormattedMessage
defaultMessage="{startCount, number} - {endCount, number} of {total, number} total"