summaryrefslogtreecommitdiffstats
path: root/web/react/utils
diff options
context:
space:
mode:
Diffstat (limited to 'web/react/utils')
-rw-r--r--web/react/utils/async_client.jsx52
-rw-r--r--web/react/utils/client.jsx46
-rw-r--r--web/react/utils/config.js48
-rw-r--r--web/react/utils/constants.jsx4
-rw-r--r--web/react/utils/text_formatting.jsx268
-rw-r--r--web/react/utils/utils.jsx221
6 files changed, 351 insertions, 288 deletions
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx
index 6ccef0506..3e23e5c33 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -319,6 +319,32 @@ export function getAudits() {
);
}
+export function getLogs() {
+ if (isCallInProgress('getLogs')) {
+ return;
+ }
+
+ callTracker.getLogs = utils.getTimestamp();
+ client.getLogs(
+ (data, textStatus, xhr) => {
+ callTracker.getLogs = 0;
+
+ if (xhr.status === 304 || !data) {
+ return;
+ }
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_LOGS,
+ logs: data
+ });
+ },
+ (err) => {
+ callTracker.getLogs = 0;
+ dispatchError(err, 'getLogs');
+ }
+ );
+}
+
export function findTeams(email) {
if (isCallInProgress('findTeams_' + email)) {
return;
@@ -556,28 +582,4 @@ export function getMyTeam() {
dispatchError(err, 'getMyTeam');
}
);
-}
-
-export function getConfig() {
- if (isCallInProgress('getConfig')) {
- return;
- }
-
- callTracker.getConfig = utils.getTimestamp();
- client.getConfig(
- function getConfigSuccess(data, textStatus, xhr) {
- callTracker.getConfig = 0;
-
- if (data && xhr.status !== 304) {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_CONFIG,
- settings: data
- });
- }
- },
- function getConfigFailure(err) {
- callTracker.getConfig = 0;
- dispatchError(err, 'getConfig');
- }
- );
-}
+} \ No newline at end of file
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 51fd16474..ba3042d78 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -14,8 +14,6 @@ export function trackPage() {
}
function handleError(methodName, xhr, status, err) {
- var LTracker = global.window.LTracker || [];
-
var e = null;
try {
e = JSON.parse(xhr.responseText);
@@ -39,7 +37,6 @@ function handleError(methodName, xhr, status, err) {
console.error(msg); //eslint-disable-line no-console
console.error(e); //eslint-disable-line no-console
- LTracker.push(msg);
track('api', 'api_weberror', methodName, 'message', msg);
@@ -294,6 +291,20 @@ export function getAudits(userId, success, error) {
});
}
+export function getLogs(success, error) {
+ $.ajax({
+ url: '/api/v1/admin/logs',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'GET',
+ success: success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('getLogs', xhr, status, err);
+ error(e);
+ }
+ });
+}
+
export function getMeSynchronous(success, error) {
var currentUser = null;
$.ajax({
@@ -977,16 +988,35 @@ export function updateValetFeature(data, success, error) {
track('api', 'api_teams_update_valet_feature');
}
-export function getConfig(success, error) {
+export function registerOAuthApp(app, success, error) {
$.ajax({
- url: '/api/v1/config/get_all',
+ url: '/api/v1/oauth/register',
dataType: 'json',
+ contentType: 'application/json',
+ type: 'POST',
+ data: JSON.stringify(app),
+ success: success,
+ error: (xhr, status, err) => {
+ const e = handleError('registerApp', xhr, status, err);
+ error(e);
+ }
+ });
+
+ module.exports.track('api', 'api_apps_register');
+}
+
+export function allowOAuth2(responseType, clientId, redirectUri, state, scope, success, error) {
+ $.ajax({
+ url: '/api/v1/oauth/allow?response_type=' + responseType + '&client_id=' + clientId + '&redirect_uri=' + redirectUri + '&scope=' + scope + '&state=' + state,
+ dataType: 'json',
+ contentType: 'application/json',
type: 'GET',
- ifModified: true,
success: success,
- error: function onError(xhr, status, err) {
- var e = handleError('getConfig', xhr, status, err);
+ error: (xhr, status, err) => {
+ const e = handleError('allowOAuth2', xhr, status, err);
error(e);
}
});
+
+ module.exports.track('api', 'api_users_allow_oauth2');
}
diff --git a/web/react/utils/config.js b/web/react/utils/config.js
deleted file mode 100644
index c7d1aa2bc..000000000
--- a/web/react/utils/config.js
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-export var config = {
-
- // Loggly configs
- LogglyWriteKey: '',
- LogglyConsoleErrors: true,
-
- // Segment configs
- SegmentWriteKey: '',
-
- // Feature switches
- AllowPublicLink: true,
- AllowInviteNames: true,
- RequireInviteNames: false,
- AllowSignupDomainsWizard: false,
-
- // Google Developer Key (for Youtube API links)
- // Leave blank to disable
- GoogleDeveloperKey: '',
-
- // Privacy switches
- ShowEmail: true,
-
- // Links
- TermsLink: '/static/help/configure_links.html',
- PrivacyLink: '/static/help/configure_links.html',
- AboutLink: '/static/help/configure_links.html',
- HelpLink: '/static/help/configure_links.html',
- ReportProblemLink: '/static/help/configure_links.html',
- HomeLink: '',
-
- // Toggle whether or not users are shown a message about agreeing to the Terms of Service during the signup process
- ShowTermsDuringSignup: false,
-
- ThemeColors: ['#2389d7', '#008a17', '#dc4fad', '#ac193d', '#0072c6', '#d24726', '#ff8f32', '#82ba00', '#03b3b2', '#008299', '#4617b4', '#8c0095', '#004b8b', '#004b8b', '#570000', '#380000', '#585858', '#000000']
-};
-
-// Flavor strings
-export var strings = {
- Team: 'team',
- TeamPlural: 'teams',
- Company: 'company',
- CompanyPlural: 'companies'
-};
-
-global.window.config = config;
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 7ead079d7..03e4635b5 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -34,7 +34,9 @@ module.exports = {
CLICK_TEAM: null,
RECIEVED_TEAM: null,
- RECIEVED_CONFIG: null
+ RECIEVED_CONFIG: null,
+
+ RECIEVED_LOGS: null
}),
PayloadSources: keyMirror({
diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx
new file mode 100644
index 000000000..2025e16da
--- /dev/null
+++ b/web/react/utils/text_formatting.jsx
@@ -0,0 +1,268 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const Autolinker = require('autolinker');
+const Constants = require('./constants.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const Utils = require('./utils.jsx');
+
+// Performs formatting of user posts including highlighting mentions and search terms and converting urls, hashtags, and
+// @mentions to links by taking a user's message and returning a string of formatted html. Also takes a number of options
+// as part of the second parameter:
+// - searchTerm - If specified, this word is highlighted in the resulting html. Defaults to nothing.
+// - mentionHighlight - Specifies whether or not to highlight mentions of the current user. Defaults to true.
+// - singleline - Specifies whether or not to remove newlines. Defaults to false.
+export function formatText(text, options = {}) {
+ let output = sanitizeHtml(text);
+ const tokens = new Map();
+
+ // replace important words and phrases with tokens
+ output = autolinkUrls(output, tokens);
+ output = autolinkAtMentions(output, tokens);
+ output = autolinkHashtags(output, tokens);
+
+ if (options.searchTerm) {
+ output = highlightSearchTerm(output, tokens, options.searchTerm);
+ }
+
+ if (!('mentionHighlight' in options) || options.mentionHighlight) {
+ output = highlightCurrentMentions(output, tokens);
+ }
+
+ // reinsert tokens with formatted versions of the important words and phrases
+ output = replaceTokens(output, tokens);
+
+ // replace newlines with html line breaks
+ output = replaceNewlines(output, options.singleline);
+
+ return output;
+}
+
+export function sanitizeHtml(text) {
+ let output = text;
+
+ // normal string.replace only does a single occurrance so use a regex instead
+ output = output.replace(/&/g, '&');
+ output = output.replace(/</g, '&lt;');
+ output = output.replace(/>/g, '&gt;');
+ output = output.replace(/'/g, '&apos;');
+ output = output.replace(/"/g, '&quot;');
+
+ return output;
+}
+
+function autolinkUrls(text, tokens) {
+ function replaceUrlWithToken(autolinker, match) {
+ const linkText = match.getMatchedText();
+ let url = linkText;
+
+ if (!url.lastIndexOf('http', 0) === 0) {
+ url = `http://${linkText}`;
+ }
+
+ const index = tokens.size;
+ const alias = `__MM_LINK${index}__`;
+
+ tokens.set(alias, {
+ value: `<a class='theme' target='_blank' href='${url}'>${linkText}</a>`,
+ originalText: linkText
+ });
+
+ return alias;
+ }
+
+ // we can't just use a static autolinker because we need to set replaceFn
+ const autolinker = new Autolinker({
+ urls: true,
+ email: true,
+ phone: false,
+ twitter: false,
+ hashtag: false,
+ replaceFn: replaceUrlWithToken
+ });
+
+ return autolinker.link(text);
+}
+
+function autolinkAtMentions(text, tokens) {
+ let output = text;
+
+ function replaceAtMentionWithToken(fullMatch, prefix, mention, username) {
+ const usernameLower = username.toLowerCase();
+ if (Constants.SPECIAL_MENTIONS.indexOf(usernameLower) !== -1 || UserStore.getProfileByUsername(usernameLower)) {
+ const index = tokens.size;
+ const alias = `__MM_ATMENTION${index}__`;
+
+ tokens.set(alias, {
+ value: `<a class='mention-link' href='#' data-mention='${usernameLower}'>${mention}</a>`,
+ originalText: mention
+ });
+
+ return prefix + alias;
+ }
+
+ return fullMatch;
+ }
+
+ output = output.replace(/(^|\s)(@([a-z0-9.\-_]*[a-z0-9]))/gi, replaceAtMentionWithToken);
+
+ return output;
+}
+
+function highlightCurrentMentions(text, tokens) {
+ let output = text;
+
+ const mentionKeys = UserStore.getCurrentMentionKeys();
+
+ // look for any existing tokens which are self mentions and should be highlighted
+ var newTokens = new Map();
+ for (const [alias, token] of tokens) {
+ if (mentionKeys.indexOf(token.originalText) !== -1) {
+ const index = tokens.size + newTokens.size;
+ const newAlias = `__MM_SELFMENTION${index}__`;
+
+ newTokens.set(newAlias, {
+ value: `<span class='mention-highlight'>${alias}</span>`,
+ originalText: token.originalText
+ });
+
+ output = output.replace(alias, newAlias);
+ }
+ }
+
+ // the new tokens are stashed in a separate map since we can't add objects to a map during iteration
+ for (const newToken of newTokens) {
+ tokens.set(newToken[0], newToken[1]);
+ }
+
+ // look for self mentions in the text
+ function replaceCurrentMentionWithToken(fullMatch, prefix, mention) {
+ const index = tokens.size;
+ const alias = `__MM_SELFMENTION${index}__`;
+
+ tokens.set(alias, {
+ value: `<span class='mention-highlight'>${mention}</span>`,
+ originalText: mention
+ });
+
+ return prefix + alias;
+ }
+
+ for (const mention of UserStore.getCurrentMentionKeys()) {
+ output = output.replace(new RegExp(`(^|\\W)(${mention})\\b`, 'gi'), replaceCurrentMentionWithToken);
+ }
+
+ return output;
+}
+
+function autolinkHashtags(text, tokens) {
+ let output = text;
+
+ var newTokens = new Map();
+ for (const [alias, token] of tokens) {
+ if (token.originalText.lastIndexOf('#', 0) === 0) {
+ const index = tokens.size + newTokens.size;
+ const newAlias = `__MM_HASHTAG${index}__`;
+
+ newTokens.set(newAlias, {
+ value: `<a class='mention-link' href='#' data-hashtag='${token.originalText}'>${token.originalText}</a>`,
+ originalText: token.originalText
+ });
+
+ output = output.replace(alias, newAlias);
+ }
+ }
+
+ // the new tokens are stashed in a separate map since we can't add objects to a map during iteration
+ for (const newToken of newTokens) {
+ tokens.set(newToken[0], newToken[1]);
+ }
+
+ // look for hashtags in the text
+ function replaceHashtagWithToken(fullMatch, prefix, hashtag) {
+ const index = tokens.size;
+ const alias = `__MM_HASHTAG${index}__`;
+
+ tokens.set(alias, {
+ value: `<a class='mention-link' href='#' data-hashtag='${hashtag}'>${hashtag}</a>`,
+ originalText: hashtag
+ });
+
+ return prefix + alias;
+ }
+
+ return output.replace(/(^|\W)(#[a-zA-Z0-9.\-_]+)\b/g, replaceHashtagWithToken);
+}
+
+function highlightSearchTerm(text, tokens, searchTerm) {
+ let output = text;
+
+ var newTokens = new Map();
+ for (const [alias, token] of tokens) {
+ if (token.originalText === searchTerm) {
+ const index = tokens.size + newTokens.size;
+ const newAlias = `__MM_SEARCHTERM${index}__`;
+
+ newTokens.set(newAlias, {
+ value: `<span class='search-highlight'>${alias}</span>`,
+ originalText: token.originalText
+ });
+
+ output = output.replace(alias, newAlias);
+ }
+ }
+
+ // the new tokens are stashed in a separate map since we can't add objects to a map during iteration
+ for (const newToken of newTokens) {
+ tokens.set(newToken[0], newToken[1]);
+ }
+
+ function replaceSearchTermWithToken(fullMatch, prefix, word) {
+ const index = tokens.size;
+ const alias = `__MM_SEARCHTERM${index}__`;
+
+ tokens.set(alias, {
+ value: `<span class='search-highlight'>${word}</span>`,
+ originalText: word
+ });
+
+ return prefix + alias;
+ }
+
+ return output.replace(new RegExp(`(^|\\W)(${searchTerm})\\b`, 'gi'), replaceSearchTermWithToken);
+}
+
+function replaceTokens(text, tokens) {
+ let output = text;
+
+ // iterate backwards through the map so that we do replacement in the opposite order that we added tokens
+ const aliases = [...tokens.keys()];
+ for (let i = aliases.length - 1; i >= 0; i--) {
+ const alias = aliases[i];
+ const token = tokens.get(alias);
+ output = output.replace(alias, token.value);
+ }
+
+ return output;
+}
+
+function replaceNewlines(text, singleline) {
+ if (!singleline) {
+ return text.replace(/\n/g, '<br />');
+ }
+
+ return text.replace(/\n/g, ' ');
+}
+
+// A click handler that can be used with the results of TextFormatting.formatText to add default functionality
+// to clicked hashtags and @mentions.
+export function handleClick(e) {
+ const mentionAttribute = e.target.getAttributeNode('data-mention');
+ const hashtagAttribute = e.target.getAttributeNode('data-hashtag');
+
+ if (mentionAttribute) {
+ Utils.searchForTerm(mentionAttribute.value);
+ } else if (hashtagAttribute) {
+ Utils.searchForTerm(hashtagAttribute.value);
+ }
+}
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 71cd1d344..032cf4ff4 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -9,7 +9,6 @@ var ActionTypes = Constants.ActionTypes;
var AsyncClient = require('./async_client.jsx');
var client = require('./client.jsx');
var Autolinker = require('autolinker');
-import {config} from '../utils/config.js';
export function isEmail(email) {
var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
@@ -295,12 +294,12 @@ function getYoutubeEmbed(link) {
$('.post-list-holder-by-time').scrollTop($('.post-list-holder-by-time')[0].scrollHeight);
}
- if (config.GoogleDeveloperKey) {
+ if (global.window.config.GoogleDeveloperKey) {
$.ajax({
async: true,
url: 'https://www.googleapis.com/youtube/v3/videos',
type: 'GET',
- data: {part: 'snippet', id: youtubeId, key: config.GoogleDeveloperKey},
+ data: {part: 'snippet', id: youtubeId, key: global.window.config.GoogleDeveloperKey},
success: success
});
}
@@ -434,205 +433,6 @@ export function searchForTerm(term) {
});
}
-var puncStartRegex = /^((?![@#])\W)+/g;
-var puncEndRegex = /(\W)+$/g;
-
-export function textToJsx(textin, options) {
- var text = textin;
- if (options && options.singleline) {
- var repRegex = new RegExp('\n', 'g'); //eslint-disable-line no-control-regex
- text = text.replace(repRegex, ' ');
- }
-
- var searchTerm = '';
- if (options && options.searchTerm) {
- searchTerm = options.searchTerm.toLowerCase();
- }
-
- var mentionClass = 'mention-highlight';
- if (options && options.noMentionHighlight) {
- mentionClass = '';
- }
-
- var inner = [];
-
- // Function specific regex
- var hashRegex = /^href="#[^']+"|(^#[A-Za-z]+[A-Za-z0-9_\-]*[A-Za-z0-9])$/g;
-
- var implicitKeywords = UserStore.getCurrentMentionKeys();
-
- var lines = text.split('\n');
- for (let i = 0; i < lines.length; i++) {
- var line = lines[i];
- var words = line.split(' ');
- var highlightSearchClass = '';
- for (let z = 0; z < words.length; z++) {
- var word = words[z];
- var trimWord = word.replace(puncStartRegex, '').replace(puncEndRegex, '').trim();
- var mentionRegex = /^(?:@)([a-z0-9_]+)$/gi; // looks loop invariant but a weird JS bug needs it to be redefined here
- var explicitMention = mentionRegex.exec(trimWord);
-
- if (searchTerm !== '') {
- let searchWords = searchTerm.split(' ');
- for (let idx in searchWords) {
- if ({}.hasOwnProperty.call(searchWords, idx)) {
- let searchWord = searchWords[idx];
- if (searchWord === word.toLowerCase() || searchWord === trimWord.toLowerCase()) {
- highlightSearchClass = ' search-highlight';
- break;
- } else if (searchWord.charAt(searchWord.length - 1) === '*') {
- let searchWordPrefix = searchWord.slice(0, -1);
- if (trimWord.toLowerCase().indexOf(searchWordPrefix) > -1 || word.toLowerCase().indexOf(searchWordPrefix) > -1) {
- highlightSearchClass = ' search-highlight';
- break;
- }
- }
- }
- }
- }
-
- if (explicitMention &&
- (UserStore.getProfileByUsername(explicitMention[1]) ||
- Constants.SPECIAL_MENTIONS.indexOf(explicitMention[1]) !== -1)) {
- let name = explicitMention[1];
-
- // do both a non-case sensitive and case senstive check
- let mClass = '';
- if (implicitKeywords.indexOf('@' + name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@' + name) !== -1) {
- mClass = mentionClass;
- }
-
- let suffix = word.match(puncEndRegex);
- let prefix = word.match(puncStartRegex);
-
- if (searchTerm === name) {
- highlightSearchClass = ' search-highlight';
- }
-
- inner.push(
- <span key={name + i + z + '_span'}>
- {prefix}
- <a
- className={mClass + highlightSearchClass + ' mention-link'}
- key={name + i + z + '_link'}
- href='#'
- onClick={() => searchForTerm(name)} //eslint-disable-line no-loop-func
- >
- @{name}
- </a>
- {suffix}
- {' '}
- </span>
- );
- } else if (testUrlMatch(word).length) {
- let match = testUrlMatch(word)[0];
- let link = match.link;
-
- let prefix = word.substring(0, word.indexOf(match.text));
- let suffix = word.substring(word.indexOf(match.text) + match.text.length);
-
- inner.push(
- <span key={word + i + z + '_span'}>
- {prefix}
- <a
- key={word + i + z + '_link'}
- className={'theme' + highlightSearchClass}
- target='_blank'
- href={link}
- >
- {match.text}
- </a>
- {suffix}
- {' '}
- </span>
- );
- } else if (trimWord.match(hashRegex)) {
- let suffix = word.match(puncEndRegex);
- let prefix = word.match(puncStartRegex);
- let mClass = '';
- if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) {
- mClass = mentionClass;
- }
-
- if (searchTerm === trimWord.substring(1).toLowerCase() || searchTerm === trimWord.toLowerCase()) {
- highlightSearchClass = ' search-highlight';
- }
-
- inner.push(
- <span key={word + i + z + '_span'}>
- {prefix}
- <a
- key={word + i + z + '_hash'}
- className={'theme ' + mClass + highlightSearchClass}
- href='#'
- onClick={searchForTerm.bind(this, trimWord)} //eslint-disable-line no-loop-func
- >
- {trimWord}
- </a>
- {suffix}
- {' '}
- </span>
- );
- } else if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) {
- let suffix = word.match(puncEndRegex);
- let prefix = word.match(puncStartRegex);
-
- if (trimWord.charAt(0) === '@') {
- if (searchTerm === trimWord.substring(1).toLowerCase()) {
- highlightSearchClass = ' search-highlight';
- }
- inner.push(
- <span key={word + i + z + '_span'}>
- {prefix}
- <a
- className={mentionClass + highlightSearchClass}
- key={name + i + z + '_link'}
- href='#'
- >
- {trimWord}
- </a>
- {suffix}
- {' '}
- </span>
- );
- } else {
- inner.push(
- <span key={word + i + z + '_span'}>
- {prefix}
- <span className={mentionClass + highlightSearchClass}>
- {replaceHtmlEntities(trimWord)}
- </span>
- {suffix}
- {' '}
- </span>
- );
- }
- } else if (word === '') {
-
- // if word is empty dont include a span
-
- } else {
- inner.push(
- <span key={word + i + z + '_span'}>
- <span className={highlightSearchClass}>
- {replaceHtmlEntities(word)}
- </span>
- {' '}
- </span>
- );
- }
- highlightSearchClass = '';
- }
- if (i !== lines.length - 1) {
- inner.push(
- <br key={'br_' + i}/>
- );
- }
- }
-
- return inner;
-}
-
export function getFileType(extin) {
var ext = extin.toLowerCase();
if (Constants.IMAGE_TYPES.indexOf(ext) > -1) {
@@ -828,14 +628,12 @@ export function isValidUsername(name) {
} else if (name.length < 3 || name.length > 15) {
error = 'Must be between 3 and 15 characters';
} else if (!(/^[a-z0-9\.\-\_]+$/).test(name)) {
- error = "Must contain only lowercase letters, numbers, and the symbols '.', '-', and '_'.";
+ error = "Must contain only letters, numbers, and the symbols '.', '-', and '_'.";
} else if (!(/[a-z]/).test(name.charAt(0))) {
error = 'First character must be a letter.';
} else {
- var lowerName = name.toLowerCase().trim();
-
for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++) {
- if (lowerName === Constants.RESERVED_USERNAMES[i]) {
+ if (name === Constants.RESERVED_USERNAMES[i]) {
error = 'Cannot use a reserved word as a username.';
break;
}
@@ -1127,3 +925,14 @@ export function importSlack(file, success, error) {
client.importSlack(formData, success, error);
}
+
+export function getTeamURLFromAddressBar() {
+ return window.location.href.split('/channels')[0];
+}
+
+export function getShortenedTeamURL() {
+ const teamURL = getTeamURLFromAddressBar();
+ if (teamURL.length > 24) {
+ return teamURL.substring(0, 10) + '...' + teamURL.substring(teamURL.length - 12, teamURL.length) + '/';
+ }
+}