summaryrefslogtreecommitdiffstats
path: root/web/react
diff options
context:
space:
mode:
Diffstat (limited to 'web/react')
-rw-r--r--web/react/components/more_direct_channels.jsx59
-rw-r--r--web/react/components/sidebar.jsx248
-rw-r--r--web/react/components/team_import_tab.jsx6
-rw-r--r--web/react/stores/preference_store.jsx122
-rw-r--r--web/react/utils/async_client.jsx52
-rw-r--r--web/react/utils/client.jsx28
-rw-r--r--web/react/utils/constants.jsx6
-rw-r--r--web/react/utils/markdown.jsx38
-rw-r--r--web/react/utils/text_formatting.jsx9
9 files changed, 389 insertions, 179 deletions
diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx
index 31ecb4c5d..bc610cd60 100644
--- a/web/react/components/more_direct_channels.jsx
+++ b/web/react/components/more_direct_channels.jsx
@@ -1,10 +1,11 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var ChannelStore = require('../stores/channel_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
var Client = require('../utils/client.jsx');
+var Constants = require('../utils/constants.jsx');
var AsyncClient = require('../utils/async_client.jsx');
+var PreferenceStore = require('../stores/preference_store.jsx');
var utils = require('../utils/utils.jsx');
export default class MoreDirectChannels extends React.Component {
@@ -15,27 +16,28 @@ export default class MoreDirectChannels extends React.Component {
}
componentDidMount() {
- var self = this;
- $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function showModal(e) {
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', (e) => {
var button = e.relatedTarget;
- self.setState({channels: $(button).data('channels')});
+ this.setState({channels: $(button).data('channels')}); // eslint-disable-line react/no-did-mount-set-state
});
}
- render() {
- var self = this;
+ handleJoinDirectChannel(channel) {
+ const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, channel.teammate_id, 'true');
+ AsyncClient.savePreferences([preference]);
+ }
- var directMessageItems = this.state.channels.map(function mapActivityToChannel(channel, index) {
+ render() {
+ var directMessageItems = this.state.channels.map((channel, index) => {
var badge = '';
var titleClass = '';
- var active = '';
var handleClick = null;
if (channel.fake) {
// It's a direct message channel that doesn't exist yet so let's create it now
var otherUserId = utils.getUserIdFromChannelName(channel);
- if (self.state.loadingDMChannel === index) {
+ if (this.state.loadingDMChannel === index) {
badge = (
<img
className='channel-loading-gif pull-right'
@@ -44,47 +46,42 @@ export default class MoreDirectChannels extends React.Component {
);
}
- if (self.state.loadingDMChannel === -1) {
- handleClick = function clickHandler(e) {
+ if (this.state.loadingDMChannel === -1) {
+ handleClick = (e) => {
e.preventDefault();
- self.setState({loadingDMChannel: index});
+ this.setState({loadingDMChannel: index});
+ this.handleJoinDirectChannel(channel);
Client.createDirectChannel(channel, otherUserId,
- function success(data) {
- $(React.findDOMNode(self.refs.modal)).modal('hide');
- self.setState({loadingDMChannel: -1});
+ (data) => {
+ $(React.findDOMNode(this.refs.modal)).modal('hide');
+ this.setState({loadingDMChannel: -1});
AsyncClient.getChannel(data.id);
utils.switchChannel(data);
},
- function error() {
- self.setState({loadingDMChannel: -1});
+ () => {
+ this.setState({loadingDMChannel: -1});
window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
}
);
};
}
} else {
- if (channel.id === ChannelStore.getCurrentId()) {
- active = 'active';
- }
-
if (channel.unread) {
badge = <span className='badge pull-right small'>{channel.unread}</span>;
titleClass = 'unread-title';
}
- handleClick = function clickHandler(e) {
+ handleClick = (e) => {
e.preventDefault();
+ this.handleJoinDirectChannel(channel);
utils.switchChannel(channel);
- $(React.findDOMNode(self.refs.modal)).modal('hide');
+ $(React.findDOMNode(this.refs.modal)).modal('hide');
};
}
return (
- <li
- key={channel.name}
- className={active}
- >
+ <li key={channel.name}>
<a
className={'sidebar-channel ' + titleClass}
href='#'
@@ -111,10 +108,10 @@ export default class MoreDirectChannels extends React.Component {
className='close'
data-dismiss='modal'
>
- <span aria-hidden='true'>&times;</span>
- <span className='sr-only'>Close</span>
+ <span aria-hidden='true'>{'×'}</span>
+ <span className='sr-only'>{'Close'}</span>
</button>
- <h4 className='modal-title'>More Direct Messages</h4>
+ <h4 className='modal-title'>{'More Direct Messages'}</h4>
</div>
<div className='modal-body'>
<ul className='nav nav-pills nav-stacked'>
@@ -126,7 +123,7 @@ export default class MoreDirectChannels extends React.Component {
type='button'
className='btn btn-default'
data-dismiss='modal'
- >Close</button>
+ >{'Close'}</button>
</div>
</div>
</div>
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 4ac1fd4a0..1caf4caa5 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -1,19 +1,20 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var ChannelStore = require('../stores/channel_store.jsx');
-var Client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var SocketStore = require('../stores/socket_store.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var TeamStore = require('../stores/team_store.jsx');
-var BrowserStore = require('../stores/browser_store.jsx');
-var Utils = require('../utils/utils.jsx');
-var SidebarHeader = require('./sidebar_header.jsx');
-var SearchBox = require('./search_bar.jsx');
-var Constants = require('../utils/constants.jsx');
-var NewChannelFlow = require('./new_channel_flow.jsx');
-var UnreadChannelIndicator = require('./unread_channel_indicator.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const BrowserStore = require('../stores/browser_store.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
+const Client = require('../utils/client.jsx');
+const Constants = require('../utils/constants.jsx');
+const PreferenceStore = require('../stores/preference_store.jsx');
+const NewChannelFlow = require('./new_channel_flow.jsx');
+const SearchBox = require('./search_bar.jsx');
+const SidebarHeader = require('./sidebar_header.jsx');
+const SocketStore = require('../stores/socket_store.jsx');
+const TeamStore = require('../stores/team_store.jsx');
+const UnreadChannelIndicator = require('./unread_channel_indicator.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const Utils = require('../utils/utils.jsx');
export default class Sidebar extends React.Component {
constructor(props) {
@@ -23,12 +24,17 @@ export default class Sidebar extends React.Component {
this.firstUnreadChannel = null;
this.lastUnreadChannel = null;
+ this.getStateFromStores = this.getStateFromStores.bind(this);
+
this.onChange = this.onChange.bind(this);
this.onScroll = this.onScroll.bind(this);
this.onResize = this.onResize.bind(this);
this.updateUnreadIndicators = this.updateUnreadIndicators.bind(this);
+ this.handleLeaveDirectChannel = this.handleLeaveDirectChannel.bind(this);
this.createChannelElement = this.createChannelElement.bind(this);
+ this.isLeaving = new Map();
+
const state = this.getStateFromStores();
state.modal = '';
state.loadingDMChannel = -1;
@@ -36,7 +42,7 @@ export default class Sidebar extends React.Component {
this.state = state;
}
getStateFromStores() {
- var members = ChannelStore.getAllMembers();
+ const members = ChannelStore.getAllMembers();
var teamMemberMap = UserStore.getActiveOnlyProfiles();
var currentId = ChannelStore.getCurrentId();
@@ -48,11 +54,13 @@ export default class Sidebar extends React.Component {
teammates.push(teamMemberMap[id]);
}
+ const preferences = PreferenceStore.getPreferences(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
+
// Create lists of all read and unread direct channels
- var showDirectChannels = [];
- var readDirectChannels = [];
+ var visibleDirectChannels = [];
+ var hiddenDirectChannels = [];
for (var i = 0; i < teammates.length; i++) {
- var teammate = teammates[i];
+ const teammate = teammates[i];
if (teammate.id === UserStore.getCurrentId()) {
continue;
@@ -65,90 +73,63 @@ export default class Sidebar extends React.Component {
channelName = teammate.id + '__' + UserStore.getCurrentId();
}
- var channel = ChannelStore.getByName(channelName);
-
- if (channel == null) {
- var tempChannel = {};
- tempChannel.fake = true;
- tempChannel.name = channelName;
- tempChannel.display_name = teammate.username;
- tempChannel.teammate_username = teammate.username;
- tempChannel.status = UserStore.getStatus(teammate.id);
- tempChannel.last_post_at = 0;
- tempChannel.total_msg_count = 0;
- tempChannel.type = 'D';
- readDirectChannels.push(tempChannel);
- } else {
- channel.display_name = teammate.username;
- channel.teammate_username = teammate.username;
+ let forceShow = false;
+ let channel = ChannelStore.getByName(channelName);
- channel.status = UserStore.getStatus(teammate.id);
+ if (channel) {
+ const member = members[channel.id];
+ const msgCount = channel.total_msg_count - member.msg_count;
- var channelMember = members[channel.id];
- var msgCount = channel.total_msg_count - channelMember.msg_count;
- if (msgCount > 0) {
- showDirectChannels.push(channel);
- } else if (currentId === channel.id) {
- showDirectChannels.push(channel);
- } else {
- readDirectChannels.push(channel);
- }
+ // always show a channel if either it is the current one or if it is unread, but it is not currently being left
+ forceShow = (currentId === channel.id || msgCount > 0) && !this.isLeaving.get(channel.id);
+ } else {
+ channel = {};
+ channel.fake = true;
+ channel.name = channelName;
+ channel.last_post_at = 0;
+ channel.total_msg_count = 0;
+ channel.type = 'D';
}
- }
- // If we don't have MAX_DMS unread channels, sort the read list by last_post_at
- if (showDirectChannels.length < Constants.MAX_DMS) {
- readDirectChannels.sort(function sortByLastPost(a, b) {
- // sort by last_post_at first
- if (a.last_post_at > b.last_post_at) {
- return -1;
- }
- if (a.last_post_at < b.last_post_at) {
- return 1;
- }
+ channel.display_name = teammate.username;
+ channel.teammate_id = teammate.id;
+ channel.status = UserStore.getStatus(teammate.id);
- // if last_post_at is equal, sort by name
- if (a.display_name < b.display_name) {
- return -1;
- }
- if (a.display_name > b.display_name) {
- return 1;
- }
- return 0;
- });
+ if (preferences.some((preference) => (preference.name === teammate.id && preference.value !== 'false'))) {
+ visibleDirectChannels.push(channel);
+ } else if (forceShow) {
+ // make sure that unread direct channels are visible
+ const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, teammate.id, 'true');
+ AsyncClient.savePreferences([preference]);
- var index = 0;
- while (showDirectChannels.length < Constants.MAX_DMS && index < readDirectChannels.length) {
- showDirectChannels.push(readDirectChannels[index]);
- index++;
+ visibleDirectChannels.push(channel);
+ } else {
+ hiddenDirectChannels.push(channel);
}
- readDirectChannels = readDirectChannels.slice(index);
-
- showDirectChannels.sort(function directSort(a, b) {
- if (a.display_name < b.display_name) {
- return -1;
- }
- if (a.display_name > b.display_name) {
- return 1;
- }
- return 0;
- });
}
+ visibleDirectChannels.sort(this.sortChannelsByDisplayName);
+ hiddenDirectChannels.sort(this.sortChannelsByDisplayName);
+
return {
activeId: currentId,
channels: ChannelStore.getAll(),
- members: members,
- showDirectChannels: showDirectChannels,
- hideDirectChannels: readDirectChannels
+ members,
+ visibleDirectChannels,
+ hiddenDirectChannels
};
}
+
componentDidMount() {
ChannelStore.addChangeListener(this.onChange);
UserStore.addChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onChange);
TeamStore.addChangeListener(this.onChange);
SocketStore.addChangeListener(this.onSocketChange);
+ PreferenceStore.addChangeListener(this.onChange);
+
+ AsyncClient.getDirectChannelPreferences();
+
$('.nav-pills__container').perfectScrollbar();
this.updateTitle();
@@ -178,6 +159,7 @@ export default class Sidebar extends React.Component {
UserStore.removeStatusesChangeListener(this.onChange);
TeamStore.removeChangeListener(this.onChange);
SocketStore.removeChangeListener(this.onSocketChange);
+ PreferenceStore.removeChangeListener(this.onChange);
}
onChange() {
var newState = this.getStateFromStores();
@@ -322,7 +304,37 @@ export default class Sidebar extends React.Component {
showBottomUnread
});
}
- createChannelElement(channel, index) {
+
+ handleLeaveDirectChannel(channel) {
+ if (!this.isLeaving.get(channel.id)) {
+ this.isLeaving.set(channel.id, true);
+
+ const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, channel.teammate_id, 'false');
+
+ // bypass AsyncClient since we've already saved the updated preferences
+ Client.savePreferences(
+ [preference],
+ () => {
+ this.isLeaving.set(channel.id, false);
+ },
+ () => {
+ this.isLeaving.set(channel.id, false);
+ }
+ );
+
+ this.setState(this.getStateFromStores());
+ }
+
+ if (channel.id === this.state.activeId) {
+ Utils.switchChannel(ChannelStore.getByName(Constants.DEFAULT_CHANNEL));
+ }
+ }
+
+ sortChannelsByDisplayName(a, b) {
+ return a.display_name.localeCompare(b.display_name);
+ }
+
+ createChannelElement(channel, index, arr, handleClose) {
var members = this.state.members;
var activeId = this.state.activeId;
var channelMember = members[channel.id];
@@ -405,8 +417,13 @@ export default class Sidebar extends React.Component {
if (!channel.fake) {
handleClick = function clickHandler(e) {
+ if (e.target.attributes.getNamedItem('data-close')) {
+ handleClose(channel);
+ } else {
+ Utils.switchChannel(channel);
+ }
+
e.preventDefault();
- Utils.switchChannel(channel);
};
} else if (channel.fake && teamURL) {
// It's a direct message channel that doesn't exist yet so let's create it now
@@ -415,23 +432,40 @@ export default class Sidebar extends React.Component {
if (this.state.loadingDMChannel === -1) {
handleClick = function clickHandler(e) {
e.preventDefault();
- this.setState({loadingDMChannel: index});
-
- Client.createDirectChannel(channel, otherUserId,
- function success(data) {
- this.setState({loadingDMChannel: -1});
- AsyncClient.getChannel(data.id);
- Utils.switchChannel(data);
- }.bind(this),
- function error() {
- this.setState({loadingDMChannel: -1});
- window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
- }.bind(this)
- );
+
+ if (e.target.attributes.getNamedItem('data-close')) {
+ handleClose(channel);
+ } else {
+ this.setState({loadingDMChannel: index});
+
+ Client.createDirectChannel(channel, otherUserId,
+ (data) => {
+ this.setState({loadingDMChannel: -1});
+ AsyncClient.getChannel(data.id);
+ Utils.switchChannel(data);
+ },
+ () => {
+ this.setState({loadingDMChannel: -1});
+ window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
+ }
+ );
+ }
}.bind(this);
}
}
+ let closeButton = null;
+ if (handleClose && !badge) {
+ closeButton = (
+ <span
+ className='sidebar-channel__close pull-right'
+ data-close='true'
+ >
+ {'×'}
+ </span>
+ );
+ }
+
return (
<li
key={channel.name}
@@ -446,6 +480,7 @@ export default class Sidebar extends React.Component {
{status}
{channel.display_name}
{badge}
+ {closeButton}
</a>
</li>
);
@@ -464,7 +499,9 @@ export default class Sidebar extends React.Component {
const privateChannels = this.state.channels.filter((channel) => channel.type === 'P');
const privateChannelItems = privateChannels.map(this.createChannelElement);
- const directMessageItems = this.state.showDirectChannels.map(this.createChannelElement);
+ const directMessageItems = this.state.visibleDirectChannels.map((channel, index, arr) => {
+ return this.createChannelElement(channel, index, arr, this.handleLeaveDirectChannel);
+ });
// update the favicon to show if there are any notifications
var link = document.createElement('link');
@@ -484,17 +521,18 @@ export default class Sidebar extends React.Component {
head.appendChild(link);
var directMessageMore = null;
- if (this.state.hideDirectChannels.length > 0) {
+ if (this.state.hiddenDirectChannels.length > 0) {
directMessageMore = (
- <li>
+ <li key='more'>
<a
+ key={`more${this.state.hiddenDirectChannels.length}`}
href='#'
data-toggle='modal'
className='nav-more'
data-target='#more_direct_channels'
- data-channels={JSON.stringify(this.state.hideDirectChannels)}
+ data-channels={JSON.stringify(this.state.hiddenDirectChannels)}
>
- {'More (' + this.state.hideDirectChannels.length + ')'}
+ {'More (' + this.state.hiddenDirectChannels.length + ')'}
</a>
</li>
);
@@ -538,7 +576,7 @@ export default class Sidebar extends React.Component {
<ul className='nav nav-pills nav-stacked'>
<li>
<h4>
- Channels
+ {'Channels'}
<a
className='add-channel-btn'
href='#'
@@ -557,7 +595,7 @@ export default class Sidebar extends React.Component {
data-target='#more_channels'
data-channeltype='O'
>
- More...
+ {'More...'}
</a>
</li>
</ul>
@@ -565,7 +603,7 @@ export default class Sidebar extends React.Component {
<ul className='nav nav-pills nav-stacked'>
<li>
<h4>
- Private Groups
+ {'Private Groups'}
<a
className='add-channel-btn'
href='#'
@@ -578,7 +616,7 @@ export default class Sidebar extends React.Component {
{privateChannelItems}
</ul>
<ul className='nav nav-pills nav-stacked'>
- <li><h4>Direct Messages</h4></li>
+ <li><h4>{'Direct Messages'}</h4></li>
{directMessageItems}
{directMessageMore}
</ul>
diff --git a/web/react/components/team_import_tab.jsx b/web/react/components/team_import_tab.jsx
index 40f06c382..a80b1a472 100644
--- a/web/react/components/team_import_tab.jsx
+++ b/web/react/components/team_import_tab.jsx
@@ -34,14 +34,14 @@ export default class TeamImportTab extends React.Component {
render() {
var uploadHelpText = (
<div>
- <p>{'Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team\'\s public channels.'}</p>
- <p>{'The Slack import to Mattermost is in "Preview". Slack bot posts do not yet import and Slack @mentions are not currently supported.'}</p>
+ <p>{'To import a team from Slack go to Slack > Team Settings > Import/Export Data > Export > Start Export. Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team\'\s public channels.'}</p>
+ <p>{'The Slack import to Mattermost is in "Beta". Slack bot posts do not yet import and Slack @mentions are not currently supported.'}</p>
</div>
);
var uploadSection = (
<SettingUpload
- title='Import from Slack'
+ title='Import from Slack (Beta)'
submit={this.doImportSlack}
helpText={uploadHelpText}
fileTypesAccepted='.zip'
diff --git a/web/react/stores/preference_store.jsx b/web/react/stores/preference_store.jsx
new file mode 100644
index 000000000..d71efa10f
--- /dev/null
+++ b/web/react/stores/preference_store.jsx
@@ -0,0 +1,122 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const ActionTypes = require('../utils/constants.jsx').ActionTypes;
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const BrowserStore = require('./browser_store.jsx');
+const EventEmitter = require('events').EventEmitter;
+const UserStore = require('../stores/user_store.jsx');
+
+const CHANGE_EVENT = 'change';
+
+function getPreferenceKey(category, name) {
+ return `${category}-${name}`;
+}
+
+function getPreferenceKeyForModel(preference) {
+ return `${preference.category}-${preference.name}`;
+}
+
+class PreferenceStoreClass extends EventEmitter {
+ constructor() {
+ super();
+
+ this.getAllPreferences = this.getAllPreferences.bind(this);
+ this.getPreference = this.getPreference.bind(this);
+ this.getPreferences = this.getPreferences.bind(this);
+ this.getPreferencesWhere = this.getPreferencesWhere.bind(this);
+ this.setAllPreferences = this.setAllPreferences.bind(this);
+ this.setPreference = this.setPreference.bind(this);
+
+ this.emitChange = this.emitChange.bind(this);
+ this.addChangeListener = this.addChangeListener.bind(this);
+ this.removeChangeListener = this.removeChangeListener.bind(this);
+
+ this.handleEventPayload = this.handleEventPayload.bind(this);
+ this.dispatchToken = AppDispatcher.register(this.handleEventPayload);
+ }
+
+ getAllPreferences() {
+ return new Map(BrowserStore.getItem('preferences', []));
+ }
+
+ getPreference(category, name, defaultValue = '') {
+ return this.getAllPreferences().get(getPreferenceKey(category, name)) || defaultValue;
+ }
+
+ getPreferences(category) {
+ return this.getPreferencesWhere((preference) => (preference.category === category));
+ }
+
+ getPreferencesWhere(pred) {
+ const all = this.getAllPreferences();
+ const preferences = [];
+
+ for (const [, preference] of all) {
+ if (pred(preference)) {
+ preferences.push(preference);
+ }
+ }
+
+ return preferences;
+ }
+
+ setAllPreferences(preferences) {
+ // note that we store the preferences as an array of key-value pairs so that we can deserialize
+ // it as a proper Map instead of an object
+ BrowserStore.setItem('preferences', [...preferences]);
+ }
+
+ setPreference(category, name, value) {
+ const preferences = this.getAllPreferences();
+
+ const key = getPreferenceKey(category, name);
+ let preference = preferences.get(key);
+
+ if (!preference) {
+ preference = {
+ user_id: UserStore.getCurrentId(),
+ category,
+ name
+ };
+ }
+ preference.value = value;
+
+ preferences.set(key, preference);
+
+ this.setAllPreferences(preferences);
+
+ return preference;
+ }
+
+ emitChange(preferences) {
+ this.emit(CHANGE_EVENT, preferences);
+ }
+
+ addChangeListener(callback) {
+ this.on(CHANGE_EVENT, callback);
+ }
+
+ removeChangeListener(callback) {
+ this.removeListener(CHANGE_EVENT, callback);
+ }
+
+ handleEventPayload(payload) {
+ const action = payload.action;
+
+ switch (action.type) {
+ case ActionTypes.RECIEVED_PREFERENCES:
+ const preferences = this.getAllPreferences();
+
+ for (const preference of action.preferences) {
+ preferences.set(getPreferenceKeyForModel(preference), preference);
+ }
+
+ this.setAllPreferences(preferences);
+ this.emitChange(preferences);
+ }
+ }
+}
+
+const PreferenceStore = new PreferenceStoreClass();
+export default PreferenceStore;
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx
index a903f055b..1bf8a6fee 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -637,3 +637,55 @@ export function getMyTeam() {
}
);
}
+
+export function getDirectChannelPreferences() {
+ if (isCallInProgress('getDirectChannelPreferences')) {
+ return;
+ }
+
+ callTracker.getDirectChannelPreferences = utils.getTimestamp();
+ client.getPreferenceCategory(
+ Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
+ (data, textStatus, xhr) => {
+ callTracker.getDirectChannelPreferences = 0;
+
+ if (xhr.status === 304 || !data) {
+ return;
+ }
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_PREFERENCES,
+ preferences: data
+ });
+ },
+ (err) => {
+ callTracker.getDirectChannelPreferences = 0;
+ dispatchError(err, 'getDirectChannelPreferences');
+ }
+ );
+}
+
+export function savePreferences(preferences, success, error) {
+ client.savePreferences(
+ preferences,
+ (data, textStatus, xhr) => {
+ if (xhr.status !== 304) {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_PREFERENCES,
+ preferences
+ });
+ }
+
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ dispatchError(err, 'savePreferences');
+
+ if (error) {
+ error();
+ }
+ }
+ );
+}
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 6dccfcdeb..76a402855 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -1141,3 +1141,31 @@ export function listIncomingHooks(success, error) {
}
});
}
+
+export function getPreferenceCategory(category, success, error) {
+ $.ajax({
+ url: `/api/v1/preferences/${category}`,
+ dataType: 'json',
+ type: 'GET',
+ success,
+ error: (xhr, status, err) => {
+ var e = handleError('getPreferenceCategory', xhr, status, err);
+ error(e);
+ }
+ });
+}
+
+export function savePreferences(preferences, success, error) {
+ $.ajax({
+ url: '/api/v1/preferences/save',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'POST',
+ data: JSON.stringify(preferences),
+ success,
+ error: (xhr, status, err) => {
+ var e = handleError('savePreferences', xhr, status, err);
+ error(e);
+ }
+ });
+}
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index e3cbfccde..cee2ec114 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -28,6 +28,7 @@ module.exports = {
RECIEVED_AUDITS: null,
RECIEVED_TEAMS: null,
RECIEVED_STATUSES: null,
+ RECIEVED_PREFERENCES: null,
RECIEVED_MSG: null,
@@ -285,5 +286,8 @@ module.exports = {
id: 'mentionHighlightLink',
uiName: 'Mention Highlight Link'
}
- ]
+ ],
+ Preferences: {
+ CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show'
+ }
};
diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx
index 848b1ea75..12d6dd424 100644
--- a/web/react/utils/markdown.jsx
+++ b/web/react/utils/markdown.jsx
@@ -6,31 +6,7 @@ const Utils = require('./utils.jsx');
const marked = require('marked');
-class MattermostInlineLexer extends marked.InlineLexer {
- constructor(links, options) {
- super(links, options);
-
- // modified version of the regex that doesn't break up words in snake_case
- // the original is /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/
- this.rules.text = /^[\s\S]+?(?=__|\b_|[\\<!\[*`]| {2,}\n|$)/;
- }
-}
-
-class MattermostParser extends marked.Parser {
- parse(src) {
- this.inline = new MattermostInlineLexer(src.links, this.options, this.renderer);
- this.tokens = src.reverse();
-
- var out = '';
- while (this.next()) {
- out += this.tok();
- }
-
- return out;
- }
-}
-
-class MattermostMarkdownRenderer extends marked.Renderer {
+export class MattermostMarkdownRenderer extends marked.Renderer {
constructor(options, formattingOptions = {}) {
super(options);
@@ -92,15 +68,3 @@ class MattermostMarkdownRenderer extends marked.Renderer {
return TextFormatting.doFormatText(text, this.formattingOptions);
}
}
-
-export function format(text, options) {
- const markdownOptions = {
- renderer: new MattermostMarkdownRenderer(null, options),
- sanitize: true
- };
-
- const tokens = marked.lexer(text, markdownOptions);
-
- return new MattermostParser(markdownOptions).parse(tokens);
-}
-
diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx
index 6778d341a..2b6e6e14e 100644
--- a/web/react/utils/text_formatting.jsx
+++ b/web/react/utils/text_formatting.jsx
@@ -8,6 +8,8 @@ const Markdown = require('./markdown.jsx');
const UserStore = require('../stores/user_store.jsx');
const Utils = require('./utils.jsx');
+const marked = require('marked');
+
// Performs formatting of user posts including highlighting mentions and search terms and converting urls, hashtags, and
// @mentions to links by taking a user's message and returning a string of formatted html. Also takes a number of options
// as part of the second parameter:
@@ -20,8 +22,11 @@ export function formatText(text, options = {}) {
let output;
if (!('markdown' in options) || options.markdown) {
- // the markdown renderer will call doFormatText as necessary
- output = Markdown.format(text, options);
+ // the markdown renderer will call doFormatText as necessary so just call marked
+ output = marked(text, {
+ renderer: new Markdown.MattermostMarkdownRenderer(null, options),
+ sanitize: true
+ });
} else {
output = sanitizeHtml(text);
output = doFormatText(output, options);