summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/react/components/admin_console/email_settings.jsx67
-rw-r--r--web/react/components/delete_post_modal.jsx1
-rw-r--r--web/react/components/member_list_item.jsx2
-rw-r--r--web/react/components/member_list_team_item.jsx2
-rw-r--r--web/react/components/popover_list_members.jsx46
-rw-r--r--web/react/components/post.jsx7
-rw-r--r--web/react/components/posts_view.jsx67
-rw-r--r--web/react/components/posts_view_container.jsx4
-rw-r--r--web/react/components/sidebar.jsx4
-rw-r--r--web/react/components/user_profile.jsx6
-rw-r--r--web/react/components/view_image.jsx34
-rw-r--r--web/react/stores/post_store.jsx16
-rw-r--r--web/react/utils/constants.jsx1
-rw-r--r--web/react/utils/markdown.jsx124
-rw-r--r--web/react/utils/text_formatting.jsx2
-rw-r--r--web/react/utils/utils.jsx18
-rw-r--r--web/sass-files/sass/partials/_base.scss2
-rw-r--r--web/sass-files/sass/partials/_modal.scss40
-rw-r--r--web/sass-files/sass/partials/_post.scss12
-rw-r--r--web/sass-files/sass/partials/_responsive.scss6
20 files changed, 315 insertions, 146 deletions
diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx
index d0565a0e0..238ace3da 100644
--- a/web/react/components/admin_console/email_settings.jsx
+++ b/web/react/components/admin_console/email_settings.jsx
@@ -18,6 +18,7 @@ export default class EmailSettings extends React.Component {
this.state = {
sendEmailNotifications: this.props.config.EmailSettings.SendEmailNotifications,
+ sendPushNotifications: this.props.config.EmailSettings.SendPushNotifications,
saveNeeded: false,
serverError: null,
emailSuccess: null,
@@ -36,6 +37,14 @@ export default class EmailSettings extends React.Component {
s.sendEmailNotifications = false;
}
+ if (action === 'sendPushNotifications_true') {
+ s.sendPushNotifications = true;
+ }
+
+ if (action === 'sendPushNotifications_false') {
+ s.sendPushNotifications = false;
+ }
+
this.setState(s);
}
@@ -43,11 +52,12 @@ export default class EmailSettings extends React.Component {
var config = this.props.config;
config.EmailSettings.EnableSignUpWithEmail = ReactDOM.findDOMNode(this.refs.allowSignUpWithEmail).checked;
config.EmailSettings.SendEmailNotifications = ReactDOM.findDOMNode(this.refs.sendEmailNotifications).checked;
+ config.EmailSettings.SendPushlNotifications = ReactDOM.findDOMNode(this.refs.sendPushNotifications).checked;
config.EmailSettings.RequireEmailVerification = ReactDOM.findDOMNode(this.refs.requireEmailVerification).checked;
- config.EmailSettings.SendEmailNotifications = ReactDOM.findDOMNode(this.refs.sendEmailNotifications).checked;
config.EmailSettings.FeedbackName = ReactDOM.findDOMNode(this.refs.feedbackName).value.trim();
config.EmailSettings.FeedbackEmail = ReactDOM.findDOMNode(this.refs.feedbackEmail).value.trim();
config.EmailSettings.SMTPServer = ReactDOM.findDOMNode(this.refs.SMTPServer).value.trim();
+ config.EmailSettings.PushNotificationServer = ReactDOM.findDOMNode(this.refs.PushNotificationServer).value.trim();
config.EmailSettings.SMTPPort = ReactDOM.findDOMNode(this.refs.SMTPPort).value.trim();
config.EmailSettings.SMTPUsername = ReactDOM.findDOMNode(this.refs.SMTPUsername).value.trim();
config.EmailSettings.SMTPPassword = ReactDOM.findDOMNode(this.refs.SMTPPassword).value.trim();
@@ -526,6 +536,61 @@ export default class EmailSettings extends React.Component {
</div>
<div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='sendPushNotifications'
+ >
+ {'Send Push Notifications: '}
+ </label>
+ <div className='col-sm-8'>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='sendPushNotifications'
+ value='true'
+ ref='sendPushNotifications'
+ defaultChecked={this.props.config.EmailSettings.SendPushNotifications}
+ onChange={this.handleChange.bind(this, 'sendPushNotifications_true')}
+ />
+ {'true'}
+ </label>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='sendPushNotifications'
+ value='false'
+ defaultChecked={!this.props.config.EmailSettings.SendPushNotifications}
+ onChange={this.handleChange.bind(this, 'sendPushNotifications_false')}
+ />
+ {'false'}
+ </label>
+ <p className='help-text'>{'Typically set to true in production. When true, Mattermost attempts to send iOS and Android push notifications through the push notification server.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='PushNotificationServer'
+ >
+ {'Push Notification Server:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='PushNotificationServer'
+ ref='PushNotificationServer'
+ placeholder='E.g.: "https://push.mattermost.com"'
+ defaultValue={this.props.config.EmailSettings.PushNotificationServer}
+ onChange={this.handleChange}
+ disabled={!this.state.sendPushNotifications}
+ />
+ <p className='help-text'>{'Location of the push notification server.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
<div className='col-sm-12'>
{serverError}
<button
diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx
index 3c4b17905..5e89a0893 100644
--- a/web/react/components/delete_post_modal.jsx
+++ b/web/react/components/delete_post_modal.jsx
@@ -152,6 +152,7 @@ export default class DeletePostModal extends React.Component {
type='button'
className='btn btn-danger'
onClick={this.handleDelete}
+ autoFocus='autofocus'
>
{'Delete'}
</button>
diff --git a/web/react/components/member_list_item.jsx b/web/react/components/member_list_item.jsx
index 390d25f2e..f5d5ab28b 100644
--- a/web/react/components/member_list_item.jsx
+++ b/web/react/components/member_list_item.jsx
@@ -110,7 +110,7 @@ export default class MemberListItem extends React.Component {
height='36'
width='36'
/>
- <div className='member-name'>{member.username}</div>
+ <div className='member-name'>{Utils.displayUsername(member.id)}</div>
<div className='member-description'>{member.email}</div>
</td>
<td className='td--action lg'>{invite}</td>
diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/member_list_team_item.jsx
index 27fb6a4c1..316fad01a 100644
--- a/web/react/components/member_list_team_item.jsx
+++ b/web/react/components/member_list_team_item.jsx
@@ -174,7 +174,7 @@ export default class MemberListTeamItem extends React.Component {
height='36'
width='36'
/>
- <span className='member-name'>{Utils.getDisplayName(user)}</span>
+ <span className='member-name'>{Utils.displayUsername(user.id)}</span>
<span className='member-email'>{email}</span>
<div className='dropdown member-drop'>
<a
diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx
index b5000141a..f4cb542e4 100644
--- a/web/react/components/popover_list_members.jsx
+++ b/web/react/components/popover_list_members.jsx
@@ -5,6 +5,7 @@ import UserStore from '../stores/user_store.jsx';
var Popover = ReactBootstrap.Popover;
var Overlay = ReactBootstrap.Overlay;
import * as Utils from '../utils/utils.jsx';
+import Constants from '../utils/constants.jsx';
import ChannelStore from '../stores/channel_store.jsx';
@@ -68,7 +69,7 @@ export default class PopoverListMembers extends React.Component {
}
render() {
- let popoverHtml = [];
+ const popoverHtml = [];
const members = this.props.members;
const teamMembers = UserStore.getProfilesUsernameMap();
const currentUserId = UserStore.getCurrentId();
@@ -76,35 +77,13 @@ export default class PopoverListMembers extends React.Component {
if (members && teamMembers) {
members.sort((a, b) => {
- return a.username.localeCompare(b.username);
+ const aName = Utils.displayUsername(a.id);
+ const bName = Utils.displayUsername(b.id);
+
+ return aName.localeCompare(bName);
});
members.forEach((m, i) => {
- const details = [];
-
- const fullName = Utils.getFullName(m);
- if (fullName) {
- details.push(
- <span
- key={`${m.id}__full-name`}
- className='full-name'
- >
- {fullName}
- </span>
- );
- }
-
- if (m.nickname) {
- const separator = fullName ? ' - ' : '';
- details.push(
- <span
- key={`${m.nickname}__nickname`}
- >
- {separator + m.nickname}
- </span>
- );
- }
-
let button = '';
if (currentUserId !== m.id && ch.type !== 'D') {
button = (
@@ -118,7 +97,12 @@ export default class PopoverListMembers extends React.Component {
);
}
- if (teamMembers[m.username] && teamMembers[m.username].delete_at <= 0) {
+ let name = '';
+ if (teamMembers[m.username]) {
+ name = Utils.displayUsername(teamMembers[m.username].id);
+ }
+
+ if (name && teamMembers[m.username].delete_at <= 0) {
popoverHtml.push(
<div
className='text-nowrap'
@@ -135,7 +119,7 @@ export default class PopoverListMembers extends React.Component {
<div
className='more-name'
>
- {m.username}
+ {name}
</div>
</div>
<div
@@ -157,8 +141,8 @@ export default class PopoverListMembers extends React.Component {
count = members.length;
}
- if (count > 20) {
- countText = '20+';
+ if (count > Constants.MAX_CHANNEL_POPOVER_COUNT) {
+ countText = Constants.MAX_CHANNEL_POPOVER_COUNT + '+';
} else if (count > 0) {
countText = count.toString();
}
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index 278261e22..66d8c507a 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -87,6 +87,10 @@ export default class Post extends React.Component {
return true;
}
+ if (nextProps.displayNameType !== this.props.displayNameType) {
+ return true;
+ }
+
if (this.getCommentCount(nextProps) !== this.getCommentCount(this.props)) {
return true;
}
@@ -224,5 +228,6 @@ Post.propTypes = {
sameRoot: React.PropTypes.bool,
hideProfilePic: React.PropTypes.bool,
isLastComment: React.PropTypes.bool,
- shouldHighlight: React.PropTypes.bool
+ shouldHighlight: React.PropTypes.bool,
+ displayNameType: React.PropTypes.string
};
diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx
index 5e374b877..242b26b91 100644
--- a/web/react/components/posts_view.jsx
+++ b/web/react/components/posts_view.jsx
@@ -2,15 +2,18 @@
// See License.txt for license information.
import UserStore from '../stores/user_store.jsx';
+import PreferenceStore from '../stores/preference_store.jsx';
import * as EventHelpers from '../dispatcher/event_helpers.jsx';
import * as Utils from '../utils/utils.jsx';
import Post from './post.jsx';
import Constants from '../utils/constants.jsx';
+const Preferences = Constants.Preferences;
export default class PostsView extends React.Component {
constructor(props) {
super(props);
+ this.updateState = this.updateState.bind(this);
this.handleScroll = this.handleScroll.bind(this);
this.isAtBottom = this.isAtBottom.bind(this);
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
@@ -22,6 +25,8 @@ export default class PostsView extends React.Component {
this.jumpToPostNode = null;
this.wasAtBottom = true;
this.scrollHeight = 0;
+
+ this.state = {displayNameType: PreferenceStore.getPreference(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', {value: 'false'}).value};
}
static get SCROLL_TYPE_FREE() {
return 1;
@@ -38,6 +43,9 @@ export default class PostsView extends React.Component {
static get SCROLL_TYPE_POST() {
return 5;
}
+ updateState() {
+ this.setState({displayNameType: PreferenceStore.getPreference(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', {value: 'false'}).value});
+ }
isAtBottom() {
return ((this.refs.postlist.scrollHeight - this.refs.postlist.scrollTop) === this.refs.postlist.clientHeight);
}
@@ -94,22 +102,53 @@ export default class PostsView extends React.Component {
const prevPostIsComment = Utils.isComment(prevPost);
const postFromWebhook = Boolean(post.props && post.props.from_webhook);
const prevPostFromWebhook = Boolean(prevPost.props && prevPost.props.from_webhook);
+ let prevWebhookName = '';
+ if (prevPost.props && prevPost.props.override_username) {
+ prevWebhookName = prevPost.props.override_username;
+ }
+ let curWebhookName = '';
+ if (post.props && post.props.override_username) {
+ curWebhookName = post.props.override_username;
+ }
+
+ // consider posts from the same user if:
+ // the previous post was made by the same user as the current post,
+ // the previous post was made within 5 minutes of the current post,
+ // the previous post and current post are both from webhooks or both not,
+ // the previous post and current post have the same webhook usernames
+ if (prevPost.user_id === post.user_id &&
+ post.create_at - prevPost.create_at <= 1000 * 60 * 5 &&
+ postFromWebhook === prevPostFromWebhook &&
+ prevWebhookName === curWebhookName) {
+ sameUser = true;
+ }
- sameUser = prevPost.user_id === post.user_id && postFromWebhook === prevPostFromWebhook &&
- post.create_at - prevPost.create_at <= 1000 * 60 * 5;
- sameRoot = (postIsComment && (prevPost.id === post.root_id || prevPost.root_id === post.root_id)) || (!postIsComment && !prevPostIsComment && sameUser);
+ // consider posts from the same root if:
+ // the current post is a comment,
+ // the current post has the same root as the previous post
+ if (postIsComment && (prevPost.id === post.root_id || prevPost.root_id === post.root_id)) {
+ sameRoot = true;
+ }
+
+ // consider posts from the same root if:
+ // the current post is not a comment,
+ // the previous post is not a comment,
+ // the previous post is from the same user
+ if (!postIsComment && !prevPostIsComment && sameUser) {
+ sameRoot = true;
+ }
// hide the profile pic if:
// the previous post was made by the same user as the current post,
// the previous post is not a comment,
// the current post is not a comment,
- // the current post is not from a webhook
- // and the previous post is not from a webhook
- if ((prevPost.user_id === post.user_id) &&
+ // the previous post and current post are both from webhooks or both not,
+ // the previous post and current post have the same webhook usernames
+ if (prevPost.user_id === post.user_id &&
!prevPostIsComment &&
!postIsComment &&
- !postFromWebhook &&
- !prevPostFromWebhook) {
+ postFromWebhook === prevPostFromWebhook &&
+ prevWebhookName === curWebhookName) {
hideProfilePic = true;
}
}
@@ -135,6 +174,7 @@ export default class PostsView extends React.Component {
isLastComment={isLastComment}
shouldHighlight={shouldHighlight}
onClick={() => EventHelpers.emitPostFocusEvent(post.id)} //eslint-disable-line no-loop-func
+ displayNameType={this.state.displayNameType}
/>
);
@@ -240,16 +280,20 @@ export default class PostsView extends React.Component {
this.updateScrolling();
}
window.addEventListener('resize', this.handleResize);
+ $(this.refs.postlist).perfectScrollbar();
+ PreferenceStore.addChangeListener(this.updateState);
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
+ PreferenceStore.removeChangeListener(this.updateState);
}
componentDidUpdate() {
if (this.props.postList != null) {
this.updateScrolling();
}
+ $(this.refs.postlist).perfectScrollbar('update');
}
- shouldComponentUpdate(nextProps) {
+ shouldComponentUpdate(nextProps, nextState) {
if (this.props.isActive !== nextProps.isActive) {
return true;
}
@@ -268,6 +312,9 @@ export default class PostsView extends React.Component {
if (!Utils.areObjectsEqual(this.props.postList, nextProps.postList)) {
return true;
}
+ if (nextState.displayNameType !== this.state.displayNameType) {
+ return true;
+ }
return false;
}
@@ -326,7 +373,7 @@ export default class PostsView extends React.Component {
return (
<div
ref='postlist'
- className={'post-list-holder-by-time ' + activeClass}
+ className={'ps-container post-list-holder-by-time ' + activeClass}
onScroll={this.handleScroll}
>
<div className='post-list__table'>
diff --git a/web/react/components/posts_view_container.jsx b/web/react/components/posts_view_container.jsx
index 367d3687e..6d6694fec 100644
--- a/web/react/components/posts_view_container.jsx
+++ b/web/react/components/posts_view_container.jsx
@@ -99,10 +99,12 @@ export default class PostsViewContainer extends React.Component {
if (newIndex === -1) {
newIndex = channels.length;
channels.push(channelId);
- postLists[newIndex] = this.getChannelPosts(channelId);
atTop[newIndex] = PostStore.getVisibilityAtTop(channelId);
}
+ // make sure we have the latest posts from the store
+ postLists[newIndex] = this.getChannelPosts(channelId);
+
this.setState({
currentChannelIndex: newIndex,
currentLastViewed: lastViewed,
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 30422ff7d..b4c037183 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -173,6 +173,10 @@ export default class Sidebar extends React.Component {
this.updateScrollbar();
window.addEventListener('resize', this.handleResize);
+
+ if ($(window).width() > 768) {
+ $('.nav-pills__container').perfectScrollbar();
+ }
}
shouldComponentUpdate(nextProps, nextState) {
if (!Utils.areObjectsEqual(nextState, this.state)) {
diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx
index 438c0bc82..ea104fedb 100644
--- a/web/react/components/user_profile.jsx
+++ b/web/react/components/user_profile.jsx
@@ -54,9 +54,11 @@ export default class UserProfile extends React.Component {
}
}
render() {
- var name = this.state.profile.username;
+ var name = Utils.displayUsername(this.state.profile.id);
if (this.props.overwriteName) {
name = this.props.overwriteName;
+ } else if (!name) {
+ name = '...';
}
if (this.props.disablePopover) {
@@ -107,7 +109,7 @@ export default class UserProfile extends React.Component {
rootClose={true}
overlay={
<Popover
- title={this.state.profile.username}
+ title={name}
id='user-profile-popover'
>
{dataContent}
diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx
index 91f4b3bdc..2b505607e 100644
--- a/web/react/components/view_image.jsx
+++ b/web/react/components/view_image.jsx
@@ -424,23 +424,27 @@ export default class ViewImageModal extends React.Component {
>
<div
className={'image-wrapper ' + bgClass}
- onMouseEnter={this.onMouseEnterImage}
- onMouseLeave={this.onMouseLeaveImage}
- onClick={(e) => e.stopPropagation()}
+ onClick={this.props.onModalDismissed}
>
<div
- className={closeButtonClass}
- onClick={this.props.onModalDismissed}
- />
- {content}
- <ViewImagePopoverBar
- show={this.state.showFooter}
- fileId={this.state.imgId}
- totalFiles={this.props.filenames.length}
- filename={name}
- fileURL={fileUrl}
- getPublicLink={this.getPublicLink}
- />
+ onMouseEnter={this.onMouseEnterImage}
+ onMouseLeave={this.onMouseLeaveImage}
+ onClick={(e) => e.stopPropagation()}
+ >
+ <div
+ className={closeButtonClass}
+ onClick={this.props.onModalDismissed}
+ />
+ {content}
+ <ViewImagePopoverBar
+ show={this.state.showFooter}
+ fileId={this.state.imgId}
+ totalFiles={this.props.filenames.length}
+ filename={name}
+ fileURL={fileUrl}
+ getPublicLink={this.getPublicLink}
+ />
+ </div>
</div>
{leftArrow}
{rightArrow}
diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx
index 24b0d0dd0..2212edadb 100644
--- a/web/react/stores/post_store.jsx
+++ b/web/react/stores/post_store.jsx
@@ -39,6 +39,7 @@ class PostStoreClass extends EventEmitter {
this.makePostsInfo = this.makePostsInfo.bind(this);
+ this.getPost = this.getPost.bind(this);
this.getAllPosts = this.getAllPosts.bind(this);
this.getEarliestPost = this.getEarliestPost.bind(this);
this.getLatestPost = this.getLatestPost.bind(this);
@@ -160,6 +161,17 @@ class PostStoreClass extends EventEmitter {
}
}
+ getPost(channelId, postId) {
+ const posts = this.postsInfo[channelId].postList;
+ let post = null;
+
+ if (posts.posts.hasOwnProperty(postId)) {
+ post = Object.assign({}, posts.posts[postId]);
+ }
+
+ return post;
+ }
+
getAllPosts(id) {
if (this.postsInfo.hasOwnProperty(id)) {
return Object.assign({}, this.postsInfo[id].postList);
@@ -199,7 +211,7 @@ class PostStoreClass extends EventEmitter {
postList.order = this.postsInfo[id].pendingPosts.order.concat(postList.order);
}
- // Add delteted posts
+ // Add deleted posts
if (this.postsInfo[id].hasOwnProperty('deletedPosts')) {
Object.assign(postList.posts, this.postsInfo[id].deletedPosts);
@@ -554,7 +566,7 @@ class PostStoreClass extends EventEmitter {
return 0;
}
getCommentCount(post) {
- const posts = this.getPosts(post.channel_id).posts;
+ const posts = this.getAllPosts(post.channel_id).posts;
let commentCount = 0;
for (const id in posts) {
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 2009e07dd..cbeab18ba 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -137,6 +137,7 @@ export default {
],
MONTHS: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
MAX_DMS: 20,
+ MAX_CHANNEL_POPOVER_COUNT: 20,
DM_CHANNEL: 'D',
OPEN_CHANNEL: 'O',
PRIVATE_CHANNEL: 'P',
diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx
index b0ec64bfd..f2721c81d 100644
--- a/web/react/utils/markdown.jsx
+++ b/web/react/utils/markdown.jsx
@@ -223,6 +223,16 @@ class MattermostMarkdownRenderer extends marked.Renderer {
return `<table class="markdown__table"><thead>${header}</thead><tbody>${body}</tbody></table>`;
}
+ listitem(text) {
+ const taskListReg = /^\[([ |xX])\] /;
+ const isTaskList = taskListReg.exec(text);
+
+ if (isTaskList) {
+ return `<li>${'<input type="checkbox" disabled="disabled" ' + (isTaskList[1] === ' ' ? '' : 'checked="checked" ') + '/> '}${text.replace(taskListReg, '')}</li>`;
+ }
+ return `<li>${text}</li>`;
+ }
+
text(txt) {
return TextFormatting.doFormatText(txt, this.formattingOptions);
}
@@ -361,78 +371,78 @@ class MattermostLexer extends marked.Lexer {
// list
cap = this.rules.list.exec(src);
if (cap) {
+ src = src.substring(cap[0].length);
const bull = cap[2];
- let l = cap[0].length;
+
+ this.tokens.push({
+ type: 'list_start',
+ ordered: bull.length > 1
+ });
// Get each top-level item.
cap = cap[0].match(this.rules.item);
- if (cap.length > 1) {
- src = src.substring(l);
-
- this.tokens.push({
- type: 'list_start',
- ordered: bull.length > 1
- });
-
- let next = false;
- l = cap.length;
-
- for (let i = 0; i < l; i++) {
- let item = cap[i];
-
- // Remove the list item's bullet
- // so it is seen as the next token.
- let space = item.length;
- item = item.replace(/^ *([*+-]|\d+\.) +/, '');
-
- // Outdent whatever the
- // list item contains. Hacky.
- if (~item.indexOf('\n ')) {
- space -= item.length;
- item = this.options.pedantic ? item.replace(/^ {1,4}/gm, '') : item.replace(new RegExp('^ \{1,' + space + '\}', 'gm'), '');
- }
+ let next = false;
+ const l = cap.length;
+ let i = 0;
+
+ for (; i < l; i++) {
+ let item = cap[i];
+
+ // Remove the list item's bullet
+ // so it is seen as the next token.
+ let space = item.length;
+ item = item.replace(/^ *([*+-]|\d+\.) +/, '');
+
+ // Outdent whatever the
+ // list item contains. Hacky.
+ if (~item.indexOf('\n ')) {
+ space -= item.length;
+ item = this.options.pedantic ?
+ item.replace(/^ {1,4}/gm, '') :
+ item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '');
+ }
- // Determine whether the next list item belongs here.
- // Backpedal if it does not belong in this list.
- if (this.options.smartLists && i !== l - 1) {
- const bullet = /(?:[*+-]|\d+\.)/;
- const b = bullet.exec(cap[i + 1])[0];
- if (bull !== b && !(bull.length > 1 && b.length > 1)) {
- src = cap.slice(i + 1).join('\n') + src;
- i = l - 1;
- }
+ // Determine whether the next list item belongs here.
+ // Backpedal if it does not belong in this list.
+ if (this.options.smartLists && i !== l - 1) {
+ const b = this.rules.bullet.exec(cap[i + 1])[0];
+ if (bull !== b && !(bull.length > 1 && b.length > 1)) {
+ src = cap.slice(i + 1).join('\n') + src;
+ i = l - 1;
}
+ }
- // Determine whether item is loose or not.
- // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
- // for discount behavior.
- let loose = next || (/\n\n(?!\s*$)/).test(item);
- if (i !== l - 1) {
- next = item.charAt(item.length - 1) === '\n';
- if (!loose) {
- loose = next;
- }
+ // Determine whether item is loose or not.
+ // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
+ // for discount behavior.
+ let loose = next || (/\n\n(?!\s*$)/).test(item);
+ if (i !== l - 1) {
+ next = item.charAt(item.length - 1) === '\n';
+ if (!loose) {
+ loose = next;
}
-
- this.tokens.push({
- type: loose ? 'loose_item_start' : 'list_item_start'
- });
-
- // Recurse.
- this.token(item, false, bq);
-
- this.tokens.push({
- type: 'list_item_end'
- });
}
this.tokens.push({
- type: 'list_end'
+ type: loose ?
+ 'loose_item_start' :
+ 'list_item_start'
});
- continue;
+ // Recurse.
+ this.token(item, false, bq);
+
+ this.tokens.push({
+ type: 'list_item_end'
+ });
}
+
+ this.tokens.push({
+ type: 'list_end'
+ });
+
+ continue;
}
// html
diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx
index 3a912fd75..f0bd46f9d 100644
--- a/web/react/utils/text_formatting.jsx
+++ b/web/react/utils/text_formatting.jsx
@@ -188,7 +188,7 @@ function highlightCurrentMentions(text, tokens) {
const newAlias = `MM_SELFMENTION${index}`;
newTokens.set(newAlias, {
- value: `<span class='mention-highlight'>${alias}</span>` + token.extraText,
+ value: `<span class='mention-highlight'>${alias}</span>` + (token.extraText || ''),
originalText: token.originalText
});
output = output.replace(alias, newAlias);
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 80c377d7f..aa9146183 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -854,7 +854,7 @@ export function isMobile() {
export function isComment(post) {
if ('root_id' in post) {
- return post.root_id !== '';
+ return post.root_id !== '' && post.root_id != null;
}
return false;
}
@@ -981,13 +981,15 @@ export function displayUsername(userId) {
const nameFormat = PreferenceStore.getPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', {value: 'false'}).value;
let username = '';
- if (nameFormat === 'nickname_full_name') {
- username = user.nickname || getFullName(user);
- } else if (nameFormat === 'full_name') {
- username = getFullName(user);
- }
- if (!username.trim().length) {
- username = user.username;
+ if (user) {
+ if (nameFormat === 'nickname_full_name') {
+ username = user.nickname || getFullName(user);
+ } else if (nameFormat === 'full_name') {
+ username = getFullName(user);
+ }
+ if (!username.trim().length) {
+ username = user.username;
+ }
}
return username;
diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss
index 2fc63443e..7efe70cb4 100644
--- a/web/sass-files/sass/partials/_base.scss
+++ b/web/sass-files/sass/partials/_base.scss
@@ -116,7 +116,7 @@ a:focus, a:hover {
.btn {
&.btn-danger {
color: #fff;
- &:hover, &:active {
+ &:hover, &:active, &:focus {
color: #fff;
}
}
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index 6270c8608..4a56bc6c7 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -3,6 +3,7 @@
}
.modal-body {
padding: 20px 15px;
+ overflow: auto;
}
.modal {
width: 100%;
@@ -47,9 +48,6 @@
margin-left: auto;
margin-right: auto;
}
- .modal-body {
- overflow: auto;
- }
.modal-push-down {
margin-top: 5%;
}
@@ -195,21 +193,35 @@
width:100%;
height: 100%;
margin: 0 auto;
+ max-width: 100%;
+
+ .modal-body {
+ @include clearfix;
+ height: 100%;
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+ max-height: 100%;
+ }
+
.image-wrapper {
position: relative;
max-width: 90%;
- min-height: 100px;
- min-width: 320px;
@include border-radius(3px);
- display: table;
- margin: 0 auto;
+ display: table-cell;
+ vertical-align: middle;
+ text-align: center;
+ width: 100%;
+
&:hover {
@include border-radius(3px 3px 0 0);
}
+
&.default {
width: 100%;
height: 80%;
}
+
.modal-close {
background: url("../images/close.png") no-repeat;
@include background-size(100% 100%);
@@ -225,24 +237,31 @@
transition: opacity 0.6s;
cursor: pointer;
z-index: 9999;
+
&.modal-close--show {
@include opacity(1);
}
+
}
- > a {
+
+ > div {
+ min-height: 100px;
+ min-width: 320px;
background: #FFF;
- display: table-cell;
- vertical-align: middle;
+ display: inline-block;
position: relative;
&:hover .file-playback-controls.stop {
@include opacity(1);
}
+
}
+
img {
max-width: 100%;
max-height: 100%;
}
+
.spinner.file__loading {
z-index: 2;
position: absolute;
@@ -259,7 +278,6 @@
height: 100%;
padding: 0;
border: none;
- display: table;
}
.image-body {
vertical-align: middle;
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index da161e54f..ed1632681 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -374,9 +374,9 @@ body.ios {
ul {
margin: 0;
padding: 0;
- list-style: none;
}
+
p {
margin: 0 0 1em;
line-height: 1.6em;
@@ -603,6 +603,16 @@ body.ios {
padding: 5px 0 0 20px;
}
+ ul, ol {
+ li ul, li ol {
+ padding: 0 0 0 20px
+ }
+
+ li input[type="checkbox"]:disabled {
+ vertical-align: sub;
+ cursor: default;
+ }
+ }
}
.post__link {
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index 00fa7d817..5f5cca89b 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -621,8 +621,10 @@
.modal {
.modal-image {
.image-wrapper {
- font-size: 12px;
- min-width: 250px;
+ > div {
+ font-size: 12px;
+ min-width: 250px;
+ }
.modal-close {
@include opacity(1);
}