diff options
Diffstat (limited to 'webapp')
-rw-r--r-- | webapp/actions/websocket_actions.jsx | 2 | ||||
-rw-r--r-- | webapp/components/admin_console/logs.jsx | 11 | ||||
-rw-r--r-- | webapp/components/channel_header.jsx | 41 | ||||
-rw-r--r-- | webapp/components/signup/components/signup_email.jsx | 6 | ||||
-rw-r--r-- | webapp/components/signup/components/signup_ldap.jsx | 6 | ||||
-rw-r--r-- | webapp/components/user_profile.jsx | 43 | ||||
-rw-r--r-- | webapp/components/user_settings/user_settings_security.jsx | 23 | ||||
-rw-r--r-- | webapp/i18n/en.json | 9 | ||||
-rw-r--r-- | webapp/sass/components/_tooltip.scss | 6 | ||||
-rw-r--r-- | webapp/sass/components/_webrtc.scss | 11 | ||||
-rw-r--r-- | webapp/sass/responsive/_mobile.scss | 1 | ||||
-rw-r--r-- | webapp/sass/utils/_mixins.scss | 61 | ||||
-rw-r--r-- | webapp/stores/browser_store.jsx | 6 | ||||
-rw-r--r-- | webapp/stores/post_store.jsx | 6 | ||||
-rw-r--r-- | webapp/utils/channel_intro_messages.jsx | 29 | ||||
-rw-r--r-- | webapp/utils/constants.jsx | 1 |
16 files changed, 149 insertions, 113 deletions
diff --git a/webapp/actions/websocket_actions.jsx b/webapp/actions/websocket_actions.jsx index 36c6cbdc9..8632f4135 100644 --- a/webapp/actions/websocket_actions.jsx +++ b/webapp/actions/websocket_actions.jsx @@ -180,7 +180,7 @@ function handleNewPostEvent(msg) { function handlePostEditEvent(msg) { // Store post const post = JSON.parse(msg.data.post); - PostStore.storePost(post); + PostStore.storePost(post, false); PostStore.emitChange(); // Update channel state diff --git a/webapp/components/admin_console/logs.jsx b/webapp/components/admin_console/logs.jsx index ad0277b7f..8dc0c1e2e 100644 --- a/webapp/components/admin_console/logs.jsx +++ b/webapp/components/admin_console/logs.jsx @@ -26,6 +26,12 @@ export default class Logs extends React.Component { AsyncClient.getLogs(); } + componentDidUpdate() { + // Scroll Down to get the latest logs + var node = this.refs.logPanel; + node.scrollTop = node.scrollHeight; + } + componentWillUnmount() { AdminStore.removeLogChangeListener(this.onLogListenerChange); } @@ -93,7 +99,10 @@ export default class Logs extends React.Component { defaultMessage='Reload' /> </button> - <div className='log__panel'> + <div + ref='logPanel' + className='log__panel' + > {content} </div> </div> diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx index 1ce7b4a0e..a281e8e1b 100644 --- a/webapp/components/channel_header.jsx +++ b/webapp/components/channel_header.jsx @@ -300,6 +300,13 @@ export default class ChannelHeader extends React.Component { if (isOffline || busy) { circleClass = 'offline'; + webrtcMessage = ( + <FormattedMessage + id='channel_header.webrtc.offline' + defaultMessage='The user is offline' + /> + ); + if (busy) { webrtcMessage = ( <FormattedMessage @@ -317,6 +324,10 @@ export default class ChannelHeader extends React.Component { ); } + const webrtcTooltip = ( + <Tooltip id='webrtcTooltip'>{webrtcMessage}</Tooltip> + ); + webrtc = ( <div className='webrtc__header'> <a @@ -324,28 +335,18 @@ export default class ChannelHeader extends React.Component { onClick={() => this.initWebrtc(contact.id, !isOffline)} disabled={isOffline} > - <svg - id='webrtc-btn' - className='webrtc__button' - xmlns='http://www.w3.org/2000/svg' + <OverlayTrigger + delayShow={Constants.WEBRTC_TIME_DELAY} + placement='bottom' + overlay={webrtcTooltip} > - <circle - className={circleClass} - cx='16' - cy='16' - r='18' + <div + id='webrtc-btn' + className={'webrtc__button ' + circleClass} > - <title> - {webrtcMessage} - </title> - </circle> - <path - className='off' - transform='scale(0.4), translate(17,16)' - d='M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zm-4 24l-8-6.4V32H12V16h16v6.4l8-6.4v16z' - fill='white' - /> - </svg> + <span dangerouslySetInnerHTML={{__html: Constants.VIDEO_ICON}}/> + </div> + </OverlayTrigger> </a> </div> ); diff --git a/webapp/components/signup/components/signup_email.jsx b/webapp/components/signup/components/signup_email.jsx index 2d4b3f277..b67179604 100644 --- a/webapp/components/signup/components/signup_email.jsx +++ b/webapp/components/signup/components/signup_email.jsx @@ -429,9 +429,11 @@ export default class SignupEmail extends React.Component { <p> <FormattedHTMLMessage id='create_team.agreement' - defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}." + defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='{TermsOfServiceLink}'>Terms of Service</a> and <a href='{PrivacyPolicyLink}'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}." values={{ - siteName: global.window.mm_config.SiteName + siteName: global.window.mm_config.SiteName, + TermsOfServiceLink: global.window.mm_config.TermsOfServiceLink, + PrivacyPolicyLink: global.window.mm_config.PrivacyPolicyLink }} /> </p> diff --git a/webapp/components/signup/components/signup_ldap.jsx b/webapp/components/signup/components/signup_ldap.jsx index 8c1b1bafb..bc8c073ad 100644 --- a/webapp/components/signup/components/signup_ldap.jsx +++ b/webapp/components/signup/components/signup_ldap.jsx @@ -179,9 +179,11 @@ export default class SignupLdap extends React.Component { <p> <FormattedHTMLMessage id='create_team.agreement' - defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}." + defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='{TermsOfServiceLink}'>Terms of Service</a> and <a href='{PrivacyPolicyLink}'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}." values={{ - siteName: global.window.mm_config.SiteName + siteName: global.window.mm_config.SiteName, + TermsOfServiceLink: global.window.mm_config.TermsOfServiceLink, + PrivacyPolicyLink: global.window.mm_config.PrivacyPolicyLink }} /> </p> diff --git a/webapp/components/user_profile.jsx b/webapp/components/user_profile.jsx index e69d917a3..21dbf9699 100644 --- a/webapp/components/user_profile.jsx +++ b/webapp/components/user_profile.jsx @@ -11,7 +11,7 @@ import Constants from 'utils/constants.jsx'; const UserStatuses = Constants.UserStatuses; const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES; -import {Popover, OverlayTrigger} from 'react-bootstrap'; +import {Popover, OverlayTrigger, Tooltip} from 'react-bootstrap'; import {FormattedMessage} from 'react-intl'; import React from 'react'; @@ -111,8 +111,19 @@ export default class UserProfile extends React.Component { defaultMessage='New call unavailable until your existing call ends' /> ); + } else { + webrtcMessage = ( + <FormattedMessage + id='user_profile.webrtc.offline' + defaultMessage='The user is offline' + /> + ); } + const webrtcTooltip = ( + <Tooltip id='webrtcTooltip'>{webrtcMessage}</Tooltip> + ); + webrtc = ( <div className='webrtc__user-profile' @@ -123,28 +134,18 @@ export default class UserProfile extends React.Component { onClick={() => this.initWebrtc()} disabled={!isOnline} > - <svg - id='webrtc-btn' - className='webrtc__button' - xmlns='http://www.w3.org/2000/svg' + <OverlayTrigger + delayShow={Constants.WEBRTC_TIME_DELAY} + placement='top' + overlay={webrtcTooltip} > - <circle - className={circleClass} - cx='16' - cy='16' - r='18' + <div + id='webrtc-btn' + className={'webrtc__button ' + circleClass} > - <title> - {webrtcMessage} - </title> - </circle> - <path - className='off' - transform='scale(0.4), translate(17,16)' - d='M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zm-4 24l-8-6.4V32H12V16h16v6.4l8-6.4v16z' - fill='white' - /> - </svg> + <span dangerouslySetInnerHTML={{__html: Constants.VIDEO_ICON}}/> + </div> + </OverlayTrigger> </a> </div> ); diff --git a/webapp/components/user_settings/user_settings_security.jsx b/webapp/components/user_settings/user_settings_security.jsx index 0cee3dfca..1f049c4bd 100644 --- a/webapp/components/user_settings/user_settings_security.jsx +++ b/webapp/components/user_settings/user_settings_security.jsx @@ -473,6 +473,20 @@ export default class SecurityTab extends React.Component { </div> </div> ); + } else if (this.props.user.auth_service === Constants.SAML_SERVICE) { + inputs.push( + <div + key='oauthEmailInfo' + className='form-group' + > + <div className='setting-list__hint'> + <FormattedMessage + id='user.settings.security.passwordSamlCantUpdate' + defaultMessage='This field is handled through your login provider. If you want to change it, you need to do so through your login provider.' + /> + </div> + </div> + ); } updateSectionStatus = function resetSection(e) { @@ -533,7 +547,7 @@ export default class SecurityTab extends React.Component { describe = ( <FormattedMessage id='user.settings.security.loginGitlab' - defaultMessage='Login done through Gitlab' + defaultMessage='Login done through GitLab' /> ); } else if (this.props.user.auth_service === Constants.LDAP_SERVICE) { @@ -543,6 +557,13 @@ export default class SecurityTab extends React.Component { defaultMessage='Login done through AD/LDAP' /> ); + } else if (this.props.user.auth_service === Constants.SAML_SERVICE) { + describe = ( + <FormattedMessage + id='user.settings.security.loginSaml' + defaultMessage='Login done through SAML' + /> + ); } updateSectionStatus = function updateSection() { diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index 220d4e2f9..532b44c23 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -998,6 +998,7 @@ "channel_header.viewMembers": "View Members", "channel_header.webrtc.call": "Start Video Call", "channel_header.webrtc.unavailable": "New call unavailable until your existing call ends", + "channel_header.webrtc.offline": "The user is offline", "channel_info.about": "About", "channel_info.close": "Close", "channel_info.header": "Header:", @@ -1108,7 +1109,7 @@ "create_post.shortcutsNotSupported": "Keyboard shortcuts are not supported on your device.", "create_post.tutorialTip": "<h4>Sending Messages</h4><p>Type here to write a message and press <strong>ENTER</strong> to post it.</p><p>Click the <strong>Attachment</strong> button to upload an image or a file.</p>", "create_post.write": "Write a message...", - "create_team.agreement": "By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}.", + "create_team.agreement": "By proceeding to create your account and use {siteName}, you agree to our <a href={TermsOfServiceLink}>Terms of Service</a> and <a href={PrivacyPolicyLink}>Privacy Policy</a>. If you do not agree, you cannot use {siteName}.", "create_team.display_name.back": "Back to previous step", "create_team.display_name.charLength": "Name must be 2 or more characters up to a maximum of 15", "create_team.display_name.nameHelp": "Name your team in any language. Your team name shows in menus and headings.", @@ -1410,7 +1411,8 @@ "intro_messages.anyMember": " Any member can join and read this channel.", "intro_messages.beginning": "Beginning of {name}", "intro_messages.channel": "channel", - "intro_messages.creator": "This is the start of the <strong>{name}</strong> {type}, created by <strong>{creator}</strong> on <strong>{date}</strong>", + "intro_messages.creator": "This is the start of the {name} {type}, created by {creator} on {date}.", + "intro_messages.purpose": " This {type}'s purpose is: {purpose}.", "intro_messages.default": "<h4 class='channel-intro__title'>Beginning of {display_name}</h4><p class='channel-intro__content'><strong>Welcome to {display_name}!</strong><br/><br/>This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.</p>", "intro_messages.group": "private group", "intro_messages.invite": "Invite others to this {type}", @@ -2012,6 +2014,7 @@ "user.settings.security.ldap": "AD/LDAP", "user.settings.security.loginGitlab": "Login done through GitLab", "user.settings.security.loginLdap": "Login done through AD/LDAP", + "user.settings.security.loginSaml": "Login done through SAML", "user.settings.security.logoutActiveSessions": "View and Logout of Active Sessions", "user.settings.security.method": "Sign-in Method", "user.settings.security.newPassword": "New Password", @@ -2040,6 +2043,7 @@ "user.settings.security.passwordErrorUppercaseSymbol": "Your password must contain at least {min} characters made up of at least one uppercase letter and at least one symbol (e.g. \"~!@#$%^&*()\").", "user.settings.security.passwordGitlabCantUpdate": "Login occurs through GitLab. Password cannot be updated.", "user.settings.security.passwordLdapCantUpdate": "Login occurs through AD/LDAP. Password cannot be updated.", + "user.settings.security.passwordSamlCantUpdate": "This field is handled through your login provider. If you want to change it, you need to do so through your login provider.", "user.settings.security.passwordMatchError": "The new passwords you entered do not match.", "user.settings.security.passwordMinLength": "Invalid minimum length, cannot show preview.", "user.settings.security.retypePassword": "Retype New Password", @@ -2055,6 +2059,7 @@ "user_list.notFound": "No users found", "user_profile.webrtc.call": "Start Video Call", "user_profile.webrtc.unavailable": "New call unavailable until your existing call ends", + "user_profile.webrtc.offline": "The user is offline", "view_image.loading": "Loading ", "view_image_popover.download": "Download", "view_image_popover.file": "File {count} of {total}", diff --git a/webapp/sass/components/_tooltip.scss b/webapp/sass/components/_tooltip.scss index 5e71e3a7b..0049fe1b8 100644 --- a/webapp/sass/components/_tooltip.scss +++ b/webapp/sass/components/_tooltip.scss @@ -8,3 +8,9 @@ word-break: break-word; } } + +#webrtcTooltip { + .tooltip-inner { + word-break: normal; + } +} diff --git a/webapp/sass/components/_webrtc.scss b/webapp/sass/components/_webrtc.scss index b025ab00c..c9deada26 100644 --- a/webapp/sass/components/_webrtc.scss +++ b/webapp/sass/components/_webrtc.scss @@ -2,10 +2,14 @@ .webrtc__user-profile { @include webrtc-button; + position: absolute; + right: 7px; text-align: center; + top: 5px; #webrtc-btn { - display: inherit; + height: 23px; + width: 23px; } } @@ -15,6 +19,11 @@ margin-right: 10px; position: relative; top: 13px; + + svg { + position: relative; + width: 20px; + } } .webrtc__notification--rhs { diff --git a/webapp/sass/responsive/_mobile.scss b/webapp/sass/responsive/_mobile.scss index 708fa4fca..00f89ff15 100644 --- a/webapp/sass/responsive/_mobile.scss +++ b/webapp/sass/responsive/_mobile.scss @@ -1337,7 +1337,6 @@ .modal-body { padding-bottom: 35px; } - } .settings-modal { diff --git a/webapp/sass/utils/_mixins.scss b/webapp/sass/utils/_mixins.scss index e252086ae..6a041d6ec 100644 --- a/webapp/sass/utils/_mixins.scss +++ b/webapp/sass/utils/_mixins.scss @@ -32,68 +32,27 @@ @mixin webrtc-button { .webrtc__button { @include border-radius(50px); + background: $button--ready; display: block; - height: 32px; - width: 32px; + height: 33px; + text-align: center; + width: 33px; &.on, &:hover { background: darken($button--ready, 5%); } - &:hover circle { - fill: darken($button--ready, 5%); - } - - circle { - fill: $button--ready; - - &.offline { - fill: $video-circle-offline; - } - } - - path { - .on { - display: none; - } - - .off { - display: block; - } - } - - &.on { - path { - .on { - display: block; - } - - .off { - display: none; - } - } + &.offline { + background: $video-circle-offline; - circle { - fill-opacity: 0; + &:hover { + background: $video-circle-offline; } } - } - a { - &[disabled] { - .webrtc__button { - &:hover { - background: none; - box-shadow: none; - } - - &:hover { - circle { - fill: $video-circle-offline; - } - } - } + svg { + fill: $white; } } } diff --git a/webapp/stores/browser_store.jsx b/webapp/stores/browser_store.jsx index 99aebc466..fcd177662 100644 --- a/webapp/stores/browser_store.jsx +++ b/webapp/stores/browser_store.jsx @@ -203,7 +203,11 @@ class BrowserStoreClass { } hasSeenLandingPage() { - return JSON.parse(sessionStorage.getItem('__landingPageSeen__')); + if (this.isLocalStorageSupported()) { + return JSON.parse(sessionStorage.getItem('__landingPageSeen__')); + } + + return true; } setLandingPageSeen(landingPageSeen) { diff --git a/webapp/stores/post_store.jsx b/webapp/stores/post_store.jsx index a4e49fc98..fbe5cd457 100644 --- a/webapp/stores/post_store.jsx +++ b/webapp/stores/post_store.jsx @@ -245,7 +245,7 @@ class PostStoreClass extends EventEmitter { this.postsInfo[id].postList = combinedPosts; } - storePost(post) { + storePost(post, isNewPost = false) { const postList = makePostListNonNull(this.getAllPosts(post.channel_id)); if (post.pending_post_id !== '') { @@ -255,7 +255,7 @@ class PostStoreClass extends EventEmitter { post.pending_post_id = ''; postList.posts[post.id] = post; - if (postList.order.indexOf(post.id) === -1) { + if (isNewPost && postList.order.indexOf(post.id) === -1) { postList.order.unshift(post.id); } @@ -629,7 +629,7 @@ PostStore.dispatchToken = AppDispatcher.register((payload) => { PostStore.emitChange(); break; case ActionTypes.RECEIVED_POST: - PostStore.storePost(action.post); + PostStore.storePost(action.post, true); PostStore.emitChange(); break; case ActionTypes.RECEIVED_EDIT_POST: diff --git a/webapp/utils/channel_intro_messages.jsx b/webapp/utils/channel_intro_messages.jsx index 899e4e5a4..36d8cdb2a 100644 --- a/webapp/utils/channel_intro_messages.jsx +++ b/webapp/utils/channel_intro_messages.jsx @@ -147,10 +147,10 @@ export function createDefaultIntroMessage(channel, centeredIntro) { export function createStandardIntroMessage(channel, centeredIntro) { var uiName = channel.display_name; - var creatorName = ''; - + var creatorName = Utils.displayUsername(channel.creator_id); var uiType; var memberMessage; + if (channel.type === 'P') { uiType = ( <FormattedMessage @@ -204,14 +204,30 @@ export function createStandardIntroMessage(channel, centeredIntro) { } else { createMessage = ( <span> - <FormattedHTMLMessage + <FormattedMessage id='intro_messages.creator' - defaultMessage='This is the start of the <strong>{name}</strong> {type}, created by <strong>{creator}</strong> on <strong>{date}</strong>' + defaultMessage='This is the start of the {name} {type}, created by {creator} on {date}.' values={{ name: (uiName), type: (uiType), - date, - creator: creatorName + creator: (creatorName), + date + }} + /> + </span> + ); + } + + var purposeMessage = ''; + if (channel.purpose && channel.purpose !== '') { + purposeMessage = ( + <span> + <FormattedMessage + id='intro_messages.purpose' + defaultMessage=" This {type}'s purpose is: {purpose}" + values={{ + purpose: channel.purpose, + type: (uiType) }} /> </span> @@ -232,6 +248,7 @@ export function createStandardIntroMessage(channel, centeredIntro) { <p className='channel-intro__content'> {createMessage} {memberMessage} + {purposeMessage} <br/> </p> {createInviteChannelMemberButton(channel, uiType)} diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx index 0da17e4b9..d8965516e 100644 --- a/webapp/utils/constants.jsx +++ b/webapp/utils/constants.jsx @@ -374,6 +374,7 @@ export const Constants = { COMMENT_ICON: "<svg version='1.1' id='Layer_2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='15px' height='15px' viewBox='1 1.5 15 15' enable-background='new 1 1.5 15 15' xml:space='preserve'> <g> <g> <path fill='#211B1B' d='M14,1.5H3c-1.104,0-2,0.896-2,2v8c0,1.104,0.896,2,2,2h1.628l1.884,3l1.866-3H14c1.104,0,2-0.896,2-2v-8 C16,2.396,15.104,1.5,14,1.5z M15,11.5c0,0.553-0.447,1-1,1H8l-1.493,2l-1.504-1.991L5,12.5H3c-0.552,0-1-0.447-1-1v-8 c0-0.552,0.448-1,1-1h11c0.553,0,1,0.448,1,1V11.5z'/> </g> </g> </svg>", REPLY_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'viewBox='-158 242 18 18' style='enable-background:new -158 242 18 18;' xml:space='preserve'> <path d='M-142.2,252.6c-2-3-4.8-4.7-8.3-4.8v-3.3c0-0.2-0.1-0.3-0.2-0.3s-0.3,0-0.4,0.1l-6.9,6.2c-0.1,0.1-0.1,0.2-0.1,0.3 c0,0.1,0,0.2,0.1,0.3l6.9,6.4c0.1,0.1,0.3,0.1,0.4,0.1c0.1-0.1,0.2-0.2,0.2-0.4v-3.8c4.2,0,7.4,0.4,9.6,4.4c0.1,0.1,0.2,0.2,0.3,0.2 c0,0,0.1,0,0.1,0c0.2-0.1,0.3-0.3,0.2-0.4C-140.2,257.3-140.6,255-142.2,252.6z M-150.8,252.5c-0.2,0-0.4,0.2-0.4,0.4v3.3l-6-5.5 l6-5.3v2.8c0,0.2,0.2,0.4,0.4,0.4c3.3,0,6,1.5,8,4.5c0.5,0.8,0.9,1.6,1.2,2.3C-144,252.8-147.1,252.5-150.8,252.5z'/> </svg>", SCROLL_BOTTOM_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'viewBox='-239 239 21 23' style='enable-background:new -239 239 21 23;' xml:space='preserve'> <path d='M-239,241.4l2.4-2.4l8.1,8.2l8.1-8.2l2.4,2.4l-10.5,10.6L-239,241.4z M-228.5,257.2l8.1-8.2l2.4,2.4l-10.5,10.6l-10.5-10.6 l2.4-2.4L-228.5,257.2z'/> </svg>", + VIDEO_ICON: "<svg width='55%'height='100%'viewBox='0 0 13 8'> <g transform='matrix(1,0,0,1,-507,-146)'> <g transform='matrix(0.0133892,0,0,0.014499,500.635,142.838)'> <path d='M1158,547.286L1158,644.276C1158,684.245 1125.55,716.694 1085.58,716.694L579.341,716.694C539.372,716.694 506.922,684.245 506.922,644.276L506.922,306.322C506.922,266.353 539.371,233.904 579.341,233.903L1085.58,233.903C1125.55,233.904 1158,266.353 1158,306.322L1158,402.939L1359.75,253.14C1365.83,248.362 1373.43,245.973 1382.56,245.973C1386.61,245.973 1390.83,246.602 1395.22,247.859C1408.4,252.134 1414.99,259.552 1414.99,270.113L1414.99,680.485C1414.99,691.046 1408.4,698.464 1395.22,702.739C1390.83,703.996 1386.61,704.624 1382.56,704.624C1373.43,704.624 1365.83,702.236 1359.75,697.458L1158,547.286Z'/> </g> </g> </svg>", UPDATE_TYPING_MS: 5000, THEMES: { default: { |