summaryrefslogtreecommitdiffstats
path: root/web/react
diff options
context:
space:
mode:
Diffstat (limited to 'web/react')
-rw-r--r--web/react/components/admin_console/user_item.jsx90
-rw-r--r--web/react/components/center_panel.jsx4
-rw-r--r--web/react/components/channel_invite_modal.jsx21
-rw-r--r--web/react/components/channel_members_modal.jsx35
-rw-r--r--web/react/components/create_post.jsx4
-rw-r--r--web/react/components/member_list.jsx2
-rw-r--r--web/react/components/member_list_team_item.jsx70
-rw-r--r--web/react/components/sidebar.jsx2
-rw-r--r--web/react/components/sidebar_header.jsx2
-rw-r--r--web/react/components/team_members.jsx10
-rw-r--r--web/react/components/user_settings/import_theme_modal.jsx6
-rw-r--r--web/react/components/user_settings/user_settings_display.jsx6
-rw-r--r--web/react/utils/highlight.jsx51
-rw-r--r--web/react/utils/markdown.jsx351
-rw-r--r--web/react/utils/utils.jsx8
15 files changed, 503 insertions, 159 deletions
diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx
index f7e92672d..2badaf0e5 100644
--- a/web/react/components/admin_console/user_item.jsx
+++ b/web/react/components/admin_console/user_item.jsx
@@ -212,50 +212,52 @@ export default class UserItem extends React.Component {
}
return (
- <div className='row member-div'>
- <img
- className='post-profile-img pull-left'
- src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
- height='36'
- width='36'
- />
- <span className='member-name'>{Utils.getDisplayName(user)}</span>
- <span className='member-email'>{email}</span>
- <div className='dropdown member-drop'>
- <a
- href='#'
- className='dropdown-toggle theme'
- type='button'
- id='channel_header_dropdown'
- data-toggle='dropdown'
- aria-expanded='true'
- >
- <span>{currentRoles} </span>
- <span className='caret'></span>
- </a>
- <ul
- className='dropdown-menu member-menu'
- role='menu'
- aria-labelledby='channel_header_dropdown'
- >
- {makeAdmin}
- {makeMember}
- {makeActive}
- {makeNotActive}
- {makeSystemAdmin}
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- onClick={this.handleResetPassword}
- >
- {'Reset Password'}
- </a>
- </li>
- </ul>
- </div>
- {serverError}
- </div>
+ <tr>
+ <td className='row member-div'>
+ <img
+ className='post-profile-img pull-left'
+ src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
+ height='36'
+ width='36'
+ />
+ <span className='member-name'>{Utils.getDisplayName(user)}</span>
+ <span className='member-email'>{email}</span>
+ <div className='dropdown member-drop'>
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ id='channel_header_dropdown'
+ data-toggle='dropdown'
+ aria-expanded='true'
+ >
+ <span>{currentRoles} </span>
+ <span className='caret'></span>
+ </a>
+ <ul
+ className='dropdown-menu member-menu'
+ role='menu'
+ aria-labelledby='channel_header_dropdown'
+ >
+ {makeAdmin}
+ {makeMember}
+ {makeActive}
+ {makeNotActive}
+ {makeSystemAdmin}
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleResetPassword}
+ >
+ {'Reset Password'}
+ </a>
+ </li>
+ </ul>
+ </div>
+ {serverError}
+ </td>
+ </tr>
);
}
}
diff --git a/web/react/components/center_panel.jsx b/web/react/components/center_panel.jsx
index ea0eec747..3ee40bb86 100644
--- a/web/react/components/center_panel.jsx
+++ b/web/react/components/center_panel.jsx
@@ -21,7 +21,7 @@ export default class CenterPanel extends React.Component {
this.onPreferenceChange = this.onPreferenceChange.bind(this);
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
this.state = {showTutorialScreens: parseInt(tutorialPref.value, 10) === TutorialSteps.INTRO_SCREENS};
}
componentDidMount() {
@@ -31,7 +31,7 @@ export default class CenterPanel extends React.Component {
PreferenceStore.removeChangeListener(this.onPreferenceChange);
}
onPreferenceChange() {
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
this.setState({showTutorialScreens: parseInt(tutorialPref.value, 10) <= TutorialSteps.INTRO_SCREENS});
}
render() {
diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx
index 2dc12c9aa..7c1032321 100644
--- a/web/react/components/channel_invite_modal.jsx
+++ b/web/react/components/channel_invite_modal.jsx
@@ -54,6 +54,16 @@ export default class ChannelInviteModal extends React.Component {
loading
};
}
+ onShow() {
+ if ($(window).width() > 768) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
+ }
+ }
+ componentDidUpdate(prevProps) {
+ if (this.props.show && !prevProps.show) {
+ this.onShow();
+ }
+ }
componentWillReceiveProps(nextProps) {
if (!this.props.show && nextProps.show) {
ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
@@ -103,6 +113,11 @@ export default class ChannelInviteModal extends React.Component {
);
}
render() {
+ var maxHeight = 1000;
+ if (Utils.windowHeight() <= 1200) {
+ maxHeight = Utils.windowHeight() - 300;
+ }
+
var inviteError = null;
if (this.state.inviteError) {
inviteError = (<label className='has-error control-label'>{this.state.inviteError}</label>);
@@ -129,13 +144,17 @@ export default class ChannelInviteModal extends React.Component {
return (
<Modal
+ dialogClassName='more-modal'
show={this.props.show}
onHide={this.props.onModalDismissed}
>
<Modal.Header closeButton={true}>
<Modal.Title>{'Add New Members to '}<span className='name'>{this.state.channelName}</span></Modal.Title>
</Modal.Header>
- <Modal.Body>
+ <Modal.Body
+ ref='modalBody'
+ style={{maxHeight}}
+ >
{inviteError}
{content}
</Modal.Body>
diff --git a/web/react/components/channel_members_modal.jsx b/web/react/components/channel_members_modal.jsx
index 1df0da9cf..2fa7ae8ff 100644
--- a/web/react/components/channel_members_modal.jsx
+++ b/web/react/components/channel_members_modal.jsx
@@ -70,6 +70,16 @@ export default class ChannelMembersModal extends React.Component {
channelName
};
}
+ onShow() {
+ if ($(window).width() > 768) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
+ }
+ }
+ componentDidUpdate(prevProps) {
+ if (this.props.show && !prevProps.show) {
+ this.onShow();
+ }
+ }
componentWillReceiveProps(nextProps) {
if (!this.props.show && nextProps.show) {
ChannelStore.addExtraInfoChangeListener(this.onChange);
@@ -128,6 +138,11 @@ export default class ChannelMembersModal extends React.Component {
);
}
render() {
+ var maxHeight = 1000;
+ if (Utils.windowHeight() <= 1200) {
+ maxHeight = Utils.windowHeight() - 300;
+ }
+
const currentMember = ChannelStore.getCurrentMember();
let isAdmin = false;
if (currentMember) {
@@ -137,6 +152,7 @@ export default class ChannelMembersModal extends React.Component {
return (
<div>
<Modal
+ dialogClassName='more-modal'
show={this.props.show}
onHide={this.props.onModalDismissed}
>
@@ -153,15 +169,16 @@ export default class ChannelMembersModal extends React.Component {
<i className='glyphicon glyphicon-envelope'/>{' Add New Members'}
</a>
</Modal.Header>
- <Modal.Body>
- <div className='col-sm-12'>
- <div className='team-member-list'>
- <MemberList
- memberList={this.state.memberList}
- isAdmin={isAdmin}
- handleRemove={this.handleRemove}
- />
- </div>
+ <Modal.Body
+ ref='modalBody'
+ style={{maxHeight}}
+ >
+ <div className='team-member-list'>
+ <MemberList
+ memberList={this.state.memberList}
+ isAdmin={isAdmin}
+ handleRemove={this.handleRemove}
+ />
</div>
</Modal.Body>
<Modal.Footer>
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index 4d1874e18..5a69c9bfb 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -50,7 +50,7 @@ export default class CreatePost extends React.Component {
PostStore.clearDraftUploads();
const draft = this.getCurrentDraft();
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
this.state = {
channelId: ChannelStore.getCurrentId(),
@@ -336,7 +336,7 @@ export default class CreatePost extends React.Component {
}
}
onPreferenceChange() {
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
this.setState({
showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.POST_POPOVER,
ctrlSend: PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', {value: 'false'}).value
diff --git a/web/react/components/member_list.jsx b/web/react/components/member_list.jsx
index 9c0243291..0238c7920 100644
--- a/web/react/components/member_list.jsx
+++ b/web/react/components/member_list.jsx
@@ -15,7 +15,7 @@ export default class MemberList extends React.Component {
members = this.props.memberList;
}
- var message = '';
+ var message = null;
if (members.length === 0) {
message = <tr><td>No users to add.</td></tr>;
}
diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/member_list_team_item.jsx
index 14db05cdb..1fa369068 100644
--- a/web/react/components/member_list_team_item.jsx
+++ b/web/react/components/member_list_team_item.jsx
@@ -166,40 +166,42 @@ export default class MemberListTeamItem extends React.Component {
}
return (
- <div className='row member-div'>
- <img
- className='post-profile-img pull-left'
- src={`/api/v1/users/${user.id}/image?time=${timestamp}&${Utils.getSessionIndex()}`}
- height='36'
- width='36'
- />
- <span className='member-name'>{Utils.getDisplayName(user)}</span>
- <span className='member-email'>{email}</span>
- <div className='dropdown member-drop'>
- <a
- href='#'
- className='dropdown-toggle theme'
- type='button'
- id='channel_header_dropdown'
- data-toggle='dropdown'
- aria-expanded='true'
- >
- <span>{currentRoles} </span>
- <span className='caret'></span>
- </a>
- <ul
- className='dropdown-menu member-menu'
- role='menu'
- aria-labelledby='channel_header_dropdown'
- >
- {makeAdmin}
- {makeMember}
- {makeActive}
- {makeNotActive}
- </ul>
- </div>
- {serverError}
- </div>
+ <tr>
+ <td className='row member-div'>
+ <img
+ className='post-profile-img pull-left'
+ src={`/api/v1/users/${user.id}/image?time=${timestamp}&${Utils.getSessionIndex()}`}
+ height='36'
+ width='36'
+ />
+ <span className='member-name'>{Utils.getDisplayName(user)}</span>
+ <span className='member-email'>{email}</span>
+ <div className='dropdown member-drop'>
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ id='channel_header_dropdown'
+ data-toggle='dropdown'
+ aria-expanded='true'
+ >
+ <span>{currentRoles} </span>
+ <span className='caret'></span>
+ </a>
+ <ul
+ className='dropdown-menu member-menu'
+ role='menu'
+ aria-labelledby='channel_header_dropdown'
+ >
+ {makeAdmin}
+ {makeMember}
+ {makeActive}
+ {makeNotActive}
+ </ul>
+ </div>
+ {serverError}
+ </td>
+ </tr>
);
}
}
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 8b5f7a381..f5ce5c10e 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -146,7 +146,7 @@ export default class Sidebar extends React.Component {
visibleDirectChannels.sort(this.sortChannelsByDisplayName);
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
return {
activeId: currentChannelId,
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index 46730e1e6..bc7f6ba50 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -31,7 +31,7 @@ export default class SidebarHeader extends React.Component {
PreferenceStore.removeChangeListener(this.onPreferenceChange);
}
getStateFromStores() {
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
return {showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.MENU_POPOVER};
}
diff --git a/web/react/components/team_members.jsx b/web/react/components/team_members.jsx
index 33590c89a..ac1ebf52d 100644
--- a/web/react/components/team_members.jsx
+++ b/web/react/components/team_members.jsx
@@ -79,7 +79,7 @@ export default class TeamMembers extends React.Component {
return (
<div
- className='modal fade'
+ className='modal fade more-modal'
ref='modal'
id='team_members'
tabIndex='-1'
@@ -106,12 +106,10 @@ export default class TeamMembers extends React.Component {
ref='modalBody'
className='modal-body'
>
- <div className='channel-settings'>
- <div className='team-member-list'>
- {renderMembers}
- </div>
- {serverError}
+ <div className='team-member-list'>
+ {renderMembers}
</div>
+ {serverError}
</div>
<div className='modal-footer'>
<button
diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx
index 24da106d0..4d594bb1b 100644
--- a/web/react/components/user_settings/import_theme_modal.jsx
+++ b/web/react/components/user_settings/import_theme_modal.jsx
@@ -50,7 +50,7 @@ export default class ImportThemeModal extends React.Component {
theme.sidebarText = colors[5];
theme.sidebarUnreadText = colors[5];
theme.sidebarTextHoverBg = colors[4];
- theme.sidebarTextActiveBg = colors[2];
+ theme.sidebarTextActiveBorder = colors[2];
theme.sidebarTextActiveColor = colors[3];
theme.sidebarHeaderBg = colors[1];
theme.sidebarHeaderTextColor = colors[5];
@@ -59,9 +59,13 @@ export default class ImportThemeModal extends React.Component {
theme.mentionColor = '#ffffff';
theme.centerChannelBg = '#ffffff';
theme.centerChannelColor = '#333333';
+ theme.newMessageSeparator = '#F80';
theme.linkColor = '#2389d7';
theme.buttonBg = '#26a970';
theme.buttonColor = '#ffffff';
+ theme.mentionHighlightBg = '#fff2bb';
+ theme.mentionHighlightLink = '#2f81b7';
+ theme.codeTheme = 'github';
let user = UserStore.getCurrentUser();
user.theme_props = theme;
diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx
index 3c12ead23..43c8d33d1 100644
--- a/web/react/components/user_settings/user_settings_display.jsx
+++ b/web/react/components/user_settings/user_settings_display.jsx
@@ -172,13 +172,13 @@ export default class UserSettingsDisplay extends React.Component {
</label>
<br/>
</div>
- <div><br/>{'How should other users be shown in Direct Messages list?'}</div>
+ <div><br/>{'Set what name to display in the Direct Messages list.'}</div>
</div>
];
nameFormatSection = (
<SettingItemMax
- title='Show real names, nick names or usernames?'
+ title='Teammate Name Display'
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
@@ -200,7 +200,7 @@ export default class UserSettingsDisplay extends React.Component {
nameFormatSection = (
<SettingItemMin
- title='Show real names, nick names or usernames?'
+ title='Teammate Name Display'
describe={describe}
updateSection={() => {
this.props.updateSection('name_format');
diff --git a/web/react/utils/highlight.jsx b/web/react/utils/highlight.jsx
new file mode 100644
index 000000000..68fef7930
--- /dev/null
+++ b/web/react/utils/highlight.jsx
@@ -0,0 +1,51 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const highlightJs = require('highlight.js/lib/highlight.js');
+const highlightJsDiff = require('highlight.js/lib/languages/diff.js');
+const highlightJsApache = require('highlight.js/lib/languages/apache.js');
+const highlightJsMakefile = require('highlight.js/lib/languages/makefile.js');
+const highlightJsHttp = require('highlight.js/lib/languages/http.js');
+const highlightJsJson = require('highlight.js/lib/languages/json.js');
+const highlightJsMarkdown = require('highlight.js/lib/languages/markdown.js');
+const highlightJsJavascript = require('highlight.js/lib/languages/javascript.js');
+const highlightJsCss = require('highlight.js/lib/languages/css.js');
+const highlightJsNginx = require('highlight.js/lib/languages/nginx.js');
+const highlightJsObjectivec = require('highlight.js/lib/languages/objectivec.js');
+const highlightJsPython = require('highlight.js/lib/languages/python.js');
+const highlightJsXml = require('highlight.js/lib/languages/xml.js');
+const highlightJsPerl = require('highlight.js/lib/languages/perl.js');
+const highlightJsBash = require('highlight.js/lib/languages/bash.js');
+const highlightJsPhp = require('highlight.js/lib/languages/php.js');
+const highlightJsCoffeescript = require('highlight.js/lib/languages/coffeescript.js');
+const highlightJsCs = require('highlight.js/lib/languages/cs.js');
+const highlightJsCpp = require('highlight.js/lib/languages/cpp.js');
+const highlightJsSql = require('highlight.js/lib/languages/sql.js');
+const highlightJsGo = require('highlight.js/lib/languages/go.js');
+const highlightJsRuby = require('highlight.js/lib/languages/ruby.js');
+const highlightJsJava = require('highlight.js/lib/languages/java.js');
+const highlightJsIni = require('highlight.js/lib/languages/ini.js');
+
+highlightJs.registerLanguage('diff', highlightJsDiff);
+highlightJs.registerLanguage('apache', highlightJsApache);
+highlightJs.registerLanguage('makefile', highlightJsMakefile);
+highlightJs.registerLanguage('http', highlightJsHttp);
+highlightJs.registerLanguage('json', highlightJsJson);
+highlightJs.registerLanguage('markdown', highlightJsMarkdown);
+highlightJs.registerLanguage('javascript', highlightJsJavascript);
+highlightJs.registerLanguage('css', highlightJsCss);
+highlightJs.registerLanguage('nginx', highlightJsNginx);
+highlightJs.registerLanguage('objectivec', highlightJsObjectivec);
+highlightJs.registerLanguage('python', highlightJsPython);
+highlightJs.registerLanguage('xml', highlightJsXml);
+highlightJs.registerLanguage('perl', highlightJsPerl);
+highlightJs.registerLanguage('bash', highlightJsBash);
+highlightJs.registerLanguage('php', highlightJsPhp);
+highlightJs.registerLanguage('coffeescript', highlightJsCoffeescript);
+highlightJs.registerLanguage('cs', highlightJsCs);
+highlightJs.registerLanguage('cpp', highlightJsCpp);
+highlightJs.registerLanguage('sql', highlightJsSql);
+highlightJs.registerLanguage('go', highlightJsGo);
+highlightJs.registerLanguage('ruby', highlightJsRuby);
+highlightJs.registerLanguage('java', highlightJsJava);
+highlightJs.registerLanguage('ini', highlightJsIni);
diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx
index 3ef09211f..374caf6dc 100644
--- a/web/react/utils/markdown.jsx
+++ b/web/react/utils/markdown.jsx
@@ -1,38 +1,14 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+require('./highlight.jsx');
const TextFormatting = require('./text_formatting.jsx');
const Utils = require('./utils.jsx');
+const highlightJs = require('highlight.js/lib/highlight.js');
const marked = require('marked');
-const highlightJs = require('highlight.js/lib/highlight.js');
-const highlightJsDiff = require('highlight.js/lib/languages/diff.js');
-const highlightJsApache = require('highlight.js/lib/languages/apache.js');
-const highlightJsMakefile = require('highlight.js/lib/languages/makefile.js');
-const highlightJsHttp = require('highlight.js/lib/languages/http.js');
-const highlightJsJson = require('highlight.js/lib/languages/json.js');
-const highlightJsMarkdown = require('highlight.js/lib/languages/markdown.js');
-const highlightJsJavascript = require('highlight.js/lib/languages/javascript.js');
-const highlightJsCss = require('highlight.js/lib/languages/css.js');
-const highlightJsNginx = require('highlight.js/lib/languages/nginx.js');
-const highlightJsObjectivec = require('highlight.js/lib/languages/objectivec.js');
-const highlightJsPython = require('highlight.js/lib/languages/python.js');
-const highlightJsXml = require('highlight.js/lib/languages/xml.js');
-const highlightJsPerl = require('highlight.js/lib/languages/perl.js');
-const highlightJsBash = require('highlight.js/lib/languages/bash.js');
-const highlightJsPhp = require('highlight.js/lib/languages/php.js');
-const highlightJsCoffeescript = require('highlight.js/lib/languages/coffeescript.js');
-const highlightJsCs = require('highlight.js/lib/languages/cs.js');
-const highlightJsCpp = require('highlight.js/lib/languages/cpp.js');
-const highlightJsSql = require('highlight.js/lib/languages/sql.js');
-const highlightJsGo = require('highlight.js/lib/languages/go.js');
-const highlightJsRuby = require('highlight.js/lib/languages/ruby.js');
-const highlightJsJava = require('highlight.js/lib/languages/java.js');
-const highlightJsIni = require('highlight.js/lib/languages/ini.js');
-
-const Constants = require('../utils/constants.jsx');
-const HighlightedLanguages = Constants.HighlightedLanguages;
+const HighlightedLanguages = require('../utils/constants.jsx').HighlightedLanguages;
function markdownImageLoaded(image) {
image.style.height = 'auto';
@@ -84,30 +60,6 @@ class MattermostMarkdownRenderer extends marked.Renderer {
this.text = this.text.bind(this);
this.formattingOptions = formattingOptions;
-
- highlightJs.registerLanguage('diff', highlightJsDiff);
- highlightJs.registerLanguage('apache', highlightJsApache);
- highlightJs.registerLanguage('makefile', highlightJsMakefile);
- highlightJs.registerLanguage('http', highlightJsHttp);
- highlightJs.registerLanguage('json', highlightJsJson);
- highlightJs.registerLanguage('markdown', highlightJsMarkdown);
- highlightJs.registerLanguage('javascript', highlightJsJavascript);
- highlightJs.registerLanguage('css', highlightJsCss);
- highlightJs.registerLanguage('nginx', highlightJsNginx);
- highlightJs.registerLanguage('objectivec', highlightJsObjectivec);
- highlightJs.registerLanguage('python', highlightJsPython);
- highlightJs.registerLanguage('xml', highlightJsXml);
- highlightJs.registerLanguage('perl', highlightJsPerl);
- highlightJs.registerLanguage('bash', highlightJsBash);
- highlightJs.registerLanguage('php', highlightJsPhp);
- highlightJs.registerLanguage('coffeescript', highlightJsCoffeescript);
- highlightJs.registerLanguage('cs', highlightJsCs);
- highlightJs.registerLanguage('cpp', highlightJsCpp);
- highlightJs.registerLanguage('sql', highlightJsSql);
- highlightJs.registerLanguage('go', highlightJsGo);
- highlightJs.registerLanguage('ruby', highlightJsRuby);
- highlightJs.registerLanguage('java', highlightJsJava);
- highlightJs.registerLanguage('ini', highlightJsIni);
}
code(code, language) {
@@ -204,6 +156,301 @@ class MattermostMarkdownRenderer extends marked.Renderer {
}
}
+class MattermostLexer extends marked.Lexer {
+ token(originalSrc, top, bq) {
+ let src = originalSrc.replace(/^ +$/gm, '');
+
+ while (src) {
+ // newline
+ let cap = this.rules.newline.exec(src);
+ if (cap) {
+ src = src.substring(cap[0].length);
+ if (cap[0].length > 1) {
+ this.tokens.push({
+ type: 'space'
+ });
+ }
+ }
+
+ // code
+ cap = this.rules.code.exec(src);
+ if (cap) {
+ src = src.substring(cap[0].length);
+ cap = cap[0].replace(/^ {4}/gm, '');
+ this.tokens.push({
+ type: 'code',
+ text: this.options.pedantic ? cap : cap.replace(/\n+$/, '')
+ });
+ continue;
+ }
+
+ // fences (gfm)
+ cap = this.rules.fences.exec(src);
+ if (cap) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'code',
+ lang: cap[2],
+ text: cap[3] || ''
+ });
+ continue;
+ }
+
+ // heading
+ cap = this.rules.heading.exec(src);
+ if (cap) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'heading',
+ depth: cap[1].length,
+ text: cap[2]
+ });
+ continue;
+ }
+
+ // table no leading pipe (gfm)
+ cap = this.rules.nptable.exec(src);
+ if (top && cap) {
+ src = src.substring(cap[0].length);
+
+ const item = {
+ type: 'table',
+ header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
+ align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
+ cells: cap[3].replace(/\n$/, '').split('\n')
+ };
+
+ for (let i = 0; i < item.align.length; i++) {
+ if (/^ *-+: *$/.test(item.align[i])) {
+ item.align[i] = 'right';
+ } else if (/^ *:-+: *$/.test(item.align[i])) {
+ item.align[i] = 'center';
+ } else if (/^ *:-+ *$/.test(item.align[i])) {
+ item.align[i] = 'left';
+ } else {
+ item.align[i] = null;
+ }
+ }
+
+ for (let i = 0; i < item.cells.length; i++) {
+ item.cells[i] = item.cells[i].split(/ *\| */);
+ }
+
+ this.tokens.push(item);
+
+ continue;
+ }
+
+ // lheading
+ cap = this.rules.lheading.exec(src);
+ if (cap) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'heading',
+ depth: cap[2] === '=' ? 1 : 2,
+ text: cap[1]
+ });
+ continue;
+ }
+
+ // hr
+ cap = this.rules.hr.exec(src);
+ if (cap) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'hr'
+ });
+ continue;
+ }
+
+ // blockquote
+ cap = this.rules.blockquote.exec(src);
+ if (cap) {
+ src = src.substring(cap[0].length);
+
+ this.tokens.push({
+ type: 'blockquote_start'
+ });
+
+ cap = cap[0].replace(/^ *> ?/gm, '');
+
+ // Pass `top` to keep the current
+ // "toplevel" state. This is exactly
+ // how markdown.pl works.
+ this.token(cap, top, true);
+
+ this.tokens.push({
+ type: 'blockquote_end'
+ });
+
+ continue;
+ }
+
+ // list
+ cap = this.rules.list.exec(src);
+ if (cap) {
+ const bull = cap[2];
+ let l = cap[0].length;
+
+ // 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'), '');
+ }
+
+ // 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 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'
+ });
+
+ continue;
+ }
+ }
+
+ // html
+ cap = this.rules.html.exec(src);
+ if (cap) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: this.options.sanitize ? 'paragraph' : 'html',
+ pre: !this.options.sanitizer && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
+ text: cap[0]
+ });
+ continue;
+ }
+
+ // def
+ cap = this.rules.def.exec(src);
+ if ((!bq && top) && cap) {
+ src = src.substring(cap[0].length);
+ this.tokens.links[cap[1].toLowerCase()] = {
+ href: cap[2],
+ title: cap[3]
+ };
+ continue;
+ }
+
+ // table (gfm)
+ cap = this.rules.table.exec(src);
+ if (top && cap) {
+ src = src.substring(cap[0].length);
+
+ const item = {
+ type: 'table',
+ header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
+ align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
+ cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
+ };
+
+ for (let i = 0; i < item.align.length; i++) {
+ if (/^ *-+: *$/.test(item.align[i])) {
+ item.align[i] = 'right';
+ } else if (/^ *:-+: *$/.test(item.align[i])) {
+ item.align[i] = 'center';
+ } else if (/^ *:-+ *$/.test(item.align[i])) {
+ item.align[i] = 'left';
+ } else {
+ item.align[i] = null;
+ }
+ }
+
+ for (let i = 0; i < item.cells.length; i++) {
+ item.cells[i] = item.cells[i].replace(/^ *\| *| *\| *$/g, '').split(/ *\| */);
+ }
+
+ this.tokens.push(item);
+
+ continue;
+ }
+
+ // top-level paragraph
+ cap = this.rules.paragraph.exec(src);
+ if (top && cap) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'paragraph',
+ text: cap[1].charAt(cap[1].length - 1) === '\n' ? cap[1].slice(0, -1) : cap[1]
+ });
+ continue;
+ }
+
+ // text
+ cap = this.rules.text.exec(src);
+ if (cap) {
+ // Top-level should never reach here.
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'text',
+ text: cap[0]
+ });
+ continue;
+ }
+
+ if (src) {
+ throw new Error('Infinite loop on byte: ' + src.charCodeAt(0));
+ }
+ }
+
+ return this.tokens;
+ }
+}
+
export function format(text, options) {
const markdownOptions = {
renderer: new MattermostMarkdownRenderer(null, options),
@@ -212,7 +459,7 @@ export function format(text, options) {
tables: true
};
- const tokens = marked.lexer(text, markdownOptions);
+ const tokens = new MattermostLexer(markdownOptions).lex(text);
return new MattermostParser(markdownOptions).parse(tokens);
}
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 22826b150..5a3000dff 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -151,10 +151,14 @@ export function notifyMe(title, body, channel) {
}
}
+var canDing = true;
+
export function ding() {
- if (!isBrowserFirefox()) {
- var audio = new Audio('/static/images/ding.mp3');
+ if (!isBrowserFirefox() && canDing) {
+ var audio = new Audio('/static/images/bing.mp3');
audio.play();
+ canDing = false;
+ setTimeout(() => canDing = true, 3000);
}
}