Merge branch 'soon'

pull/1/head
ansuz 7 years ago
commit 82b805fea6

@ -24,7 +24,7 @@
"ckeditor": "4.7.3", "ckeditor": "4.7.3",
"codemirror": "^5.19.0", "codemirror": "^5.19.0",
"requirejs": "2.3.5", "requirejs": "2.3.5",
"marked": "0.3.5", "marked": "0.5.0",
"rangy": "rangy-release#~1.3.0", "rangy": "rangy-release#~1.3.0",
"json.sortify": "~2.1.0", "json.sortify": "~2.1.0",
"secure-fabric.js": "secure-v1.7.9", "secure-fabric.js": "secure-v1.7.9",

@ -191,7 +191,10 @@ define([
// load the user's object using the legacy credentials // load the user's object using the legacy credentials
loadUserObject(opt, waitFor(function (err, rt) { loadUserObject(opt, waitFor(function (err, rt) {
if (err) { return void cb(err); } if (err) {
waitFor.abort();
return void cb(err);
}
// if a proxy is marked as deprecated, it is because someone had a non-owned drive // if a proxy is marked as deprecated, it is because someone had a non-owned drive
// but changed their password, and couldn't delete their old data. // but changed their password, and couldn't delete their old data.
@ -199,6 +202,7 @@ define([
// allow them to proceed. In time, their old drive should get deleted, since // allow them to proceed. In time, their old drive should get deleted, since
// it will should be pinned by anyone's drive. // it will should be pinned by anyone's drive.
if (rt.proxy[Constants.deprecatedKey]) { if (rt.proxy[Constants.deprecatedKey]) {
waitFor.abort();
return void cb('NO_SUCH_USER', res); return void cb('NO_SUCH_USER', res);
} }
@ -514,7 +518,22 @@ define([
if (testing) { return void proceed(result); } if (testing) { return void proceed(result); }
proceed(result); if (!(proxy.curvePrivate && proxy.curvePublic &&
proxy.edPrivate && proxy.edPublic)) {
console.log("recovering derived public/private keypairs");
// **** reset keys ****
proxy.curvePrivate = result.curvePrivate;
proxy.curvePublic = result.curvePublic;
proxy.edPrivate = result.edPrivate;
proxy.edPublic = result.edPublic;
}
setTimeout(function () {
Realtime.whenRealtimeSyncs(result.realtime, function () {
proceed(result);
});
});
}); });
}, 500); }, 500);
}, 200); }, 200);

@ -94,7 +94,7 @@ define([
]) ])
]) ])
]), ]),
h('div.cp-version-footer', "CryptPad v2.7.0 (Hedgehog)") h('div.cp-version-footer', "CryptPad v2.8.0 (Ibis)")
]); ]);
}; };
@ -615,6 +615,19 @@ define([
} }
]); ]);
var crowdFunding = AppConfig.disableCrowdfundingMessages ? undefined : h('button', [
Msg.crowdfunding_home1,
h('br'),
Msg.crowdfunding_home2
]);
$(crowdFunding).click(function () {
var a = document.createElement("a");
a.href = "https://opencollective.com/cryptpad/contribute";
a.target = "_blank";
a.rel = "noopener";
a.click();
});
return [ return [
h('div#cp-main', [ h('div#cp-main', [
infopageTopbar(), infopageTopbar(),
@ -629,6 +642,11 @@ define([
icons, icons,
more more
]) ])
]),
h('div.row', [
h('div.cp-crowdfunding', [
crowdFunding
])
]) ])
]), ]),
]), ]),
@ -810,7 +828,7 @@ define([
placeholder: Msg.login_password, placeholder: Msg.login_password,
}), }),
h('div.checkbox-container', [ h('div.checkbox-container', [
Pages.createCheckbox('import-recent', Msg.register_importRecent, true), Pages.createCheckbox('import-recent', Msg.register_importRecent),
]), ]),
h('div.extra', [ h('div.extra', [
h('button.login.first.btn', Msg.login_login) h('button.login.first.btn', Msg.login_login)

@ -43,6 +43,10 @@
//transform: scale(0.1); //transform: scale(0.1);
//transform: scale(1); //transform: scale(1);
h1, h2, h3 {
font-size: 1.5em;
}
.cp-corner-filler { .cp-corner-filler {
float: left; float: left;
clear: left; clear: left;
@ -94,6 +98,8 @@
.cp-corner-footer { .cp-corner-footer {
font-style: italic; font-style: italic;
font-size: 0.8em; font-size: 0.8em;
}
.cp-corner-footer, .cp-corner-text {
a { a {
color: @corner-link; color: @corner-link;
&:hover { &:hover {
@ -103,10 +109,11 @@
} }
button { button {
color: white;
border: 0px; border: 0px;
padding: 5px; padding: 5px;
color: @colortheme_base; color: @colortheme_base;
margin-left: 5px;
outline: none;
&.cp-corner-primary { &.cp-corner-primary {
background-color: @corner-button-ok; background-color: @corner-button-ok;
font-weight: bold; font-weight: bold;

@ -12,6 +12,7 @@
@import (reference) './font.less'; @import (reference) './font.less';
@import (reference) "./app-print.less"; @import (reference) "./app-print.less";
@import (reference) "./app-noscroll.less"; @import (reference) "./app-noscroll.less";
@import (reference) "./messenger.less";
.framework_main(@bg-color, @warn-color, @color) { .framework_main(@bg-color, @warn-color, @color) {
--LessLoader_require: LessLoader_currentFile(); --LessLoader_require: LessLoader_currentFile();
@ -36,6 +37,7 @@
.tippy_main(); .tippy_main();
.checkmark_main(20px); .checkmark_main(20px);
.password_main(); .password_main();
.messenger_main();
.creation_main( .creation_main(
@bg-color: @bg-color, @bg-color: @bg-color,
@color: @color @color: @color

@ -0,0 +1,372 @@
@import (reference) './avatar.less';
@import (reference) "./colortheme-all.less";
.messenger_main() {
--LessLoader_require: LessLoader_currentFile();
};
& {
@keyframes example {
0% {
background: rgba(0,0,0,0.1);
}
50% {
background: rgba(0,0,0,0.3);
}
100% {
background: rgba(0,0,0,0.1);
}
}
@button-border: 2px;
@bg-color: @colortheme_friends-bg;
@color: @colortheme_friends-color;
@room-height: 48px;
#cp-app-contacts-container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
min-height: 0;
&.ready {
background-size: cover;
background-position: center;
}
}
.cp-app-contacts-spinner {
display: none;
}
.cp-app-contacts-initializing {
.cp-app-contacts-spinner {
color: white;
display: block;
}
.cp-app-contacts-info {
display: none;
}
#cp-app-contacts-friendlist,
#cp-app-contacts-messaging {
display: flex;
justify-content: center;
align-items: center;
}
}
#cp-app-contacts-friendlist {
width: 200px;
height: 100%;
background-color: lighten(@bg-color, 10%);
overflow-y: auto;
display: flex;
flex-flow: column;
.cp-app-contacts-friend {
background: rgba(0,0,0,0.1);
padding: 5px;
margin: 10px;
margin-bottom: 0;
cursor: pointer;
position: relative;
height: @room-height;
.cp-app-contacts-right-col {
margin-left: 5px;
display: flex;
flex-flow: column;
flex: 1;
min-width: 0;
.cp-app-contacts-name {
white-space: nowrap;
}
}
&:hover {
background-color: rgba(0,0,0,0.3);
}
&.cp-app-contacts-notify {
animation: example 2s ease-in-out infinite;
}
}
.cp-app-contacts-remove {
cursor: pointer;
width: 20px;
&:hover {
color: darken(@color, 20%);
}
}
.cp-app-contacts-category {
display: flex;
flex-flow: column;
flex-grow: 0;
flex-shrink: 0;
.cp-app-contacts-category-title {
order: 1;
font-size: 18px;
margin: 0px 5px;
text-align: center;
background: rgba(0,0,0,0.1);
font-weight: bold;
height: 22px;
line-height: 22px;
}
.cp-app-contacts-category-content {
order: 2;
display: flex;
flex-flow: column-reverse;
padding-bottom: 10px;
&:empty {
display: none;
& ~ .cp-app-contacts-category-title {
display: none;
}
}
}
}
}
#cp-app-contacts-container.cp-app-contacts-inapp {
#cp-app-contacts-friendlist {
display: none;
/*
transition: width 0.2s ease-in-out 0.2s;
width: 68px;
.cp-app-contacts-friend {
.cp-app-contacts-right-col {
overflow: hidden;
}
}
.cp-app-contacts-category-title {
transition: font-size 0.2s ease-in-out 0.2s;
margin: 0px 2px;
font-size: 16px;
}
&:hover {
transition-delay: 1.5s;
width: 200px !important;
.cp-app-contacts-category-title {
transition-delay: 1.5s;
font-size: 18px;
}
}
*/
}
}
#cp-app-contacts-friendlist .cp-app-contacts-friend, #cp-app-contacts-messaging .cp-avatar {
.avatar_main(30px);
&.cp-avatar {
display: flex;
}
cursor: pointer;
color: @color;
media-tag {
img {
color: #000;
}
}
media-tag, .cp-avatar-default {
//margin-right: 5px;
flex-shrink: 0;
z-index: 1;
margin: 4px;
}
.cp-app-contacts-status {
//width: 5px;
display: inline-block;
position: absolute;
//right: 0;
//top: 0;
//bottom: 0;
//opacity: 0.7;
//background-color: #777;
/* width: (@room-height - 6px);
top: 3px;
bottom: 3px;
left: 3px;
border-radius: 100%;
*/
width: 10px;
height: 10px;
top: 0;
right: 0;
border-bottom-left-radius: 100%;
&.cp-app-contacts-online {
//background-color: green;
//background-color: white;
background-color: #c5ffa8;
}
&.cp-app-contacts-offline {
display: none;
//background-color: red;
}
}
}
.placeholder (@color: #bbb) {
&::-webkit-input-placeholder { /* WebKit, Blink, Edge */
color: @color;
}
&:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
color: @color;
opacity: 1;
}
&::-moz-placeholder { /* Mozilla Firefox 19+ */
color: @color;
opacity: 1;
}
&:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: @color;
}
&::-ms-input-placeholder { /* Microsoft Edge */
color: @color;
}
}
#cp-app-contacts-messaging {
flex: 1;
height: 100%;
background-color: lighten(@bg-color, 20%);
min-width: 0;
.cp-app-contacts-info {
padding: 20px;
}
.cp-app-contacts-header {
background-color: lighten(@bg-color, 15%);
padding: 0;
display: flex;
justify-content: space-between;
align-items: center;
height: 50px;
.hover () {
height: 100%;
line-height: 30px;
padding: 10px;
&:hover {
background-color: rgba(50,50,50,0.3);
}
}
.cp-avatar,
.cp-app-contacts-right-col {
flex: 1 1 auto;
}
.cp-app-contacts-remove-history {
.hover;
}
.cp-avatar {
margin: 10px;
}
.cp-app-contacts-more-history {
//display: none;
.hover;
&.cp-app-contacts-faded {
color: darken(@bg-color, 5%);
}
}
.cp-app-contacts-header-title {
padding: 10px;
flex: 1;
}
}
.cp-app-contacts-tips {
margin: 1em;
background-color: lighten(@bg-color, 15%);
font-size: 14px;
padding: 10px;
position: relative;
.cp-app-contacts-tips-close {
cursor: pointer;
position: absolute;
top: 2px;
right: 2px;
}
}
.cp-app-contacts-chat {
height: 100%;
display: flex;
flex-flow: column;
.cp-app-contacts-messages {
padding: 0 20px;
margin: 10px 0;
flex: 1;
overflow-x: auto;
.cp-app-contacts-message {
display: flex;
flex-wrap: wrap;
& > div {
padding: 0 10px;
}
.cp-app-contacts-content {
overflow: hidden;
word-wrap: break-word;
&> * {
margin: 0;
}
flex: 1;
min-width: 70%;
}
.cp-app-contacts-date {
display: none;
font-style: italic;
}
.cp-app-contacts-sender {
margin-top: 10px;
font-weight: bold;
background-color: rgba(0,0,0,0.1);
display: flex;
justify-content: space-between;
width: 100%;
}
.cp-app-contacts-time {
display: none;
font-size: 0.8em;
align-items: center;
color: @color;
}
&:hover {
.cp-app-contacts-time {
display: flex;
}
}
}
}
}
.cp-app-contacts-input {
background-color: lighten(@bg-color, 15%);
height: auto;
min-height: 50px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 5%;
textarea {
margin: 5px 0;
padding: 5px 10px;
border: none;
height: 54px; // 2 lines (22px height) + 2 margins (5px)
flex: 1;
background-color: darken(@bg-color, 10%);
color: @color;
resize: none;
overflow-y: auto;
.placeholder(#bbb);
&[disabled="true"] {
.placeholder(#999);
}
}
button {
height: 54px;
border-radius: 0;
border: none;
background-color: darken(@bg-color, 15%);
&:hover {
background-color: darken(@bg-color, 20%);
}
}
}
}
}

@ -72,6 +72,18 @@
.modal_main(); .modal_main();
}; };
& { & {
@keyframes notification {
0% {
background: rgba(0,0,0,0);
}
50% {
background: rgba(0,0,0,0.2);
}
100% {
background: rgba(0,0,0,0);
}
}
.toolbar_vars(); .toolbar_vars();
@toolbar_line-height: 32px; @toolbar_line-height: 32px;
@toolbar_top-height: 64px; @toolbar_top-height: 64px;
@ -134,9 +146,39 @@
} }
} }
.cp-toolbar-userlist-drawer { .cp-toolbar-chat-drawer {
background-color: @toolbar-bg-color; background-color: @toolbar-bg-color;
background-color: var(--toolbar-bg-color); background-color: var(--toolbar-bg-color);
font: @colortheme_app-font-size @colortheme_font;
width: 20%;
min-width: 200px;
display: block;
overflow-y: auto;
overflow-x: hidden;
padding: 0;
box-sizing: border-box;
position: relative;
order: -2;
resize: horizontal;
#cp-app-contacts-container {
height: 100%;
}
.cp-toolbar-chat-drawer-close {
color: @toolbar-color;
color: var(--toolbar-color);
position: absolute;
top: 0;
right: 1px;
font-size: 15px;
opacity: 0.5;
cursor: pointer;
text-shadow: unset;
&:hover {
opacity: 1;
}
}
}
.cp-toolbar-userlist-drawer {
font: @colortheme_app-font-size @colortheme_font; font: @colortheme_app-font-size @colortheme_font;
min-width: 175px; min-width: 175px;
width: 175px; width: 175px;
@ -145,6 +187,7 @@
overflow-x: hidden; overflow-x: hidden;
padding: 10px; padding: 10px;
box-sizing: border-box; box-sizing: border-box;
order: -3;
.cp-toolbar-userlist-drawer-close { .cp-toolbar-userlist-drawer-close {
position: absolute; position: absolute;
margin-top: -10px; margin-top: -10px;
@ -219,6 +262,14 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
button {
width: 20px;
font-size: 16px;
padding: 0;
border: none;
height: 20px;
cursor: pointer;
}
} }
.cp-toolbar-userlist-name-input { .cp-toolbar-userlist-name-input {
flex: 1; flex: 1;
@ -235,14 +286,6 @@
min-height: 0; min-height: 0;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.cp-toolbar-userlist-name-edit {
width: 20px;
font-size: 16px;
padding: 0;
border: none;
height: 20px;
cursor: pointer;
}
.cp-toolbar-userlist-friend { .cp-toolbar-userlist-friend {
padding: 0; padding: 0;
} }
@ -276,11 +319,15 @@
margin: 50px; margin: 50px;
} }
&> div { &> div {
flex: 1;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
align-content: center; align-content: center;
overflow-y: auto; }
&> div:last-child {
flex: unset;
margin: 50px 0;
} }
} }
@ -299,13 +346,15 @@
margin-left: 5px; margin-left: 5px;
} }
@media screen and (max-height: @browser_media-not-big) { @media screen and (max-height: @browser_media-medium-screen),
screen and (max-width: @browser_media-medium-screen) {
.cp-modal { .cp-modal {
& > p { & > p {
display: none; display: none;
} }
& > div { & > div {
align-content: unset; align-content: unset;
align-items: center;
li { li {
height: 40px; height: 40px;
width: 200px; width: 200px;
@ -313,9 +362,11 @@
align-items: center; align-items: center;
.fa { .fa {
font-size: 32px; font-size: 32px;
min-width: 50px;
} }
.cp-icons-name { .cp-icons-name {
height: auto; height: auto;
text-align: left;
} }
} }
} }
@ -344,7 +395,7 @@
color: @toolbar-color; color: @toolbar-color;
color: var(--toolbar-color); color: var(--toolbar-color);
} }
.cp-toolbar-userlist-name-edit { .cp-toolbar-userlist-button {
color: @toolbar-userlist-name-edit; color: @toolbar-userlist-name-edit;
color: var(--toolbar-userlist-name-edit); color: var(--toolbar-userlist-name-edit);
background: transparent; background: transparent;
@ -961,7 +1012,8 @@
height: @toolbar_line-height; height: @toolbar_line-height;
} }
#cp-toolbar-userlist-drawer-open { order: 1; } #cp-toolbar-userlist-drawer-open { order: 0; }
#cp-toolbar-chat-drawer-open { order: 1; }
.cp-toolbar-share-button { order: 2; } .cp-toolbar-share-button { order: 2; }
.cp-toolbar-spinner { order: 3; } .cp-toolbar-spinner { order: 3; }
@ -969,6 +1021,11 @@
width: 125px; width: 125px;
text-align: center; text-align: center;
} }
#cp-toolbar-chat-drawer-open button {
&.cp-toolbar-notification {
animation: notification 2s ease-in-out infinite;
}
}
.cp-toolbar-share-button { .cp-toolbar-share-button {
width: 50px; width: 50px;
text-align: center; text-align: center;

@ -180,6 +180,24 @@
} }
} }
} }
.cp-crowdfunding {
width: 100%;
text-align: center;
button {
outline: none;
background-color: @colortheme_logo-2;
color: @colortheme_base;
border: none;
padding: 10px 20px;
border-radius: 44px;
cursor: pointer;
&:hover {
background-color: lighten(@colortheme_logo-2, 3%);
}
}
}
@media (min-width: 576px) and (max-width: 767px) { @media (min-width: 576px) and (max-width: 767px) {
.container { .container {
padding-left: 0; padding-left: 0;

@ -135,6 +135,8 @@ define(function () {
out.userListButton = "Liste d'utilisateurs"; out.userListButton = "Liste d'utilisateurs";
out.chatButton = "Chat";
out.userAccountButton = "Votre compte"; out.userAccountButton = "Votre compte";
out.newButton = 'Nouveau'; out.newButton = 'Nouveau';
@ -363,7 +365,8 @@ define(function () {
out.contacts_remove = 'Supprimer ce contact'; out.contacts_remove = 'Supprimer ce contact';
out.contacts_confirmRemove = 'Êtes-vous sûr de vouloir supprimer <em>{0}</em> de vos contacts ?'; out.contacts_confirmRemove = 'Êtes-vous sûr de vouloir supprimer <em>{0}</em> de vos contacts ?';
out.contacts_typeHere = "Entrez un message ici..."; out.contacts_typeHere = "Entrez un message ici...";
out.contacts_warning = "Tout ce que vous tapez ici est permanent et visible par tous les utilisateurs actuels et futurs de ce pad. Soyez prudent avec vos données confidentielles !";
out.contacts_padTitle = "Chat";
out.contacts_info1 = "Voici vos contacts. Ici, vous pouvez :"; out.contacts_info1 = "Voici vos contacts. Ici, vous pouvez :";
out.contacts_info2 = "Cliquer sur le nom d'un contact pour discuter avec lui"; out.contacts_info2 = "Cliquer sur le nom d'un contact pour discuter avec lui";
@ -375,6 +378,12 @@ define(function () {
out.contacts_removeHistoryServerError = 'Une erreur est survenue lors de la supprimer de l\'historique du chat. Veuillez réessayer plus tard.'; out.contacts_removeHistoryServerError = 'Une erreur est survenue lors de la supprimer de l\'historique du chat. Veuillez réessayer plus tard.';
out.contacts_fetchHistory = "Récupérer l'historique plus ancien"; out.contacts_fetchHistory = "Récupérer l'historique plus ancien";
out.contacts_friends = "Amis";
out.contacts_rooms = "Salons";
out.contacts_leaveRoom = "Quitter ce salon";
out.contacts_online = "Un autre utilisateur est en ligne dans ce salon";
// File manager // File manager
out.fm_rootName = "Documents"; out.fm_rootName = "Documents";
@ -407,6 +416,7 @@ define(function () {
out.fm_noname = "Document sans titre"; out.fm_noname = "Document sans titre";
out.fm_emptyTrashDialog = "Êtes-vous sûr de vouloir vider la corbeille ?"; out.fm_emptyTrashDialog = "Êtes-vous sûr de vouloir vider la corbeille ?";
out.fm_removeSeveralPermanentlyDialog = "Êtes-vous sûr de vouloir supprimer ces {0} éléments de votre CryptDrive de manière permanente ?"; out.fm_removeSeveralPermanentlyDialog = "Êtes-vous sûr de vouloir supprimer ces {0} éléments de votre CryptDrive de manière permanente ?";
out.fm_removePermanentlyNote = "Les pads dont vous êtes le propriétaire seront supprimés du serveur.";
out.fm_removePermanentlyDialog = "Êtes-vous sûr de vouloir supprimer cet élément de votre CryptDrive de manière permanente ?"; out.fm_removePermanentlyDialog = "Êtes-vous sûr de vouloir supprimer cet élément de votre CryptDrive de manière permanente ?";
out.fm_deleteOwnedPad = "Êtes-vous sûr de vouloir supprimer définitivement ce pad du serveur ?"; out.fm_deleteOwnedPad = "Êtes-vous sûr de vouloir supprimer définitivement ce pad du serveur ?";
out.fm_deleteOwnedPads = "Êtes-vous sûr de vouloir supprimer définitivement ces pads du serveur ?"; out.fm_deleteOwnedPads = "Êtes-vous sûr de vouloir supprimer définitivement ces pads du serveur ?";
@ -1204,5 +1214,16 @@ define(function () {
out.autostore_forceSave = "Stocker le fichier dans votre CryptDrive"; // File upload modal out.autostore_forceSave = "Stocker le fichier dans votre CryptDrive"; // File upload modal
out.autostore_notAvailable = "Vous devez stocker ce pad dans votre CryptDrive avant de pouvoir utiliser cette fonctionnalité."; out.autostore_notAvailable = "Vous devez stocker ce pad dans votre CryptDrive avant de pouvoir utiliser cette fonctionnalité.";
// Crowdfunding messages
out.crowdfunding_home1 = "CryptPad a besoin d'aide !";
out.crowdfunding_home2 = "Cliquez pour découvrir notre campagne de financement participatif.";
out.crowdfunding_popup_text = "<h3>Aider CryptPad</h3>" +
"Pour vous assurer que CryptPad soit activement développé, nous vous invitons à supporter le projet via la " +
'<a href="https://opencollective.com/cryptpad">page OpenCollective</a>, où vous pouvez trouver notre <b>Roadmap</b> et nos <b>objectifs de financement</b>.';
out.crowdfunding_popup_yes = "Voir la page";
out.crowdfunding_popup_no = "Pas maintenant";
out.crowdfunding_popup_never = "Ne plus demander";
return out; return out;
}); });

@ -136,6 +136,8 @@ define(function () {
out.userListButton = "User list"; out.userListButton = "User list";
out.chatButton = "Chat";
out.userAccountButton = "Your account"; out.userAccountButton = "Your account";
out.newButton = 'New'; out.newButton = 'New';
@ -365,6 +367,8 @@ define(function () {
out.contacts_remove = 'Remove this contact'; out.contacts_remove = 'Remove this contact';
out.contacts_confirmRemove = 'Are you sure you want to remove <em>{0}</em> from your contacts?'; out.contacts_confirmRemove = 'Are you sure you want to remove <em>{0}</em> from your contacts?';
out.contacts_typeHere = "Type a message here..."; out.contacts_typeHere = "Type a message here...";
out.contacts_warning = "Everything you type here is persistent and available to all the existing and future users of this pad. Be careful with sensitive information!";
out.contacts_padTitle = "Chat";
out.contacts_info1 = "These are your contacts. From here, you can:"; out.contacts_info1 = "These are your contacts. From here, you can:";
out.contacts_info2 = "Click your contact's icon to chat with them"; out.contacts_info2 = "Click your contact's icon to chat with them";
@ -376,6 +380,12 @@ define(function () {
out.contacts_removeHistoryServerError = 'There was an error while removing your chat history. Try again later'; out.contacts_removeHistoryServerError = 'There was an error while removing your chat history. Try again later';
out.contacts_fetchHistory = "Retrieve older history"; out.contacts_fetchHistory = "Retrieve older history";
out.contacts_friends = "Friends";
out.contacts_rooms = "Rooms";
out.contacts_leaveRoom = "Leave this room";
out.contacts_online = "Another user from this room is online";
// File manager // File manager
out.fm_rootName = "Documents"; out.fm_rootName = "Documents";
@ -407,12 +417,13 @@ define(function () {
out.fm_openParent = "Show in folder"; out.fm_openParent = "Show in folder";
out.fm_noname = "Untitled Document"; out.fm_noname = "Untitled Document";
out.fm_emptyTrashDialog = "Are you sure you want to empty the trash?"; out.fm_emptyTrashDialog = "Are you sure you want to empty the trash?";
out.fm_removeSeveralPermanentlyDialog = "Are you sure you want to remove these {0} elements from your CryptDrive permanently?"; out.fm_removeSeveralPermanentlyDialog = "Are you sure you want to permanently remove these {0} elements from your CryptDrive?";
out.fm_removePermanentlyDialog = "Are you sure you want to remove that element from your CryptDrive permanently?"; out.fm_removePermanentlyNote = "Owned pads will be removed from the server if you continue.";
out.fm_removePermanentlyDialog = "Are you sure you want to permanently remove that element from your CryptDrive?";
out.fm_removeSeveralDialog = "Are you sure you want to move these {0} elements to the trash?"; out.fm_removeSeveralDialog = "Are you sure you want to move these {0} elements to the trash?";
out.fm_removeDialog = "Are you sure you want to move {0} to the trash?"; out.fm_removeDialog = "Are you sure you want to move {0} to the trash?";
out.fm_deleteOwnedPad = "Are you sure you want to remove permanently this pad from the server?"; out.fm_deleteOwnedPad = "Are you sure you want to permanently remove this pad from the server?";
out.fm_deleteOwnedPads = "Are you sure you want to remove permanently these pads from the server?"; out.fm_deleteOwnedPads = "Are you sure you want to permanently remove these pads from the server?";
out.fm_restoreDialog = "Are you sure you want to restore {0} to its previous location?"; out.fm_restoreDialog = "Are you sure you want to restore {0} to its previous location?";
out.fm_unknownFolderError = "The selected or last visited directory no longer exist. Opening the parent folder..."; out.fm_unknownFolderError = "The selected or last visited directory no longer exist. Opening the parent folder...";
out.fm_contextMenuError = "Unable to open the context menu for that element. If the problem persist, try to reload the page."; out.fm_contextMenuError = "Unable to open the context menu for that element. If the problem persist, try to reload the page.";
@ -1253,5 +1264,16 @@ define(function () {
out.autostore_forceSave = "Store the file in your CryptDrive"; // File upload modal out.autostore_forceSave = "Store the file in your CryptDrive"; // File upload modal
out.autostore_notAvailable = "You must store this pad in your CryptDrive before being able to use this feature."; // Properties/tags/move to trash out.autostore_notAvailable = "You must store this pad in your CryptDrive before being able to use this feature."; // Properties/tags/move to trash
// Crowdfunding messages
out.crowdfunding_home1 = "CryptPad needs your help!";
out.crowdfunding_home2 = "Click to learn about our crowdfunding campaign.";
out.crowdfunding_popup_text = "<h3>We need your help!</h3>" +
"To ensure that CryptPad is actively developed, consider supporting the project via the " +
'<a href="https://opencollective.com/cryptpad">OpenCollective page</a>, where you can see our <b>Roadmap</b> and <b>Funding goals</b>.';
out.crowdfunding_popup_yes = "Go to OpenCollective";
out.crowdfunding_popup_no = "Not now";
out.crowdfunding_popup_never = "Don't ask me again";
return out; return out;
}); });

@ -1,7 +1,7 @@
{ {
"name": "cryptpad", "name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server", "description": "realtime collaborative visual editor with zero knowlege server",
"version": "2.7.0", "version": "2.8.0",
"license": "AGPL-3.0+", "license": "AGPL-3.0+",
"repository": { "repository": {
"type": "git", "type": "git",

@ -19,17 +19,16 @@
flex-flow: column; flex-flow: column;
height: 100%; height: 100%;
min-height: 100%; min-height: 100%;
width: 50%;
min-width: 20%; min-width: 20%;
max-width: 80%; max-width: 80%;
resize: horizontal; resize: horizontal;
overflow: hidden; overflow: hidden;
width: 50%;
&.cp-app-code-fullpage { &.cp-app-code-fullpage {
max-width: 100%; max-width: 100%;
resize: none; resize: none;
flex: 1; flex: 1;
} }
} }
.CodeMirror { .CodeMirror {
flex: 1; flex: 1;
@ -51,9 +50,13 @@
#cp-app-code-container { display: none; } #cp-app-code-container { display: none; }
#cp-app-code-preview { border: 0; } #cp-app-code-preview { border: 0; }
} }
&.cp-chat-visible {
#cp-app-code-container {
width: 35%;
}
}
} }
#cp-app-code-preview { #cp-app-code-preview {
flex: 1;
padding: 5px 20px; padding: 5px 20px;
overflow: auto; overflow: auto;
display: inline-block; display: inline-block;
@ -63,6 +66,7 @@
font-family: Calibri,Ubuntu,sans-serif; font-family: Calibri,Ubuntu,sans-serif;
word-wrap: break-word; word-wrap: break-word;
position: relative; position: relative;
flex: 1;
media-tag { media-tag {
* { * {
max-width:100%; max-width:100%;

@ -13,6 +13,8 @@ define(function () {
storageKey: 'filesData', storageKey: 'filesData',
tokenKey: 'loginToken', tokenKey: 'loginToken',
displayPadCreationScreen: 'displayPadCreationScreen', displayPadCreationScreen: 'displayPadCreationScreen',
deprecatedKey: 'deprecated' deprecatedKey: 'deprecated',
// Sub
plan: 'CryptPad_plan'
}; };
}); });

@ -892,7 +892,7 @@ define([
h('div.cp-corner-filler', { style: "width:60px;" }), h('div.cp-corner-filler', { style: "width:60px;" }),
h('div.cp-corner-filler', { style: "width:40px;" }), h('div.cp-corner-filler', { style: "width:40px;" }),
h('div.cp-corner-filler', { style: "width:20px;" }), h('div.cp-corner-filler', { style: "width:20px;" }),
h('div.cp-corner-text', text), Pages.setHTML(h('div.cp-corner-text'), text),
h('div.cp-corner-actions', actions), h('div.cp-corner-actions', actions),
Pages.setHTML(h('div.cp-corner-footer'), footer) Pages.setHTML(h('div.cp-corner-footer'), footer)
]); ]);

@ -150,7 +150,8 @@ define([
} }
cfg.friendComplete({ cfg.friendComplete({
logText: Messages.contacts_added, logText: Messages.contacts_added,
netfluxId: sender netfluxId: sender,
friend: msgData
}); });
var msg = ["FRIEND_REQ_ACK", chan]; var msg = ["FRIEND_REQ_ACK", chan];
var msgStr = Crypto.encrypt(JSON.stringify(msg), key); var msgStr = Crypto.encrypt(JSON.stringify(msg), key);
@ -163,7 +164,7 @@ define([
if (i !== -1) { pendingRequests.splice(i, 1); } if (i !== -1) { pendingRequests.splice(i, 1); }
cfg.friendComplete({ cfg.friendComplete({
logText: Messages.contacts_rejected, logText: Messages.contacts_rejected,
netfluxId: sender netfluxId: sender,
}); });
cfg.updateMetadata(); cfg.updateMetadata();
return; return;
@ -180,7 +181,8 @@ define([
} }
cfg.friendComplete({ cfg.friendComplete({
logText: Messages.contacts_added, logText: Messages.contacts_added,
netfluxId: sender netfluxId: sender,
friend: data
}); });
}); });
return; return;

@ -5,7 +5,9 @@ define([
'/common/common-util.js', '/common/common-util.js',
'/common/common-realtime.js', '/common/common-realtime.js',
'/common/common-constants.js', '/common/common-constants.js',
], function (Crypto, Curve, Hash, Util, Realtime, Constants) {
'/bower_components/nthen/index.js',
], function (Crypto, Curve, Hash, Util, Realtime, Constants, nThen) {
'use strict'; 'use strict';
var Msg = { var Msg = {
inputs: [], inputs: [],
@ -52,7 +54,7 @@ define([
var msgAlreadyKnown = function (channel, sig) { var msgAlreadyKnown = function (channel, sig) {
return channel.messages.some(function (message) { return channel.messages.some(function (message) {
return message[0] === sig; return message.sig === sig;
}); });
}; };
@ -65,6 +67,7 @@ define([
update: [], update: [],
friend: [], friend: [],
unfriend: [], unfriend: [],
event: []
}, },
range_requests: {}, range_requests: {},
}; };
@ -73,6 +76,12 @@ define([
messenger.handlers[type].forEach(g); messenger.handlers[type].forEach(g);
}; };
var emit = function (ev, data) {
eachHandler('event', function (f) {
f(ev, data);
});
};
messenger.on = function (type, f) { messenger.on = function (type, f) {
var stack = messenger.handlers[type]; var stack = messenger.handlers[type];
if (!Array.isArray(stack)) { if (!Array.isArray(stack)) {
@ -95,20 +104,26 @@ define([
Msg.hk = network.historyKeeper; Msg.hk = network.historyKeeper;
var friends = getFriendList(proxy); var friends = getFriendList(proxy);
var getChannel = function (curvePublic) { var getChannel = function (chanId) {
var friend = friends[curvePublic];
if (!friend) { return; }
var chanId = friend.channel;
if (!chanId) { return; }
return channels[chanId]; return channels[chanId];
}; };
var initRangeRequest = function (txid, curvePublic, sig, cb) { var getFriendFromChannel = function (id) {
var friend;
for (var k in friends) {
if (friends[k].channel === id) {
friend = friends[k];
break;
}
}
return friend;
};
var initRangeRequest = function (txid, chanId, cb) {
messenger.range_requests[txid] = { messenger.range_requests[txid] = {
messages: [], messages: [],
cb: cb, cb: cb,
curvePublic: curvePublic, chanId: chanId,
sig: sig,
}; };
}; };
@ -120,24 +135,22 @@ define([
delete messenger.range_requests[txid]; delete messenger.range_requests[txid];
}; };
messenger.getMoreHistory = function (curvePublic, hash, count, cb) { messenger.getMoreHistory = function (chanId, hash, count, cb) {
if (typeof(cb) !== 'function') { return; } if (typeof(cb) !== 'function') { return; }
if (typeof(hash) !== 'string') { if (typeof(hash) !== 'string') {
// FIXME hash is not necessarily defined. // Channel is empty!
// What does this mean? return void cb(void 0, []);
console.error("not sure what to do here");
return;
} }
var chan = getChannel(curvePublic); var chan = getChannel(chanId);
if (typeof(chan) === 'undefined') { if (typeof(chan) === 'undefined') {
console.error("chan is undefined. we're going to have a problem here"); console.error("chan is undefined. we're going to have a problem here");
return; return;
} }
var txid = Util.uid(); var txid = Util.uid();
initRangeRequest(txid, curvePublic, hash, cb); initRangeRequest(txid, chanId, cb);
var msg = [ 'GET_HISTORY_RANGE', chan.id, { var msg = [ 'GET_HISTORY_RANGE', chan.id, {
from: hash, from: hash,
count: count, count: count,
@ -151,38 +164,80 @@ define([
}); });
}; };
var getCurveForChannel = function (id) { /*var getCurveForChannel = function (id) {
var channel = channels[id]; var channel = channels[id];
if (!channel) { return; } if (!channel) { return; }
return channel.curve; return channel.curve;
}; };*/
/*messenger.getChannelHead = function (id, cb) {
var channel = getChannel(id);
if (channel.isFriendChat) {
var friend;
for (var k in friends) {
if (friends[k].channel === id) {
friend = friends[k];
break;
}
}
if (!friend) { return void cb('NO_SUCH_FRIEND'); }
cb(void 0, friend.lastKnownHash);
} else {
// TODO room
cb('NOT_IMPLEMENTED');
}
};*/
messenger.getChannelHead = function (curvePublic, cb) { messenger.setChannelHead = function (id, hash, cb) {
var friend = friends[curvePublic]; var channel = getChannel(id);
if (!friend) { return void cb('NO_SUCH_FRIEND'); } if (channel.isFriendChat) {
cb(void 0, friend.lastKnownHash); var friend = getFriendFromChannel(id);
if (!friend) { return void cb('NO_SUCH_FRIEND'); }
friend.lastKnownHash = hash;
} else if (channel.isPadChat) {
// Nothing to do
} else {
// TODO room
return void cb('NOT_IMPLEMENTED');
}
cb();
}; };
messenger.setChannelHead = function (curvePublic, hash, cb) { // Make sure the data we have about our friends are up-to-date when we see them online
var friend = friends[curvePublic]; var checkFriendData = function (curve, data, channel) {
if (!friend) { return void cb('NO_SUCH_FRIEND'); } if (curve === proxy.curvePublic) { return; }
friend.lastKnownHash = hash; var friend = getFriend(proxy, curve);
cb(); if (!friend) { return; }
var types = [];
Object.keys(data).forEach(function (k) {
if (friend[k] !== data[k]) {
types.push(k);
friend[k] = data[k];
}
});
eachHandler('update', function (f) {
f(clone(data), types, channel);
});
}; };
// Id message allows us to map a netfluxId with a public curve key // Id message allows us to map a netfluxId with a public curve key
var onIdMessage = function (msg, sender) { var onIdMessage = function (msg, sender) {
var channel; var channel, parsed0;
var isId = Object.keys(channels).some(function (chanId) {
if (channels[chanId].userList.indexOf(sender) !== -1) {
channel = channels[chanId];
return true;
}
});
if (!isId) { return; } try {
parsed0 = JSON.parse(msg);
channel = channels[parsed0.channel];
if (!channel) { return; }
if (channel.userList.indexOf(sender) === -1) { return; }
} catch (e) {
console.log(msg);
console.error(e);
// Not an ID message
return;
}
var decryptedMsg = channel.encryptor.decrypt(msg); var decryptedMsg = channel.encryptor.decrypt(parsed0.msg);
if (decryptedMsg === null) { if (decryptedMsg === null) {
return void console.error("Failed to decrypt message"); return void console.error("Failed to decrypt message");
@ -206,20 +261,26 @@ define([
// the sender field. This is to prevent replay attacks. // the sender field. This is to prevent replay attacks.
if (parsed[2] !== sender || !parsed[1]) { return; } if (parsed[2] !== sender || !parsed[1]) { return; }
channel.mapId[sender] = parsed[1]; channel.mapId[sender] = parsed[1];
checkFriendData(parsed[1].curvePublic, parsed[1], channel.id);
eachHandler('join', function (f) { eachHandler('join', function (f) {
f(parsed[1], channel.id); f(parsed[1], channel.id);
}); });
if (parsed[0] !== Types.mapId) { return; } // Don't send your key if it's already an ACK if (parsed[0] !== Types.mapId) { return; } // Don't send your key if it's already an ACK
// Answer with your own key // Answer with your own key
var rMsg = [Types.mapIdAck, proxy.curvePublic, channel.wc.myID]; var myData = createData(proxy);
delete myData.channel;
var rMsg = [Types.mapIdAck, myData, channel.wc.myID];
var rMsgStr = JSON.stringify(rMsg); var rMsgStr = JSON.stringify(rMsg);
var cryptMsg = channel.encryptor.encrypt(rMsgStr); var cryptMsg = channel.encryptor.encrypt(rMsgStr);
network.sendto(sender, cryptMsg); var data = {
channel: channel.id,
msg: cryptMsg
};
network.sendto(sender, JSON.stringify(data));
}; };
var orderMessages = function (curvePublic, new_messages /*, sig */) { var orderMessages = function (channel, new_messages) {
var channel = getChannel(curvePublic);
var messages = channel.messages; var messages = channel.messages;
// TODO improve performance, guarantee correct ordering // TODO improve performance, guarantee correct ordering
@ -236,9 +297,9 @@ define([
}; };
var pushMsg = function (channel, cryptMsg) { var pushMsg = function (channel, cryptMsg) {
var msg = channel.encryptor.decrypt(cryptMsg);
var sig = cryptMsg.slice(0, 64); var sig = cryptMsg.slice(0, 64);
if (msgAlreadyKnown(channel, sig)) { return; } if (msgAlreadyKnown(channel, sig)) { return; }
var msg = channel.encryptor.decrypt(cryptMsg);
var parsedMsg = JSON.parse(msg); var parsedMsg = JSON.parse(msg);
var curvePublic; var curvePublic;
@ -250,43 +311,38 @@ define([
author: parsedMsg[1], author: parsedMsg[1],
time: parsedMsg[2], time: parsedMsg[2],
text: parsedMsg[3], text: parsedMsg[3],
channel: channel.id,
name: parsedMsg[4] // Display name for multi-user rooms
// this makes debugging a whole lot easier // this makes debugging a whole lot easier
curve: getCurveForChannel(channel.id), //curve: getCurveForChannel(channel.id),
}; };
channel.messages.push(res); channel.messages.push(res);
eachHandler('message', function (f) { if (!joining[channel.id]) {
f(res); // Channel is ready
}); eachHandler('message', function (f) {
f(res);
});
}
return true; return true;
} }
if (parsedMsg[0] === Types.update) { if (parsedMsg[0] === Types.update) {
if (parsedMsg[1] === proxy.curvePublic) { return; } checkFriendData(parsedMsg[1], parsedMsg[3], channel.id);
curvePublic = parsedMsg[1];
var newdata = parsedMsg[3];
var data = getFriend(proxy, parsedMsg[1]);
var types = [];
Object.keys(newdata).forEach(function (k) {
if (data[k] !== newdata[k]) {
types.push(k);
data[k] = newdata[k];
}
});
eachHandler('update', function (f) {
f(clone(newdata), curvePublic);
});
return; return;
} }
if (parsedMsg[0] === Types.unfriend) { if (parsedMsg[0] === Types.unfriend) {
curvePublic = parsedMsg[1]; curvePublic = parsedMsg[1];
delete friends[curvePublic];
removeFromFriendList(parsedMsg[1], function () { // If this a removal from our part by in another tab, do nothing.
// The channel is already closed in the proxy.on('remove') part
if (curvePublic === proxy.curvePublic) { return; }
removeFromFriendList(curvePublic, function () {
channel.wc.leave(Types.unfriend); channel.wc.leave(Types.unfriend);
delete channels[channel.id];
eachHandler('unfriend', function (f) { eachHandler('unfriend', function (f) {
f(curvePublic); f(curvePublic, false);
}); });
}); });
return; return;
@ -324,7 +380,7 @@ define([
}); });
}); });
eachHandler('update', function (f) { eachHandler('update', function (f) {
f(myData, myData.curvePublic); f(myData, ['displayName', 'profile', 'avatar']);
}); });
friends.me = myData; friends.me = myData;
} }
@ -352,12 +408,26 @@ define([
return void console.error("received response to unknown request"); return void console.error("received response to unknown request");
} }
if (!req.cb) {
// This is the initial history for a pad chat
if (type === 'HISTORY_RANGE') {
if (!getChannel(req.chanId)) { return; }
if (!Array.isArray(parsed[2])) { return; }
pushMsg(getChannel(req.chanId), parsed[2][4]);
} else if (type === 'HISTORY_RANGE_END') {
if (!getChannel(req.chanId)) { return; }
getChannel(req.chanId).ready = true;
onChannelReady(req.chanId);
return;
}
return;
}
if (type === 'HISTORY_RANGE') { if (type === 'HISTORY_RANGE') {
req.messages.push(parsed[2]); req.messages.push(parsed[2]);
} else if (type === 'HISTORY_RANGE_END') { } else if (type === 'HISTORY_RANGE_END') {
// process all the messages (decrypt) // process all the messages (decrypt)
var curvePublic = req.curvePublic; var channel = getChannel(req.chanId);
var channel = getChannel(curvePublic);
var decrypted = req.messages.map(function (msg) { var decrypted = req.messages.map(function (msg) {
if (msg[2] !== 'MSG') { return; } if (msg[2] !== 'MSG') { return; }
@ -371,6 +441,8 @@ define([
return null; return null;
} }
}).filter(function (decrypted) { }).filter(function (decrypted) {
if (!decrypted.d || decrypted.d[0] !== Types.message) { return; }
if (msgAlreadyKnown(channel, decrypted.sig)) { return; }
return decrypted; return decrypted;
}).map(function (O) { }).map(function (O) {
return { return {
@ -379,11 +451,12 @@ define([
author: O.d[1], author: O.d[1],
time: O.d[2], time: O.d[2],
text: O.d[3], text: O.d[3],
curve: curvePublic, channel: req.chanId,
name: O.d[4]
}; };
}); });
orderMessages(curvePublic, decrypted, req.sig); orderMessages(channel, decrypted);
req.cb(void 0, decrypted); req.cb(void 0, decrypted);
return deleteRangeRequest(txid); return deleteRangeRequest(txid);
} else { } else {
@ -395,20 +468,17 @@ define([
if ((parsed.validateKey || parsed.owners) && parsed.channel) { if ((parsed.validateKey || parsed.owners) && parsed.channel) {
return; return;
} }
// End of initial history
if (parsed.state && parsed.state === 1 && parsed.channel) { if (parsed.state && parsed.state === 1 && parsed.channel) {
if (channels[parsed.channel]) { if (channels[parsed.channel]) {
// parsed.channel is Ready // parsed.channel is Ready
// channel[parsed.channel].ready(); // channel[parsed.channel].ready();
channels[parsed.channel].ready = true; channels[parsed.channel].ready = true;
onChannelReady(parsed.channel); onChannelReady(parsed.channel);
var updateTypes = channels[parsed.channel].updateOnReady;
if (updateTypes) {
//channels[parsed.channel].updateUI(updateTypes);
}
} }
return; return;
} }
// Initial history message
var chan = parsed[3]; var chan = parsed[3];
if (!chan || !channels[chan]) { return; } if (!chan || !channels[chan]) { return; }
pushMsg(channels[chan], parsed[4]); pushMsg(channels[chan], parsed[4]);
@ -440,7 +510,7 @@ define([
if (!data) { if (!data) {
// friend is not valid // friend is not valid
console.error('friend is not valid'); console.error('friend is not valid');
return; return void cb('INVALID_FRIEND');
} }
var channel = channels[data.channel]; var channel = channels[data.channel];
@ -458,12 +528,13 @@ define([
var msgStr = JSON.stringify(msg); var msgStr = JSON.stringify(msg);
var cryptMsg = channel.encryptor.encrypt(msgStr); var cryptMsg = channel.encryptor.encrypt(msgStr);
// TODO emit remove_friend event?
try { try {
channel.wc.bcast(cryptMsg).then(function () { channel.wc.bcast(cryptMsg).then(function () {
delete friends[curvePublic]; removeFromFriendList(curvePublic, function () {
delete channels[curvePublic]; delete channels[channel.id];
Realtime.whenRealtimeSyncs(realtime, function () { eachHandler('unfriend', function (f) {
f(curvePublic, true);
});
cb(); cb();
}); });
}, function (err) { }, function (err) {
@ -476,9 +547,27 @@ define([
}; };
var getChannelMessagesSince = function (chan, data, keys) { var getChannelMessagesSince = function (chan, data, keys) {
console.log('Fetching [%s] messages since [%s]', data.curvePublic, data.lastKnownHash || ''); console.log('Fetching [%s] messages since [%s]', chan.id, data.lastKnownHash || '');
if (chan.isPadChat) {
// We need to use GET_HISTORY_RANGE to make sure we won't get the full history
var txid = Util.uid();
initRangeRequest(txid, chan.id, undefined);
var msg0 = ['GET_HISTORY_RANGE', chan.id, {
//from: hash,
count: 10,
txid: txid,
}
];
network.sendto(network.historyKeeper, JSON.stringify(msg0)).then(function () {
}, function (err) {
throw new Error(err);
});
return;
}
var cfg = { var cfg = {
validateKey: keys.validateKey, validateKey: keys ? keys.validateKey : undefined,
owners: [proxy.edPublic, data.edPublic], owners: [proxy.edPublic, data.edPublic],
lastKnownHash: data.lastKnownHash lastKnownHash: data.lastKnownHash
}; };
@ -489,79 +578,88 @@ define([
}); });
}; };
var openFriendChannel = function (data, f) { var openChannel = function (data) {
var keys = Curve.deriveKeys(data.curvePublic, proxy.curvePrivate); var keys = data.keys;
var encryptor = Curve.createEncryptor(keys); var encryptor = data.encryptor || Curve.createEncryptor(keys);
network.join(data.channel).then(function (chan) { var channel = {
var channel = channels[data.channel] = { id: data.channel,
id: data.channel, isFriendChat: data.isFriendChat,
sending: false, isPadChat: data.isPadChat,
friendEd: f, sending: false,
keys: keys, encryptor: encryptor,
curve: data.curvePublic, messages: [],
encryptor: encryptor, userList: [],
messages: [], mapId: {},
wc: chan, };
userList: [],
mapId: {},
send: function (payload, cb) {
if (!network.webChannels.some(function (wc) {
if (wc.id === channel.wc.id) { return true; }
})) {
return void cb('NO_SUCH_CHANNEL');
}
var msg = [Types.message, proxy.curvePublic, +new Date(), payload]; var onJoining = function (peer) {
var msgStr = JSON.stringify(msg); if (peer === Msg.hk) { return; }
var cryptMsg = channel.encryptor.encrypt(msgStr); if (channel.userList.indexOf(peer) !== -1) { return; }
channel.userList.push(peer);
channel.wc.bcast(cryptMsg).then(function () { // Join event will be sent once we are able to ID this peer
pushMsg(channel, cryptMsg); var myData = createData(proxy);
cb(); delete myData.channel;
}, function (err) { var msg = [Types.mapId, myData, channel.wc.myID];
cb(err); var msgStr = JSON.stringify(msg);
}); var cryptMsg = channel.encryptor.encrypt(msgStr);
} var data = {
channel: channel.id,
msg: cryptMsg
}; };
network.sendto(peer, JSON.stringify(data));
};
var onLeaving = function (peer) {
var i = channel.userList.indexOf(peer);
while (i !== -1) {
channel.userList.splice(i, 1);
i = channel.userList.indexOf(peer);
}
// update status
var otherData = channel.mapId[peer];
if (!otherData) { return; }
// Make sure the leaving user is not connected with another netflux id
if (channel.userList.some(function (nId) {
return channel.mapId[nId]
&& channel.mapId[nId].curvePublic === otherData.curvePublic;
})) { return; }
// Send the notification
eachHandler('leave', function (f) {
f(otherData, channel.id);
});
};
var onOpen = function (chan) {
channel.wc = chan;
channels[data.channel] = channel;
chan.on('message', function (msg, sender) { chan.on('message', function (msg, sender) {
onMessage(msg, sender, chan); onMessage(msg, sender, chan);
}); });
var onJoining = function (peer) {
if (peer === Msg.hk) { return; }
if (channel.userList.indexOf(peer) !== -1) { return; }
channel.userList.push(peer);
var msg = [Types.mapId, proxy.curvePublic, chan.myID];
var msgStr = JSON.stringify(msg);
var cryptMsg = channel.encryptor.encrypt(msgStr);
network.sendto(peer, cryptMsg);
};
chan.members.forEach(function (peer) { chan.members.forEach(function (peer) {
if (peer === Msg.hk) { return; } if (peer === Msg.hk) { return; }
if (channel.userList.indexOf(peer) !== -1) { return; } if (channel.userList.indexOf(peer) !== -1) { return; }
channel.userList.push(peer); channel.userList.push(peer);
}); });
chan.on('join', onJoining); chan.on('join', onJoining);
chan.on('leave', function (peer) { chan.on('leave', onLeaving);
var curvePublic = channel.mapId[peer];
var i = channel.userList.indexOf(peer);
while (i !== -1) {
channel.userList.splice(i, 1);
i = channel.userList.indexOf(peer);
}
// update status
if (!curvePublic) { return; }
eachHandler('leave', function (f) {
f(curvePublic, channel.id);
});
});
// FIXME don't subscribe to the channel implicitly // FIXME don't subscribe to the channel implicitly
getChannelMessagesSince(chan, data, keys); getChannelMessagesSince(channel, data, keys);
}, function (err) { };
network.join(data.channel).then(onOpen, function (err) {
console.error(err); console.error(err);
}); });
network.on('reconnect', function () {
if (!channels[data.channel]) { return; }
network.join(data.channel).then(onOpen, function (err) {
console.error(err);
});
});
}; };
messenger.getFriendList = function (cb) { messenger.getFriendList = function (cb) {
@ -573,7 +671,7 @@ define([
})); }));
}; };
messenger.openFriendChannel = function (curvePublic, cb) { /*messenger.openFriendChannel = function (curvePublic, cb) {
if (typeof(curvePublic) !== 'string') { return void cb('INVALID_ID'); } if (typeof(curvePublic) !== 'string') { return void cb('INVALID_ID'); }
if (typeof(cb) !== 'function') { throw new Error('expected callback'); } if (typeof(cb) !== 'function') { throw new Error('expected callback'); }
@ -585,10 +683,10 @@ define([
if (!channel) { return void cb('E_NO_CHANNEL'); } if (!channel) { return void cb('E_NO_CHANNEL'); }
joining[channel] = cb; joining[channel] = cb;
openFriendChannel(friend, curvePublic); openFriendChannel(friend, curvePublic);
}; };*/
messenger.sendMessage = function (curvePublic, payload, cb) { messenger.sendMessage = function (id, payload, cb) {
var channel = getChannel(curvePublic); var channel = getChannel(id);
if (!channel) { return void cb('NO_CHANNEL'); } if (!channel) { return void cb('NO_CHANNEL'); }
if (!network.webChannels.some(function (wc) { if (!network.webChannels.some(function (wc) {
if (wc.id === channel.wc.id) { return true; } if (wc.id === channel.wc.id) { return true; }
@ -597,6 +695,9 @@ define([
} }
var msg = [Types.message, proxy.curvePublic, +new Date(), payload]; var msg = [Types.message, proxy.curvePublic, +new Date(), payload];
if (!channel.isFriendChat) {
msg.push(proxy[Constants.displayNameKey]);
}
var msgStr = JSON.stringify(msg); var msgStr = JSON.stringify(msg);
var cryptMsg = channel.encryptor.encrypt(msgStr); var cryptMsg = channel.encryptor.encrypt(msgStr);
@ -608,18 +709,27 @@ define([
}); });
}; };
messenger.getStatus = function (curvePublic, cb) { messenger.getStatus = function (chanId, cb) {
var channel = getChannel(curvePublic); // Display green status if one member is not me
var channel = getChannel(chanId);
if (!channel) { return void cb('NO_SUCH_CHANNEL'); } if (!channel) { return void cb('NO_SUCH_CHANNEL'); }
var online = channel.userList.some(function (nId) { var online = channel.userList.some(function (nId) {
return channel.mapId[nId] === curvePublic; var data = channel.mapId[nId] || undefined;
if (!data) { return false; }
return data.curvePublic !== proxy.curvePublic;
}); });
cb(void 0, online); cb(void 0, online);
}; };
messenger.getFriendInfo = function (curvePublic, cb) { messenger.getFriendInfo = function (channel, cb) {
setTimeout(function () { setTimeout(function () {
var friend = friends[curvePublic]; var friend;
for (var k in friends) {
if (friends[k].channel === channel) {
friend = friends[k];
break;
}
}
if (!friend) { return void cb('NO_SUCH_FRIEND'); } if (!friend) { return void cb('NO_SUCH_FRIEND'); }
// this clone will be redundant when ui uses postmessage // this clone will be redundant when ui uses postmessage
cb(void 0, clone(friend)); cb(void 0, clone(friend));
@ -633,28 +743,215 @@ define([
}); });
}; };
// TODO listen for changes to your friend list var loadFriend = function (friend, cb) {
// emit 'update' events for clients var channel = friend.channel;
if (getChannel(channel)) { return void cb(); }
//var update = function (curvePublic joining[channel] = cb;
var keys = Curve.deriveKeys(friend.curvePublic, proxy.curvePrivate);
var data = {
keys: keys,
channel: friend.channel,
lastKnownHash: friend.lastKnownHash,
owners: [proxy.edPublic, friend.edPublic],
isFriendChat: true
};
openChannel(data);
};
// Detect friends changes made in another worker
proxy.on('change', ['friends'], function (o, n, p) { proxy.on('change', ['friends'], function (o, n, p) {
var curvePublic; var curvePublic;
if (o === undefined) { if (o === undefined) {
// new friend added // new friend added
curvePublic = p.slice(-1)[0]; curvePublic = p.slice(-1)[0];
eachHandler('friend', function (f) {
f(curvePublic, clone(n)); // Load channel
var friend = friends[curvePublic];
if (typeof(friend) !== 'object') { return; }
var channel = friend.channel;
if (!channel) { return; }
loadFriend(friend, function () {
eachHandler('friend', function (f) {
f(curvePublic);
});
}); });
return; return;
} }
console.error(o, n, p); if (typeof(n) === 'undefined') {
// Handled by .on('remove')
return;
}
}).on('remove', ['friends'], function (o, p) { }).on('remove', ['friends'], function (o, p) {
var curvePublic = p[1];
if (!curvePublic) { return; }
if (p[2] !== 'channel') { return; }
var channel = channels[o];
channel.wc.leave(Types.unfriend);
delete channels[channel.id];
eachHandler('unfriend', function (f) { eachHandler('unfriend', function (f) {
f(p[1]); // TODO f(curvePublic, true);
}); });
}); });
// Friend added in our contacts in the current worker
messenger.onFriendAdded = function (friendData) {
var friend = friends[friendData.curvePublic];
if (typeof(friend) !== 'object') { return; }
var channel = friend.channel;
if (!channel) { return; }
loadFriend(friend, function () {
eachHandler('friend', function (f) {
f(friend.curvePublic);
});
});
};
var ready = false;
var initialized = false;
var init = function () {
if (initialized) { return; }
initialized = true;
var friends = getFriendList(proxy);
nThen(function (waitFor) {
Object.keys(friends).forEach(function (key) {
if (key === 'me') { return; }
var friend = clone(friends[key]);
if (typeof(friend) !== 'object') { return; }
var channel = friend.channel;
if (!channel) { return; }
loadFriend(friend, waitFor());
});
// TODO load rooms
}).nThen(function () {
ready = true;
emit('READY');
});
};
//init();
var getRooms = function (data, cb) {
if (data && data.curvePublic) {
var curvePublic = data.curvePublic;
// We need to get data about a new friend's room
var friend = getFriend(proxy, curvePublic);
if (!friend) { return void cb({error: 'NO_SUCH_FRIEND'}); }
var channel = getChannel(friend.channel);
if (!channel) { return void cb({error: 'NO_SUCH_CHANNEL'}); }
return void cb([{
id: channel.id,
isFriendChat: true,
name: friend.displayName,
lastKnownHash: friend.lastKnownHash,
curvePublic: friend.curvePublic,
messages: channel.messages
}]);
}
if (data && data.padChat) {
var pCChannel = getChannel(data.padChat);
if (!pCChannel) { return void cb({error: 'NO_SUCH_CHANNEL'}); }
return void cb([{
id: pCChannel.id,
isPadChat: true,
messages: pCChannel.messages
}]);
}
var rooms = Object.keys(channels).map(function (id) {
var r = getChannel(id);
var name, lastKnownHash, curvePublic;
if (r.isFriendChat) {
var friend = getFriendFromChannel(id);
if (!friend) { return null; }
name = friend.displayName;
lastKnownHash = friend.lastKnownHash;
curvePublic = friend.curvePublic;
} else if (r.isPadChat) {
return;
} else {
// TODO room get metadata (name) && lastKnownHash
}
return {
id: r.id,
isFriendChat: r.isFriendChat,
name: name,
lastKnownHash: lastKnownHash,
curvePublic: curvePublic,
messages: r.messages
};
}).filter(function (x) { return x; });
cb(rooms);
};
var getUserList = function (data, cb) {
var room = getChannel(data.id);
if (!room) { return void cb({error: 'NO_SUCH_CHANNEL'}); }
if (room.isFriendChat) {
var friend = getFriendFromChannel(data.id);
if (!friend) { return void cb({error: 'NO_SUCH_FRIEND'}); }
cb([friend]);
} else {
// TODO room userlist in rooms...
// (this is the static userlist, not the netflux one)
cb([]);
}
};
var openPadChat = function (data, cb) {
var channel = data.channel;
if (getChannel(channel)) {
emit('PADCHAT_READY', channel);
return void cb();
}
var keys = data.secret && data.secret.keys;
var cryptKey = keys.viewKeyStr ? Crypto.b64AddSlashes(keys.viewKeyStr) : data.secret.key;
var encryptor = Crypto.createEncryptor(cryptKey);
var chanData = {
encryptor: encryptor,
channel: data.channel,
isPadChat: true,
//lastKnownHash: friend.lastKnownHash,
//owners: [proxy.edPublic, friend.edPublic],
//isFriendChat: true
};
openChannel(chanData);
joining[channel] = function () {
emit('PADCHAT_READY', channel);
};
cb();
};
network.on('disconnect', function () {
emit('DISCONNECT');
});
network.on('reconnect', function () {
emit('RECONNECT');
});
messenger.execCommand = function (obj, cb) {
var cmd = obj.cmd;
var data = obj.data;
if (cmd === 'INIT_FRIENDS') {
init();
return void cb();
}
if (cmd === 'IS_READY') {
return void cb(ready);
}
if (cmd === 'GET_ROOMS') {
return void getRooms(data, cb);
}
if (cmd === 'GET_USERLIST') {
return void getUserList(data, cb);
}
if (cmd === 'OPEN_PAD_CHAT') {
return void openPadChat(data, cb);
}
};
Object.freeze(messenger); Object.freeze(messenger);
return messenger; return messenger;

@ -12,10 +12,11 @@ define([
'/common/clipboard.js', '/common/clipboard.js',
'/customize/messages.js', '/customize/messages.js',
'/customize/application_config.js', '/customize/application_config.js',
'/customize/pages.js',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'css!/customize/fonts/cptools/style.css' 'css!/customize/fonts/cptools/style.css'
], function ($, Config, Util, Hash, Language, UI, Constants, Feedback, h, MediaTag, Clipboard, ], function ($, Config, Util, Hash, Language, UI, Constants, Feedback, h, MediaTag, Clipboard,
Messages, AppConfig, NThen) { Messages, AppConfig, Pages, NThen) {
var UIElements = {}; var UIElements = {};
// Configure MediaTags to use our local viewer // Configure MediaTags to use our local viewer
@ -630,23 +631,25 @@ define([
if (!data.FM) { return; } if (!data.FM) { return; }
var $input = $('<input>', { var $input = $('<input>', {
'type': 'file', 'type': 'file',
'style': 'display: none;' 'style': 'display: none;',
'multiple': 'multiple'
}).on('change', function (e) { }).on('change', function (e) {
var file = e.target.files[0]; var files = Util.slice(e.target.files);
var ev = { files.forEach(function (file) {
target: data.target var ev = {
}; target: data.target
if (data.filter && !data.filter(file)) { };
return; if (data.filter && !data.filter(file)) {
} return;
if (data.transformer) { }
data.transformer(file, function (newFile) { if (data.transformer) {
data.FM.handleFile(newFile, ev); data.transformer(file, function (newFile) {
if (callback) { callback(); } data.FM.handleFile(newFile, ev);
}); });
return; return;
} }
data.FM.handleFile(file, ev); data.FM.handleFile(file, ev);
});
if (callback) { callback(); } if (callback) { callback(); }
}); });
if (data.accept) { $input.attr('accept', data.accept); } if (data.accept) { $input.attr('accept', data.accept); }
@ -1779,13 +1782,16 @@ define([
var $container = $('<div>'); var $container = $('<div>');
var i = 0; var i = 0;
AppConfig.availablePadTypes.forEach(function (p) { var types = AppConfig.availablePadTypes.filter(function (p) {
if (p === 'drive') { return; } if (p === 'drive') { return; }
if (p === 'contacts') { return; } if (p === 'contacts') { return; }
if (p === 'todo') { return; } if (p === 'todo') { return; }
if (p === 'file') { return; } if (p === 'file') { return; }
if (!common.isLoggedIn() && AppConfig.registeredOnlyTypes && if (!common.isLoggedIn() && AppConfig.registeredOnlyTypes &&
AppConfig.registeredOnlyTypes.indexOf(p) !== -1) { return; } AppConfig.registeredOnlyTypes.indexOf(p) !== -1) { return; }
return true;
});
types.forEach(function (p) {
var $element = $('<li>', { var $element = $('<li>', {
'class': 'cp-icons-element', 'class': 'cp-icons-element',
'id': 'cp-newpad-icons-'+ (i++) 'id': 'cp-newpad-icons-'+ (i++)
@ -1809,7 +1815,7 @@ define([
var selected = -1; var selected = -1;
var next = function () { var next = function () {
selected = ++selected % 5; selected = ++selected % types.length;
$container.find('.cp-icons-element-selected').removeClass('cp-icons-element-selected'); $container.find('.cp-icons-element-selected').removeClass('cp-icons-element-selected');
$container.find('#cp-newpad-icons-'+selected).addClass('cp-icons-element-selected'); $container.find('#cp-newpad-icons-'+selected).addClass('cp-icons-element-selected');
}; };
@ -2325,6 +2331,52 @@ define([
$(password).find('.cp-password-input').focus(); $(password).find('.cp-password-input').focus();
}; };
var crowdfundingState = false;
UIElements.displayCrowdfunding = function (common) {
if (crowdfundingState) { return; }
if (AppConfig.disableCrowdfundingMessages) { return; }
var priv = common.getMetadataMgr().getPrivateData();
if (priv.plan) { return; }
crowdfundingState = true;
setTimeout(function () {
common.getAttribute(['general', 'crowdfunding'], function (err, val) {
if (err || val === false) { return; }
// Display the popup
var text = Messages.crowdfunding_popup_text;
var yes = h('button.cp-corner-primary', Messages.crowdfunding_popup_yes);
var no = h('button.cp-corner-primary', Messages.crowdfunding_popup_no);
var never = h('button.cp-corner-cancel', Messages.crowdfunding_popup_never);
var actions = h('div', [yes, no, never]);
var modal = UI.cornerPopup(text, actions, null, {big: true});
$(yes).click(function () {
modal.delete();
common.openURL('https://opencollective.com/cryptpad/contribute');
Feedback.send('CROWDFUNDING_YES');
});
$(modal.popup).find('a').click(function (e) {
e.stopPropagation();
e.preventDefault();
modal.delete();
common.openURL('https://opencollective.com/cryptpad/');
Feedback.send('CROWDFUNDING_LINK');
});
$(no).click(function () {
modal.delete();
Feedback.send('CROWDFUNDING_NO');
});
$(never).click(function () {
modal.delete();
common.setAttribute(['general', 'crowdfunding'], false);
Feedback.send('CROWDFUNDING_NEVER');
});
});
}, 5000);
};
var storePopupState = false; var storePopupState = false;
UIElements.displayStorePadPopup = function (common, data) { UIElements.displayStorePadPopup = function (common, data) {
if (storePopupState) { return; } if (storePopupState) { return; }
@ -2347,15 +2399,20 @@ define([
}); });
$(hide).click(function () { $(hide).click(function () {
UIElements.displayCrowdfunding(common);
modal.delete(); modal.delete();
}); });
$(store).click(function () { $(store).click(function () {
modal.delete();
common.getSframeChannel().query("Q_AUTOSTORE_STORE", null, function (err, obj) { common.getSframeChannel().query("Q_AUTOSTORE_STORE", null, function (err, obj) {
if (err || (obj && obj.error)) { var error = err || (obj && obj.error);
console.error(err || obj.error); if (error) {
if (error === 'E_OVER_LIMIT') {
return void UI.warn(Messages.pinLimitReached);
}
return void UI.warn(Messages.autostore_error); return void UI.warn(Messages.autostore_error);
} }
modal.delete();
UIElements.displayCrowdfunding(common);
UI.log(Messages.autostore_saved); UI.log(Messages.autostore_saved);
}); });
}); });

@ -345,6 +345,9 @@ define([
}; };
common.getPadAttribute = function (attr, cb, href) { common.getPadAttribute = function (attr, cb, href) {
href = Hash.getRelativeHref(href || window.location.href); href = Hash.getRelativeHref(href || window.location.href);
if (!href) {
return void cb('E404');
}
postMessage("GET_PAD_ATTRIBUTE", { postMessage("GET_PAD_ATTRIBUTE", {
href: href, href: href,
attr: attr, attr: attr,
@ -622,6 +625,12 @@ define([
messenger.setChannelHead = function (data, cb) { messenger.setChannelHead = function (data, cb) {
postMessage("CONTACTS_SET_CHANNEL_HEAD", data, cb); postMessage("CONTACTS_SET_CHANNEL_HEAD", data, cb);
}; };
messenger.execCommand = function (data, cb) {
postMessage("CHAT_COMMAND", data, cb);
};
messenger.onEvent = Util.mkEvent();
messenger.onMessageEvent = Util.mkEvent(); messenger.onMessageEvent = Util.mkEvent();
messenger.onJoinEvent = Util.mkEvent(); messenger.onJoinEvent = Util.mkEvent();
messenger.onLeaveEvent = Util.mkEvent(); messenger.onLeaveEvent = Util.mkEvent();
@ -1059,6 +1068,8 @@ define([
CONTACTS_UPDATE: common.messenger.onUpdateEvent.fire, CONTACTS_UPDATE: common.messenger.onUpdateEvent.fire,
CONTACTS_FRIEND: common.messenger.onFriendEvent.fire, CONTACTS_FRIEND: common.messenger.onFriendEvent.fire,
CONTACTS_UNFRIEND: common.messenger.onUnfriendEvent.fire, CONTACTS_UNFRIEND: common.messenger.onUnfriendEvent.fire,
// Chat
CHAT_EVENT: common.messenger.onEvent.fire,
// Pad // Pad
PAD_READY: common.padRpc.onReadyEvent.fire, PAD_READY: common.padRpc.onReadyEvent.fire,
PAD_MESSAGE: common.padRpc.onMessageEvent.fire, PAD_MESSAGE: common.padRpc.onMessageEvent.fire,
@ -1425,7 +1436,7 @@ define([
postMessage("INIT_RPC", null, waitFor(function (obj) { postMessage("INIT_RPC", null, waitFor(function (obj) {
console.log('RPC handshake complete'); console.log('RPC handshake complete');
if (obj.error) { return; } if (obj.error) { return; }
localStorage.plan = obj.plan; localStorage[Constants.plan] = obj.plan;
})); }));
} else if (PINNING_ENABLED) { } else if (PINNING_ENABLED) {
console.log('not logged in. pads will not be pinned'); console.log('not logged in. pads will not be pinned');

@ -25,9 +25,11 @@ define([
// Tasks list // Tasks list
var checkedTaskItemPtn = /^\s*(<p>)?\[[xX]\](<\/p>)?\s*/; var checkedTaskItemPtn = /^\s*(<p>)?\[[xX]\](<\/p>)?\s*/;
var uncheckedTaskItemPtn = /^\s*(<p>)?\[ ?\](<\/p>)?\s*/; var uncheckedTaskItemPtn = /^\s*(<p>)?\[ ?\](<\/p>)?\s*/;
var bogusCheckPtn = /<input( checked=""){0,1} disabled="" type="checkbox">/;
renderer.listitem = function (text) { renderer.listitem = function (text) {
var isCheckedTaskItem = checkedTaskItemPtn.test(text); var isCheckedTaskItem = checkedTaskItemPtn.test(text);
var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text); var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text);
var hasBogusInput = bogusCheckPtn.test(text);
if (isCheckedTaskItem) { if (isCheckedTaskItem) {
text = text.replace(checkedTaskItemPtn, text = text.replace(checkedTaskItemPtn,
'<i class="fa fa-check-square" aria-hidden="true"></i>&nbsp;') + '\n'; '<i class="fa fa-check-square" aria-hidden="true"></i>&nbsp;') + '\n';
@ -36,6 +38,15 @@ define([
text = text.replace(uncheckedTaskItemPtn, text = text.replace(uncheckedTaskItemPtn,
'<i class="fa fa-square-o" aria-hidden="true"></i>&nbsp;') + '\n'; '<i class="fa fa-square-o" aria-hidden="true"></i>&nbsp;') + '\n';
} }
if (!isCheckedTaskItem && !isUncheckedTaskItem && hasBogusInput) {
if (/checked/.test(text)) {
text = text.replace(bogusCheckPtn,
'<i class="fa fa-check-square" aria-hidden="true"></i>&nbsp;') + '\n';
} else if (/disabled/.test(text)) {
text = text.replace(bogusCheckPtn,
'<i class="fa fa-square-o" aria-hidden="true"></i>&nbsp;') + '\n';
}
}
var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : ''; var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : '';
return '<li'+ cls + '>' + text + '</li>\n'; return '<li'+ cls + '>' + text + '</li>\n';
}; };

@ -59,6 +59,9 @@ define([
obj[key] = data.value; obj[key] = data.value;
} }
broadcast([clientId], "UPDATE_METADATA"); broadcast([clientId], "UPDATE_METADATA");
if (Array.isArray(path) && path[0] === 'profile' && store.messenger) {
store.messenger.updateMyData();
}
onSync(cb); onSync(cb);
}; };
@ -462,7 +465,7 @@ define([
if (data.password) { pad.password = data.password; } if (data.password) { pad.password = data.password; }
if (data.channel) { pad.channel = data.channel; } if (data.channel) { pad.channel = data.channel; }
store.manager.addPad(data.path, pad, function (e) { store.manager.addPad(data.path, pad, function (e) {
if (e) { return void cb({error: "Error while adding the pad:"+ e}); } if (e) { return void cb({error: e}); }
sendDriveEvent('DRIVE_CHANGE', { sendDriveEvent('DRIVE_CHANGE', {
path: ['drive', UserObject.FILES_DATA] path: ['drive', UserObject.FILES_DATA]
}, clientId); }, clientId);
@ -597,6 +600,7 @@ define([
Store.setDisplayName = function (clientId, value, cb) { Store.setDisplayName = function (clientId, value, cb) {
store.proxy[Constants.displayNameKey] = value; store.proxy[Constants.displayNameKey] = value;
broadcast([clientId], "UPDATE_METADATA"); broadcast([clientId], "UPDATE_METADATA");
if (store.messenger) { store.messenger.updateMyData(); }
onSync(cb); onSync(cb);
}; };
@ -859,6 +863,9 @@ define([
}, },
pinPads: function (data, cb) { Store.pinPads(null, data, cb); }, pinPads: function (data, cb) { Store.pinPads(null, data, cb); },
friendComplete: function (data) { friendComplete: function (data) {
if (data.friend && store.messenger && store.messenger.onFriendAdded) {
store.messenger.onFriendAdded(data.friend);
}
postMessage(clientId, "EV_FRIEND_COMPLETE", data); postMessage(clientId, "EV_FRIEND_COMPLETE", data);
}, },
friendRequest: function (data, cb) { friendRequest: function (data, cb) {
@ -892,6 +899,7 @@ define([
Store.messenger = { Store.messenger = {
getFriendList: function (clientId, data, cb) { getFriendList: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.getFriendList(function (e, keys) { store.messenger.getFriendList(function (e, keys) {
cb({ cb({
error: e, error: e,
@ -900,6 +908,7 @@ define([
}); });
}, },
getMyInfo: function (clientId, data, cb) { getMyInfo: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.getMyInfo(function (e, info) { store.messenger.getMyInfo(function (e, info) {
cb({ cb({
error: e, error: e,
@ -908,6 +917,7 @@ define([
}); });
}, },
getFriendInfo: function (clientId, data, cb) { getFriendInfo: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.getFriendInfo(data, function (e, info) { store.messenger.getFriendInfo(data, function (e, info) {
cb({ cb({
error: e, error: e,
@ -916,6 +926,7 @@ define([
}); });
}, },
removeFriend: function (clientId, data, cb) { removeFriend: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.removeFriend(data, function (e, info) { store.messenger.removeFriend(data, function (e, info) {
cb({ cb({
error: e, error: e,
@ -924,11 +935,13 @@ define([
}); });
}, },
openFriendChannel: function (clientId, data, cb) { openFriendChannel: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.openFriendChannel(data, function (e) { store.messenger.openFriendChannel(data, function (e) {
cb({ error: e, }); cb({ error: e, });
}); });
}, },
getFriendStatus: function (clientId, data, cb) { getFriendStatus: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.getStatus(data, function (e, online) { store.messenger.getStatus(data, function (e, online) {
cb({ cb({
error: e, error: e,
@ -937,6 +950,7 @@ define([
}); });
}, },
getMoreHistory: function (clientId, data, cb) { getMoreHistory: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.getMoreHistory(data.curvePublic, data.sig, data.count, function (e, history) { store.messenger.getMoreHistory(data.curvePublic, data.sig, data.count, function (e, history) {
cb({ cb({
error: e, error: e,
@ -945,6 +959,7 @@ define([
}); });
}, },
sendMessage: function (clientId, data, cb) { sendMessage: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.sendMessage(data.curvePublic, data.content, function (e) { store.messenger.sendMessage(data.curvePublic, data.content, function (e) {
cb({ cb({
error: e, error: e,
@ -952,11 +967,17 @@ define([
}); });
}, },
setChannelHead: function (clientId, data, cb) { setChannelHead: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.setChannelHead(data.curvePublic, data.sig, function (e) { store.messenger.setChannelHead(data.curvePublic, data.sig, function (e) {
cb({ cb({
error: e error: e
}); });
}); });
},
execCommand: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.execCommand(data, cb);
} }
}; };
@ -1317,7 +1338,6 @@ define([
} }
}; };
var messengerEventInit = false;
var sendMessengerEvent = function (q, data) { var sendMessengerEvent = function (q, data) {
messengerEventClients.forEach(function (cId) { messengerEventClients.forEach(function (cId) {
postMessage(cId, q, data); postMessage(cId, q, data);
@ -1327,41 +1347,49 @@ define([
if (messengerEventClients.indexOf(clientId) === -1) { if (messengerEventClients.indexOf(clientId) === -1) {
messengerEventClients.push(clientId); messengerEventClients.push(clientId);
} }
if (!messengerEventInit) { };
var messenger = store.messenger = Messenger.messenger(store); var loadMessenger = function () {
messenger.on('message', function (message) { if (AppConfig.availablePadTypes.indexOf('contacts') === -1) { return; }
sendMessengerEvent('CONTACTS_MESSAGE', message); var messenger = store.messenger = Messenger.messenger(store);
messenger.on('message', function (message) {
sendMessengerEvent('CONTACTS_MESSAGE', message);
});
messenger.on('join', function (curvePublic, channel) {
sendMessengerEvent('CONTACTS_JOIN', {
curvePublic: curvePublic,
channel: channel,
}); });
messenger.on('join', function (curvePublic, channel) { });
sendMessengerEvent('CONTACTS_JOIN', { messenger.on('leave', function (curvePublic, channel) {
curvePublic: curvePublic, sendMessengerEvent('CONTACTS_LEAVE', {
channel: channel, curvePublic: curvePublic,
}); channel: channel,
}); });
messenger.on('leave', function (curvePublic, channel) { });
sendMessengerEvent('CONTACTS_LEAVE', { messenger.on('update', function (info, types, channel) {
curvePublic: curvePublic, sendMessengerEvent('CONTACTS_UPDATE', {
channel: channel, types: types,
}); info: info,
channel: channel
}); });
messenger.on('update', function (info, curvePublic) { });
sendMessengerEvent('CONTACTS_UPDATE', { messenger.on('friend', function (curvePublic) {
curvePublic: curvePublic, sendMessengerEvent('CONTACTS_FRIEND', {
info: info, curvePublic: curvePublic,
});
}); });
messenger.on('friend', function (curvePublic) { });
sendMessengerEvent('CONTACTS_FRIEND', { messenger.on('unfriend', function (curvePublic, removedByMe) {
curvePublic: curvePublic, sendMessengerEvent('CONTACTS_UNFRIEND', {
}); curvePublic: curvePublic,
removedByMe: removedByMe
}); });
messenger.on('unfriend', function (curvePublic) { });
sendMessengerEvent('CONTACTS_UNFRIEND', { messenger.on('event', function (ev, data) {
curvePublic: curvePublic, sendMessengerEvent('CHAT_EVENT', {
}); ev: ev,
data: data
}); });
messengerEventInit = true; });
}
}; };
@ -1450,6 +1478,7 @@ define([
}); });
userObject.fixFiles(); userObject.fixFiles();
loadSharedFolders(waitFor); loadSharedFolders(waitFor);
loadMessenger();
}).nThen(function () { }).nThen(function () {
var requestLogin = function () { var requestLogin = function () {
broadcast([], "REQUEST_LOGIN"); broadcast([], "REQUEST_LOGIN");

@ -69,6 +69,8 @@ define([
CONTACTS_GET_MORE_HISTORY: Store.messenger.getMoreHistory, CONTACTS_GET_MORE_HISTORY: Store.messenger.getMoreHistory,
CONTACTS_SEND_MESSAGE: Store.messenger.sendMessage, CONTACTS_SEND_MESSAGE: Store.messenger.sendMessage,
CONTACTS_SET_CHANNEL_HEAD: Store.messenger.setChannelHead, CONTACTS_SET_CHANNEL_HEAD: Store.messenger.setChannelHead,
// Chat
CHAT_COMMAND: Store.messenger.execCommand,
// Pad // Pad
SEND_PAD_MSG: Store.sendPadMsg, SEND_PAD_MSG: Store.sendPadMsg,
JOIN_PAD: Store.joinPad, JOIN_PAD: Store.joinPad,

@ -322,6 +322,8 @@ define([
if (!readOnly) { onLocal(); } if (!readOnly) { onLocal(); }
evOnReady.fire(newPad); evOnReady.fire(newPad);
common.openPadChat(onLocal);
UI.removeLoadingScreen(emitResize); UI.removeLoadingScreen(emitResize);
var privateDat = cpNfInner.metadataMgr.getPrivateData(); var privateDat = cpNfInner.metadataMgr.getPrivateData();
@ -559,6 +561,7 @@ define([
}, onLocal); }, onLocal);
var configTb = { var configTb = {
displayed: [ displayed: [
'chat',
'userlist', 'userlist',
'title', 'title',
'useradmin', 'useradmin',

@ -36,7 +36,8 @@ define([
window.addEventListener('message', onMsg); window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) { }).nThen(function (/*waitFor*/) {
SFCommonO.start({ SFCommonO.start({
useCreationScreen: true useCreationScreen: true,
messaging: true
}); });
}); });
}); });

@ -262,6 +262,7 @@ define([
donateURL: Cryptpad.donateURL, donateURL: Cryptpad.donateURL,
upgradeURL: Cryptpad.upgradeURL upgradeURL: Cryptpad.upgradeURL
}, },
plan: localStorage[Utils.Constants.plan],
isNewFile: isNewFile, isNewFile: isNewFile,
isDeleted: isNewFile && window.location.hash.length > 0, isDeleted: isNewFile && window.location.hash.length > 0,
forceCreationScreen: forceCreationScreen, forceCreationScreen: forceCreationScreen,
@ -370,7 +371,7 @@ define([
forceSave: true forceSave: true
}; };
Cryptpad.setPadTitle(data, function (err) { Cryptpad.setPadTitle(data, function (err) {
cb(err); cb({error: err});
}); });
}); });
sframeChan.on('Q_IS_PAD_STORED', function (data, cb) { sframeChan.on('Q_IS_PAD_STORED', function (data, cb) {
@ -776,6 +777,22 @@ define([
Cryptpad.messenger.setChannelHead(opt, cb); Cryptpad.messenger.setChannelHead(opt, cb);
}); });
sframeChan.on('Q_CHAT_OPENPADCHAT', function (data, cb) {
Cryptpad.messenger.execCommand({
cmd: 'OPEN_PAD_CHAT',
data: {
channel: data,
secret: secret
}
}, cb);
});
sframeChan.on('Q_CHAT_COMMAND', function (data, cb) {
Cryptpad.messenger.execCommand(data, cb);
});
Cryptpad.messenger.onEvent.reg(function (data) {
sframeChan.event('EV_CHAT_EVENT', data);
});
Cryptpad.messenger.onMessageEvent.reg(function (data) { Cryptpad.messenger.onMessageEvent.reg(function (data) {
sframeChan.event('EV_CONTACTS_MESSAGE', data); sframeChan.event('EV_CONTACTS_MESSAGE', data);
}); });

@ -161,6 +161,24 @@ define([
}); });
}; };
// Chat
var padChatChannel;
funcs.getPadChat = function () {
return padChatChannel;
};
funcs.openPadChat = function (saveChanges) {
var md = JSON.parse(JSON.stringify(ctx.metadataMgr.getMetadata()));
var channel = md.chat || Hash.createChannelId();
if (!md.chat) {
md.chat = channel;
ctx.metadataMgr.updateMetadata(md);
setTimeout(saveChanges);
}
padChatChannel = channel;
ctx.sframeChan.query('Q_CHAT_OPENPADCHAT', channel, function (err, obj) {
if (err || (obj && obj.error)) { console.error(err || (obj && obj.error)); }
});
};
// CodeMirror // CodeMirror
funcs.initCodeMirrorApp = callWithCommon(CodeMirror.create); funcs.initCodeMirrorApp = callWithCommon(CodeMirror.create);
@ -517,6 +535,11 @@ define([
UI.alert(Messages.chrome68); UI.alert(Messages.chrome68);
}); });
funcs.isPadStored(function (err, val) {
if (err || !val) { return; }
UIElements.displayCrowdfunding(funcs);
});
ctx.sframeChan.ready(); ctx.sframeChan.ready();
cb(funcs); cb(funcs);
}); });

@ -35,7 +35,7 @@ define([], function () {
}); });
sFrameChan.on('EV_CONTACTS_UPDATE', function (data) { sFrameChan.on('EV_CONTACTS_UPDATE', function (data) {
_handlers.update.forEach(function (f) { _handlers.update.forEach(function (f) {
f(data.info, data.curvePublic); f(data.info, data.types, data.channel);
}); });
}); });
sFrameChan.on('EV_CONTACTS_FRIEND', function (data) { sFrameChan.on('EV_CONTACTS_FRIEND', function (data) {
@ -45,7 +45,7 @@ define([], function () {
}); });
sFrameChan.on('EV_CONTACTS_UNFRIEND', function (data) { sFrameChan.on('EV_CONTACTS_UNFRIEND', function (data) {
_handlers.unfriend.forEach(function (f) { _handlers.unfriend.forEach(function (f) {
f(data.curvePublic); f(data.curvePublic, data.removedByMe);
}); });
}); });

@ -171,6 +171,11 @@ define({
'Q_CONTACTS_SET_CHANNEL_HEAD': true, 'Q_CONTACTS_SET_CHANNEL_HEAD': true,
'Q_CONTACTS_CLEAR_OWNED_CHANNEL': true, 'Q_CONTACTS_CLEAR_OWNED_CHANNEL': true,
// Chat
'EV_CHAT_EVENT': true,
'Q_CHAT_COMMAND': true,
'Q_CHAT_OPENPADCHAT': true,
// Put one or more entries to the localStore which will go in localStorage. // Put one or more entries to the localStore which will go in localStorage.
'EV_LOCALSTORE_PUT': true, 'EV_LOCALSTORE_PUT': true,
// Put one entry in the parent sessionStorage // Put one entry in the parent sessionStorage
@ -218,6 +223,8 @@ define({
// Refresh the drive when the drive has changed ('change' or 'remove' events) // Refresh the drive when the drive has changed ('change' or 'remove' events)
'EV_DRIVE_CHANGE': true, 'EV_DRIVE_CHANGE': true,
'EV_DRIVE_REMOVE': true, 'EV_DRIVE_REMOVE': true,
// Set shared folder hash in the address bar
'EV_DRIVE_SET_HASH': true,
// Remove an owned pad from the server // Remove an owned pad from the server
'Q_REMOVE_OWNED_CHANNEL': true, 'Q_REMOVE_OWNED_CHANNEL': true,

@ -6,8 +6,11 @@ define([
'/common/common-interface.js', '/common/common-interface.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-feedback.js', '/common/common-feedback.js',
'/common/sframe-messenger-inner.js',
'/contacts/messenger-ui.js',
'/customize/messages.js', '/customize/messages.js',
], function ($, Config, ApiConfig, UIElements, UI, Hash, Feedback, Messages) { ], function ($, Config, ApiConfig, UIElements, UI, Hash, Feedback,
Messenger, MessengerUI, Messages) {
var Common; var Common;
var Bar = { var Bar = {
@ -231,16 +234,15 @@ define([
var name = data.name || Messages.anonymous; var name = data.name || Messages.anonymous;
var $span = $('<span>', {'class': 'cp-avatar'}); var $span = $('<span>', {'class': 'cp-avatar'});
var $rightCol = $('<span>', {'class': 'cp-toolbar-userlist-rightcol'}); var $rightCol = $('<span>', {'class': 'cp-toolbar-userlist-rightcol'});
var $nameSpan = $('<span>', {'class': 'cp-toolbar-userlist-name'}).text(name).appendTo($rightCol); var $nameSpan = $('<span>', {'class': 'cp-toolbar-userlist-name'}).appendTo($rightCol);
var $nameValue = $('<span>', {
'class': 'cp-toolbar-userlist-name-value'
}).text(name).appendTo($nameSpan);
var isMe = data.uid === user.uid; var isMe = data.uid === user.uid;
if (isMe && !priv.readOnly) { if (isMe && !priv.readOnly) {
$nameSpan.html('');
var $nameValue = $('<span>', {
'class': 'cp-toolbar-userlist-name-value'
}).text(name).appendTo($nameSpan);
if (!Config.disableProfile) { if (!Config.disableProfile) {
var $button = $('<button>', { var $button = $('<button>', {
'class': 'fa fa-pencil cp-toolbar-userlist-name-edit', 'class': 'fa fa-pencil cp-toolbar-userlist-button',
title: Messages.user_rename title: Messages.user_rename
}).appendTo($nameSpan); }).appendTo($nameSpan);
$button.hover(function (e) { e.preventDefault(); e.stopPropagation(); }); $button.hover(function (e) { e.preventDefault(); e.stopPropagation(); });
@ -296,16 +298,24 @@ define([
$('<span>', {'class': 'cp-toolbar-userlist-friend'}).text(Messages.userlist_pending) $('<span>', {'class': 'cp-toolbar-userlist-friend'}).text(Messages.userlist_pending)
.appendTo($rightCol); .appendTo($rightCol);
} else { } else {
$('<span>', { $('<button>', {
'class': 'fa fa-user-plus cp-toolbar-userlist-friend', 'class': 'fa fa-user-plus cp-toolbar-userlist-button',
'title': Messages._getKey('userlist_addAsFriendTitle', [ 'title': Messages._getKey('userlist_addAsFriendTitle', [
name name
]) ])
}).appendTo($rightCol).click(function (e) { }).appendTo($nameSpan).click(function (e) {
e.stopPropagation(); e.stopPropagation();
Common.sendFriendRequest(data.netfluxId); Common.sendFriendRequest(data.netfluxId);
}); });
} }
} else if (Common.isLoggedIn() && data.curvePublic && friends[data.curvePublic]) {
$('<button>', {
'class': 'fa fa-comments-o cp-toolbar-userlist-button',
'title': Messages.userlist_chat
}).appendTo($nameSpan).click(function (e) {
e.stopPropagation();
Common.openURL('/contacts/');
});
} }
if (data.profile) { if (data.profile) {
$span.addClass('cp-userlist-clickable'); $span.addClass('cp-userlist-clickable');
@ -410,6 +420,77 @@ define([
return $container; return $container;
}; };
var initChat = function (toolbar) {
var $container = $('<div>', {
id: 'cp-app-contacts-container',
'class': 'cp-app-contacts-inapp'
}).prependTo(toolbar.chatContent);
var sframeChan = Common.getSframeChannel();
var messenger = Messenger.create(sframeChan);
MessengerUI.create(messenger, $container, Common, toolbar);
};
var createChat = function (toolbar, config) {
if (!config.metadataMgr) {
throw new Error("You must provide a `metadataMgr` to display the chat");
}
if (Config.availablePadTypes.indexOf('contacts') === -1) { return; }
var $content = $('<div>', {'class': 'cp-toolbar-chat-drawer'});
$content.on('drop dragover', function (e) {
e.preventDefault();
e.stopPropagation();
});
var $closeIcon = $('<span>', {"class": "fa fa-window-close cp-toolbar-chat-drawer-close"}).appendTo($content);
//$('<h2>').text(Messages.users).appendTo($content);
//$('<p>', {'class': USERLIST_CLS}).appendTo($content);
toolbar.chatContent = $content;
var $container = $('<span>', {id: 'cp-toolbar-chat-drawer-open', title: Messages.chatButton});
var $button = $('<button>', {'class': 'fa fa-comments'}).appendTo($container);
$('<span>',{'class': 'cp-dropdown-button-title'}).appendTo($button);
toolbar.$leftside.prepend($container);
if (config.$contentContainer) {
config.$contentContainer.prepend($content);
}
var hide = function () {
$content.hide();
$button.removeClass('cp-toolbar-button-active');
config.$contentContainer.removeClass('cp-chat-visible');
};
var show = function () {
if (Bar.isEmbed) { $content.hide(); return; }
$content.show();
$button.addClass('cp-toolbar-button-active');
config.$contentContainer.addClass('cp-chat-visible');
$button.removeClass('cp-toolbar-notification');
};
$closeIcon.click(function () {
Common.setAttribute(['toolbar', 'chat-drawer'], false);
hide();
});
$button.click(function () {
var visible = $content.is(':visible');
if (visible) { hide(); }
else { show(); }
visible = !visible;
Common.setAttribute(['toolbar', 'chat-drawer'], visible);
});
show();
Common.getAttribute(['toolbar', 'chat-drawer'], function (err, val) {
if (val === false || ($(window).height() < 800 && $(window).width() < 800)) {
return void hide();
}
show();
});
initChat(toolbar, config);
return $container;
};
var createShare = function (toolbar, config) { var createShare = function (toolbar, config) {
if (!config.metadataMgr) { if (!config.metadataMgr) {
throw new Error("You must provide a `metadataMgr` to display the userlist"); throw new Error("You must provide a `metadataMgr` to display the userlist");
@ -997,6 +1078,7 @@ define([
// Create the subelements // Create the subelements
var tb = {}; var tb = {};
tb['userlist'] = createUserList; tb['userlist'] = createUserList;
tb['chat'] = createChat;
tb['share'] = createShare; tb['share'] = createShare;
tb['fileshare'] = createFileShare; tb['fileshare'] = createFileShare;
tb['title'] = createTitle; tb['title'] = createTitle;

@ -1,5 +1,5 @@
@import (reference) '../../customize/src/less2/include/avatar.less';
@import (reference) '../../customize/src/less2/include/framework.less'; @import (reference) '../../customize/src/less2/include/framework.less';
@import (reference) '../../customize/src/less2/include/messenger.less';
// body // body
&.cp-app-contacts { &.cp-app-contacts {
@ -9,238 +9,14 @@
@color: @colortheme_friends-color @color: @colortheme_friends-color
); );
@keyframes example {
0% {
background: rgba(0,0,0,0.1);
}
50% {
background: rgba(0,0,0,0.3);
}
100% {
background: rgba(0,0,0,0.1);
}
}
display: flex; display: flex;
flex-flow: column; flex-flow: column;
background-color: red !important;
@button-border: 2px;
@bg-color: @colortheme_friends-bg;
@color: @colortheme_friends-color;
#cp-app-contacts-container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
min-height: 0;
&.ready {
background-size: cover;
background-position: center;
}
}
#cp-toolbar { #cp-toolbar {
display: flex; // We need this to remove a 3px border at the bottom of the toolbar display: flex; // We need this to remove a 3px border at the bottom of the toolbar
} }
#cp-app-contacts-friendlist { .messenger_main();
width: 350px;
height: 100%;
background-color: lighten(@bg-color, 10%);
overflow-y: auto;
.cp-app-contacts-friend {
background: rgba(0,0,0,0.1);
padding: 5px;
margin: 10px;
cursor: pointer;
position: relative;
.cp-app-contacts-right-col {
margin-left: 5px;
display: flex;
flex-flow: column;
}
&:hover {
background-color: rgba(0,0,0,0.3);
}
&.cp-app-contacts-notify {
animation: example 2s ease-in-out infinite;
}
}
.cp-app-contacts-remove {
cursor: pointer;
width: 20px;
&:hover {
color: darken(@color, 20%);
}
}
}
#cp-app-contacts-friendlist .cp-app-contacts-friend, #cp-app-contacts-messaging .cp-avatar {
.avatar_main(30px);
&.cp-avatar {
display: flex;
}
cursor: pointer;
color: @color;
media-tag {
img {
color: #000;
}
}
media-tag, .cp-avatar-default {
margin-right: 5px;
}
.cp-app-contacts-status {
width: 5px;
display: inline-block;
position: absolute;
right: 0;
top: 0;
bottom: 0;
opacity: 0.7;
background-color: #777;
&.cp-app-contacts-online {
background-color: green;
}
&.cp-app-contacts-offline {
background-color: red;
}
}
}
.placeholder (@color: #bbb) {
&::-webkit-input-placeholder { /* WebKit, Blink, Edge */
color: @color;
}
&:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
color: @color;
opacity: 1;
}
&::-moz-placeholder { /* Mozilla Firefox 19+ */
color: @color;
opacity: 1;
}
&:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: @color;
}
&::-ms-input-placeholder { /* Microsoft Edge */
color: @color;
}
}
#cp-app-contacts-messaging {
flex: 1;
height: 100%;
background-color: lighten(@bg-color, 20%);
min-width: 0;
.cp-app-contacts-info {
padding: 20px;
}
.cp-app-contacts-header {
background-color: lighten(@bg-color, 15%);
padding: 0;
display: flex;
justify-content: space-between;
align-items: center;
height: 50px;
.hover () {
height: 100%;
line-height: 30px;
padding: 10px;
&:hover {
background-color: rgba(50,50,50,0.3);
}
}
.cp-avatar,
.cp-app-contacts-right-col {
flex:1 1 auto;
}
.cp-app-contacts-remove-history {
.hover;
}
.cp-avatar {
margin: 10px;
}
.cp-app-contacts-more-history {
//display: none;
.hover;
&.cp-app-contacts-faded {
color: darken(@bg-color, 5%);
}
}
}
.cp-app-contacts-chat {
height: 100%;
display: flex;
flex-flow: column;
.cp-app-contacts-messages {
padding: 0 20px;
margin: 10px 0;
flex: 1;
overflow-x: auto;
.cp-app-contacts-message {
& > div {
padding: 0 10px;
}
.cp-app-contacts-content {
overflow: hidden;
word-wrap: break-word;
&> * {
margin: 0;
}
}
.cp-app-contacts-date {
display: none;
font-style: italic;
}
.cp-app-contacts-sender {
margin-top: 10px;
font-weight: bold;
background-color: rgba(0,0,0,0.1);
}
}
}
}
.cp-app-contacts-input {
background-color: lighten(@bg-color, 15%);
height: auto;
min-height: 50px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 75px;
textarea {
margin: 5px 0;
padding: 0 10px;
border: none;
height: 50px;
flex: 1;
background-color: darken(@bg-color, 10%);
color: @color;
resize: none;
line-height: 50px;
overflow-y: auto;
.placeholder(#bbb);
&[disabled=true] {
.placeholder(#999);
}
&:placeholder-shown { line-height: 50px; }
}
button {
height: 50px;
border-radius: 0;
border: none;
background-color: darken(@bg-color, 15%);
&:hover {
background-color: darken(@bg-color, 20%);
}
}
}
}
} }

@ -41,22 +41,7 @@ define([
document.body.appendChild(toolbarElement); document.body.appendChild(toolbarElement);
var messaging = h('div#cp-app-contacts-messaging', [ var appElement = h('div#cp-app-contacts-container');
h('div.cp-app-contacts-info', [
h('h2', Messages.contacts_info1),
h('ul', [
h('li', Messages.contacts_info2),
h('li', Messages.contacts_info3),
])
])
]);
var friendList = h('div#cp-app-contacts-friendlist');
var appElement = h('div#cp-app-contacts-container', [
friendList,
messaging,
]);
document.body.appendChild(appElement); document.body.appendChild(appElement);
@ -73,7 +58,7 @@ define([
var messenger = Messenger.create(sFrameChan); var messenger = Messenger.create(sFrameChan);
MessengerUI.create(messenger, $(friendList), $(messaging), common); MessengerUI.create(messenger, $(appElement), common);
UI.removeLoadingScreen(); UI.removeLoadingScreen();

@ -3,58 +3,140 @@ define([
'/customize/messages.js', '/customize/messages.js',
'/common/common-util.js', '/common/common-util.js',
'/common/common-interface.js', '/common/common-interface.js',
'/common/common-notifier.js',
'/common/hyperscript.js', '/common/hyperscript.js',
'/bower_components/marked/marked.min.js', '/bower_components/marked/marked.min.js',
'/common/media-tag.js', '/common/media-tag.js',
], function ($, Messages, Util, UI, Notifier, h, Marked, MediaTag) { ], function ($, Messages, Util, UI, h, Marked, MediaTag) {
'use strict'; 'use strict';
var debug = console.log;
debug = function () {};
var MessengerUI = {}; var MessengerUI = {};
var dataQuery = function (curvePublic) { var dataQuery = function (id) {
return '[data-key="' + curvePublic + '"]'; return '[data-key="' + id + '"]';
};
var userQuery = function (curve) {
return '[data-user="' + curve + '"]';
}; };
var initChannel = function (state, curvePublic, info) { var initChannel = function (state, info) {
console.log('initializing channel for [%s]', curvePublic); console.log('initializing channel for [%s]', info.id);
state.channels[curvePublic] = { var h, t;
messages: [], if (Array.isArray(info.messages) && info.messages.length) {
HEAD: info.lastKnownHash, h = info.messages[info.messages.length -1].sig;
TAIL: null, t = info.messages[0].sig;
}
state.channels[info.id] = {
messages: info.messages || [],
name: info.name,
isFriendChat: info.isFriendChat,
needMoreHistory: !info.isPadChat,
isPadChat: info.isPadChat,
curvePublic: info.curvePublic,
HEAD: h || info.lastKnownHash,
TAIL: t || null,
}; };
}; };
MessengerUI.create = function (messenger, $userlist, $messages, common) { MessengerUI.create = function (messenger, $container, common, toolbar) {
var origin = common.getMetadataMgr().getPrivateData().origin; var sframeChan = common.getSframeChannel();
var metadataMgr = common.getMetadataMgr();
var origin = metadataMgr.getPrivateData().origin;
var isApp = typeof(toolbar) !== "undefined";
$container.addClass('cp-app-contacts-initializing');
var messaging = h('div#cp-app-contacts-messaging', [
h('span.fa.fa-spinner.fa-pulse.fa-4x.fa-fw.cp-app-contacts-spinner'),
h('div.cp-app-contacts-info', [
h('h2', Messages.contacts_info1),
h('ul', [
h('li', Messages.contacts_info2),
h('li', Messages.contacts_info3),
h('li', Messages.contacts_info4),
])
])
]);
var friendList = h('div#cp-app-contacts-friendlist', [
h('span.fa.fa-spinner.fa-pulse.fa-4x.fa-fw.cp-app-contacts-spinner'),
h('div.cp-app-contacts-padchat.cp-app-contacts-category', [
h('div.cp-app-contacts-category-content')
]),
h('div.cp-app-contacts-friends.cp-app-contacts-category', [
h('div.cp-app-contacts-category-content'),
h('h2.cp-app-contacts-category-title', Messages.contacts_friends),
]),
h('div.cp-app-contacts-rooms.cp-app-contacts-category', [
h('div.cp-app-contacts-category-content'),
h('h2.cp-app-contacts-category-title', Messages.contacts_rooms),
]),
]);
var $userlist = $(friendList).appendTo($container);
var $messages = $(messaging).appendTo($container);
var state = window.state = { var state = window.state = {
active: '', active: '',
channels: {}
}; };
state.channels = {}; var contactsData = state.contactsData = {};
var displayNames = state.displayNames = {};
var avatars = state.avatars = {}; var avatars = state.avatars = {};
var setActive = function (curvePublic) { var setActive = function (id) {
state.active = curvePublic; state.active = id;
}; };
var isActive = function (curvePublic) { var isActive = function (id) {
return curvePublic === state.active; return id === state.active;
}; };
var find = {}; var find = {};
find.inList = function (curvePublic) { find.inList = function (id) {
return $userlist.find(dataQuery(curvePublic)); return $userlist.find(dataQuery(id));
};
var notifyToolbar = function () {
if (!toolbar || !toolbar['chat']) { return; }
if (toolbar['chat'].find('button').hasClass('cp-toolbar-button-active')) { return; }
toolbar['chat'].find('button').addClass('cp-toolbar-notification');
};
var notify = function (id) {
find.inList(id).addClass('cp-app-contacts-notify');
};
var unnotify = function (id) {
find.inList(id).removeClass('cp-app-contacts-notify');
}; };
var notify = function (curvePublic) { var onResize = function () {
find.inList(curvePublic).addClass('cp-app-contacts-notify'); // Don't update the width if we are in the contacts app
if (!isApp) { return; }
var w = $userlist[0].offsetWidth - $userlist[0].clientWidth;
$userlist.css('width', (68 + w)+'px');
}; };
var unnotify = function (curvePublic) { var reorderRooms = function () {
find.inList(curvePublic).removeClass('cp-app-contacts-notify'); var channels = Object.keys(state.channels).sort(function (a, b) {
var m1 = state.channels[a].messages.slice(-1)[0];
var m2 = state.channels[b].messages.slice(-1)[0];
if (!m2) { return !m1 ? 0 : 1; }
if (!m1) { return -1; }
return m1.time - m2.time;
});
channels.forEach(function (c, i) {
$userlist.find(dataQuery(c)).css('order', i);
});
// Make sure the width is correct even if there is a scrollbar
onResize();
}; };
$(window).on('resize', onResize);
var m = function (md) { var m = function (md) {
var d = h('div.cp-app-contacts-content'); var d = h('div.cp-app-contacts-content');
try { try {
@ -82,25 +164,36 @@ define([
var markup = {}; var markup = {};
markup.message = function (msg) { markup.message = function (msg) {
if (msg.type !== 'MSG') { return; }
var curvePublic = msg.author; var curvePublic = msg.author;
var name = displayNames[msg.author]; var name = typeof msg.name !== "undefined" ? (msg.name || Messages.anonymous)
: contactsData[msg.author].displayName;
var d = msg.time ? new Date(msg.time) : undefined;
var day = d ? d.toLocaleDateString() : '';
var hour = d ? d.toLocaleTimeString() : '';
return h('div.cp-app-contacts-message', { return h('div.cp-app-contacts-message', {
title: msg.time? new Date(msg.time).toLocaleString(): '?', //title: time || '?',
'data-key': curvePublic, 'data-user': curvePublic,
'data-day': day
}, [ }, [
name? h('div.cp-app-contacts-sender', name): undefined, name? h('div.cp-app-contacts-sender', [
h('span.cp-app-contacts-sender-name', name),
h('span.cp-app-contacts-sender-time', day)
]): undefined,
m(msg.text), m(msg.text),
h('div.cp-app-contacts-time', hour)
]); ]);
}; };
var getChat = function (curvePublic) { var getChat = function (id) {
return $messages.find(dataQuery(curvePublic)); return $messages.find(dataQuery(id));
}; };
var normalizeLabels = function ($messagebox) { var normalizeLabels = function ($messagebox) {
$messagebox.find('div.cp-app-contacts-message').toArray().reduce(function (a, b) { $messagebox.find('div.cp-app-contacts-message').toArray().reduce(function (a, b) {
var $b = $(b); var $b = $(b);
if ($(a).data('key') === $b.data('key')) { if ($(a).data('user') === $b.data('user') &&
$(a).data('day') === $b.data('day')) {
$b.find('.cp-app-contacts-sender').hide(); $b.find('.cp-app-contacts-sender').hide();
return a; return a;
} }
@ -108,29 +201,31 @@ define([
}, []); }, []);
}; };
markup.chatbox = function (curvePublic, data) { markup.chatbox = function (id, data, curvePublic) {
var moreHistory = h('span.cp-app-contacts-more-history.fa.fa-history', { var moreHistory = h('span.cp-app-contacts-more-history.fa.fa-history', {
title: Messages.contacts_fetchHistory, title: Messages.contacts_fetchHistory,
}); });
var displayName = data.displayName;
var chan = state.channels[id];
var displayName = chan.name;
var fetching = false; var fetching = false;
var $moreHistory = $(moreHistory).click(function () { var $moreHistory = $(moreHistory).click(function () {
if (fetching) { return; } if (fetching) { return; }
// get oldest known message... // get oldest known message...
var channel = state.channels[curvePublic]; var channel = state.channels[id];
if (channel.exhausted) { if (channel.exhausted) {
return void $moreHistory.addClass('cp-app-contacts-faded'); return void $moreHistory.addClass('cp-app-contacts-faded');
} }
console.log('getting history'); debug('getting history');
var sig = channel.TAIL || channel.HEAD; var sig = channel.TAIL || channel.HEAD;
fetching = true; fetching = true;
var $messagebox = $(getChat(curvePublic)).find('.cp-app-contacts-messages'); var $messagebox = $(getChat(id)).find('.cp-app-contacts-messages');
messenger.getMoreHistory(curvePublic, sig, 10, function (e, history) { messenger.getMoreHistory(id, sig, 10, function (e, history) {
fetching = false; fetching = false;
if (e) { return void console.error(e); } if (e) { return void console.error(e); }
@ -139,13 +234,16 @@ define([
return; return;
} }
history.forEach(function (msg) { history.forEach(function (msg, i) {
if (channel.exhausted) { return; } if (channel.exhausted) { return; }
if (msg.sig) { if (msg.sig) {
if (i === 0 && history.length > 1 && msg.sig === channel.TAIL) {
// First message is usually the lastKnownHash, ignore it
return;
}
if (msg.sig === channel.TAIL) { if (msg.sig === channel.TAIL) {
console.error('No more messages to fetch'); console.error('No more messages to fetch');
channel.exhausted = true; channel.exhausted = true;
console.log(channel);
return void $moreHistory.addClass('cp-app-contacts-faded'); return void $moreHistory.addClass('cp-app-contacts-faded');
} else { } else {
channel.TAIL = msg.sig; channel.TAIL = msg.sig;
@ -156,9 +254,11 @@ define([
if (msg.type !== 'MSG') { return; } if (msg.type !== 'MSG') { return; }
// FIXME Schlameil the painter (performance does not scale well) // FIXME Schlameil the painter (performance does not scale well)
// XXX trust the server?
/*
if (channel.messages.some(function (old) { if (channel.messages.some(function (old) {
return msg.sig === old.sig; return msg.sig === old.sig;
})) { return; } })) { return; }*/
channel.messages.unshift(msg); channel.messages.unshift(msg);
var el_message = markup.message(msg); var el_message = markup.message(msg);
@ -176,22 +276,43 @@ define([
UI.confirm(Messages.contacts_confirmRemoveHistory, function (yes) { UI.confirm(Messages.contacts_confirmRemoveHistory, function (yes) {
if (!yes) { return; } if (!yes) { return; }
messenger.clearOwnedChannel(data.channel, function (e) { messenger.clearOwnedChannel(id, function (e) {
if (e) { if (e) {
console.error(e); console.error(e);
UI.alert(Messages.contacts_removeHistoryServerError); UI.alert(Messages.contacts_removeHistoryServerError);
return; return;
} }
// XXX clear the UI?
}); });
}); });
}); });
var avatar = h('div.cp-avatar'); var avatar = h('div.cp-avatar');
var header = h('div.cp-app-contacts-header', [
avatar, var headerContent = [avatar, moreHistory, data.isFriendCHat ? removeHistory : undefined];
moreHistory, if (isApp) {
removeHistory, headerContent = [
]); h('div.cp-app-contacts-header-title', Messages.contacts_padTitle),
moreHistory
];
}
var header = h('div.cp-app-contacts-header', headerContent);
var priv = metadataMgr.getPrivateData();
var closeTips = h('span.fa.fa-window-close.cp-app-contacts-tips-close');
var tips;
if (isApp && Util.find(priv.settings, ['general', 'hidetips', 'chat']) !== true) {
tips = h('div.cp-app-contacts-tips', [
closeTips,
Messages.contacts_warning
]);
}
$(closeTips).click(function () {
$(tips).hide();
common.setAttribute(['general', 'hidetips', 'chat'], true);
});
var messages = h('div.cp-app-contacts-messages'); var messages = h('div.cp-app-contacts-messages');
var input = h('textarea', { var input = h('textarea', {
placeholder: Messages.contacts_typeHere placeholder: Messages.contacts_typeHere
@ -205,12 +326,13 @@ define([
]); ]);
var $avatar = $(avatar); var $avatar = $(avatar);
if (data.avatar && avatars[data.avatar]) { var friend = contactsData[curvePublic] || {};
$avatar.append(avatars[data.avatar]).append(rightCol); if (friend.avatar && avatars[friend.avatar]) {
$avatar.append(avatars[friend.avatar]).append(rightCol);
} else { } else {
common.displayAvatar($avatar, data.avatar, data.displayName, function ($img) { common.displayAvatar($avatar, friend.avatar, displayName, function ($img) {
if (data.avatar && $img) { if (friend.avatar && $img) {
avatars[data.avatar] = $img[0].outerHTML; avatars[friend.avatar] = $img[0].outerHTML;
} }
$(rightCol).insertAfter($avatar); $(rightCol).insertAfter($avatar);
}); });
@ -221,14 +343,14 @@ define([
if (typeof(content) !== 'string' || !content.trim()) { return; } if (typeof(content) !== 'string' || !content.trim()) { return; }
if (sending) { return false; } if (sending) { return false; }
sending = true; sending = true;
messenger.sendMessage(curvePublic, content, function (e) { messenger.sendMessage(id, content, function (e) {
if (e) { if (e) {
// failed to send // failed to send
return void console.error('failed to send'); return void console.error('failed to send');
} }
input.value = ''; input.value = '';
sending = false; sending = false;
console.log('sent successfully'); debug('sent successfully');
var $messagebox = $(messages); var $messagebox = $(messages);
var height = $messagebox[0].scrollHeight; var height = $messagebox[0].scrollHeight;
@ -268,9 +390,11 @@ define([
$(sendButton).click(function () { send(input.value); }); $(sendButton).click(function () { send(input.value); });
return h('div.cp-app-contacts-chat', { return h('div.cp-app-contacts-chat', {
'data-key': curvePublic, 'data-key': id,
'data-user': data.isFriendChat && curvePublic
}, [ }, [
header, header,
tips,
messages, messages,
h('div.cp-app-contacts-input', [ h('div.cp-app-contacts-input', [
input, input,
@ -283,16 +407,20 @@ define([
$messages.find('.cp-app-contacts-info').hide(); $messages.find('.cp-app-contacts-info').hide();
}; };
var updateStatus = function (curvePublic) { var showInfo = function () {
var $status = find.inList(curvePublic).find('.cp-app-contacts-status'); $messages.find('.cp-app-contacts-info').show();
// FIXME this stopped working :( };
messenger.getStatus(curvePublic, function (e, online) {
var updateStatus = function (id) {
if (!state.channels[id]) { return; }
var $status = find.inList(id).find('.cp-app-contacts-status');
messenger.getStatus(id, function (e, online) {
// if error maybe you shouldn't display this friend... // if error maybe you shouldn't display this friend...
if (e) { if (e) {
find.inList(curvePublic).hide(); find.inList(id).hide();
getChat(curvePublic).hide(); getChat(id).hide();
return void console.error(curvePublic, e); return void console.error(id, e);
} }
if (online) { if (online) {
return void $status return void $status
@ -302,72 +430,81 @@ define([
}); });
}; };
var display = function (curvePublic) { var display = function (chanId) {
var channel = state.channels[curvePublic]; var channel = state.channels[chanId];
var lastMsg = channel.messages.slice(-1)[0]; var lastMsg = channel.messages.slice(-1)[0];
if (lastMsg) { if (lastMsg) {
channel.HEAD = lastMsg.sig; channel.HEAD = lastMsg.sig;
messenger.setChannelHead(curvePublic, channel.HEAD, function (e) { messenger.setChannelHead(chanId, channel.HEAD, function (e) {
if (e) { console.error(e); } if (e) { console.error(e); }
}); });
} }
setActive(curvePublic); setActive(chanId);
unnotify(curvePublic); unnotify(chanId);
var $chat = getChat(curvePublic); var $chat = getChat(chanId);
hideInfo(); hideInfo();
$messages.find('div.cp-app-contacts-chat[data-key]').hide(); $messages.find('div.cp-app-contacts-chat[data-key]').hide();
if ($chat.length) { if ($chat.length) {
var $chat_messages = $chat.find('div.cp-app-contacts-message'); var $chat_messages = $chat.find('div.cp-app-contacts-message');
if (!$chat_messages.length) { if ($chat_messages.length < 10) { //|| channel.needMoreHistory) { XXX
delete channel.needMoreHistory;
var $more = $chat.find('.cp-app-contacts-more-history'); var $more = $chat.find('.cp-app-contacts-more-history');
$more.click(); $more.click();
} }
return void $chat.show(); $chat.show();
return;
} else {
console.error("Chat is missing... Please reload the page and try again.");
} }
messenger.getFriendInfo(curvePublic, function (e, info) {
if (e) { return void console.error(e); } // FIXME
var chatbox = markup.chatbox(curvePublic, info);
$messages.append(chatbox);
});
}; };
var removeFriend = function (curvePublic) { var removeFriend = function (curvePublic) {
messenger.removeFriend(curvePublic, function (e /*, removed */) { messenger.removeFriend(curvePublic, function (e /*, removed */) {
if (e) { return void console.error(e); } if (e) { return void console.error(e); }
find.inList(curvePublic).remove();
//console.log(removed);
}); });
}; };
markup.friend = function (data) { markup.room = function (id, room, userlist) {
var curvePublic = data.curvePublic; var roomEl = h('div.cp-app-contacts-friend.cp-avatar', {
var friend = h('div.cp-app-contacts-friend.cp-avatar', { 'data-key': id,
'data-key': curvePublic, 'data-user': room.isFriendChat ? userlist[0].curvePublic : '',
title: room.name
}); });
var remove = h('span.cp-app-contacts-remove.fa.fa-user-times', { var remove = h('span.cp-app-contacts-remove.fa.fa-user-times', {
title: Messages.contacts_remove title: Messages.contacts_remove
}); });
var status = h('span.cp-app-contacts-status'); var leaveRoom = h('span.cp-app-contacts-remove.fa.fa-sign-out', {
title: Messages.contacts_leaveRoom
});
var status = h('span.cp-app-contacts-status', {
title: Messages.contacts_online
});
var rightCol = h('span.cp-app-contacts-right-col', [ var rightCol = h('span.cp-app-contacts-right-col', [
h('span.cp-app-contacts-name', [data.displayName]), h('span.cp-app-contacts-name', [room.name]),
remove, room.isFriendChat ? remove :
room.isPadChat ? undefined : leaveRoom,
]); ]);
var $friend = $(friend) var friendData = room.isFriendChat ? userlist[0] : {};
.click(function () {
display(curvePublic); var $room = $(roomEl).click(function () {
}) display(id);
.dblclick(function () { }).dblclick(function () {
if (data.profile) { window.open(origin + '/profile/#' + data.profile); } if (friendData.profile) { window.open(origin + '/profile/#' + friendData.profile); }
}); });
$(remove).click(function (e) { $(remove).click(function (e) {
e.stopPropagation(); e.stopPropagation();
var channel = state.channels[id];
if (!channel.isFriendChat) { return; }
var curvePublic = channel.curvePublic;
var friend = contactsData[curvePublic] || friendData;
UI.confirm(Messages._getKey('contacts_confirmRemove', [ UI.confirm(Messages._getKey('contacts_confirmRemove', [
Util.fixHTML(data.displayName) Util.fixHTML(friend.name)
]), function (yes) { ]), function (yes) {
if (!yes) { return; } if (!yes) { return; }
removeFriend(curvePublic, function (e) { removeFriend(curvePublic, function (e) {
@ -379,41 +516,42 @@ define([
}, undefined, true); }, undefined, true);
}); });
if (data.avatar && avatars[data.avatar]) { if (friendData.avatar && avatars[friendData.avatar]) {
$friend.append(avatars[data.avatar]); $room.append(avatars[friendData.avatar]);
$friend.append(rightCol); $room.append(rightCol);
} else { } else {
common.displayAvatar($friend, data.avatar, data.displayName, function ($img) { common.displayAvatar($room, friendData.avatar, room.name, function ($img) {
if (data.avatar && $img) { if (friendData.avatar && $img) {
avatars[data.avatar] = $img[0].outerHTML; avatars[friendData.avatar] = $img[0].outerHTML;
} }
$friend.append(rightCol); $room.append(rightCol);
}); });
} }
$friend.append(status); $room.append(status);
return $friend; return $room;
}; };
var isBottomedOut = function ($elem) { var isBottomedOut = function ($elem) {
return ($elem[0].scrollHeight - $elem.scrollTop() === $elem.outerHeight()); return ($elem[0].scrollHeight - $elem.scrollTop() === $elem.outerHeight());
}; };
var initializing = true;
messenger.on('message', function (message) { messenger.on('message', function (message) {
if (!initializing) { Notifier.notify(); } var chanId = message.channel;
var curvePublic = message.curve; var channel = state.channels[chanId];
if (!channel) { return; }
var name = displayNames[curvePublic]; var chat = getChat(chanId);
var chat = getChat(curvePublic, name);
console.log(message); debug(message);
var el_message = markup.message(message); var el_message = markup.message(message);
state.channels[curvePublic].messages.push(message); common.notify();
notifyToolbar();
var $chat = $(chat); channel.messages.push(message);
var $chat = $(chat);
if (!$chat.length) { if (!$chat.length) {
console.error("Got a message but the chat isn't open"); console.error("Got a message but the chat isn't open");
} }
@ -427,111 +565,266 @@ define([
$messagebox.scrollTop($messagebox.outerHeight()); $messagebox.scrollTop($messagebox.outerHeight());
} }
normalizeLabels($messagebox); normalizeLabels($messagebox);
reorderRooms();
var channel = state.channels[curvePublic]; if (isActive(chanId)) {
if (!channel) {
console.error('expected channel [%s] to be open', curvePublic);
return;
}
if (isActive(curvePublic)) {
channel.HEAD = message.sig; channel.HEAD = message.sig;
messenger.setChannelHead(curvePublic, message.sig, function (e) { messenger.setChannelHead(chanId, message.sig, function (e) {
if (e) { return void console.error(e); } if (e) { return void console.error(e); }
}); });
return; return;
} }
var lastMsg = channel.messages.slice(-1)[0]; var lastMsg = channel.messages.slice(-1)[0];
if (lastMsg.sig !== channel.HEAD) { if (lastMsg.sig !== channel.HEAD) {
return void notify(curvePublic); return void notify(chanId);
} }
unnotify(curvePublic); unnotify(chanId);
}); });
messenger.on('join', function (curvePublic, channel) { messenger.on('join', function (data, channel) {
channel = channel; if (data.curvePublic) {
updateStatus(curvePublic); contactsData[data.curvePublic] = data;
}
updateStatus(channel);
// TODO room refresh online userlist
}); });
messenger.on('leave', function (curvePublic, channel) { messenger.on('leave', function (data, channel) {
channel = channel; if (contactsData[data.curvePublic]) {
updateStatus(curvePublic); delete contactsData[data.curvePublic];
}
updateStatus(channel);
// TODO room refresh online userlist
}); });
// change in your friend list // change in your friend list
messenger.on('update', function (info, curvePublic) { messenger.on('update', function (info, types, channel) {
var name = displayNames[curvePublic] = info.displayName; if (!info || !info.curvePublic) { return; }
// Make sure we don't store useless data (friends data in pad chat or the other way)
if (channel && !state.channels[channel]) { return; }
var curvePublic = info.curvePublic;
contactsData[curvePublic] = info;
if (types.indexOf('displayName') !== -1) {
var name = info.displayName;
// update label in friend list
$userlist.find(userQuery(curvePublic)).find('.cp-app-contacts-name').text(name);
$userlist.find(userQuery(curvePublic)).attr('title', name);
// update title bar and messages
$messages.find(userQuery(curvePublic) + ' .cp-app-contacts-header ' +
'.cp-app-contacts-name, div.cp-app-contacts-message'+
userQuery(curvePublic) + ' div.cp-app-contacts-sender').text(name);
// TODO room
// Update name in room userlist
}
if (types.indexOf('profile') !== -1) {
// update dblclick event in friend list
$userlist.find(userQuery(curvePublic)).off('dblclick').dblclick(function () {
if (info.profile) { window.open(origin + '/profile/#' + info.profile); }
});
}
// update label in friend list if (types.indexOf('avatar') !== -1) {
find.inList(curvePublic).find('.cp-app-contacts-name').text(name); var $mAvatar = $messages
.find(userQuery(curvePublic) +' .cp-app-contacts-header .cp-avatar');
var $lAvatar = $userlist.find(userQuery(curvePublic));
$lAvatar.find('.cp-avatar-default, media-tag').remove();
// update title bar and messages var $div = $('<div>');
$messages.find(dataQuery(curvePublic) + ' .cp-app-contacts-header ' + common.displayAvatar($div, info.avatar, info.displayName, function ($img) {
'.cp-app-contacts-name, div.cp-app-contacts-message'+ if (info.avatar && $img) {
dataQuery(curvePublic) + ' div.cp-app-contacts-sender').text(name).text(name); avatars[info.avatar] = $img[0].outerHTML;
}
$mAvatar.html($div.html());
$lAvatar.find('.cp-app-contacts-right-col').before($div.html());
});
}
}); });
var connectToFriend = function (curvePublic, cb) { var execCommand = function (cmd, data, cb) {
messenger.getFriendInfo(curvePublic, function (e, info) { sframeChan.query('Q_CHAT_COMMAND', {cmd: cmd, data: data}, function (err, obj) {
if (e) { return void console.error(e); } if (err || (obj && obj.error)) { return void cb(err || (obj && obj.error)); }
var name = displayNames[curvePublic] = info.displayName; cb(void 0, obj);
initChannel(state, curvePublic, info); });
};
var initializeRoom = function (room) {
var id = room.id;
initChannel(state, room);
execCommand('GET_USERLIST', {id: id}, function (e, list) {
if (e || list.error) { return void console.error(e || list.error); }
if (!room.isPadChat && (!Array.isArray(list) || !list.length)) {
return void console.error("Empty room!");
}
debug('userlist: ' + JSON.stringify(list), id);
var friend = {};
if (room.isFriendChat) {
// This is a friend, the userlist is only one user.
friend = list[0];
contactsData[friend.curvePublic] = friend;
}
var chatbox = markup.chatbox(curvePublic, info); var chatbox = markup.chatbox(id, room, friend.curvePublic);
$(chatbox).hide(); $(chatbox).hide();
$messages.append(chatbox); $messages.append(chatbox);
var friend = markup.friend(info, name); var $messagebox = $(chatbox).find('.cp-app-contacts-messages');
$userlist.append(friend); room.messages.forEach(function (msg) {
messenger.openFriendChannel(curvePublic, function (e) { var el_message = markup.message(msg);
if (e) { return void console.error(e); } $messagebox.append(el_message);
cb();
updateStatus(curvePublic);
// don't add friends that are already in your userlist
//if (friendExistsInUserList(k)) { return; }
}); });
normalizeLabels($messagebox);
var roomEl = markup.room(id, room, list);
var $parentEl;
if (room.isFriendChat) {
$parentEl = $userlist.find('.cp-app-contacts-friends');
} else if (room.isPadChat) {
$parentEl = $userlist.find('.cp-app-contacts-padchat');
} else {
$parentEl = $userlist.find('.cp-app-contacts-rooms');
}
$parentEl.find('.cp-app-contacts-category-content').append(roomEl);
reorderRooms();
updateStatus(id);
if (isApp && room.isPadChat) {
$container.removeClass('cp-app-contacts-initializing');
display(room.id);
}
}); });
}; };
messenger.on('friend', function (curvePublic) { messenger.on('friend', function (curvePublic) {
console.log('new friend: ', curvePublic); if (isApp) { return; }
//console.error("TODO redraw user list"); debug('new friend: ', curvePublic);
//console.error("TODO connect to new friend"); execCommand('GET_ROOMS', {curvePublic: curvePublic}, function (err, rooms) {
// FIXME this doesn't work right now because the friend hasn't been fully added? if (err) { return void console.error(err); }
connectToFriend(curvePublic, function () { debug('rooms: ' + JSON.stringify(rooms));
//console.error('connected'); rooms.forEach(initializeRoom);
}); });
}); });
messenger.on('unfriend', function (curvePublic) { messenger.on('unfriend', function (curvePublic, removedByMe) {
console.log('unfriend', curvePublic); if (isApp) { return; }
find.inList(curvePublic).remove(); var channel = state.channels[state.active];
console.error('TODO remove chatbox'); $userlist.find(userQuery(curvePublic)).remove();
console.error('TODO show something if that chatbox was active'); $messages.find(userQuery(curvePublic)).remove();
if (channel && channel.curvePublic === curvePublic) {
showInfo();
}
if (!removedByMe) {
// TODO UI.alert if this is triggered by the other guy
}
}); });
common.getMetadataMgr().onChange(function () { common.getMetadataMgr().onTitleChange(function () {
//messenger.checkNewFriends(); var padChat = common.getPadChat();
messenger.updateMyData(); var md = common.getMetadataMgr().getMetadata();
var name = md.title || md.defaultTitle;
$userlist.find(dataQuery(padChat)).find('.cp-app-contacts-name').text(name);
$userlist.find(dataQuery(padChat)).attr('title', name);
$messages.find(dataQuery(padChat) + ' .cp-app-contacts-header .cp-app-contacts-name')
.text(name);
var $mAvatar = $messages.find(dataQuery(padChat) +' .cp-app-contacts-header .cp-avatar');
var $lAvatar = $userlist.find(dataQuery(padChat));
$lAvatar.find('.cp-avatar-default, media-tag').remove();
var $div = $('<div>');
common.displayAvatar($div, null, name, function () {
$mAvatar.html($div.html());
$lAvatar.find('.cp-app-contacts-right-col').before($div.html());
});
}); });
// FIXME dirty hack // TODO room
// messenger.on('joinroom', function (chanid))
// messenger.on('leaveroom', function (chanid))
messenger.getMyInfo(function (e, info) { messenger.getMyInfo(function (e, info) {
displayNames[info.curvePublic] = info.displayName; contactsData[info.curvePublic] = info;
}); });
messenger.getFriendList(function (e, keys) { var ready = false;
var count = keys.length + 1; var onMessengerReady = function () {
var ready = function () { if (isApp) { return; }
count--; if (ready) { return; }
if (count === 0) { ready = true;
initializing = false;
UI.removeLoadingScreen(); execCommand('GET_ROOMS', null, function (err, rooms) {
if (err) { return void console.error(err); }
debug('rooms: ' + JSON.stringify(rooms));
rooms.forEach(initializeRoom);
});
$container.removeClass('cp-app-contacts-initializing');
};
var onPadChatReady = function (data) {
var padChat = common.getPadChat();
if (data !== padChat) { return; }
if (state.channels[data]) { return; }
execCommand('GET_ROOMS', {padChat: data}, function (err, rooms) {
if (err) { return void console.error(err); }
if (!Array.isArray(rooms) || rooms.length !== 1) {
return void console.error('Invalid pad chat');
} }
}; var room = rooms[0];
ready(); var md = common.getMetadataMgr().getMetadata();
keys.forEach(function (curvePublic) { var name = md.title || md.defaultTitle;
connectToFriend(curvePublic, ready); room.name = name;
rooms.forEach(initializeRoom);
}); });
};
var onDisconnect = function () {
debug('disconnected');
$messages.find('.cp-app-contacts-input textarea').prop('disabled', true);
};
var onReconnect = function () {
debug('reconnected');
$messages.find('.cp-app-contacts-input textarea').prop('disabled', false);
};
// Initialize chat when outer is ready (all channels loaded)
// TODO: try again in outer if fail to load a channel
if (!isApp) {
execCommand('INIT_FRIENDS', null, function () {});
execCommand('IS_READY', null, function (err, yes) {
if (yes) { onMessengerReady(); }
});
}
sframeChan.on('EV_CHAT_EVENT', function (obj) {
if (obj.ev === 'READY') {
onMessengerReady();
return;
}
if (obj.ev === 'PADCHAT_READY') {
onPadChatReady(obj.data);
return;
}
if (obj.ev === 'DISCONNECT') {
onDisconnect();
return;
}
if (obj.ev === 'RECONNECT') {
onReconnect();
return;
}
}); });
}; };

@ -653,8 +653,10 @@
#cp-app-drive-new-ghost-dialog.cp-modal-container { #cp-app-drive-new-ghost-dialog.cp-modal-container {
.drive_fileIcon; .drive_fileIcon;
border: 1px solid @colortheme_modal-fg;
li:not(.cp-app-drive-element-selected):hover { li:not(.cp-app-drive-element-selected):hover {
border: 1px solid white; background: @colortheme_modal-fg;
color: @colortheme_modal-bg;
} }
.cp-modal { .cp-modal {
display: flex; display: flex;
@ -663,14 +665,16 @@
cursor: pointer; cursor: pointer;
} }
&> p { &> p {
margin: 50px; display: flex;
align-items: center;
justify-content: center;
} }
&> div { &> div {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
align-content: center; align-content: center;
overflow-y: auto; flex: 1;
.cp-app-drive-new-upload { .cp-app-drive-new-upload {
break-after: always; break-after: always;
page-break-after: always; page-break-after: always;
@ -682,23 +686,27 @@
white-space: nowrap; white-space: nowrap;
} }
@media screen and (max-height: @browser_media-not-big) { @media screen and (max-width: @browser_media-medium-screen),
screen and (max-height: @browser_media-medium-screen) {
.cp-modal { .cp-modal {
& > p { & > p {
display: none; display: none;
} }
& > div { & > div {
align-content: unset;
li { li {
height: 40px; height: 40px;
width: 90%; width: 90%;
display: flex; display: flex;
align-items: center; align-items: center;
align-content: unset;
.fa { .fa {
font-size: 32px; font-size: 32px;
min-width: 50px;
} }
.cp-app-drive-new-name { .cp-app-drive-new-name {
height: auto; height: auto;
overflow: hidden;
text-overflow: ellipsis;
} }
} }
} }

@ -1207,15 +1207,23 @@ define([
if (paths) { if (paths) {
paths.forEach(function (p) { pathsList.push(p.path); }); paths.forEach(function (p) { pathsList.push(p.path); });
} }
var hasOwned = pathsList.some(function (p) {
var el = manager.find(p);
var data = manager.isSharedFolder(el) ? manager.getSharedFolderData(el)
: manager.getFileData(el);
return data.owners && data.owners.indexOf(edPublic) !== -1;
});
var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [pathsList.length]); var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [pathsList.length]);
if (pathsList.length === 1) { if (pathsList.length === 1) {
msg = Messages.fm_removePermanentlyDialog; msg = hasOwned ? Messages.fm_deleteOwnedPad : Messages.fm_removePermanentlyDialog;
} else if (hasOwned) {
msg = msg + '<br><em>' + Messages.fm_removePermanentlyNote + '</em>';
} }
UI.confirm(msg, function(res) { UI.confirm(msg, function(res) {
$(window).focus(); $(window).focus();
if (!res) { return; } if (!res) { return; }
manager.delete(pathsList, refresh); manager.delete(pathsList, refresh);
}); }, null, true);
}; };
// Drag & drop: // Drag & drop:
// The data transferred is a stringified JSON containing the path of the dragged element // The data transferred is a stringified JSON containing the path of the dragged element
@ -1290,6 +1298,7 @@ define([
if (!oldPaths) { return; } if (!oldPaths) { return; }
// A moved element should be removed from its previous location // A moved element should be removed from its previous location
var movedPaths = []; var movedPaths = [];
var sharedF = false; var sharedF = false;
oldPaths.forEach(function (p) { oldPaths.forEach(function (p) {
movedPaths.push(p.path); movedPaths.push(p.path);
@ -1301,9 +1310,25 @@ define([
var newPath = findDropPath(ev.target); var newPath = findDropPath(ev.target);
if (!newPath) { return; } if (!newPath) { return; }
if (sharedF && manager.isPathIn(newPath, [TRASH])) { if (sharedF && manager.isPathIn(newPath, [TRASH])) {
deletePaths(null, movedPaths); return void deletePaths(null, movedPaths);
return;
} }
if (manager.isPathIn(newPath, [TRASH])) {
// Filter the selection to remove shared folders.
// Shared folders can't be moved to the trash!
var filteredPaths = movedPaths.filter(function (p) {
var el = manager.find(p);
return !manager.isSharedFolder(el);
});
if (!filteredPaths.length) {
// We only have shared folder, delete them
return void deletePaths(null, movedPaths);
}
movedPaths = filteredPaths;
}
if (movedPaths && movedPaths.length) { if (movedPaths && movedPaths.length) {
moveElements(movedPaths, newPath, null, refresh); moveElements(movedPaths, newPath, null, refresh);
} }
@ -1805,14 +1830,17 @@ define([
.click(function () { .click(function () {
var $input = $('<input>', { var $input = $('<input>', {
'type': 'file', 'type': 'file',
'style': 'display: none;' 'style': 'display: none;',
'multiple': 'multiple'
}).on('change', function (e) { }).on('change', function (e) {
var file = e.target.files[0]; var files = Util.slice(e.target.files);
var ev = { files.forEach(function (file) {
target: $content[0], var ev = {
path: findDropPath($content[0]) target: $content[0],
}; path: findDropPath($content[0])
APP.FM.handleFile(file, ev); };
APP.FM.handleFile(file, ev);
});
}); });
$input.click(); $input.click();
}); });
@ -1889,9 +1917,11 @@ define([
var createShareButton = function (id, $container) { var createShareButton = function (id, $container) {
var $shareBlock = $('<button>', { var $shareBlock = $('<button>', {
'class': 'fa fa-shhare-alt cp-toolbar-share-button', 'class': 'cp-toolbar-share-button',
title: Messages.shareButton title: Messages.shareButton
}); });
$sharedIcon.clone().appendTo($shareBlock);
$('<span>').text(Messages.shareButton).appendTo($shareBlock);
var data = manager.getSharedFolderData(id); var data = manager.getSharedFolderData(id);
var parsed = Hash.parsePadUrl(data.href); var parsed = Hash.parsePadUrl(data.href);
if (!parsed || !parsed.hash) { return void console.error("Invalid href: "+data.href); } if (!parsed || !parsed.hash) { return void console.error("Invalid href: "+data.href); }
@ -2574,7 +2604,12 @@ define([
createNewButton(isInRoot, $toolbar.find('.cp-app-drive-toolbar-leftside')); createNewButton(isInRoot, $toolbar.find('.cp-app-drive-toolbar-leftside'));
var sfId = manager.isInSharedFolder(currentPath); var sfId = manager.isInSharedFolder(currentPath);
if (sfId) { if (sfId) {
var sfData = manager.getSharedFolderData(sfId);
var parsed = Hash.parsePadUrl(sfData.href);
sframeChan.event('EV_DRIVE_SET_HASH', parsed.hash || '');
createShareButton(sfId, $toolbar.find('.cp-app-drive-toolbar-leftside')); createShareButton(sfId, $toolbar.find('.cp-app-drive-toolbar-leftside'));
} else {
sframeChan.event('EV_DRIVE_SET_HASH', '');
} }
createTitle($toolbar.find('.cp-app-drive-path'), path); createTitle($toolbar.find('.cp-app-drive-path'), path);
@ -2721,7 +2756,7 @@ define([
} }
var dataPath = isSharedFolder ? path.slice(0, -1) : path; var dataPath = isSharedFolder ? path.slice(0, -1) : path;
$elementRow.data('path', dataPath); $elementRow.data('path', dataPath);
addDragAndDropHandlers($elementRow, path, true, droppable); addDragAndDropHandlers($elementRow, dataPath, true, droppable);
if (active) { if (active) {
$elementRow.addClass('cp-app-drive-element-active cp-leftside-active'); $elementRow.addClass('cp-app-drive-element-active cp-leftside-active');
} }
@ -3221,16 +3256,23 @@ define([
paths.push($(elmt).data('path')); paths.push($(elmt).data('path'));
}); });
if (!paths.length) { return; } if (!paths.length) { return; }
// Remove shared folders from the selection (they can't be moved to the trash)
// unless the selection is only shared folders
var paths2 = paths.filter(function (p) {
var el = manager.find(p);
return !manager.isSharedFolder(el);
});
// If we are in the trash or anon pad or if we are holding the "shift" key, // If we are in the trash or anon pad or if we are holding the "shift" key,
// delete permanently // delete permanently
// Or if we are in a shared folder // Or if we are in a shared folder
// Or if the selection is only shared folders
if (!APP.loggedIn || isTrash || manager.isInSharedFolder(currentPath) if (!APP.loggedIn || isTrash || manager.isInSharedFolder(currentPath)
|| e.shiftKey) { || e.shiftKey || !paths2.length) {
deletePaths(null, paths); deletePaths(null, paths);
return; return;
} }
// else move to trash // else move to trash
moveElements(paths, [TRASH], false, refresh); moveElements(paths2, [TRASH], false, refresh);
return; return;
} }
}); });

@ -87,6 +87,14 @@ define([
cb(obj); cb(obj);
}); });
}); });
sframeChan.on('EV_DRIVE_SET_HASH', function (hash) {
// Update the hash in the address bar
var ohc = window.onhashchange;
window.onhashchange = function () {};
window.location.hash = hash || '';
window.onhashchange = ohc;
ohc({reset:true});
});
Cryptpad.onNetworkDisconnect.reg(function () { Cryptpad.onNetworkDisconnect.reg(function () {
sframeChan.event('EV_NETWORK_DISCONNECT'); sframeChan.event('EV_NETWORK_DISCONNECT');
}); });

@ -67,7 +67,6 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
#kanban-edit { #kanban-edit {
color: black;
font-weight: bold; font-weight: bold;
} }
} }
@ -77,6 +76,7 @@
width: 100%; width: 100%;
background: transparent; background: transparent;
border: 1px solid rgba(0,0,0,0.3); border: 1px solid rgba(0,0,0,0.3);
color: inherit;
} }
@button-size: 50px; @button-size: 50px;

@ -27,6 +27,7 @@ body.cp-app-pad {
height: 100%; height: 100%;
border: 0; border: 0;
> .cke_inner { > .cke_inner {
overflow: hidden;
flex: 1; flex: 1;
position: unset; position: unset;
display: flex; display: flex;

@ -1092,6 +1092,11 @@ define([
Test(passIfOk); Test(passIfOk);
} }
// No need for onLocal in openPadChat because in poll, we listen for metadata changes
// and save them everytime.
// See `metadataMgr.onChange(function () {`
common.openPadChat(function () {});
UI.removeLoadingScreen(); UI.removeLoadingScreen();
var privateDat = metadataMgr.getPrivateData(); var privateDat = metadataMgr.getPrivateData();
var skipTemp = Util.find(privateDat, var skipTemp = Util.find(privateDat,
@ -1160,6 +1165,7 @@ define([
var configTb = { var configTb = {
displayed: [ displayed: [
'chat',
'userlist', 'userlist',
'title', 'title',
'useradmin', 'useradmin',

@ -37,7 +37,8 @@ define([
window.addEventListener('message', onMsg); window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) { }).nThen(function (/*waitFor*/) {
SFCommonO.start({ SFCommonO.start({
useCreationScreen: true useCreationScreen: true,
messaging: true
}); });
}); });
}); });

@ -107,6 +107,7 @@
.cp-app-slide-viewer { .cp-app-slide-viewer {
width: 50vw; width: 50vw;
overflow: hidden; overflow: hidden;
z-index: 998;
div#cp-app-slide-modal:not(.cp-app-slide-shown) { div#cp-app-slide-modal:not(.cp-app-slide-shown) {
position: relative; position: relative;
top: auto; top: auto;
@ -417,4 +418,4 @@
pre.cp-slide-css-error { pre.cp-slide-css-error {
color: white; color: white;
} }
} }

@ -5,7 +5,7 @@
<meta content="text/html; charset=utf-8" http-equiv="content-type"/> <meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" /> <meta name="referrer" content="no-referrer" />
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script> <script async data-bootload="/common/sframe-app-outer.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style> <style>
html, body { html, body {
margin: 0px; margin: 0px;

@ -1,43 +0,0 @@
// Load #1, load as little as possible because we are in a race to get the loading screen up.
define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js'
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
// Loaded in load #2
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/whiteboard/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req)));
// This is a cheap trick to avoid loading sframe-channel in parallel with the
// loading screen setup.
var done = waitFor();
var onMsg = function (msg) {
var data = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) {
SFCommonO.start({
useCreationScreen: true
});
});
});
Loading…
Cancel
Save