diff options
Diffstat (limited to 'web')
26 files changed, 343 insertions, 184 deletions
diff --git a/web/react/components/admin_console/analytics.jsx b/web/react/components/admin_console/analytics.jsx index 0a159d2e3..ec9ad4da0 100644 --- a/web/react/components/admin_console/analytics.jsx +++ b/web/react/components/admin_console/analytics.jsx @@ -1,7 +1,6 @@ // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import * as Utils from '../../utils/utils.jsx'; import Constants from '../../utils/constants.jsx'; import LineChart from './line_chart.jsx'; import DoughnutChart from './doughnut_chart.jsx'; @@ -10,7 +9,7 @@ import StatisticCount from './statistic_count.jsx'; var Tooltip = ReactBootstrap.Tooltip; var OverlayTrigger = ReactBootstrap.OverlayTrigger; -import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl'; +import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedDate} from 'mm-intl'; const holders = defineMessages({ analyticsTotalUsers: { @@ -75,10 +74,12 @@ export default class Analytics extends React.Component { } let loading = ( - <FormattedMessage - id='admin.analytics.loading' - defaultMessage='Loading...' - /> + <h5> + <FormattedMessage + id='admin.analytics.loading' + defaultMessage='Loading...' + /> + </h5> ); let firstRow; @@ -322,7 +323,17 @@ export default class Analytics extends React.Component { </time> </OverlayTrigger> </td> - <td>{Utils.displayDateTime(user.last_activity_at)}</td> + <td> + <FormattedDate + value={user.last_activity_at} + day='numeric' + month='long' + year='numeric' + hour12={true} + hour='2-digit' + minute='2-digit' + /> + </td> </tr> ); }) @@ -378,7 +389,17 @@ export default class Analytics extends React.Component { </time> </OverlayTrigger> </td> - <td>{Utils.displayDateTime(user.create_at)}</td> + <td> + <FormattedDate + value={user.create_at} + day='numeric' + month='long' + year='numeric' + hour12={true} + hour='2-digit' + minute='2-digit' + /> + </td> </tr> ); }) diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx index 2cc68d1ed..f232d4633 100644 --- a/web/react/components/admin_console/service_settings.jsx +++ b/web/react/components/admin_console/service_settings.jsx @@ -75,6 +75,7 @@ class ServiceSettings extends React.Component { config.ServiceSettings.EnableTesting = ReactDOM.findDOMNode(this.refs.EnableTesting).checked; config.ServiceSettings.EnableDeveloper = ReactDOM.findDOMNode(this.refs.EnableDeveloper).checked; config.ServiceSettings.EnableSecurityFixAlert = ReactDOM.findDOMNode(this.refs.EnableSecurityFixAlert).checked; + config.ServiceSettings.EnableInsecureOutgoingConnections = ReactDOM.findDOMNode(this.refs.EnableInsecureOutgoingConnections).checked; config.ServiceSettings.EnableCommands = ReactDOM.findDOMNode(this.refs.EnableCommands).checked; config.ServiceSettings.EnableOnlyAdminIntegrations = ReactDOM.findDOMNode(this.refs.EnableOnlyAdminIntegrations).checked; @@ -720,6 +721,53 @@ class ServiceSettings extends React.Component { <div className='form-group'> <label className='control-label col-sm-4' + htmlFor='EnableInsecureOutgoingConnections' + > + <FormattedMessage + id='admin.service.insecureTlsTitle' + defaultMessage='Enable Insecure Outgoing Connections: ' + /> + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnableInsecureOutgoingConnections' + value='true' + ref='EnableInsecureOutgoingConnections' + defaultChecked={this.props.config.ServiceSettings.EnableInsecureOutgoingConnections} + onChange={this.handleChange} + /> + <FormattedMessage + id='admin.service.true' + defaultMessage='true' + /> + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnableInsecureOutgoingConnections' + value='false' + defaultChecked={!this.props.config.ServiceSettings.EnableInsecureOutgoingConnections} + onChange={this.handleChange} + /> + <FormattedMessage + id='admin.service.false' + defaultMessage='false' + /> + </label> + <p className='help-text'> + <FormattedMessage + id='admin.service.insecureTlsDesc' + defaultMessage='When true, any outgoing HTTPS requests will accept unverified, self-signed certificates. For example, outgoing webhooks to a server with a self-signed TLS certificate, using any domain, will be allowed. Note that this makes these connections susceptible to man-in-the-middle attacks.' + /> + </p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' htmlFor='SessionLengthWebInDays' > <FormattedMessage @@ -896,4 +944,4 @@ ServiceSettings.propTypes = { config: React.PropTypes.object }; -export default injectIntl(ServiceSettings);
\ No newline at end of file +export default injectIntl(ServiceSettings); diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index 005a82209..8fc3cd63d 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -419,7 +419,7 @@ export default class ChannelHeader extends React.Component { </ul> </div> <OverlayTrigger - trigger={['hover', 'focus']} + trigger={'click'} placement='bottom' overlay={popoverContent} ref='headerOverlay' diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 9e7c67515..709485991 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -59,6 +59,7 @@ class CreateComment extends React.Component { this.getFileCount = this.getFileCount.bind(this); this.handleResize = this.handleResize.bind(this); this.onPreferenceChange = this.onPreferenceChange.bind(this); + this.focusTextbox = this.focusTextbox.bind(this); PostStore.clearCommentDraftUploads(); @@ -76,7 +77,7 @@ class CreateComment extends React.Component { PreferenceStore.addChangeListener(this.onPreferenceChange); window.addEventListener('resize', this.handleResize); - this.refs.textbox.focus(); + this.focusTextbox(); } componentWillUnmount() { PreferenceStore.removeChangeListener(this.onPreferenceChange); @@ -99,7 +100,7 @@ class CreateComment extends React.Component { } if (prevProps.rootId !== this.props.rootId) { - this.refs.textbox.focus(); + this.focusTextbox(); } } handleSubmit(e) { @@ -226,7 +227,7 @@ class CreateComment extends React.Component { } } handleUploadClick() { - this.refs.textbox.focus(); + this.focusTextbox(); } handleUploadStart(clientIds) { let draft = PostStore.getCommentDraft(this.props.rootId); @@ -238,7 +239,7 @@ class CreateComment extends React.Component { // this is a bit redundant with the code that sets focus when the file input is clicked, // but this also resets the focus after a drag and drop - this.refs.textbox.focus(); + this.focusTextbox(); } handleFileUploadComplete(filenames, clientIds) { let draft = PostStore.getCommentDraft(this.props.rootId); @@ -306,6 +307,11 @@ class CreateComment extends React.Component { getFileCount() { return this.state.previews.length + this.state.uploadsInProgress.length; } + focusTextbox() { + if (!Utils.isMobile()) { + this.refs.textbox.focus(); + } + } render() { let serverError = null; if (this.state.serverError) { diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 6ea80cd13..ecabdaee6 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -63,6 +63,7 @@ class CreatePost extends React.Component { this.getFileCount = this.getFileCount.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this); this.sendMessage = this.sendMessage.bind(this); + this.focusTextbox = this.focusTextbox.bind(this); PostStore.clearDraftUploads(); @@ -193,6 +194,11 @@ class CreatePost extends React.Component { } ); } + focusTextbox() { + if (!Utils.isMobile()) { + this.refs.textbox.focus(); + } + } postMsgKeyPress(e) { if (this.state.ctrlSend && e.ctrlKey || !this.state.ctrlSend) { if (e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) { @@ -216,7 +222,7 @@ class CreatePost extends React.Component { PostStore.storeCurrentDraft(draft); } handleUploadClick() { - this.refs.textbox.focus(); + this.focusTextbox(); } handleUploadStart(clientIds, channelId) { const draft = PostStore.getDraft(channelId); @@ -228,7 +234,7 @@ class CreatePost extends React.Component { // this is a bit redundant with the code that sets focus when the file input is clicked, // but this also resets the focus after a drag and drop - this.refs.textbox.focus(); + this.focusTextbox(); } handleFileUploadComplete(filenames, clientIds, channelId) { const draft = PostStore.getDraft(channelId); @@ -305,11 +311,12 @@ class CreatePost extends React.Component { componentDidMount() { ChannelStore.addChangeListener(this.onChange); PreferenceStore.addChangeListener(this.onPreferenceChange); - this.refs.textbox.focus(); + + this.focusTextbox(); } componentDidUpdate(prevProps, prevState) { if (prevState.channelId !== this.state.channelId) { - this.refs.textbox.focus(); + this.focusTextbox(); } } componentWillUnmount() { diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 98a832542..672213d1a 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -303,7 +303,7 @@ class SignupUserComplete extends React.Component { ref='name' className='form-control' placeholder='' - maxLength='128' + maxLength={Constants.MAX_USERNAME_LENGTH} spellCheck='false' /> {nameError} diff --git a/web/react/components/suggestion/command_provider.jsx b/web/react/components/suggestion/command_provider.jsx index 91d556bb9..09c9b9982 100644 --- a/web/react/components/suggestion/command_provider.jsx +++ b/web/react/components/suggestion/command_provider.jsx @@ -2,7 +2,6 @@ // See License.txt for license information. import * as AsyncClient from '../../utils/async_client.jsx'; -import SuggestionStore from '../../stores/suggestion_store.jsx'; class CommandSuggestion extends React.Component { render() { @@ -38,8 +37,6 @@ CommandSuggestion.propTypes = { export default class CommandProvider { handlePretextChanged(suggestionId, pretext) { if (pretext.startsWith('/')) { - SuggestionStore.setMatchedPretext(suggestionId, pretext); - AsyncClient.getSuggestedCommands(pretext, suggestionId, CommandSuggestion); } } diff --git a/web/react/components/team_signup_username_page.jsx b/web/react/components/team_signup_username_page.jsx index a7332975d..0fa9cb103 100644 --- a/web/react/components/team_signup_username_page.jsx +++ b/web/react/components/team_signup_username_page.jsx @@ -115,7 +115,7 @@ class TeamSignupUsernamePage extends React.Component { className='form-control' placeholder='' defaultValue={this.props.state.user.username} - maxLength='128' + maxLength={Constants.MAX_USERNAME_LENGTH} spellCheck='false' /> {nameHelpText} diff --git a/web/react/components/time_since.jsx b/web/react/components/time_since.jsx index ba8dbffcc..1560d2469 100644 --- a/web/react/components/time_since.jsx +++ b/web/react/components/time_since.jsx @@ -2,6 +2,7 @@ // See License.txt for license information. import Constants from '../utils/constants.jsx'; +import * as Utils from '../utils/utils.jsx'; import {FormattedRelative, FormattedDate} from 'mm-intl'; @@ -24,7 +25,7 @@ export default class TimeSince extends React.Component { if (this.props.sameUser) { return ( <time className='post__time'> - <FormattedRelative value={this.props.eventTime} /> + {Utils.displayTimeFormatted(this.props.eventTime)} </time> ); } diff --git a/web/react/components/user_settings/manage_command_hooks.jsx b/web/react/components/user_settings/manage_command_hooks.jsx index b2fc0a4e1..948ab7885 100644 --- a/web/react/components/user_settings/manage_command_hooks.jsx +++ b/web/react/components/user_settings/manage_command_hooks.jsx @@ -537,17 +537,11 @@ export default class ManageCommandCmds extends React.Component { onChange={this.updateAutoComplete} /> <FormattedMessage - id='user.settings.cmds.auto_complete_desc_desc' - defaultMessage='A short description of what this commands does' + id='user.settings.cmds.auto_complete_help' + defaultMessage='Show this command in autocomplete list' /> </label> </div> - <div className='padding-top'> - <FormattedMessage - id='user.settings.cmds.auto_complete_help' - defaultMessage='Show this command in autocomplete list.' - /> - </div> </div> <div className='padding-top x2'> <label className='control-label'> @@ -565,12 +559,6 @@ export default class ManageCommandCmds extends React.Component { placeholder={this.props.intl.formatMessage(holders.addAutoCompleteDescPlaceholder)} /> </div> - <div className='padding-top'> - <FormattedMessage - id='user.settings.cmds.auto_complete_desc_desc' - defaultMessage='A short description of what this commands does' - /> - </div> </div> <div className='padding-top x2'> <label className='control-label'> diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx index f20b4b807..cd229775f 100644 --- a/web/react/components/user_settings/user_settings_general.jsx +++ b/web/react/components/user_settings/user_settings_general.jsx @@ -514,6 +514,7 @@ class UserSettingsGeneralTab extends React.Component { <label className='col-sm-5 control-label'>{usernameLabel}</label> <div className='col-sm-7'> <input + maxLength={Constants.MAX_USERNAME_LENGTH} className='form-control' type='text' onChange={this.updateUsername} diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx index 9c3270f68..bc2bdbe64 100644 --- a/web/react/stores/socket_store.jsx +++ b/web/react/stores/socket_store.jsx @@ -28,10 +28,13 @@ class SocketStoreClass extends EventEmitter { this.addChangeListener = this.addChangeListener.bind(this); this.removeChangeListener = this.removeChangeListener.bind(this); this.sendMessage = this.sendMessage.bind(this); + this.close = this.close.bind(this); + this.failCount = 0; this.initialize(); } + initialize() { if (!UserStore.getCurrentId()) { return; @@ -106,15 +109,19 @@ class SocketStoreClass extends EventEmitter { }; } } + emitChange(msg) { this.emit(CHANGE_EVENT, msg); } + addChangeListener(callback) { this.on(CHANGE_EVENT, callback); } + removeChangeListener(callback) { this.removeListener(CHANGE_EVENT, callback); } + handleMessage(msg) { switch (msg.action) { case SocketEvents.POSTED: @@ -153,6 +160,7 @@ class SocketStoreClass extends EventEmitter { default: } } + sendMessage(msg) { if (conn && conn.readyState === WebSocket.OPEN) { conn.send(JSON.stringify(msg)); @@ -161,9 +169,16 @@ class SocketStoreClass extends EventEmitter { this.initialize(); } } + setTranslations(messages) { this.translations = messages; } + + close() { + if (conn && conn.readyState === WebSocket.OPEN) { + conn.close(); + } + } } function handleNewPostEvent(msg, translations) { @@ -305,12 +320,5 @@ function handlePreferenceChangedEvent(msg) { var SocketStore = new SocketStoreClass(); -/*SocketStore.dispatchToken = AppDispatcher.register((payload) => { - var action = payload.action; - - switch (action.type) { - default: - } - });*/ - export default SocketStore; +window.SocketStore = SocketStore; diff --git a/web/react/stores/suggestion_store.jsx b/web/react/stores/suggestion_store.jsx index 9cd566c22..dd5c107e0 100644 --- a/web/react/stores/suggestion_store.jsx +++ b/web/react/stores/suggestion_store.jsx @@ -223,7 +223,9 @@ class SuggestionStore extends EventEmitter { this.emitSuggestionsChanged(id); break; case ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS: - if (other.matchedPretext === this.getMatchedPretext(id)) { + if (this.getMatchedPretext(id) === '') { + this.setMatchedPretext(id, other.matchedPretext); + // ensure the matched pretext hasn't changed so that we don't receive suggestions for outdated pretext this.addSuggestions(id, other.terms, other.items, other.component); diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx index 328a7a7f2..c5957e8cc 100644 --- a/web/react/utils/async_client.jsx +++ b/web/react/utils/async_client.jsx @@ -789,14 +789,16 @@ export function getSuggestedCommands(command, suggestionId, component) { // pull out the suggested commands from the returned data const terms = matches.map((suggestion) => suggestion.suggestion); - AppDispatcher.handleServerAction({ - type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS, - id: suggestionId, - matchedPretext: command, - terms, - items: matches, - component - }); + if (terms.length > 0) { + AppDispatcher.handleServerAction({ + type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS, + id: suggestionId, + matchedPretext: command, + terms, + items: matches, + component + }); + } }, (err) => { dispatchError(err, 'getCommandSuggestions'); diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index c1bd41b88..d78776aa3 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -464,8 +464,9 @@ export default { }, OVERLAY_TIME_DELAY: 400, MIN_USERNAME_LENGTH: 3, - MAX_USERNAME_LENGTH: 15, + MAX_USERNAME_LENGTH: 64, MIN_PASSWORD_LENGTH: 5, MAX_PASSWORD_LENGTH: 50, - TIME_SINCE_UPDATE_INTERVAL: 30000 + TIME_SINCE_UPDATE_INTERVAL: 30000, + MIN_HASHTAG_LINK_LENGTH: 3 }; diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx index e837ded53..dae2252a6 100644 --- a/web/react/utils/text_formatting.jsx +++ b/web/react/utils/text_formatting.jsx @@ -248,8 +248,14 @@ function autolinkHashtags(text, tokens) { const index = tokens.size; const alias = `MM_HASHTAG${index}`; + let value = hashtag; + + if (hashtag.length > Constants.MIN_HASHTAG_LINK_LENGTH) { + value = `<a class='mention-link' href='#' data-hashtag='${hashtag}'>${hashtag}</a>`; + } + tokens.set(alias, { - value: `<a class='mention-link' href='#' data-hashtag='${hashtag}'>${hashtag}</a>`, + value, originalText: hashtag }); diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 6bb7baa64..4beec8d64 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -14,6 +14,8 @@ import * as AsyncClient from './async_client.jsx'; import * as client from './client.jsx'; import Autolinker from 'autolinker'; +import {FormattedTime} from 'mm-intl'; + export function isEmail(email) { // writing a regex to match all valid email addresses is really, really hard (see http://stackoverflow.com/a/201378) // so we just do a simple check and rely on a verification email to tell if it's a real address @@ -245,6 +247,19 @@ export function displayTime(ticks, utc) { return hours + ':' + minutes + ampm + timezone; } +export function displayTimeFormatted(ticks) { + const useMilitaryTime = PreferenceStore.getBool(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time'); + + return ( + <FormattedTime + value={ticks} + hour='numeric' + minute='numeric' + hour12={!useMilitaryTime} + /> + ); +} + export function displayDateTime(ticks) { var seconds = Math.floor((Date.now() - ticks) / 1000); diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss index 04ba9c51e..a13689382 100644 --- a/web/sass-files/sass/partials/_base.scss +++ b/web/sass-files/sass/partials/_base.scss @@ -1,28 +1,28 @@ @charset "UTF-8"; html, body { - height: 100%; + height: 100%; } body { - font-family: 'Open Sans', sans-serif; - -webkit-font-smoothing: antialiased; - background: $body-bg; - position: relative; - width: 100%; - height: 100%; - &.white { - background: #fff; - > .container-fluid { - overflow: auto; - } - .inner__wrap { - > .row.content { - min-height: 100%; - margin-bottom: -89px; - } - } - } + font-family: 'Open Sans', sans-serif; + -webkit-font-smoothing: antialiased; + background: $body-bg; + position: relative; + width: 100%; + height: 100%; + &.white { + background: #fff; + > .container-fluid { + overflow: auto; + } + .inner__wrap { + > .row.content { + min-height: 100%; + margin-bottom: -89px; + } + } + } } .inner__wrap { @@ -46,175 +46,177 @@ body { } img { - max-width: 100%; - height: auto; - &.rounded { - @include border-radius(100%); - } + max-width: 100%; + height: auto; + &.rounded { + @include border-radius(100%); + } } .popover { - @include border-radius(3px); - color: #333; - &.bottom, &.right, &.top, &.left { - >.arrow:after { - border-color: transparent; + @include border-radius(3px); + color: #333; + &.bottom, &.right, &.top, &.left { + >.arrow:after { + border-color: transparent; + } + } + .popover-title { + background: rgba(black, 0.05); + } + .popover-content { + p:last-child { + margin-bottom: 5px; + } } - } - .popover-title { - background: rgba(black, 0.05); - } - .popover-content { - white-space: pre-wrap; - } } .dropdown-menu { - .divider { - @include opacity(0.15); - } - > li > a { - color: inherit; - &:focus, &:hover { - color: inherit; + .divider { + @include opacity(0.15); + } + > li > a { + color: inherit; + &:focus, &:hover { + color: inherit; + } } - } } .word-break--all { - word-break: break-all; + word-break: break-all; } a { - word-break: break-word; - color: $primary-color; - cursor: pointer; + word-break: break-word; + color: $primary-color; + cursor: pointer; } a:focus, a:hover { - color: $primary-color; + color: $primary-color; } .tooltip { - .tooltip-inner { - word-break: break-word; - font-size: 13px; - padding: 3px 10px 4px; - font-weight: 500; - } + .tooltip-inner { + word-break: break-word; + font-size: 13px; + padding: 3px 10px 4px; + font-weight: 500; + } } .nopadding { - padding: 0; - margin: 0; + padding: 0; + margin: 0; } .text-danger, a.text-danger { - color: #E05F5D; - &:hover, &:focus { color: #E05F5D; - } + &:hover, &:focus { + color: #E05F5D; + } } .btn { - &.btn-danger { - color: #fff; - &:hover, &:active, &:focus { - color: #fff; + &.btn-danger { + color: #fff; + &:hover, &:active, &:focus { + color: #fff; + } } - } } .form-control { - @include border-radius(2px); - &:focus { - @include box-shadow(none); - } - &.no-padding { - line-height: 32px; - padding: 0; - } - &.no-resize { - resize: none; - } + @include border-radius(2px); + &:focus { + @include box-shadow(none); + } + &.no-padding { + line-height: 32px; + padding: 0; + } + &.no-resize { + resize: none; + } } .form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control { - cursor: auto; - background: rgba(#fff, 0.1); - color: inherit; + cursor: auto; + background: rgba(#fff, 0.1); + color: inherit; } .form-group { - &.form-group--small { - margin-bottom: 10px; - } + &.form-group--small { + margin-bottom: 10px; + } } .error-panel { - max-width: 275px; - position: absolute; - right: 10px; - top: 40px; - z-index: 100; + max-width: 275px; + position: absolute; + right: 10px; + top: 40px; + z-index: 100; } .nav>li>a:focus, .nav>li>a:hover { - background: transparent; + background: transparent; } .btn { - @include single-transition(all, 0.25s, ease-in); - @include border-radius(1px); - &.btn-primary { - border-color: transparent; - background: $primary-color; - &:hover, &:focus, &:active { - background: $primary-color--hover; - } - } - &.btn-inactive { - border-color: transparent; - background: #707070; - color: #fff; - } + @include single-transition(all, 0.25s, ease-in); + @include border-radius(1px); + &.btn-primary { + border-color: transparent; + background: $primary-color; + &:hover, &:focus, &:active { + background: $primary-color--hover; + } + } + &.btn-inactive { + border-color: transparent; + background: #707070; + color: #fff; + } } .relative-div { - position:relative; + position:relative; } @-webkit-keyframes spin2 { - from { -webkit-transform: rotate(0deg);} - to { -webkit-transform: rotate(360deg);} + from { -webkit-transform: rotate(0deg);} + to { -webkit-transform: rotate(360deg);} } @keyframes spin { - from { transform: scale(1) rotate(0deg);} - to { transform: scale(1) rotate(360deg);} + from { transform: scale(1) rotate(0deg);} + to { transform: scale(1) rotate(360deg);} } .glyphicon-refresh-animate { - @include animation(spin .7s infinite linear); + @include animation(spin .7s infinite linear); } .black-bg { - background-color: black !important; + background-color: black !important; } .white-bg { - background-color: white !important; + background-color: white !important; } .alert { - padding: 8px 12px; - @include border-radius(2px); + padding: 8px 12px; + @include border-radius(2px); } .emoji { - width: 1.5em; - height: 1.5em; - display: inline-block; - margin-bottom: 0.25em; - background-size: contain; + width: 1.5em; + height: 1.5em; + display: inline-block; + margin-bottom: 0.25em; + background-size: contain; } diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss index 4a4de5c3b..93cb04198 100644 --- a/web/sass-files/sass/partials/_headers.scss +++ b/web/sass-files/sass/partials/_headers.scss @@ -298,8 +298,13 @@ height: 30px; width: 24px; line-height: 26px; - margin-right: 10px; + margin-right: 9px; font-size: 22px; + .channel__wrap.move--left & { + position: absolute; + right: -400px; + top: 14px; + } > a { color: inherit; text-decoration: none; diff --git a/web/sass-files/sass/partials/_markdown.scss b/web/sass-files/sass/partials/_markdown.scss index 7aa29d95d..14e12ecd2 100644 --- a/web/sass-files/sass/partials/_markdown.scss +++ b/web/sass-files/sass/partials/_markdown.scss @@ -20,6 +20,14 @@ .post-body--code { position: relative; + + pre { + margin-bottom: 0; + word-break: normal; + overflow: auto; + word-wrap: normal; + } + } .post-body--code__language { @@ -43,7 +51,7 @@ @include opacity(0.2); } code { - white-space: pre-line; + white-space: pre; } } .markdown__table { diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index 77b66a1a8..a018315e3 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -283,8 +283,8 @@ body.ios { width: 40px; height: 40px; position: absolute; - bottom: 50px; - right: 5px; + bottom: 0; + left: 10px; z-index: 50; @include opacity(0); @include single-transition(all, 0.3s); @@ -647,10 +647,26 @@ body.ios { } .post__remove { + font-family: 'Open Sans', sans-serif; + position: relative; display: inline-block; + vertical-align: top; + right: 15px; + top: -5px; + font-size: 20px; + width: 20px; + height: 20px; + line-height: 20px; + font-weight: 600; visibility: hidden; - margin-right: 5px; - top: -1px; + color: inherit; + @include opacity(0.5); + text-decoration: none; + + &:hover { + @include opacity(0.8); + } + } .post__body { @@ -663,6 +679,10 @@ body.ios { margin: 0 0 0.4em; } + p + p { + margin-top: 1.4em; + } + img { max-height: 400px; } diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index 832481cc5..09d498a69 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -60,6 +60,11 @@ &.move--left { @include translateX(0); + + .search-bar__container { + padding-right: 8px; + } + } } @@ -68,6 +73,13 @@ &.move--left { margin-right: 0; + + .channel-header__links { + position: relative; + right: auto; + top: auto; + } + } } diff --git a/web/sass-files/sass/partials/_search.scss b/web/sass-files/sass/partials/_search.scss index 693c59a31..aa398e916 100644 --- a/web/sass-files/sass/partials/_search.scss +++ b/web/sass-files/sass/partials/_search.scss @@ -1,11 +1,14 @@ @charset "UTF-8"; #channel-header .search-bar__container { - padding: 0 8px 0 3px; + padding: 0 9px 0 3px; } .search-bar__container { padding: 12px 8px 0 0; @include flex(0 0 56px); + .sidebar--right.move--left & { + padding-right: 42px; + } } .search__clear { display: none; diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json index 53dfd2d4e..5b03f0130 100644 --- a/web/static/i18n/en.json +++ b/web/static/i18n/en.json @@ -365,6 +365,8 @@ "admin.service.developerDesc": "(Developer Option) When true, extra information around errors will be displayed in the UI.", "admin.service.securityTitle": "Enable Security Alerts: ", "admin.service.securityDesc": "When true, System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.", + "admin.service.insecureTlsTitle": "Enable Insecure Outgoing Connections: ", + "admin.service.insecureTlsDesc": "When true, any outgoing HTTPS requests will accept unverified, self-signed certificates. For example, outgoing webhooks to a server with a self-signed TLS certificate, using any domain, will be allowed. Note that this makes these connections susceptible to man-in-the-middle attacks.", "admin.service.webSessionDays": "Session Length for Web in Days:", "admin.service.webSessionDaysDesc": "The web session will expire after the number of days specified and will require a user to login again.", "admin.service.mobileSessionDays": "Session Length for Mobile Device in Days:", @@ -1087,7 +1089,6 @@ "user.settings.cmds.username_desc": "The username to use when overriding the post.", "user.settings.cmds.icon_url_desc": "URL to an icon", "user.settings.cmds.trigger_desc": "Word to trigger on", - "user.settings.cmds.auto_complete_desc_desc": "A short description of what this commands does", "user.settings.cmds.auto_complete_help": "Show this command in autocomplete list.", "user.settings.cmds.auto_complete_hint_desc": "List parameters to be passed to the command.", "user.settings.cmds.request_type_desc": "Command request type issued to the callback URL.", diff --git a/web/static/i18n/es.json b/web/static/i18n/es.json index 0e5c7cfae..25515ca86 100644 --- a/web/static/i18n/es.json +++ b/web/static/i18n/es.json @@ -1068,7 +1068,6 @@ "user.settings.cmds.auto_complete.yes": "sí", "user.settings.cmds.auto_complete_desc": "Descripción del Auto Completado: ", "user.settings.cmds.auto_complete_desc.placeholder": "Una pequeña descripción de que hace el comando.", - "user.settings.cmds.auto_complete_desc_desc": "Una pequeña descripción de que hace el comando", "user.settings.cmds.auto_complete_help": "Mostrar este comando en la lista de auto completado.", "user.settings.cmds.auto_complete_hint": "Pista de auto completado: ", "user.settings.cmds.auto_complete_hint.placeholder": "[código postal]", diff --git a/web/templates/head.html b/web/templates/head.html index b1ec905b5..da65e1779 100644 --- a/web/templates/head.html +++ b/web/templates/head.html @@ -122,6 +122,12 @@ } }); }); + + $(window).on('beforeunload', function(){ + if (window.SocketStore) { + SocketStore.close(); + } + }); </script> <script> |