diff options
Diffstat (limited to 'web/react/utils')
-rw-r--r-- | web/react/utils/async_client.jsx | 104 | ||||
-rw-r--r-- | web/react/utils/client.jsx | 280 | ||||
-rw-r--r-- | web/react/utils/config.js | 48 | ||||
-rw-r--r-- | web/react/utils/constants.jsx | 174 | ||||
-rw-r--r-- | web/react/utils/emoticons.jsx | 158 | ||||
-rw-r--r-- | web/react/utils/markdown.jsx | 62 | ||||
-rw-r--r-- | web/react/utils/text_formatting.jsx | 295 | ||||
-rw-r--r-- | web/react/utils/utils.jsx | 449 |
8 files changed, 1174 insertions, 396 deletions
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx index 6ccef0506..ab2965000 100644 --- a/web/react/utils/async_client.jsx +++ b/web/react/utils/async_client.jsx @@ -319,6 +319,84 @@ 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 getConfig() { + if (isCallInProgress('getConfig')) { + return; + } + + callTracker.getConfig = utils.getTimestamp(); + client.getConfig( + (data, textStatus, xhr) => { + callTracker.getConfig = 0; + + if (xhr.status === 304 || !data) { + return; + } + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_CONFIG, + config: data + }); + }, + (err) => { + callTracker.getConfig = 0; + dispatchError(err, 'getConfig'); + } + ); +} + +export function getAllTeams() { + if (isCallInProgress('getAllTeams')) { + return; + } + + callTracker.getAllTeams = utils.getTimestamp(); + client.getAllTeams( + (data, textStatus, xhr) => { + callTracker.getAllTeams = 0; + + if (xhr.status === 304 || !data) { + return; + } + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_ALL_TEAMS, + teams: data + }); + }, + (err) => { + callTracker.getAllTeams = 0; + dispatchError(err, 'getAllTeams'); + } + ); +} + export function findTeams(email) { if (isCallInProgress('findTeams_' + email)) { return; @@ -556,28 +634,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 10f9c0b37..715e26197 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -4,18 +4,14 @@ var BrowserStore = require('../stores/browser_store.jsx'); var TeamStore = require('../stores/team_store.jsx'); export function track(category, action, label, prop, val) { - global.window.snowplow('trackStructEvent', category, action, label, prop, val); global.window.analytics.track(action, {category: category, label: label, property: prop, value: val}); } export function trackPage() { - global.window.snowplow('trackPageView'); global.window.analytics.page(); } function handleError(methodName, xhr, status, err) { - var LTracker = global.window.LTracker || []; - var e = null; try { e = JSON.parse(xhr.responseText); @@ -31,7 +27,7 @@ function handleError(methodName, xhr, status, err) { msg = 'error in ' + methodName + ' status=' + status + ' statusCode=' + xhr.status + ' err=' + err; if (xhr.status === 0) { - e = {message: 'There appears to be a problem with your internet connection'}; + e = {message: 'There appears to be a problem with your internet connection', connErrorCount: 1}; } else { e = {message: 'We received an unexpected status code from the server (' + xhr.status + ')'}; } @@ -39,7 +35,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); @@ -62,7 +57,7 @@ export function createTeamFromSignup(teamSignup, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(teamSignup), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('createTeamFromSignup', xhr, status, err); error(e); @@ -77,7 +72,7 @@ export function createTeamWithSSO(team, service, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(team), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('createTeamWithSSO', xhr, status, err); error(e); @@ -92,7 +87,7 @@ export function createUser(user, data, emailHash, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(user), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('createUser', xhr, status, err); error(e); @@ -109,7 +104,7 @@ export function updateUser(user, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(user), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('updateUser', xhr, status, err); error(e); @@ -126,7 +121,7 @@ export function updatePassword(data, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(data), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('newPassword', xhr, status, err); error(e); @@ -143,7 +138,7 @@ export function updateUserNotifyProps(data, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(data), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('updateUserNotifyProps', xhr, status, err); error(e); @@ -158,7 +153,7 @@ export function updateRoles(data, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(data), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('updateRoles', xhr, status, err); error(e); @@ -179,7 +174,7 @@ export function updateActive(userId, active, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(data), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('updateActive', xhr, status, err); error(e); @@ -196,7 +191,7 @@ export function sendPasswordReset(data, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(data), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('sendPasswordReset', xhr, status, err); error(e); @@ -213,7 +208,7 @@ export function resetPassword(data, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(data), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('resetPassword', xhr, status, err); error(e); @@ -257,7 +252,7 @@ export function revokeSession(altId, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify({id: altId}), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('revokeSession', xhr, status, err); error(e); @@ -272,7 +267,7 @@ export function getSessions(userId, success, error) { dataType: 'json', contentType: 'application/json', type: 'GET', - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('getSessions', xhr, status, err); error(e); @@ -286,7 +281,7 @@ export function getAudits(userId, success, error) { dataType: 'json', contentType: 'application/json', type: 'GET', - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('getAudits', xhr, status, err); error(e); @@ -294,6 +289,78 @@ 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, + error: function onError(xhr, status, err) { + var e = handleError('getLogs', xhr, status, err); + error(e); + } + }); +} + +export function getConfig(success, error) { + $.ajax({ + url: '/api/v1/admin/config', + dataType: 'json', + contentType: 'application/json', + type: 'GET', + success, + error: function onError(xhr, status, err) { + var e = handleError('getConfig', xhr, status, err); + error(e); + } + }); +} + +export function saveConfig(config, success, error) { + $.ajax({ + url: '/api/v1/admin/save_config', + dataType: 'json', + contentType: 'application/json', + type: 'POST', + data: JSON.stringify(config), + success, + error: function onError(xhr, status, err) { + var e = handleError('saveConfig', xhr, status, err); + error(e); + } + }); +} + +export function testEmail(config, success, error) { + $.ajax({ + url: '/api/v1/admin/test_email', + dataType: 'json', + contentType: 'application/json', + type: 'POST', + data: JSON.stringify(config), + success, + error: function onError(xhr, status, err) { + var e = handleError('testEmail', xhr, status, err); + error(e); + } + }); +} + +export function getAllTeams(success, error) { + $.ajax({ + url: '/api/v1/teams/all', + dataType: 'json', + contentType: 'application/json', + type: 'GET', + success, + error: function onError(xhr, status, err) { + var e = handleError('getAllTeams', xhr, status, err); + error(e); + } + }); +} + export function getMeSynchronous(success, error) { var currentUser = null; $.ajax({ @@ -327,7 +394,7 @@ export function inviteMembers(data, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(data), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('inviteMembers', xhr, status, err); error(e); @@ -344,7 +411,7 @@ export function updateTeamDisplayName(data, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(data), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('updateTeamDisplayName', xhr, status, err); error(e); @@ -361,7 +428,7 @@ export function signupTeam(email, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify({email: email}), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('singupTeam', xhr, status, err); error(e); @@ -378,7 +445,7 @@ export function createTeam(team, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(team), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('createTeam', xhr, status, err); error(e); @@ -393,7 +460,7 @@ export function findTeamByName(teamName, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify({name: teamName}), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('findTeamByName', xhr, status, err); error(e); @@ -408,7 +475,7 @@ export function findTeamsSendEmail(email, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify({email: email}), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('findTeamsSendEmail', xhr, status, err); error(e); @@ -425,7 +492,7 @@ export function findTeams(email, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify({email: email}), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('findTeams', xhr, status, err); error(e); @@ -440,7 +507,7 @@ export function createChannel(channel, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(channel), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('createChannel', xhr, status, err); error(e); @@ -457,7 +524,7 @@ export function createDirectChannel(channel, userId, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify({user_id: userId}), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('createDirectChannel', xhr, status, err); error(e); @@ -474,7 +541,7 @@ export function updateChannel(channel, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(channel), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('updateChannel', xhr, status, err); error(e); @@ -491,7 +558,7 @@ export function updateChannelDesc(data, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(data), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('updateChannelDesc', xhr, status, err); error(e); @@ -508,7 +575,7 @@ export function updateNotifyLevel(data, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(data), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('updateNotifyLevel', xhr, status, err); error(e); @@ -522,7 +589,7 @@ export function joinChannel(id, success, error) { dataType: 'json', contentType: 'application/json', type: 'POST', - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('joinChannel', xhr, status, err); error(e); @@ -538,7 +605,7 @@ export function leaveChannel(id, success, error) { dataType: 'json', contentType: 'application/json', type: 'POST', - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('leaveChannel', xhr, status, err); error(e); @@ -554,7 +621,7 @@ export function deleteChannel(id, success, error) { dataType: 'json', contentType: 'application/json', type: 'POST', - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('deleteChannel', xhr, status, err); error(e); @@ -570,7 +637,7 @@ export function updateLastViewedAt(channelId, success, error) { dataType: 'json', contentType: 'application/json', type: 'POST', - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('updateLastViewedAt', xhr, status, err); error(e); @@ -584,7 +651,7 @@ export function getChannels(success, error) { url: '/api/v1/channels/', dataType: 'json', type: 'GET', - success: success, + success, ifModified: true, error: function onError(xhr, status, err) { var e = handleError('getChannels', xhr, status, err); @@ -599,7 +666,7 @@ export function getChannel(id, success, error) { url: '/api/v1/channels/' + id + '/', dataType: 'json', type: 'GET', - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('getChannel', xhr, status, err); error(e); @@ -614,7 +681,7 @@ export function getMoreChannels(success, error) { url: '/api/v1/channels/more', dataType: 'json', type: 'GET', - success: success, + success, ifModified: true, error: function onError(xhr, status, err) { var e = handleError('getMoreChannels', xhr, status, err); @@ -629,7 +696,7 @@ export function getChannelCounts(success, error) { url: '/api/v1/channels/counts', dataType: 'json', type: 'GET', - success: success, + success, ifModified: true, error: function onError(xhr, status, err) { var e = handleError('getChannelCounts', xhr, status, err); @@ -643,7 +710,7 @@ export function getChannelExtraInfo(id, success, error) { url: '/api/v1/channels/' + id + '/extra_info', dataType: 'json', type: 'GET', - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('getChannelExtraInfo', xhr, status, err); error(e); @@ -658,7 +725,7 @@ export function executeCommand(channelId, command, suggest, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify({channelId: channelId, command: command, suggest: '' + suggest}), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('executeCommand', xhr, status, err); error(e); @@ -673,7 +740,7 @@ export function getPostsPage(channelId, offset, limit, success, error, complete) dataType: 'json', type: 'GET', ifModified: true, - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('getPosts', xhr, status, err); error(e); @@ -688,7 +755,7 @@ export function getPosts(channelId, since, success, error, complete) { dataType: 'json', type: 'GET', ifModified: true, - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('getPosts', xhr, status, err); error(e); @@ -704,7 +771,7 @@ export function getPost(channelId, postId, success, error) { dataType: 'json', type: 'GET', ifModified: false, - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('getPost', xhr, status, err); error(e); @@ -718,7 +785,7 @@ export function search(terms, success, error) { dataType: 'json', type: 'GET', data: {terms: terms}, - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('search', xhr, status, err); error(e); @@ -734,7 +801,7 @@ export function deletePost(channelId, id, success, error) { dataType: 'json', contentType: 'application/json', type: 'POST', - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('deletePost', xhr, status, err); error(e); @@ -751,7 +818,7 @@ export function createPost(post, channel, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(post), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('createPost', xhr, status, err); error(e); @@ -777,7 +844,7 @@ export function updatePost(post, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(post), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('updatePost', xhr, status, err); error(e); @@ -794,7 +861,7 @@ export function addChannelMember(id, data, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(data), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('addChannelMember', xhr, status, err); error(e); @@ -811,7 +878,7 @@ export function removeChannelMember(id, data, success, error) { contentType: 'application/json', type: 'POST', data: JSON.stringify(data), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('removeChannelMember', xhr, status, err); error(e); @@ -828,7 +895,7 @@ export function getProfiles(success, error) { dataType: 'json', contentType: 'application/json', type: 'GET', - success: success, + success, ifModified: true, error: function onError(xhr, status, err) { var e = handleError('getProfiles', xhr, status, err); @@ -837,6 +904,21 @@ export function getProfiles(success, error) { }); } +export function getProfilesForTeam(teamId, success, error) { + $.ajax({ + cache: false, + url: '/api/v1/users/profiles/' + teamId, + dataType: 'json', + contentType: 'application/json', + type: 'GET', + success, + error: function onError(xhr, status, err) { + var e = handleError('getProfilesForTeam', xhr, status, err); + error(e); + } + }); +} + export function uploadFile(formData, success, error) { var request = $.ajax({ url: '/api/v1/files/upload', @@ -845,7 +927,7 @@ export function uploadFile(formData, success, error) { cache: false, contentType: false, processData: false, - success: success, + success, error: function onError(xhr, status, err) { if (err !== 'abort') { var e = handleError('uploadFile', xhr, status, err); @@ -865,7 +947,7 @@ export function getFileInfo(filename, success, error) { dataType: 'json', contentType: 'application/json', type: 'GET', - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('getFileInfo', xhr, status, err); error(e); @@ -879,7 +961,7 @@ export function getPublicLink(data, success, error) { dataType: 'json', type: 'POST', data: JSON.stringify(data), - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('getPublicLink', xhr, status, err); error(e); @@ -895,7 +977,7 @@ export function uploadProfileImage(imageData, success, error) { cache: false, contentType: false, processData: false, - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('uploadProfileImage', xhr, status, err); error(e); @@ -911,7 +993,7 @@ export function importSlack(fileData, success, error) { cache: false, contentType: false, processData: false, - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('importTeam', xhr, status, err); error(e); @@ -919,13 +1001,26 @@ export function importSlack(fileData, success, error) { }); } +export function exportTeam(success, error) { + $.ajax({ + url: '/api/v1/teams/export_team', + type: 'GET', + dataType: 'json', + success, + error: function onError(xhr, status, err) { + var e = handleError('exportTeam', xhr, status, err); + error(e); + } + }); +} + export function getStatuses(success, error) { $.ajax({ url: '/api/v1/users/status', dataType: 'json', contentType: 'application/json', type: 'GET', - success: success, + success, error: function onError(xhr, status, err) { var e = handleError('getStatuses', xhr, status, err); error(e); @@ -938,7 +1033,7 @@ export function getMyTeam(success, error) { url: '/api/v1/teams/me', dataType: 'json', type: 'GET', - success: success, + success, ifModified: true, error: function onError(xhr, status, err) { var e = handleError('getMyTeam', xhr, status, err); @@ -947,32 +1042,77 @@ export function getMyTeam(success, error) { }); } -export function updateValetFeature(data, success, error) { +export function registerOAuthApp(app, success, error) { $.ajax({ - url: '/api/v1/teams/update_valet_feature', + url: '/api/v1/oauth/register', dataType: 'json', contentType: 'application/json', type: 'POST', - data: JSON.stringify(data), + data: JSON.stringify(app), success: success, - error: function onError(xhr, status, err) { - var e = handleError('updateValetFeature', xhr, status, err); + error: (xhr, status, err) => { + const e = handleError('registerApp', xhr, status, err); error(e); } }); - track('api', 'api_teams_update_valet_feature'); + module.exports.track('api', 'api_apps_register'); } -export function getConfig(success, error) { +export function allowOAuth2(responseType, clientId, redirectUri, state, scope, success, error) { $.ajax({ - url: '/api/v1/config/get_all', + 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); + success, + error: (xhr, status, err) => { + const e = handleError('allowOAuth2', xhr, status, err); + error(e); + } + }); + + module.exports.track('api', 'api_users_allow_oauth2'); +} + +export function addIncomingHook(hook, success, error) { + $.ajax({ + url: '/api/v1/hooks/incoming/create', + dataType: 'json', + contentType: 'application/json', + type: 'POST', + data: JSON.stringify(hook), + success, + error: (xhr, status, err) => { + var e = handleError('addIncomingHook', xhr, status, err); + error(e); + } + }); +} + +export function deleteIncomingHook(data, success, error) { + $.ajax({ + url: '/api/v1/hooks/incoming/delete', + dataType: 'json', + contentType: 'application/json', + type: 'POST', + data: JSON.stringify(data), + success, + error: (xhr, status, err) => { + var e = handleError('deleteIncomingHook', xhr, status, err); + error(e); + } + }); +} + +export function listIncomingHooks(success, error) { + $.ajax({ + url: '/api/v1/hooks/incoming/list', + dataType: 'json', + type: 'GET', + success, + error: (xhr, status, err) => { + var e = handleError('listIncomingHooks', xhr, status, err); error(e); } }); 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 18b7ff59c..75e80bc7e 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -9,6 +9,7 @@ module.exports = { CLICK_CHANNEL: null, CREATE_CHANNEL: null, + LEAVE_CHANNEL: null, RECIEVED_CHANNELS: null, RECIEVED_CHANNEL: null, RECIEVED_MORE_CHANNELS: null, @@ -33,7 +34,11 @@ module.exports = { CLICK_TEAM: null, RECIEVED_TEAM: null, - RECIEVED_CONFIG: null + RECIEVED_CONFIG: null, + RECIEVED_LOGS: null, + RECIEVED_ALL_TEAMS: null, + + TOGGLE_IMPORT_THEME_MODAL: null }), PayloadSources: keyMirror({ @@ -67,6 +72,10 @@ module.exports = { MAX_FILE_SIZE: 50000000, // 50 MB THUMBNAIL_WIDTH: 128, THUMBNAIL_HEIGHT: 100, + WEB_VIDEO_WIDTH: 640, + WEB_VIDEO_HEIGHT: 480, + MOBILE_VIDEO_WIDTH: 480, + MOBILE_VIDEO_HEIGHT: 360, DEFAULT_CHANNEL: 'town-square', OFFTOPIC_CHANNEL: 'off-topic', GITLAB_SERVICE: 'gitlab', @@ -107,5 +116,166 @@ module.exports = { ONLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path class='online--icon' d='M6,5.487c1.371,0,2.482-1.116,2.482-2.493c0-1.378-1.111-2.495-2.482-2.495S3.518,1.616,3.518,2.994C3.518,4.371,4.629,5.487,6,5.487z M10.452,8.545c-0.101-0.829-0.36-1.968-0.726-2.541C9.475,5.606,8.5,5.5,8.5,5.5S8.43,7.521,6,7.521C3.507,7.521,3.5,5.5,3.5,5.5S2.527,5.606,2.273,6.004C1.908,6.577,1.648,7.716,1.547,8.545C1.521,8.688,1.49,9.082,1.498,9.142c0.161,1.295,2.238,2.322,4.375,2.358C5.916,11.501,5.958,11.501,6,11.501c0.043,0,0.084,0,0.127-0.001c2.076-0.026,4.214-1.063,4.375-2.358C10.509,9.082,10.471,8.696,10.452,8.545z'/></g></g></svg>", OFFLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path fill='#cccccc' d='M6.002,7.143C5.645,7.363,5.167,7.52,4.502,7.52c-2.493,0-2.5-2.02-2.5-2.02S1.029,5.607,0.775,6.004C0.41,6.577,0.15,7.716,0.049,8.545c-0.025,0.145-0.057,0.537-0.05,0.598c0.162,1.295,2.237,2.321,4.375,2.357c0.043,0.001,0.085,0.001,0.127,0.001c0.043,0,0.084,0,0.127-0.001c1.879-0.023,3.793-0.879,4.263-2h-2.89L6.002,7.143L6.002,7.143z M4.501,5.488c1.372,0,2.483-1.117,2.483-2.494c0-1.378-1.111-2.495-2.483-2.495c-1.371,0-2.481,1.117-2.481,2.495C2.02,4.371,3.13,5.488,4.501,5.488z M7.002,6.5v2h5v-2H7.002z'/></g></g></svg>", MENU_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'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>", - 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>" + 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>", + THEMES: { + default: { + type: 'Mattermost', + sidebarBg: '#fafafa', + sidebarText: '#999999', + sidebarUnreadText: '#333333', + sidebarTextHoverBg: '#e6f2fa', + sidebarTextHoverColor: '#999999', + sidebarTextActiveBg: '#e1e1e1', + sidebarTextActiveColor: '#111111', + sidebarHeaderBg: '#2389d7', + sidebarHeaderTextColor: '#ffffff', + onlineIndicator: '#7DBE00', + mentionBj: '#2389d7', + mentionColor: '#ffffff', + centerChannelBg: '#ffffff', + centerChannelColor: '#333333', + newMessageSeparator: '#FF8800', + linkColor: '#2389d7', + buttonBg: '#2389d7', + buttonColor: '#FFFFFF' + }, + organization: { + type: 'Organization', + sidebarBg: '#2071a7', + sidebarText: '#bfcde8', + sidebarUnreadText: '#fff', + sidebarTextHoverBg: '#136197', + sidebarTextHoverColor: '#bfcde8', + sidebarTextActiveBg: '#136197', + sidebarTextActiveColor: '#FFFFFF', + sidebarHeaderBg: '#2f81b7', + sidebarHeaderTextColor: '#FFFFFF', + onlineIndicator: '#7DBE00', + mentionBj: '#136197', + mentionColor: '#bfcde8', + centerChannelBg: '#f2f4f8', + centerChannelColor: '#333333', + newMessageSeparator: '#FF8800', + linkColor: '#2f81b7', + buttonBg: '#1dacfc', + buttonColor: '#FFFFFF' + }, + mattermostDark: { + type: 'Mattermost Dark', + sidebarBg: '#1B2C3E', + sidebarText: '#bbbbbb', + sidebarUnreadText: '#fff', + sidebarTextHoverBg: '#4A5664', + sidebarTextHoverColor: '#bbbbbb', + sidebarTextActiveBg: '#39769C', + sidebarTextActiveColor: '#FFFFFF', + sidebarHeaderBg: '#1B2C3E', + sidebarHeaderTextColor: '#FFFFFF', + onlineIndicator: '#55C5B2', + mentionBj: '#B74A4A', + mentionColor: '#FFFFFF', + centerChannelBg: '#2F3E4E', + centerChannelColor: '#DDDDDD', + newMessageSeparator: '#5de5da', + linkColor: '#A4FFEB', + buttonBg: '#1dacfc', + buttonColor: '#FFFFFF' + }, + windows10: { + type: 'Windows Dark', + sidebarBg: '#171717', + sidebarText: '#eee', + sidebarUnreadText: '#fff', + sidebarTextHoverBg: '#302e30', + sidebarTextHoverColor: '#fff', + sidebarTextActiveBg: '#484748', + sidebarTextActiveColor: '#FFFFFF', + sidebarHeaderBg: '#1f1f1f', + sidebarHeaderTextColor: '#FFFFFF', + onlineIndicator: '#0177e7', + mentionBj: '#0177e7', + mentionColor: '#FFFFFF', + centerChannelBg: '#1F1F1F', + centerChannelColor: '#DDDDDD', + newMessageSeparator: '#CC992D', + linkColor: '#0177e7', + buttonBg: '#0177e7', + buttonColor: '#FFFFFF' + } + }, + THEME_ELEMENTS: [ + { + id: 'sidebarBg', + uiName: 'Sidebar BG' + }, + { + id: 'sidebarText', + uiName: 'Sidebar Text' + }, + { + id: 'sidebarHeaderBg', + uiName: 'Sidebar Header BG' + }, + { + id: 'sidebarHeaderTextColor', + uiName: 'Sidebar Header Text' + }, + { + id: 'sidebarUnreadText', + uiName: 'Sidebar Unread Text' + }, + { + id: 'sidebarTextHoverBg', + uiName: 'Sidebar Text Hover BG' + }, + { + id: 'sidebarTextHoverColor', + uiName: 'Sidebar Text Hover Color' + }, + { + id: 'sidebarTextActiveBg', + uiName: 'Sidebar Text Active BG' + }, + { + id: 'sidebarTextActiveColor', + uiName: 'Sidebar Text Active Color' + }, + { + id: 'onlineIndicator', + uiName: 'Online Indicator' + }, + { + id: 'mentionBj', + uiName: 'Mention Jewel BG' + }, + { + id: 'mentionColor', + uiName: 'Mention Jewel Text' + }, + { + id: 'centerChannelBg', + uiName: 'Center Channel BG' + }, + { + id: 'centerChannelColor', + uiName: 'Center Channel Text' + }, + { + id: 'newMessageSeparator', + uiName: 'New message separator' + }, + { + id: 'linkColor', + uiName: 'Link Color' + }, + { + id: 'buttonBg', + uiName: 'Button BG' + }, + + { + id: 'buttonColor', + uiName: 'Button Text' + } + ] }; diff --git a/web/react/utils/emoticons.jsx b/web/react/utils/emoticons.jsx new file mode 100644 index 000000000..a7c837199 --- /dev/null +++ b/web/react/utils/emoticons.jsx @@ -0,0 +1,158 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +const emoticonPatterns = { + smile: /:-?\)/g, // :) + open_mouth: /:o/gi, // :o + scream: /:-o/gi, // :-o + smirk: /:-?]/g, // :] + grinning: /:-?d/gi, // :D + stuck_out_tongue_closed_eyes: /x-d/gi, // x-d + stuck_out_tongue_winking_eye: /:-?p/gi, // :p + rage: /:-?[\[@]/g, // :@ + frowning: /:-?\(/g, // :( + sob: /:['’]-?\(|:'\(/g, // :`( + kissing_heart: /:-?\*/g, // :* + pensive: /:-?\//g, // :/ + confounded: /:-?s/gi, // :s + flushed: /:-?\|/g, // :| + relaxed: /:-?\$/g, // :$ + mask: /:-x/gi, // :-x + heart: /<3|<3/g, // <3 + broken_heart: /<\/3|</3/g, // </3 + thumbsup: /:\+1:/g, // :+1: + thumbsdown: /:\-1:/g // :-1: +}; + +function initializeEmoticonMap() { + const emoticonNames = + ('+1,-1,100,1234,8ball,a,ab,abc,abcd,accept,aerial_tramway,airplane,alarm_clock,alien,ambulance,anchor,angel,' + + 'anger,angry,anguished,ant,apple,aquarius,aries,arrow_backward,arrow_double_down,arrow_double_up,arrow_down,' + + 'arrow_down_small,arrow_forward,arrow_heading_down,arrow_heading_up,arrow_left,arrow_lower_left,' + + 'arrow_lower_right,arrow_right,arrow_right_hook,arrow_up,arrow_up_down,arrow_up_small,arrow_upper_left,' + + 'arrow_upper_right,arrows_clockwise,arrows_counterclockwise,art,articulated_lorry,astonished,atm,b,baby,' + + 'baby_bottle,baby_chick,baby_symbol,back,baggage_claim,balloon,ballot_box_with_check,bamboo,banana,bangbang,' + + 'bank,bar_chart,barber,baseball,basketball,bath,bathtub,battery,bear,bee,beer,beers,beetle,beginner,bell,bento,' + + 'bicyclist,bike,bikini,bird,birthday,black_circle,black_joker,black_medium_small_square,black_medium_square,' + + 'black_nib,black_small_square,black_square,black_square_button,blossom,blowfish,blue_book,blue_car,blue_heart,' + + 'blush,boar,boat,bomb,book,bookmark,bookmark_tabs,books,boom,boot,bouquet,bow,bowling,bowtie,boy,bread,' + + 'bride_with_veil,bridge_at_night,briefcase,broken_heart,bug,bulb,bullettrain_front,bullettrain_side,bus,busstop,' + + 'bust_in_silhouette,busts_in_silhouette,cactus,cake,calendar,calling,camel,camera,cancer,candy,capital_abcd,' + + 'capricorn,car,card_index,carousel_horse,cat,cat2,cd,chart,chart_with_downwards_trend,chart_with_upwards_trend,' + + 'checkered_flag,cherries,cherry_blossom,chestnut,chicken,children_crossing,chocolate_bar,christmas_tree,church,' + + 'cinema,circus_tent,city_sunrise,city_sunset,cl,clap,clapper,clipboard,clock1,clock10,clock1030,clock11,' + + 'clock1130,clock12,clock1230,clock130,clock2,clock230,clock3,clock330,clock4,clock430,clock5,clock530,clock6,' + + 'clock630,clock7,clock730,clock8,clock830,clock9,clock930,closed_book,closed_lock_with_key,closed_umbrella,cloud,' + + 'clubs,cn,cocktail,coffee,cold_sweat,collision,computer,confetti_ball,confounded,confused,congratulations,' + + 'construction,construction_worker,convenience_store,cookie,cool,cop,copyright,corn,couple,couple_with_heart,' + + 'couplekiss,cow,cow2,credit_card,crescent_moon,crocodile,crossed_flags,crown,cry,crying_cat_face,crystal_ball,' + + 'cupid,curly_loop,currency_exchange,curry,custard,customs,cyclone,dancer,dancers,dango,dart,dash,date,de,' + + 'deciduous_tree,department_store,diamond_shape_with_a_dot_inside,diamonds,disappointed,disappointed_relieved,' + + 'dizzy,dizzy_face,do_not_litter,dog,dog2,dollar,dolls,dolphin,donut,door,doughnut,dragon,dragon_face,dress,' + + 'dromedary_camel,droplet,dvd,e-mail,ear,ear_of_rice,earth_africa,earth_americas,earth_asia,egg,eggplant,eight,' + + 'eight_pointed_black_star,eight_spoked_asterisk,electric_plug,elephant,email,end,envelope,es,euro,' + + 'european_castle,european_post_office,evergreen_tree,exclamation,expressionless,eyeglasses,eyes,facepunch,' + + 'factory,fallen_leaf,family,fast_forward,fax,fearful,feelsgood,feet,ferris_wheel,file_folder,finnadie,fire,' + + 'fire_engine,fireworks,first_quarter_moon,first_quarter_moon_with_face,fish,fish_cake,fishing_pole_and_fish,fist,' + + 'five,flags,flashlight,floppy_disk,flower_playing_cards,flushed,foggy,football,fork_and_knife,fountain,four,' + + 'four_leaf_clover,fr,free,fried_shrimp,fries,frog,frowning,fu,fuelpump,full_moon,full_moon_with_face,game_die,gb,' + + 'gem,gemini,ghost,gift,gift_heart,girl,globe_with_meridians,goat,goberserk,godmode,golf,grapes,green_apple,' + + 'green_book,green_heart,grey_exclamation,grey_question,grimacing,grin,grinning,guardsman,guitar,gun,haircut,' + + 'hamburger,hammer,hamster,hand,handbag,hankey,hash,hatched_chick,hatching_chick,headphones,hear_no_evil,heart,' + + 'heart_decoration,heart_eyes,heart_eyes_cat,heartbeat,heartpulse,hearts,heavy_check_mark,heavy_division_sign,' + + 'heavy_dollar_sign,heavy_exclamation_mark,heavy_minus_sign,heavy_multiplication_x,heavy_plus_sign,helicopter,' + + 'herb,hibiscus,high_brightness,high_heel,hocho,honey_pot,honeybee,horse,horse_racing,hospital,hotel,hotsprings,' + + 'hourglass,hourglass_flowing_sand,house,house_with_garden,hurtrealbad,hushed,ice_cream,icecream,id,' + + 'ideograph_advantage,imp,inbox_tray,incoming_envelope,information_desk_person,information_source,innocent,' + + 'interrobang,iphone,it,izakaya_lantern,jack_o_lantern,japan,japanese_castle,japanese_goblin,japanese_ogre,jeans,' + + 'joy,joy_cat,jp,key,keycap_ten,kimono,kiss,kissing,kissing_cat,kissing_closed_eyes,kissing_face,kissing_heart,' + + 'kissing_smiling_eyes,koala,koko,kr,large_blue_circle,large_blue_diamond,large_orange_diamond,last_quarter_moon,' + + 'last_quarter_moon_with_face,laughing,leaves,ledger,left_luggage,left_right_arrow,leftwards_arrow_with_hook,' + + 'lemon,leo,leopard,libra,light_rail,link,lips,lipstick,lock,lock_with_ink_pen,lollipop,loop,loudspeaker,' + + 'love_hotel,love_letter,low_brightness,m,mag,mag_right,mahjong,mailbox,mailbox_closed,mailbox_with_mail,' + + 'mailbox_with_no_mail,man,man_with_gua_pi_mao,man_with_turban,mans_shoe,maple_leaf,mask,massage,meat_on_bone,' + + 'mega,melon,memo,mens,metal,metro,microphone,microscope,milky_way,minibus,minidisc,mobile_phone_off,' + + 'money_with_wings,moneybag,monkey,monkey_face,monorail,mortar_board,mount_fuji,mountain_bicyclist,' + + 'mountain_cableway,mountain_railway,mouse,mouse2,movie_camera,moyai,muscle,mushroom,musical_keyboard,' + + 'musical_note,musical_score,mute,nail_care,name_badge,neckbeard,necktie,negative_squared_cross_mark,' + + 'neutral_face,new,new_moon,new_moon_with_face,newspaper,ng,nine,no_bell,no_bicycles,no_entry,no_entry_sign,' + + 'no_good,no_mobile_phones,no_mouth,no_pedestrians,no_smoking,non-potable_water,nose,notebook,' + + 'notebook_with_decorative_cover,notes,nut_and_bolt,o,o2,ocean,octocat,octopus,oden,office,ok,ok_hand,' + + 'ok_woman,older_man,older_woman,on,oncoming_automobile,oncoming_bus,oncoming_police_car,oncoming_taxi,one,' + + 'open_file_folder,open_hands,open_mouth,ophiuchus,orange_book,outbox_tray,ox,package,page_facing_up,' + + 'page_with_curl,pager,palm_tree,panda_face,paperclip,parking,part_alternation_mark,partly_sunny,' + + 'passport_control,paw_prints,peach,pear,pencil,pencil2,penguin,pensive,performing_arts,persevere,' + + 'person_frowning,person_with_blond_hair,person_with_pouting_face,phone,pig,pig2,pig_nose,pill,pineapple,pisces,' + + 'pizza,plus1,point_down,point_left,point_right,point_up,point_up_2,police_car,poodle,poop,post_office,' + + 'postal_horn,postbox,potable_water,pouch,poultry_leg,pound,pouting_cat,pray,princess,punch,purple_heart,purse,' + + 'pushpin,put_litter_in_its_place,question,rabbit,rabbit2,racehorse,radio,radio_button,rage,rage1,rage2,rage3,' + + 'rage4,railway_car,rainbow,raised_hand,raised_hands,raising_hand,ram,ramen,rat,recycle,red_car,red_circle,' + + 'registered,relaxed,relieved,repeat,repeat_one,restroom,revolving_hearts,rewind,ribbon,rice,rice_ball,' + + 'rice_cracker,rice_scene,ring,rocket,roller_coaster,rooster,rose,rotating_light,round_pushpin,rowboat,ru,' + + 'rugby_football,runner,running,running_shirt_with_sash,sa,sagittarius,sailboat,sake,sandal,santa,satellite,' + + 'satisfied,saxophone,school,school_satchel,scissors,scorpius,scream,scream_cat,scroll,seat,secret,see_no_evil,' + + 'seedling,seven,shaved_ice,sheep,shell,ship,shipit,shirt,shit,shoe,shower,signal_strength,six,six_pointed_star,' + + 'ski,skull,sleeping,sleepy,slot_machine,small_blue_diamond,small_orange_diamond,small_red_triangle,' + + 'small_red_triangle_down,smile,smile_cat,smiley,smiley_cat,smiling_imp,smirk,smirk_cat,smoking,snail,snake,' + + 'snowboarder,snowflake,snowman,sob,soccer,soon,sos,sound,space_invader,spades,spaghetti,sparkle,sparkler,' + + 'sparkles,sparkling_heart,speak_no_evil,speaker,speech_balloon,speedboat,squirrel,star,star2,stars,station,' + + 'statue_of_liberty,steam_locomotive,stew,straight_ruler,strawberry,stuck_out_tongue,stuck_out_tongue_closed_eyes,' + + 'stuck_out_tongue_winking_eye,sun_with_face,sunflower,sunglasses,sunny,sunrise,sunrise_over_mountains,surfer,' + + 'sushi,suspect,suspension_railway,sweat,sweat_drops,sweat_smile,sweet_potato,swimmer,symbols,syringe,tada,' + + 'tanabata_tree,tangerine,taurus,taxi,tea,telephone,telephone_receiver,telescope,tennis,tent,thought_balloon,' + + 'three,thumbsdown,thumbsup,ticket,tiger,tiger2,tired_face,tm,toilet,tokyo_tower,tomato,tongue,top,tophat,' + + 'tractor,traffic_light,train,train2,tram,triangular_flag_on_post,triangular_ruler,trident,triumph,trolleybus,' + + 'trollface,trophy,tropical_drink,tropical_fish,truck,trumpet,tshirt,tulip,turtle,tv,twisted_rightwards_arrows,' + + 'two,two_hearts,two_men_holding_hands,two_women_holding_hands,u5272,u5408,u55b6,u6307,u6708,u6709,u6e80,u7121,' + + 'u7533,u7981,u7a7a,uk,umbrella,unamused,underage,unlock,up,us,v,vertical_traffic_light,vhs,vibration_mode,' + + 'video_camera,video_game,violin,virgo,volcano,vs,walking,waning_crescent_moon,waning_gibbous_moon,warning,watch,' + + 'water_buffalo,watermelon,wave,wavy_dash,waxing_crescent_moon,waxing_gibbous_moon,wc,weary,wedding,whale,whale2,' + + 'wheelchair,white_check_mark,white_circle,white_flower,white_large_square,white_medium_small_square,' + + 'white_medium_square,white_small_square,white_square_button,wind_chime,wine_glass,wink,wolf,woman,' + + 'womans_clothes,womans_hat,womens,worried,wrench,x,yellow_heart,yen,yum,zap,zero,zzz').split(','); + + // use a map to help make lookups faster instead of having to use indexOf on an array + const out = new Map(); + + for (let i = 0; i < emoticonNames.length; i++) { + out[emoticonNames[i]] = true; + } + + return out; +} + +const emoticonMap = initializeEmoticonMap(); + +export function handleEmoticons(text, tokens) { + let output = text; + + function replaceEmoticonWithToken(match, name) { + if (emoticonMap[name]) { + const index = tokens.size; + const alias = `MM_EMOTICON${index}`; + + tokens.set(alias, { + value: `<img align="absmiddle" alt=${match} class="emoji" src=${getImagePathForEmoticon(name)} title=${match} />`, + originalText: match + }); + + return alias; + } + + return match; + } + + output = output.replace(/:([a-zA-Z0-9_-]+):/g, replaceEmoticonWithToken); + + $.each(emoticonPatterns, (name, pattern) => { + // this might look a bit funny, but since the name isn't contained in the actual match + // like with the named emoticons, we need to add it in manually + output = output.replace(pattern, (match) => replaceEmoticonWithToken(match, name)); + }); + + return output; +} + +function getImagePathForEmoticon(name) { + return `/static/images/emoji/${name}.png`; +} diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx new file mode 100644 index 000000000..7e88f8644 --- /dev/null +++ b/web/react/utils/markdown.jsx @@ -0,0 +1,62 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +const TextFormatting = require('./text_formatting.jsx'); + +const marked = require('marked'); + +export class MattermostMarkdownRenderer extends marked.Renderer { + constructor(options, formattingOptions = {}) { + super(options); + + this.heading = this.heading.bind(this); + this.text = this.text.bind(this); + + this.formattingOptions = formattingOptions; + } + + br() { + if (this.formattingOptions.singleline) { + return ' '; + } + + return super.br(); + } + + heading(text, level, raw) { + const id = `${this.options.headerPrefix}${raw.toLowerCase().replace(/[^\w]+/g, '-')}`; + return `<h${level} id="${id}" class="markdown__heading">${text}</h${level}>`; + } + + link(href, title, text) { + let outHref = href; + + if (outHref.lastIndexOf('http', 0) !== 0) { + outHref = `http://${outHref}`; + } + + let output = '<a class="theme markdown__link" href="' + outHref + '"'; + if (title) { + output += ' title="' + title + '"'; + } + output += ' target="_blank">' + text + '</a>'; + + return output; + } + + paragraph(text) { + if (this.formattingOptions.singleline) { + return `<p class="markdown__paragraph-inline">${text}</p>`; + } + + return super.paragraph(text); + } + + table(header, body) { + return `<table class="markdown__table"><thead>${header}</thead><tbody>${body}</tbody></table>`; + } + + text(text) { + return TextFormatting.doFormatText(text, this.formattingOptions); + } +} diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx new file mode 100644 index 000000000..56bf49c3f --- /dev/null +++ b/web/react/utils/text_formatting.jsx @@ -0,0 +1,295 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +const Autolinker = require('autolinker'); +const Constants = require('./constants.jsx'); +const Emoticons = require('./emoticons.jsx'); +const Markdown = require('./markdown.jsx'); +const UserStore = require('../stores/user_store.jsx'); +const Utils = require('./utils.jsx'); + +const marked = require('marked'); + +// 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. +// - emoticons - Enables emoticon parsing. Defaults to true. +// - markdown - Enables markdown parsing. Defaults to true. +export function formatText(text, options = {}) { + let output; + + if (!('markdown' in options) || options.markdown) { + // the markdown renderer will call doFormatText as necessary so just call marked + output = marked(text, { + renderer: new Markdown.MattermostMarkdownRenderer(null, options), + sanitize: true + }); + } else { + output = sanitizeHtml(text); + output = doFormatText(output, options); + } + + // replace newlines with spaces if necessary + if (options.singleline) { + output = replaceNewlines(output); + } + + return output; +} + +// Performs most of the actual formatting work for formatText. Not intended to be called normally. +export function doFormatText(text, options) { + let output = 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 (!('emoticons' in options) || options.emoticon) { + output = Emoticons.handleEmoticons(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); + + 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, '<'); + output = output.replace(/>/g, '>'); + output = output.replace(/'/g, '''); + output = output.replace(/"/g, '"'); + + 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-Z][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) { + 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..61dcae6d8 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -4,12 +4,12 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var ChannelStore = require('../stores/channel_store.jsx'); var UserStore = require('../stores/user_store.jsx'); +var TeamStore = require('../stores/team_store.jsx'); var Constants = require('../utils/constants.jsx'); 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})+$/; @@ -55,6 +55,29 @@ export function isTestDomain() { return false; } +export function isInRole(roles, inRole) { + var parts = roles.split(' '); + for (var i = 0; i < parts.length; i++) { + if (parts[i] === inRole) { + return true; + } + } + + return false; +} + +export function isAdmin(roles) { + if (isInRole(roles, 'admin')) { + return true; + } + + if (isInRole(roles, 'system_admin')) { + return true; + } + + return false; +} + export function getDomainWithOutSub() { var parts = window.location.host.split('.'); @@ -91,7 +114,7 @@ export function notifyMe(title, body, channel) { if (channel) { switchChannel(channel); } else { - window.location.href = '/'; + window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/town-square'; } }; setTimeout(function closeNotificationOnTimeout() { @@ -292,15 +315,14 @@ function getYoutubeEmbed(link) { $('.video-type.' + youtubeId).html('Youtube - '); $('.video-uploader.' + youtubeId).html(metadata.channelTitle); $('.video-title.' + youtubeId).find('a').html(metadata.title); - $('.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 +456,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) { @@ -719,7 +542,131 @@ export function toTitleCase(str) { return str.replace(/\w\S*/g, doTitleCase); } -export function changeCss(className, classValue) { +export function applyTheme(theme) { + if (theme.sidebarBg) { + changeCss('.sidebar--left', 'background:' + theme.sidebarBg, 1); + changeCss('@media(max-width: 768px){.search-bar__container', 'background:' + theme.sidebarBg, 1); + } + + if (theme.sidebarText) { + changeCss('.sidebar--left .nav li>a, .sidebar--right', 'color:' + theme.sidebarText, 1); + changeCss('.sidebar--left .nav li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.8), 1); + changeCss('.sidebar--left .add-channel-btn:hover, .sidebar--left .add-channel-btn:focus', 'color:' + theme.sidebarText, 1); + changeCss('.sidebar--left, .sidebar--right .sidebar--right__header', 'border-color:' + changeOpacity(theme.sidebarText, 0.2), 1); + changeCss('.sidebar--left .status path', 'fill:' + changeOpacity(theme.sidebarText, 0.5), 1); + } + + if (theme.sidebarUnreadText) { + changeCss('.sidebar--left .nav li>a.unread-title', 'color:' + theme.sidebarUnreadText + '!important;', 1); + } + + if (theme.sidebarTextHoverBg) { + changeCss('.sidebar--left .nav li>a:hover, .sidebar--left .nav li>a:focus', 'background:' + theme.sidebarTextHoverBg, 1); + } + + if (theme.sidebarTextHoverColor) { + changeCss('.sidebar--left .nav li>a:hover, .sidebar--left .nav li>a:focus', 'color:' + theme.sidebarTextHoverColor, 2); + } + + if (theme.sidebarTextActiveBg) { + changeCss('.sidebar--left .nav li.active a, .sidebar--left .nav li.active a:hover, .sidebar--left .nav li.active a:focus', 'background:' + theme.sidebarTextActiveBg, 1); + } + + if (theme.sidebarTextActiveColor) { + changeCss('.sidebar--left .nav li.active a, .sidebar--left .nav li.active a:hover, .sidebar--left .nav li.active a:focus', 'color:' + theme.sidebarTextActiveColor, 2); + } + + if (theme.sidebarHeaderBg) { + changeCss('.sidebar--left .team__header, .sidebar--menu .team__header', 'background:' + theme.sidebarHeaderBg, 1); + changeCss('.modal .modal-header', 'background:' + theme.sidebarHeaderBg, 1); + changeCss('#navbar .navbar-default', 'background:' + theme.sidebarHeaderBg, 1); + } + + if (theme.sidebarHeaderTextColor) { + changeCss('.sidebar--left .team__header .header__info, .sidebar--menu .team__header .header__info', 'color:' + theme.sidebarHeaderTextColor, 1); + changeCss('.sidebar--left .team__header .user__name, .sidebar--menu .team__header .user__name', 'color:' + changeOpacity(theme.sidebarHeaderTextColor, 0.8), 1); + changeCss('.sidebar--left .team__header:hover .user__name, .sidebar--menu .team__header:hover .user__name', 'color:' + theme.sidebarHeaderTextColor, 1); + changeCss('.modal .modal-header .modal-title, .modal .modal-header .modal-title .name, .modal .modal-header button.close', 'color:' + theme.sidebarHeaderTextColor, 1); + changeCss('#navbar .navbar-default .navbar-brand .heading, ', 'color:' + theme.sidebarHeaderTextColor, 1); + changeCss('#navbar .navbar-default .navbar-toggle .icon-bar, ', 'background:' + theme.sidebarHeaderTextColor, 1); + } + + if (theme.onlineIndicator) { + changeCss('.sidebar--left .status .online--icon', 'fill:' + theme.onlineIndicator, 1); + } + + if (theme.mentionBj) { + changeCss('.sidebar--left .nav-pills__unread-indicator', 'background:' + theme.mentionBj, 1); + changeCss('.sidebar--left .badge', 'background:' + theme.mentionBj, 1); + } + + if (theme.mentionColor) { + changeCss('.sidebar--left .nav-pills__unread-indicator', 'color:' + theme.mentionColor, 2); + changeCss('.sidebar--left .badge', 'color:' + theme.mentionColor, 2); + } + + if (theme.centerChannelBg) { + changeCss('.app__content, .markdown__table, .markdown__table tbody tr', 'background:' + theme.centerChannelBg, 1); + changeCss('#post-list .post-list-holder-by-time', 'background:' + theme.centerChannelBg, 1); + changeCss('#post-create', 'background:' + theme.centerChannelBg, 1); + changeCss('.search-bar__container .search__form .search-bar', 'background:' + theme.centerChannelBg, 1); + changeCss('.date-separator .separator__text, .new-separator .separator__text', 'background:' + theme.centerChannelBg, 1); + changeCss('.post-image__column .post-image__details', 'background:' + theme.centerChannelBg, 1); + changeCss('.sidebar--right', 'background:' + theme.centerChannelBg, 1); + } + + if (theme.centerChannelColor) { + changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .loading-screen .loading__content .round', 'color:' + theme.centerChannelColor, 1); + changeCss('#post-create', 'color:' + theme.centerChannelColor, 2); + changeCss('.post-body hr', 'background:' + theme.centerChannelColor, 1); + changeCss('.channel-header .heading', 'color:' + theme.centerChannelColor, 1); + changeCss('.markdown__table tbody tr:nth-child(2n)', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); + changeCss('.channel-header__info>div.dropdown .header-dropdown__icon', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1); + changeCss('.channel-header #member_popover', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1); + changeCss('.custom-textarea, .custom-textarea:focus, .preview-container .preview-div, .post-image__column .post-image__details, .sidebar--right .sidebar-right__body, .markdown__table th, .markdown__table td', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.custom-textarea', 'color:' + theme.centerChannelColor, 1); + changeCss('.post-image__column', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 2); + changeCss('.post-image__column .post-image__details', 'color:' + theme.centerChannelColor, 2); + changeCss('.post-image__column a, .post-image__column a:hover, .post-image__column a:focus', 'color:' + theme.centerChannelColor, 1); + changeCss('.search-bar__container .search__form .search-bar', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2) + '; color: ' + theme.centerChannelColor, 2); + changeCss('.search-bar__container .search__form', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.channel-intro .channel-intro__content', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1); + changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2); + changeCss('.date-separator .separator__hr, .post-right__container .post.post--root hr, .search-item-container', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.channel-intro', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); + changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment, .post.post--comment.other--root .post-comment, .post.same--root .post-body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); + changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); + changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); + changeCss('@media(max-width: 1800px){.inner__wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); + changeCss('.post:hover, .sidebar--right .sidebar--right__header', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); + changeCss('.date-separator.hovered--before:after, .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); + changeCss('.date-separator.hovered--after:before, .new-separator.hovered--after:before', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); + changeCss('.post.current--user:hover .post-body ', 'background: none;', 1); + changeCss('.sidebar--right', 'color:' + theme.centerChannelColor, 2); + } + + if (theme.newMessageSeparator) { + changeCss('.new-separator .separator__text', 'color:' + theme.newMessageSeparator, 1); + changeCss('.new-separator .separator__hr', 'border-color:' + changeOpacity(theme.newMessageSeparator, 0.5), 1); + } + + if (theme.linkColor) { + changeCss('a, a:focus, a:hover', 'color:' + theme.linkColor, 1); + changeCss('.post .comment-icon__container', 'fill:' + theme.linkColor, 1); + } + + if (theme.buttonBg) { + changeCss('.btn.btn-primary', 'background:' + theme.buttonBg, 1); + changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background:' + changeColor(theme.buttonBg, -0.25), 1); + } + + if (theme.buttonColor) { + changeCss('.btn.btn-primary', 'color:' + theme.buttonColor, 2); + } +} + +export function changeCss(className, classValue, classRepeat) { // we need invisible container to store additional css definitions var cssMainContainer = $('#css-modifier-container'); if (cssMainContainer.length === 0) { @@ -729,9 +676,9 @@ export function changeCss(className, classValue) { } // and we need one div for each class - var classContainer = cssMainContainer.find('div[data-class="' + className + '"]'); + var classContainer = cssMainContainer.find('div[data-class="' + className + classRepeat + '"]'); if (classContainer.length === 0) { - classContainer = $('<div data-class="' + className + '"></div>'); + classContainer = $('<div data-class="' + className + classRepeat + '"></div>'); classContainer.appendTo(cssMainContainer); } @@ -828,14 +775,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; } @@ -939,57 +884,47 @@ Image.prototype.load = function imageLoad(url, progressCallback) { Image.prototype.completedPercentage = 0; export function changeColor(colourIn, amt) { - var usePound = false; - var col = colourIn; - - if (col[0] === '#') { - col = col.slice(1); - usePound = true; - } - - var num = parseInt(col, 16); - - var r = (num >> 16) + amt; + var hex = colourIn; + var lum = amt; - if (r > 255) { - r = 255; - } else if (r < 0) { - r = 0; + // validate hex string + hex = String(hex).replace(/[^0-9a-f]/gi, ''); + if (hex.length < 6) { + hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; } + lum = lum || 0; - var b = ((num >> 8) & 0x00FF) + amt; - - if (b > 255) { - b = 255; - } else if (b < 0) { - b = 0; + // convert to decimal and change luminosity + var rgb = '#'; + var c; + var i; + for (i = 0; i < 3; i++) { + c = parseInt(hex.substr(i * 2, 2), 16); + c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16); + rgb += ('00' + c).substr(c.length); } - var g = (num & 0x0000FF) + amt; - - if (g > 255) { - g = 255; - } else if (g < 0) { - g = 0; - } + return rgb; +} - var pound = '#'; - if (!usePound) { - pound = ''; +export function changeOpacity(oldColor, opacity) { + var color = oldColor; + if (color[0] === '#') { + color = color.slice(1); } - return pound + String('000000' + (g | (b << 8) | (r << 16)).toString(16)).slice(-6); -} + if (color.length === 3) { + const tempColor = color; + color = ''; -export function changeOpacity(oldColor, opacity) { - var col = oldColor; - if (col[0] === '#') { - col = col.slice(1); + color += tempColor[0] + tempColor[0]; + color += tempColor[1] + tempColor[1]; + color += tempColor[2] + tempColor[2]; } - var r = parseInt(col.substring(0, 2), 16); - var g = parseInt(col.substring(2, 4), 16); - var b = parseInt(col.substring(4, 6), 16); + var r = parseInt(color.substring(0, 2), 16); + var g = parseInt(color.substring(2, 4), 16); + var b = parseInt(color.substring(4, 6), 16); return 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')'; } @@ -1127,3 +1062,15 @@ 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 > 35) { + return teamURL.substring(0, 10) + '...' + teamURL.substring(teamURL.length - 12, teamURL.length) + '/'; + } + return teamURL + '/'; +} |