diff options
Diffstat (limited to 'web')
-rw-r--r-- | web/react/components/delete_post_modal.jsx | 1 | ||||
-rw-r--r-- | web/react/components/navbar.jsx | 2 | ||||
-rw-r--r-- | web/react/components/textbox.jsx | 23 | ||||
-rw-r--r-- | web/react/components/user_settings/user_settings_advanced.jsx | 144 | ||||
-rw-r--r-- | web/react/stores/post_store.jsx | 14 | ||||
-rw-r--r-- | web/react/utils/constants.jsx | 7 | ||||
-rw-r--r-- | web/react/utils/markdown.jsx | 114 | ||||
-rw-r--r-- | web/react/utils/utils.jsx | 4 | ||||
-rw-r--r-- | web/sass-files/sass/partials/_base.scss | 2 | ||||
-rw-r--r-- | web/sass-files/sass/partials/_post.scss | 12 |
10 files changed, 233 insertions, 90 deletions
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/navbar.jsx b/web/react/components/navbar.jsx index 03cc75a08..6c3bfc7db 100644 --- a/web/react/components/navbar.jsx +++ b/web/react/components/navbar.jsx @@ -174,7 +174,7 @@ export default class Navbar extends React.Component { <a role='menuitem' href='#' - onClick={() => this.setState({showInviteModal: false})} + onClick={() => this.setState({showInviteModal: true})} > {'Add Members'} </a> diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx index e2868e946..10b3c0069 100644 --- a/web/react/components/textbox.jsx +++ b/web/react/components/textbox.jsx @@ -11,6 +11,7 @@ import * as Utils from '../utils/utils.jsx'; import Constants from '../utils/constants.jsx'; const ActionTypes = Constants.ActionTypes; const KeyCodes = Constants.KeyCodes; +const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES; export default class Textbox extends React.Component { constructor(props) { @@ -303,7 +304,19 @@ export default class Textbox extends React.Component { } render() { - const previewLinkVisible = this.props.messageText.length > 0; + let previewLink = null; + if (Utils.isFeatureEnabled(PreReleaseFeatures.MARKDOWN_PREVIEW)) { + const previewLinkVisible = this.props.messageText.length > 0; + previewLink = ( + <a + style={{visibility: previewLinkVisible ? 'visible' : 'hidden'}} + onClick={this.showPreview} + className='textbox-preview-link' + > + {this.state.preview ? 'Edit message' : 'Preview'} + </a> + ); + } return ( <div @@ -342,19 +355,13 @@ export default class Textbox extends React.Component { dangerouslySetInnerHTML={{__html: this.state.preview ? TextFormatting.formatText(this.props.messageText) : ''}} > </div> + {previewLink} <a onClick={this.showHelp} className='textbox-help-link' > {'Help'} </a> - <a - style={{visibility: previewLinkVisible ? 'visible' : 'hidden'}} - onClick={this.showPreview} - className='textbox-preview-link' - > - {this.state.preview ? 'Edit' : 'Preview'} - </a> </div> ); } diff --git a/web/react/components/user_settings/user_settings_advanced.jsx b/web/react/components/user_settings/user_settings_advanced.jsx index ac82595f5..b4d34c658 100644 --- a/web/react/components/user_settings/user_settings_advanced.jsx +++ b/web/react/components/user_settings/user_settings_advanced.jsx @@ -6,6 +6,7 @@ import SettingItemMin from '../setting_item_min.jsx'; import SettingItemMax from '../setting_item_max.jsx'; import Constants from '../../utils/constants.jsx'; import PreferenceStore from '../../stores/preference_store.jsx'; +const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES; export default class AdvancedSettingsDisplay extends React.Component { constructor(props) { @@ -13,21 +14,33 @@ export default class AdvancedSettingsDisplay extends React.Component { this.updateSection = this.updateSection.bind(this); this.updateSetting = this.updateSetting.bind(this); - this.setupInitialState = this.setupInitialState.bind(this); + this.toggleFeature = this.toggleFeature.bind(this); + this.saveEnabledFeatures = this.saveEnabledFeatures.bind(this); - this.state = this.setupInitialState(); - } + const preReleaseFeaturesKeys = Object.keys(PreReleaseFeatures); + const advancedSettings = PreferenceStore.getPreferences(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS); + const settings = { + send_on_ctrl_enter: PreferenceStore.getPreference( + Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, + 'send_on_ctrl_enter', + {value: 'false'} + ).value + }; - setupInitialState() { - const sendOnCtrlEnter = PreferenceStore.getPreference( - Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, - 'send_on_ctrl_enter', - {value: 'false'} - ).value; + let enabledFeatures = 0; + advancedSettings.forEach((setting) => { + preReleaseFeaturesKeys.forEach((key) => { + const feature = PreReleaseFeatures[key]; + if (setting.name === Constants.FeatureTogglePrefix + feature.label) { + settings[setting.name] = setting.value; + if (setting.value === 'true') { + enabledFeatures++; + } + } + }); + }); - return { - settings: {send_on_ctrl_enter: sendOnCtrlEnter} - }; + this.state = {preReleaseFeatures: PreReleaseFeatures, settings, preReleaseFeaturesKeys, enabledFeatures}; } updateSetting(setting, value) { @@ -36,14 +49,45 @@ export default class AdvancedSettingsDisplay extends React.Component { this.setState(settings); } - handleSubmit(setting) { - const preference = PreferenceStore.setPreference( - Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, - setting, - this.state.settings[setting] - ); + toggleFeature(feature, checked) { + const settings = this.state.settings; + settings[Constants.FeatureTogglePrefix + feature] = String(checked); + + let enabledFeatures = 0; + Object.keys(this.state.settings).forEach((setting) => { + if (setting.lastIndexOf(Constants.FeatureTogglePrefix) === 0 && this.state.settings[setting] === 'true') { + enabledFeatures++; + } + }); + + this.setState({settings, enabledFeatures}); + } + + saveEnabledFeatures() { + const features = []; + Object.keys(this.state.settings).forEach((setting) => { + if (setting.lastIndexOf(Constants.FeatureTogglePrefix) === 0) { + features.push(setting); + } + }); + + this.handleSubmit(features); + } - Client.savePreferences([preference], + handleSubmit(settings) { + const preferences = []; + + (Array.isArray(settings) ? settings : [settings]).forEach((setting) => { + preferences.push( + PreferenceStore.setPreference( + Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, + setting, + String(this.state.settings[setting]) + ) + ); + }); + + Client.savePreferences(preferences, () => { PreferenceStore.emitChange(); this.updateSection(''); @@ -118,6 +162,66 @@ export default class AdvancedSettingsDisplay extends React.Component { ); } + let previewFeaturesSection; + let previewFeaturesSectionDivider; + if (this.state.preReleaseFeaturesKeys.length > 0) { + previewFeaturesSectionDivider = ( + <div className='divider-light'/> + ); + + if (this.props.activeSection === 'advancedPreviewFeatures') { + const inputs = []; + + this.state.preReleaseFeaturesKeys.forEach((key) => { + const feature = this.state.preReleaseFeatures[key]; + inputs.push( + <div key={'advancedPreviewFeatures_' + feature.label}> + <div className='checkbox'> + <label> + <input + type='checkbox' + checked={this.state.settings[Constants.FeatureTogglePrefix + feature.label] === 'true'} + onChange={(e) => { + this.toggleFeature(feature.label, e.target.checked); + }} + /> + {feature.description} + </label> + </div> + </div> + ); + }); + + inputs.push( + <div key='advancedPreviewFeatures_helptext'> + <br/> + {'Check any pre-released features you\'d like to preview.'} + </div> + ); + + previewFeaturesSection = ( + <SettingItemMax + title='Preview pre-release features' + inputs={inputs} + submit={this.saveEnabledFeatures} + server_error={serverError} + updateSection={(e) => { + this.updateSection(''); + e.preventDefault(); + }} + /> + ); + } else { + previewFeaturesSection = ( + <SettingItemMin + title='Preview pre-release features' + describe={this.state.enabledFeatures + (this.state.enabledFeatures === 1 ? ' Feature ' : ' Features ') + 'enabled'} + updateSection={() => this.props.updateSection('advancedPreviewFeatures')} + /> + ); + } + } + return ( <div> <div className='modal-header'> @@ -145,6 +249,8 @@ export default class AdvancedSettingsDisplay extends React.Component { <h3 className='tab-header'>{'Advanced Settings'}</h3> <div className='divider-dark first'/> {ctrlSendSection} + {previewFeaturesSectionDivider} + {previewFeaturesSection} <div className='divider-dark'/> </div> </div> diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx index 24b0d0dd0..a8f0f9c63 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); @@ -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 372e15556..2009e07dd 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -398,5 +398,12 @@ export default { }, NotificationPrefs: { MENTION: 'mention' + }, + FeatureTogglePrefix: 'feature_enabled_', + PRE_RELEASE_FEATURES: { + MARKDOWN_PREVIEW: { + label: 'markdown_preview', // github issue: https://github.com/mattermost/platform/pull/1389 + description: 'Show markdown preview option in message input box' + } } }; diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx index b0ec64bfd..9d9bdfb7a 100644 --- a/web/react/utils/markdown.jsx +++ b/web/react/utils/markdown.jsx @@ -361,78 +361,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/utils.jsx b/web/react/utils/utils.jsx index 9b2f7e057..80c377d7f 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -1221,3 +1221,7 @@ export function getPostTerm(post) { return postTerm; } + +export function isFeatureEnabled(feature) { + return PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, Constants.FeatureTogglePrefix + feature.label, {value: 'false'}).value === 'true'; +} 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/_post.scss b/web/sass-files/sass/partials/_post.scss index b7609bb7d..b7a305427 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -53,6 +53,7 @@ body.ios { top: 0; left: 0; box-shadow: none; + white-space: normal; } .textbox-preview-link, .textbox-help-link { position: absolute; @@ -283,14 +284,14 @@ body.ios { .custom-textarea { padding-top: 8px; padding-right: 28px; - max-height: 160px; + max-height: 162px !important; overflow: auto; line-height: 1.5; } .textarea-div { padding-top: 8px; padding-right: 30px; - max-height: 160px; + max-height: 163px !important; overflow: auto; line-height: 1.5; } @@ -373,9 +374,9 @@ body.ios { ul { margin: 0; padding: 0; - list-style: none; } + p { margin: 0 0 1em; line-height: 1.6em; @@ -602,6 +603,11 @@ body.ios { padding: 5px 0 0 20px; } + ul, ol { + li ul, li ol { + padding: 0 0 0 20px + } + } } .post__link { |