diff options
Diffstat (limited to 'web/react')
-rw-r--r-- | web/react/components/admin_console/user_item.jsx | 90 | ||||
-rw-r--r-- | web/react/components/center_panel.jsx | 4 | ||||
-rw-r--r-- | web/react/components/channel_invite_modal.jsx | 21 | ||||
-rw-r--r-- | web/react/components/channel_members_modal.jsx | 35 | ||||
-rw-r--r-- | web/react/components/create_post.jsx | 4 | ||||
-rw-r--r-- | web/react/components/member_list.jsx | 2 | ||||
-rw-r--r-- | web/react/components/member_list_team_item.jsx | 70 | ||||
-rw-r--r-- | web/react/components/sidebar.jsx | 2 | ||||
-rw-r--r-- | web/react/components/sidebar_header.jsx | 2 | ||||
-rw-r--r-- | web/react/components/team_members.jsx | 10 | ||||
-rw-r--r-- | web/react/components/user_settings/import_theme_modal.jsx | 6 | ||||
-rw-r--r-- | web/react/components/user_settings/user_settings_display.jsx | 6 | ||||
-rw-r--r-- | web/react/utils/highlight.jsx | 51 | ||||
-rw-r--r-- | web/react/utils/markdown.jsx | 351 | ||||
-rw-r--r-- | web/react/utils/utils.jsx | 8 |
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); } } |