mirror of
https://mau.dev/maunium/synapse.git
synced 2024-11-20 12:24:59 +01:00
nasty big monolithic commit of a whole bunch of UI/UX improvements:
- add a simple CSS template across the app for navigation & cosmetics - split login into login & register, and totally reskin it - restructure room CSS to play nicely with it - implement basis 1:1 chat from user pages - disable autofocus on iOS to improve UX
This commit is contained in:
parent
b040bd6157
commit
1bc036a12d
15 changed files with 363 additions and 317 deletions
|
@ -37,6 +37,8 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
|
||||||
mPresence.start();
|
mPresence.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.user_id = matrixService.config().user_id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a given page.
|
* Open a given page.
|
||||||
* @param {String} url url of the page
|
* @param {String} url url of the page
|
||||||
|
@ -45,6 +47,16 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
|
||||||
$location.url(url);
|
$location.url(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Open the given user profile page
|
||||||
|
$scope.goToUserPage = function(user_id) {
|
||||||
|
if (user_id === $scope.user_id) {
|
||||||
|
$location.url("/settings");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$location.url("/user/" + user_id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Logs the user out
|
// Logs the user out
|
||||||
$scope.logout = function() {
|
$scope.logout = function() {
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,12 @@ angular.module('matrixWebClient')
|
||||||
.directive('ngFocus', ['$timeout', function($timeout) {
|
.directive('ngFocus', ['$timeout', function($timeout) {
|
||||||
return {
|
return {
|
||||||
link: function(scope, element, attr) {
|
link: function(scope, element, attr) {
|
||||||
|
// XXX: slightly evil hack to disable autofocus on iOS, as in general
|
||||||
|
// it causes more problems than it fixes, by bouncing the page
|
||||||
|
// around
|
||||||
|
if (!/(iPad|iPhone|iPod)/g.test(navigator.userAgent)) {
|
||||||
$timeout(function() { element[0].focus(); }, 0);
|
$timeout(function() { element[0].focus(); }, 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}]);
|
}]);
|
|
@ -1,122 +1,195 @@
|
||||||
/*** Mobile voodoo ***/
|
/** Common layout **/
|
||||||
@media all and (max-device-width: 640px) {
|
|
||||||
|
|
||||||
#messageTableWrapper {
|
|
||||||
margin-right: 0px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leftBlock {
|
|
||||||
width: 8em ! important;
|
|
||||||
font-size: 8px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightBlock {
|
|
||||||
width: 0px ! important;
|
|
||||||
display: none ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 36px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header,
|
|
||||||
#messageTable,
|
|
||||||
#wrapper,
|
|
||||||
#roomName,
|
|
||||||
#controls {
|
|
||||||
max-width: 640px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#userIdCell,
|
|
||||||
#roomRecentsTableWrapper,
|
|
||||||
#usersTableWrapper,
|
|
||||||
#extraControls {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#buttonsCell {
|
|
||||||
width: 60px ! important;
|
|
||||||
padding-left: 20px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#roomLogo {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#roomName {
|
|
||||||
text-align: left ! important;
|
|
||||||
top: -35px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble {
|
|
||||||
font-size: 12px ! important;
|
|
||||||
min-height: 20px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#page {
|
|
||||||
top: 35px ! important;
|
|
||||||
bottom: 70px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header,
|
|
||||||
#page {
|
|
||||||
margin: 5px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header {
|
|
||||||
padding: 5px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* stop zoom on select */
|
|
||||||
select:focus,
|
|
||||||
textarea,
|
|
||||||
input
|
|
||||||
{
|
|
||||||
font-size: 16px ! important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
height: 100%;
|
||||||
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-family: Helvetica, Arial, sans-serif;
|
font-size: 20pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Overall page layout ***/
|
a:link { color: #666; }
|
||||||
|
a:visited { color: #666; }
|
||||||
|
a:hover { color: #000; }
|
||||||
|
a:active { color: #000; }
|
||||||
|
|
||||||
#page {
|
#page {
|
||||||
position: absolute;
|
min-height: 100%;
|
||||||
top: 80px;
|
margin-bottom: -32px; /* to make room for the footer */
|
||||||
bottom: 100px;
|
|
||||||
left: 0px;
|
|
||||||
right: 0px;
|
|
||||||
margin: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#wrapper {
|
#wrapper {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
max-width: 1280px;
|
max-width: 1280px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
padding-top: 40px;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #333;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#headerContent {
|
||||||
|
color: #ccc;
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: auto;
|
||||||
|
text-align: right;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#headerContent a:link,
|
||||||
|
#headerContent a:visited,
|
||||||
|
#headerContent a:hover,
|
||||||
|
#headerContent a:active {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
border-top: #666 1px solid;
|
||||||
|
background-color: #aaa;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footerContent
|
||||||
|
{
|
||||||
|
font-size: 8pt;
|
||||||
|
color: #fff;
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: auto;
|
||||||
|
text-align: center;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#genericHeading
|
||||||
|
{
|
||||||
|
margin-top: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#feedback {
|
||||||
|
color: #800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mouse-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invited {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** Login Pages ***/
|
||||||
|
|
||||||
|
.loginWrapper {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginForm {
|
||||||
|
text-align: left;
|
||||||
|
padding: 1em;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
-webkit-border-radius: 10px;
|
||||||
|
-moz-border-radius: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||||
|
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||||
|
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||||
|
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border: 1px #ccc solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginForm input[type='radio'] {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#serverConfig {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#serverConfig,
|
||||||
|
#serverConfig input,
|
||||||
|
#serverConfig button
|
||||||
|
{
|
||||||
|
font-size: 10pt ! important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.smallPrint {
|
||||||
|
color: #888;
|
||||||
|
font-size: 9pt ! important;
|
||||||
|
font-style: italic ! important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#serverConfig label {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
width: 7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginForm,
|
||||||
|
#loginForm input,
|
||||||
|
#loginForm button,
|
||||||
|
#loginForm select {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** Room page ***/
|
||||||
|
|
||||||
|
#roomPage {
|
||||||
|
position: absolute;
|
||||||
|
top: 120px;
|
||||||
|
bottom: 120px;
|
||||||
|
left: 20px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#roomWrapper {
|
||||||
|
margin: auto;
|
||||||
|
max-width: 1280px;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#roomName {
|
#roomName {
|
||||||
max-width: 1280px;
|
float: right;
|
||||||
width: 100%;
|
|
||||||
text-align: right;
|
|
||||||
top: -40px;
|
|
||||||
position: absolute;
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#roomHeader {
|
||||||
|
margin: auto;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
padding-top: 53px;
|
||||||
|
max-width: 1280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#controlPanel {
|
#controlPanel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100px;
|
||||||
background-color: #f8f8f8;
|
background-color: #f8f8f8;
|
||||||
border-top: #aaa 1px solid;
|
border-top: #aaa 1px solid;
|
||||||
}
|
}
|
||||||
|
@ -147,10 +220,6 @@ h1 {
|
||||||
background-color: #faa;
|
background-color: #faa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mouse-pointer {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*** Participant list ***/
|
/*** Participant list ***/
|
||||||
|
|
||||||
#usersTableWrapper {
|
#usersTableWrapper {
|
||||||
|
@ -409,11 +478,14 @@ h1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Recents in the room page ***/
|
/*** Recents in the room page ***/
|
||||||
|
|
||||||
#roomRecentsTableWrapper {
|
#roomRecentsTableWrapper {
|
||||||
float: left;
|
float: left;
|
||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
margin-right: 20px;
|
padding-right: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,46 +506,8 @@ h1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** User profile page ***/
|
/*** User profile page ***/
|
||||||
|
|
||||||
#user-displayname {
|
#user-displayname {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
/******************************/
|
|
||||||
|
|
||||||
#header
|
|
||||||
{
|
|
||||||
padding: 20px;
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#logo,
|
|
||||||
#roomLogo {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header-buttons {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text_entry_section {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 100;
|
|
||||||
left: 0;
|
|
||||||
right: 10em;
|
|
||||||
width: 100%;
|
|
||||||
background: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.member_invited {
|
|
||||||
color: blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.member_joined {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.member_left {
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ var matrixWebClient = angular.module('matrixWebClient', [
|
||||||
'ngRoute',
|
'ngRoute',
|
||||||
'MatrixWebClientController',
|
'MatrixWebClientController',
|
||||||
'LoginController',
|
'LoginController',
|
||||||
|
'RegisterController',
|
||||||
'RoomController',
|
'RoomController',
|
||||||
'HomeController',
|
'HomeController',
|
||||||
'RecentsController',
|
'RecentsController',
|
||||||
|
@ -38,6 +39,10 @@ matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider',
|
||||||
templateUrl: 'login/login.html',
|
templateUrl: 'login/login.html',
|
||||||
controller: 'LoginController'
|
controller: 'LoginController'
|
||||||
}).
|
}).
|
||||||
|
when('/register', {
|
||||||
|
templateUrl: 'login/register.html',
|
||||||
|
controller: 'RegisterController'
|
||||||
|
}).
|
||||||
when('/room/:room_id_or_alias', {
|
when('/room/:room_id_or_alias', {
|
||||||
templateUrl: 'room/room.html',
|
templateUrl: 'room/room.html',
|
||||||
controller: 'RoomController'
|
controller: 'RoomController'
|
||||||
|
@ -84,7 +89,10 @@ matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider',
|
||||||
matrixWebClient.run(['$location', 'matrixService', function($location, matrixService) {
|
matrixWebClient.run(['$location', 'matrixService', function($location, matrixService) {
|
||||||
|
|
||||||
// If user auth details are not in cache, go to the login page
|
// If user auth details are not in cache, go to the login page
|
||||||
if (!matrixService.isUserLoggedIn()) {
|
if (!matrixService.isUserLoggedIn() &&
|
||||||
|
$location.path() !== "/login" &&
|
||||||
|
$location.path() !== "/register")
|
||||||
|
{
|
||||||
$location.path("login");
|
$location.path("login");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,14 +95,18 @@ angular.module('matrixService', [])
|
||||||
},
|
},
|
||||||
|
|
||||||
// Create a room
|
// Create a room
|
||||||
create: function(room_id, visibility) {
|
create: function(room_alias, visibility) {
|
||||||
// The REST path spec
|
// The REST path spec
|
||||||
var path = "/createRoom";
|
var path = "/createRoom";
|
||||||
|
|
||||||
return doRequest("POST", path, undefined, {
|
var req = {
|
||||||
visibility: visibility,
|
"visibility": visibility
|
||||||
room_alias_name: room_id
|
};
|
||||||
});
|
if (room_alias) {
|
||||||
|
req.room_alias_name = room_alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
return doRequest("POST", path, undefined, req);
|
||||||
},
|
},
|
||||||
|
|
||||||
// List all rooms joined or been invited to
|
// List all rooms joined or been invited to
|
||||||
|
|
|
@ -58,14 +58,14 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.createNewRoom = function(room_id, isPrivate) {
|
$scope.createNewRoom = function(room_alias, isPrivate) {
|
||||||
|
|
||||||
var visibility = "public";
|
var visibility = "public";
|
||||||
if (isPrivate) {
|
if (isPrivate) {
|
||||||
visibility = "private";
|
visibility = "private";
|
||||||
}
|
}
|
||||||
|
|
||||||
matrixService.create(room_id, visibility).then(
|
matrixService.create(room_alias, visibility).then(
|
||||||
function(response) {
|
function(response) {
|
||||||
// This room has been created. Refresh the rooms list
|
// This room has been created. Refresh the rooms list
|
||||||
console.log("Created room " + response.data.room_alias + " with id: "+
|
console.log("Created room " + response.data.room_alias + " with id: "+
|
||||||
|
|
|
@ -1,29 +1,24 @@
|
||||||
<div ng-controller="HomeController" data-ng-init="onInit()">
|
<div ng-controller="HomeController" data-ng-init="onInit()">
|
||||||
|
|
||||||
<div id="page">
|
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
|
|
||||||
<div>
|
<div id="genericHeading">
|
||||||
<form>
|
<img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/>
|
||||||
<table>
|
</div>
|
||||||
<tr>
|
|
||||||
<td>
|
<h1>Welcome to homeserver {{ config.homeserver }}</h1>
|
||||||
<div class="profile-avatar">
|
|
||||||
<img ng-src="{{ (null !== profile.avatarUrl) ? profile.avatarUrl : 'img/default-profile.jpg' }}"/>
|
<div>
|
||||||
|
<div class="profile-avatar">
|
||||||
|
<img ng-src="{{ (null !== profile.avatarUrl) ? profile.avatarUrl : 'img/default-profile.png' }}"/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div id="user-ids">
|
<div id="user-ids">
|
||||||
<div id="user-displayname">{{ profile.displayName }}</div>
|
<div id="user-displayname">{{ profile.displayName }}</div>
|
||||||
<div>{{ config.user_id }}</div>
|
<div>{{ config.user_id }}</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Recents</h3>
|
<h3>Recent conversations</h3>
|
||||||
<div ng-include="'recents/recents.html'"></div>
|
<div ng-include="'recents/recents.html'"></div>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
@ -38,9 +33,9 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<form>
|
<form>
|
||||||
<input size="40" ng-model="newRoom.room_id" ng-enter="createNewRoom(newRoom.room_id, newRoom.private)" placeholder="(e.g. foo_channel)"/>
|
<input size="40" ng-model="newRoom.room_alias" ng-enter="createNewRoom(newRoom.room_alias, newRoom.private)" placeholder="(e.g. foo_channel)"/>
|
||||||
<input type="checkbox" ng-model="newRoom.private">private
|
<input type="checkbox" ng-model="newRoom.private">private
|
||||||
<button ng-disabled="!newRoom.room_id" ng-click="createNewRoom(newRoom.room_id, newRoom.private)">Create room</button>
|
<button ng-disabled="!newRoom.room_alias" ng-click="createNewRoom(newRoom.room_alias, newRoom.private)">Create room</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -55,4 +50,3 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
<title>[matrix]</title>
|
<title>[matrix]</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="app.css">
|
<link rel="stylesheet" href="app.css">
|
||||||
|
<link rel="stylesheet" href="mobile.css">
|
||||||
|
|
||||||
<link rel="icon" href="favicon.ico">
|
<link rel="icon" href="favicon.ico">
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width">
|
<meta name="viewport" content="width=device-width">
|
||||||
|
@ -19,6 +21,7 @@
|
||||||
<script src="app-filter.js"></script>
|
<script src="app-filter.js"></script>
|
||||||
<script src="home/home-controller.js"></script>
|
<script src="home/home-controller.js"></script>
|
||||||
<script src="login/login-controller.js"></script>
|
<script src="login/login-controller.js"></script>
|
||||||
|
<script src="login/register-controller.js"></script>
|
||||||
<script src="recents/recents-controller.js"></script>
|
<script src="recents/recents-controller.js"></script>
|
||||||
<script src="recents/recents-filter.js"></script>
|
<script src="recents/recents-filter.js"></script>
|
||||||
<script src="room/room-controller.js"></script>
|
<script src="room/room-controller.js"></script>
|
||||||
|
@ -38,16 +41,23 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<header id="header">
|
<div id="header">
|
||||||
<!-- Do not show buttons on the login page -->
|
<!-- Do not show buttons on the login page -->
|
||||||
<div id="header-buttons" ng-hide="'/login' == location ">
|
<div id="headerContent" ng-hide="'/login' == location || '/register' == location">
|
||||||
|
<a href id="headerUserId" ng-click='goToUserPage(user_id)'>{{ user_id }}</a>
|
||||||
|
|
||||||
<button ng-click='goToPage("/")'>Home</button>
|
<button ng-click='goToPage("/")'>Home</button>
|
||||||
<button ng-click='goToPage("settings")'>Settings</button>
|
<button ng-click='goToPage("settings")'>Settings</button>
|
||||||
<button ng-click="logout()">Log out</button>
|
<button ng-click="logout()">Log out</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</div>
|
||||||
|
|
||||||
<div ng-view></div>
|
<div id="page" ng-view></div>
|
||||||
|
|
||||||
|
<div id="footer" ng-hide="location.indexOf('/room') == 0">
|
||||||
|
<div id="footerContent">
|
||||||
|
© 2014 Matrix.org
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -10,62 +10,25 @@ angular.module('LoginController', ['matrixService'])
|
||||||
if ($location.port()) {
|
if ($location.port()) {
|
||||||
hs_url += ":" + $location.port();
|
hs_url += ":" + $location.port();
|
||||||
}
|
}
|
||||||
|
var example_domain = $location.host();
|
||||||
|
|
||||||
$scope.account = {
|
$scope.account = {
|
||||||
homeserver: hs_url,
|
homeserver: hs_url,
|
||||||
|
example_domain: example_domain,
|
||||||
desired_user_name: "",
|
desired_user_name: "",
|
||||||
user_id: "",
|
user_id: "",
|
||||||
password: "",
|
password: "",
|
||||||
identityServer: "",
|
identityServer: "http://matrix.org:8090",
|
||||||
pwd1: "",
|
pwd1: "",
|
||||||
pwd2: ""
|
pwd2: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.register = function() {
|
$scope.login_types = [ "email", "mxid" ];
|
||||||
|
$scope.login_type_label = {
|
||||||
// Set the urls
|
"email": "Email address",
|
||||||
matrixService.setConfig({
|
"mxid": "Matrix ID (e.g. @bob:matrix.org or bob)",
|
||||||
homeserver: $scope.account.homeserver,
|
|
||||||
identityServer: $scope.account.identityServer
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($scope.account.pwd1 !== $scope.account.pwd2) {
|
|
||||||
$scope.feedback = "Passwords don't match.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if ($scope.account.pwd1.length < 6) {
|
|
||||||
$scope.feedback = "Password must be at least 6 characters.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
matrixService.register($scope.account.desired_user_name, $scope.account.pwd1).then(
|
|
||||||
function(response) {
|
|
||||||
$scope.feedback = "Success";
|
|
||||||
// Update the current config
|
|
||||||
var config = matrixService.config();
|
|
||||||
angular.extend(config, {
|
|
||||||
access_token: response.data.access_token,
|
|
||||||
user_id: response.data.user_id
|
|
||||||
});
|
|
||||||
matrixService.setConfig(config);
|
|
||||||
|
|
||||||
// And permanently save it
|
|
||||||
matrixService.saveConfig();
|
|
||||||
eventStreamService.resume();
|
|
||||||
// Go to the user's rooms list page
|
|
||||||
$location.url("home");
|
|
||||||
},
|
|
||||||
function(error) {
|
|
||||||
if (error.data) {
|
|
||||||
if (error.data.errcode === "M_USER_IN_USE") {
|
|
||||||
$scope.feedback = "Username already taken.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (error.status === 0) {
|
|
||||||
$scope.feedback = "Unable to talk to the server.";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
$scope.login_type = 'mxid'; // TODO: remember the user's preferred login_type
|
||||||
|
|
||||||
$scope.login = function() {
|
$scope.login = function() {
|
||||||
matrixService.setConfig({
|
matrixService.setConfig({
|
||||||
|
|
|
@ -1,56 +1,50 @@
|
||||||
<div ng-controller="LoginController" class="login">
|
<div ng-controller="LoginController" class="login">
|
||||||
<h1 id="logo">[matrix]</h1>
|
<div id="wrapper" class="loginWrapper">
|
||||||
|
|
||||||
<div id="page">
|
<a href ng-click="goToPage('/login')">
|
||||||
<div id="wrapper">
|
<img src="img/logo.png" width="240" height="102" alt="[matrix]" style="padding: 50px"/>
|
||||||
|
</a>
|
||||||
|
|
||||||
{{ feedback }}
|
<br/>
|
||||||
|
|
||||||
<h3>Register for an account:</h3>
|
<form id="loginForm" novalidate>
|
||||||
<form novalidate>
|
|
||||||
<input id="desired_user_name" size="70" type="text" auto-focus ng-model="account.desired_user_name" placeholder="User name (ex:bob)"/>
|
|
||||||
<br/>
|
|
||||||
<input id="pwd1" size="70" type="password" auto-focus ng-model="account.pwd1" placeholder="Type a password"/>
|
|
||||||
<br/>
|
|
||||||
<input id="pwd2" size="70" type="password" auto-focus ng-model="account.pwd2" placeholder="Re-type your password"/>
|
|
||||||
<br/>
|
|
||||||
<!-- New user registration -->
|
|
||||||
<div>
|
|
||||||
<br/>
|
|
||||||
<button ng-click="register()" ng-disabled="!account.desired_user_name || !account.homeserver || !account.pwd1 || !account.pwd2 || account.pwd1 !== account.pwd2">Register</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<h3>Got an account?</h3>
|
|
||||||
<form novalidate>
|
|
||||||
<!-- Login with an registered user -->
|
<!-- Login with an registered user -->
|
||||||
<div>{{ login_error_msg }} </div>
|
|
||||||
<div>
|
<div>
|
||||||
<input id="user_id" size="70" type="text" auto-focus ng-model="account.user_id" placeholder="User ID (ex:@bob:localhost or bob)"/>
|
Log in using:<br/>
|
||||||
|
|
||||||
|
<div ng-repeat="type in login_types">
|
||||||
|
<input type="radio" ng-model="$parent.login_type" value="{{ type }}" id="radio_{{ type }}"/>
|
||||||
|
<label for="radio_{{ type }}">{{ login_type_label[type] }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align: center">
|
||||||
<br/>
|
<br/>
|
||||||
<input id="password" size="70" type="password" ng-model="account.password" placeholder="Password"/><br />
|
<input id="user_id" size="32" type="text" ng-focus="true" ng-model="account.user_id" placeholder="{{ login_type_label[login_type] }}"/>
|
||||||
<br/>
|
<br/>
|
||||||
|
<input id="password" size="32" type="password" ng-model="account.password" placeholder="Password"/>
|
||||||
|
<br/><br/>
|
||||||
<button ng-click="login()" ng-disabled="!account.user_id || !account.password || !account.homeserver">Login</button>
|
<button ng-click="login()" ng-disabled="!account.user_id || !account.password || !account.homeserver">Login</button>
|
||||||
|
<br/><br/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="feedback">{{ feedback }} {{ login_error_msg }}</div>
|
||||||
|
|
||||||
|
<div id="serverConfig">
|
||||||
|
<label for="homeserver">Home Server:</label>
|
||||||
|
<input id="homeserver" size="32" type="text" ng-model="account.homeserver" placeholder="URL (e.g. http://matrix.org:8080)"/>
|
||||||
|
<div class="smallPrint">Your home server stores all your conversation and account data.</div>
|
||||||
|
<label for="identityServer">Identity Server:</label>
|
||||||
|
<input id="identityServer" size="32" type="text" ng-model="account.identityServer" placeholder="URL (e.g. http://matrix.org:8090)"/>
|
||||||
|
<div class="smallPrint">Matrix provides identity servers to track which emails etc. belong to which Matrix IDs.<br/>
|
||||||
|
Only http://matrix.org:8090 currently exists.</div>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<a href="#/register" style="padding-right: 3em">Create account</a>
|
||||||
|
<a href="#/reset_password">Forgotten password?</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<h3>Servers</h3>
|
|
||||||
<form novalidate>
|
|
||||||
<div>
|
|
||||||
Home Server:
|
|
||||||
<input id="homeserver" size="57" type="text" ng-model="account.homeserver" placeholder="Home server URL (ex: http://localhost:8080)"/>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div>
|
|
||||||
Identity Server:
|
|
||||||
<input id="identityServer" size="56" type="text" ng-model="account.identityServer" placeholder="Identity server URL (ex: http://localhost:8090)"/>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
</form>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -388,18 +388,13 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
|
||||||
matrixService.invite($scope.room_id, user_id).then(
|
matrixService.invite($scope.room_id, user_id).then(
|
||||||
function() {
|
function() {
|
||||||
console.log("Invited.");
|
console.log("Invited.");
|
||||||
$scope.feedback = "Request for invitation succeeds";
|
$scope.feedback = "Invite sent successfully";
|
||||||
},
|
},
|
||||||
function(reason) {
|
function(reason) {
|
||||||
$scope.feedback = "Failure: " + reason;
|
$scope.feedback = "Failure: " + reason;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Open the user profile page
|
|
||||||
$scope.goToUserPage = function(user_id) {
|
|
||||||
$location.url("/user/" + user_id);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.leaveRoom = function() {
|
$scope.leaveRoom = function() {
|
||||||
|
|
||||||
matrixService.leave($scope.room_id).then(
|
matrixService.leave($scope.room_id).then(
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
<div ng-controller="RoomController" data-ng-init="onInit()" class="room">
|
<div ng-controller="RoomController" data-ng-init="onInit()" class="room" style="height: 100%;">
|
||||||
<h1 id="roomLogo">[matrix]</h1>
|
|
||||||
|
|
||||||
<div id="page">
|
|
||||||
<div id="wrapper">
|
|
||||||
|
|
||||||
|
<div id="roomHeader">
|
||||||
|
<img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/>
|
||||||
<div id="roomName">
|
<div id="roomName">
|
||||||
{{ room_alias || room_id }}
|
{{ room_alias || room_id }}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="roomPage">
|
||||||
|
<div id="roomWrapper">
|
||||||
|
|
||||||
<div id="roomRecentsTableWrapper">
|
<div id="roomRecentsTableWrapper">
|
||||||
<div ng-include="'recents/recents.html'"></div>
|
<div ng-include="'recents/recents.html'"></div>
|
||||||
|
@ -15,16 +17,16 @@
|
||||||
<div id="usersTableWrapper">
|
<div id="usersTableWrapper">
|
||||||
<table id="usersTable">
|
<table id="usersTable">
|
||||||
<tr ng-repeat="member in members | orderMembersList">
|
<tr ng-repeat="member in members | orderMembersList">
|
||||||
<td class="userAvatar mouse-pointer" ng-click="goToUserPage(member.id)">
|
<td class="userAvatar mouse-pointer" ng-click="$parent.goToUserPage(member.id)" ng-class="member.membership == 'invite' ? 'invited' : ''">
|
||||||
<img class="userAvatarImage"
|
<img class="userAvatarImage"
|
||||||
ng-src="{{member.avatar_url || 'img/default-profile.jpg'}}"
|
ng-src="{{member.avatar_url || 'img/default-profile.png'}}"
|
||||||
alt="{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}"
|
alt="{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}"
|
||||||
title="{{ member.id }}"
|
title="{{ member.id }}"
|
||||||
width="80" height="80"/>
|
width="80" height="80"/>
|
||||||
<img class="userAvatarGradient" src="img/gradient.png" title="{{ member.id }}" width="80" height="24"/>
|
<img class="userAvatarGradient" src="img/gradient.png" title="{{ member.id }}" width="80" height="24"/>
|
||||||
<div class="userName">{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}<br/>{{ member.displayname ? "" : member.id.substr(member.id.indexOf(':')) }}</div>
|
<div class="userName">{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}<br/>{{ member.displayname ? "" : member.id.substr(member.id.indexOf(':')) }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="userPresence" ng-class="member.presenceState === 'online' ? 'online' : (member.presenceState === 'unavailable' ? 'unavailable' : '')">
|
<td class="userPresence" ng-class="(member.presenceState === 'online' ? 'online' : (member.presenceState === 'unavailable' ? 'unavailable' : '')) + ' ' + (member.membership == 'invite' ? 'invited' : '')">
|
||||||
{{ member.mtime_age + (now - member.last_updated) | duration }}<br/>{{ member.mtime_age ? "ago" : "" }}
|
{{ member.mtime_age + (now - member.last_updated) | duration }}<br/>{{ member.mtime_age ? "ago" : "" }}
|
||||||
</td>
|
</td>
|
||||||
</table>
|
</table>
|
||||||
|
@ -40,7 +42,7 @@
|
||||||
<div class="timestamp">{{ (msg.content.hsob_ts || msg.ts) | date:'MMM d HH:mm' }}</div>
|
<div class="timestamp">{{ (msg.content.hsob_ts || msg.ts) | date:'MMM d HH:mm' }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="avatar">
|
<td class="avatar">
|
||||||
<img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.jpg' }}" width="32" height="32"
|
<img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.png' }}" width="32" height="32"
|
||||||
ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"/>
|
ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"/>
|
||||||
</td>
|
</td>
|
||||||
<td ng-class="!msg.content.membership ? (msg.content.msgtype === 'm.emote' ? 'emote text' : 'text') : 'membership text'">
|
<td ng-class="!msg.content.membership ? (msg.content.msgtype === 'm.emote' ? 'emote text' : 'text') : 'membership text'">
|
||||||
|
@ -64,7 +66,7 @@
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="rightBlock">
|
<td class="rightBlock">
|
||||||
<img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.jpg' }}" width="32" height="32"
|
<img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.png' }}" width="32" height="32"
|
||||||
ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id !== state.user_id"/>
|
ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id !== state.user_id"/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -86,12 +88,12 @@
|
||||||
</td>
|
</td>
|
||||||
<td id="buttonsCell">
|
<td id="buttonsCell">
|
||||||
<button ng-click="send()">Send</button>
|
<button ng-click="send()">Send</button>
|
||||||
<button m-file-input="imageFileToSend">Image</button>
|
<button m-file-input="imageFileToSend" class="extraControls">Image</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div id="extraControls">
|
<div class="extraControls">
|
||||||
<span>
|
<span>
|
||||||
Invite a user:
|
Invite a user:
|
||||||
<input ng-model="userIDToInvite" size="32" type="text" placeholder="User ID (ex:@user:homeserver)"/>
|
<input ng-model="userIDToInvite" size="32" type="text" placeholder="User ID (ex:@user:homeserver)"/>
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
<div ng-controller="SettingsController" class="user" data-ng-init="onInit()">
|
<div ng-controller="SettingsController" class="user" data-ng-init="onInit()">
|
||||||
|
|
||||||
<div id="page">
|
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
|
|
||||||
<h3>Me</h3>
|
<div id="genericHeading">
|
||||||
|
<img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>Settings</h1>
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<form>
|
<form>
|
||||||
<div class="profile-avatar">
|
<div class="profile-avatar">
|
||||||
<img ng-src="{{ (null !== profile.avatarUrl) ? profile.avatarUrl : 'img/default-profile.jpg' }}" m-file-input="profile.avatarFile"/>
|
<img ng-src="{{ (null !== profile.avatarUrl) ? profile.avatarUrl : 'img/default-profile.png' }}" m-file-input="profile.avatarFile"/>
|
||||||
</div>
|
</div>
|
||||||
<div id="user-ids">
|
<div id="user-ids">
|
||||||
<input size="40" ng-model="profile.displayName" placeholder="Your name"/>
|
<input size="40" ng-model="profile.displayName" placeholder="Your display name"/>
|
||||||
|
<br/>
|
||||||
<button ng-disabled="(profile.displayName == profileOnServer.displayName) && (profile.avatarUrl == profileOnServer.avatarUrl)"
|
<button ng-disabled="(profile.displayName == profileOnServer.displayName) && (profile.avatarUrl == profileOnServer.avatarUrl)"
|
||||||
ng-click="saveProfile()">Save</button>
|
ng-click="saveProfile()">Save</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,7 +57,7 @@
|
||||||
To enable it, reset the notification setting for this web site into your browser settings.
|
To enable it, reset the notification setting for this web site into your browser settings.
|
||||||
</div>
|
</div>
|
||||||
<div ng-switch-when="default">
|
<div ng-switch-when="default">
|
||||||
<button ng-click="requestNotifications()">Click here to enable them</button>
|
<button ng-click="requestNotifications()" style="font-size: 14pt">Enable desktop notifications</button>
|
||||||
</div>
|
</div>
|
||||||
<div ng-switch-default="">
|
<div ng-switch-default="">
|
||||||
Sorry, your browser does not support notifications.
|
Sorry, your browser does not support notifications.
|
||||||
|
@ -74,4 +78,3 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
|
@ -25,14 +25,42 @@ angular.module('UserController', ['matrixService'])
|
||||||
avatar_url: undefined
|
avatar_url: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.user_id = matrixService.config().user_id;
|
||||||
|
|
||||||
matrixService.getDisplayName($scope.user.id).then(
|
matrixService.getDisplayName($scope.user.id).then(
|
||||||
function(response) {
|
function(response) {
|
||||||
$scope.user.displayname = response.data.displayname;
|
$scope.user.displayname = response.data.displayname;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
matrixService.getProfilePictureUrl($scope.user.id).then(
|
matrixService.getProfilePictureUrl($scope.user.id).then(
|
||||||
function(response) {
|
function(response) {
|
||||||
$scope.user.avatar_url = response.data.avatar_url;
|
$scope.user.avatar_url = response.data.avatar_url;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$scope.messageUser = function() {
|
||||||
|
|
||||||
|
// FIXME: create a new room every time, for now
|
||||||
|
|
||||||
|
matrixService.create(null, 'private').then(
|
||||||
|
function(response) {
|
||||||
|
// This room has been created. Refresh the rooms list
|
||||||
|
var room_id = response.data.room_id;
|
||||||
|
console.log("Created room with id: "+ room_id);
|
||||||
|
|
||||||
|
matrixService.invite(room_id, $scope.user.id).then(
|
||||||
|
function() {
|
||||||
|
$scope.feedback = "Invite sent successfully";
|
||||||
|
$scope.$parent.goToPage("/room/" + room_id);
|
||||||
|
},
|
||||||
|
function(reason) {
|
||||||
|
$scope.feedback = "Failure: " + JSON.stringify(reason);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(error) {
|
||||||
|
$scope.feedback = "Failure: " + JSON.stringify(error.data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
}]);
|
}]);
|
|
@ -1,31 +1,25 @@
|
||||||
<div ng-controller="UserController" class="user">
|
<div ng-controller="UserController" class="user">
|
||||||
<h1 id="logo">[matrix]</h1>
|
|
||||||
|
|
||||||
<div id="page">
|
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
|
|
||||||
<div>
|
<div id="genericHeading">
|
||||||
<form>
|
<img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/>
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="profile-avatar">
|
|
||||||
<img ng-src="{{ user.avatar_url || 'img/default-profile.jpg' }}"/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div id="user-ids">
|
|
||||||
<div id="user-displayname">{{ user.displayname }}</div>
|
|
||||||
<div>{{ user.id }}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h1>{{ user.displayname || user.id }}</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="profile-avatar">
|
||||||
|
<img ng-src="{{ user.avatar_url || 'img/default-profile.png' }}"/>
|
||||||
|
</div>
|
||||||
|
<div id="user-ids">
|
||||||
|
<div>{{ user.id }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button ng-hide="user.id == user_id" ng-click="messageUser()" style="font-size: 14pt; margin-top: 40px; margin-bottom: 40px">Start chat</button>
|
||||||
|
<br/>
|
||||||
{{ feedback }}
|
{{ feedback }}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
Loading…
Reference in a new issue