diff --git a/bower.json b/bower.json index 79859659e..828045139 100644 --- a/bower.json +++ b/bower.json @@ -24,7 +24,7 @@ "ckeditor": "4.7.3", "codemirror": "^5.19.0", "requirejs": "2.3.5", - "marked": "0.3.5", + "marked": "0.5.0", "rangy": "rangy-release#~1.3.0", "json.sortify": "~2.1.0", "secure-fabric.js": "secure-v1.7.9", diff --git a/customize.dist/login.js b/customize.dist/login.js index ab9fabfa8..25fcc8dcf 100644 --- a/customize.dist/login.js +++ b/customize.dist/login.js @@ -191,7 +191,10 @@ define([ // load the user's object using the legacy credentials 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 // 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 // it will should be pinned by anyone's drive. if (rt.proxy[Constants.deprecatedKey]) { + waitFor.abort(); return void cb('NO_SUCH_USER', res); } @@ -514,7 +518,22 @@ define([ 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); }, 200); diff --git a/customize.dist/pages.js b/customize.dist/pages.js index 559d0e452..578c1dc24 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -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 [ h('div#cp-main', [ infopageTopbar(), @@ -629,6 +642,11 @@ define([ icons, more ]) + ]), + h('div.row', [ + h('div.cp-crowdfunding', [ + crowdFunding + ]) ]) ]), ]), @@ -810,7 +828,7 @@ define([ placeholder: Msg.login_password, }), h('div.checkbox-container', [ - Pages.createCheckbox('import-recent', Msg.register_importRecent, true), + Pages.createCheckbox('import-recent', Msg.register_importRecent), ]), h('div.extra', [ h('button.login.first.btn', Msg.login_login) diff --git a/customize.dist/src/less2/include/corner.less b/customize.dist/src/less2/include/corner.less index 174c27eef..8df0f172a 100644 --- a/customize.dist/src/less2/include/corner.less +++ b/customize.dist/src/less2/include/corner.less @@ -43,6 +43,10 @@ //transform: scale(0.1); //transform: scale(1); + h1, h2, h3 { + font-size: 1.5em; + } + .cp-corner-filler { float: left; clear: left; @@ -94,6 +98,8 @@ .cp-corner-footer { font-style: italic; font-size: 0.8em; + } + .cp-corner-footer, .cp-corner-text { a { color: @corner-link; &:hover { @@ -103,10 +109,11 @@ } button { - color: white; border: 0px; padding: 5px; color: @colortheme_base; + margin-left: 5px; + outline: none; &.cp-corner-primary { background-color: @corner-button-ok; font-weight: bold; diff --git a/customize.dist/src/less2/include/framework.less b/customize.dist/src/less2/include/framework.less index 4853f663b..a273e8f77 100644 --- a/customize.dist/src/less2/include/framework.less +++ b/customize.dist/src/less2/include/framework.less @@ -12,6 +12,7 @@ @import (reference) './font.less'; @import (reference) "./app-print.less"; @import (reference) "./app-noscroll.less"; +@import (reference) "./messenger.less"; .framework_main(@bg-color, @warn-color, @color) { --LessLoader_require: LessLoader_currentFile(); @@ -36,6 +37,7 @@ .tippy_main(); .checkmark_main(20px); .password_main(); + .messenger_main(); .creation_main( @bg-color: @bg-color, @color: @color diff --git a/customize.dist/src/less2/include/messenger.less b/customize.dist/src/less2/include/messenger.less new file mode 100644 index 000000000..7a56af1d5 --- /dev/null +++ b/customize.dist/src/less2/include/messenger.less @@ -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%); + } + } + } + } +} diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index 858c0a8d9..ec0d68187 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -72,6 +72,18 @@ .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_line-height: 32px; @toolbar_top-height: 64px; @@ -134,9 +146,39 @@ } } - .cp-toolbar-userlist-drawer { + .cp-toolbar-chat-drawer { background-color: @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; min-width: 175px; width: 175px; @@ -145,6 +187,7 @@ overflow-x: hidden; padding: 10px; box-sizing: border-box; + order: -3; .cp-toolbar-userlist-drawer-close { position: absolute; margin-top: -10px; @@ -219,6 +262,14 @@ display: flex; justify-content: space-between; align-items: center; + button { + width: 20px; + font-size: 16px; + padding: 0; + border: none; + height: 20px; + cursor: pointer; + } } .cp-toolbar-userlist-name-input { flex: 1; @@ -235,14 +286,6 @@ min-height: 0; 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 { padding: 0; } @@ -276,11 +319,15 @@ margin: 50px; } &> div { + flex: 1; display: flex; flex-wrap: wrap; justify-content: center; align-content: center; - overflow-y: auto; + } + &> div:last-child { + flex: unset; + margin: 50px 0; } } @@ -299,13 +346,15 @@ 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 { & > p { display: none; } & > div { align-content: unset; + align-items: center; li { height: 40px; width: 200px; @@ -313,9 +362,11 @@ align-items: center; .fa { font-size: 32px; + min-width: 50px; } .cp-icons-name { height: auto; + text-align: left; } } } @@ -344,7 +395,7 @@ color: @toolbar-color; color: var(--toolbar-color); } - .cp-toolbar-userlist-name-edit { + .cp-toolbar-userlist-button { color: @toolbar-userlist-name-edit; color: var(--toolbar-userlist-name-edit); background: transparent; @@ -961,7 +1012,8 @@ 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-spinner { order: 3; } @@ -969,6 +1021,11 @@ width: 125px; text-align: center; } + #cp-toolbar-chat-drawer-open button { + &.cp-toolbar-notification { + animation: notification 2s ease-in-out infinite; + } + } .cp-toolbar-share-button { width: 50px; text-align: center; diff --git a/customize.dist/src/less2/pages/page-index.less b/customize.dist/src/less2/pages/page-index.less index 167423ad1..a97485f31 100644 --- a/customize.dist/src/less2/pages/page-index.less +++ b/customize.dist/src/less2/pages/page-index.less @@ -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) { .container { padding-left: 0; diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index f24cf04b0..4b5b9f825 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -135,6 +135,8 @@ define(function () { out.userListButton = "Liste d'utilisateurs"; + out.chatButton = "Chat"; + out.userAccountButton = "Votre compte"; out.newButton = 'Nouveau'; @@ -363,7 +365,8 @@ define(function () { out.contacts_remove = 'Supprimer ce contact'; out.contacts_confirmRemove = 'Êtes-vous sûr de vouloir supprimer {0} de vos contacts ?'; 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_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_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 out.fm_rootName = "Documents"; @@ -407,6 +416,7 @@ define(function () { out.fm_noname = "Document sans titre"; 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_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_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 ?"; @@ -1204,5 +1214,16 @@ define(function () { 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é."; + // 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 = "

Aider CryptPad

" + + "Pour vous assurer que CryptPad soit activement développé, nous vous invitons à supporter le projet via la " + + 'page OpenCollective, où vous pouvez trouver notre Roadmap et nos objectifs de financement.'; + out.crowdfunding_popup_yes = "Voir la page"; + out.crowdfunding_popup_no = "Pas maintenant"; + out.crowdfunding_popup_never = "Ne plus demander"; + return out; }); diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index efe898558..0a6ec0e5c 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -136,6 +136,8 @@ define(function () { out.userListButton = "User list"; + out.chatButton = "Chat"; + out.userAccountButton = "Your account"; out.newButton = 'New'; @@ -365,6 +367,8 @@ define(function () { out.contacts_remove = 'Remove this contact'; out.contacts_confirmRemove = 'Are you sure you want to remove {0} from your contacts?'; 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_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_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 out.fm_rootName = "Documents"; @@ -407,12 +417,13 @@ define(function () { out.fm_openParent = "Show in folder"; out.fm_noname = "Untitled Document"; 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_removePermanentlyDialog = "Are you sure you want to remove that element from your CryptDrive permanently?"; + out.fm_removeSeveralPermanentlyDialog = "Are you sure you want to permanently remove these {0} elements from your CryptDrive?"; + 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_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_deleteOwnedPads = "Are you sure you want to remove permanently these pads 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 permanently remove these pads from the server?"; 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_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_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 = "

We need your help!

" + + "To ensure that CryptPad is actively developed, consider supporting the project via the " + + 'OpenCollective page, where you can see our Roadmap and Funding goals.'; + out.crowdfunding_popup_yes = "Go to OpenCollective"; + out.crowdfunding_popup_no = "Not now"; + out.crowdfunding_popup_never = "Don't ask me again"; + return out; }); diff --git a/package.json b/package.json index 2dca65ee5..8706b37e1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "2.7.0", + "version": "2.8.0", "license": "AGPL-3.0+", "repository": { "type": "git", diff --git a/www/code/app-code.less b/www/code/app-code.less index 37a18b4ee..b2b3a1a64 100644 --- a/www/code/app-code.less +++ b/www/code/app-code.less @@ -19,17 +19,16 @@ flex-flow: column; height: 100%; min-height: 100%; - width: 50%; min-width: 20%; max-width: 80%; resize: horizontal; overflow: hidden; + width: 50%; &.cp-app-code-fullpage { max-width: 100%; resize: none; flex: 1; } - } .CodeMirror { flex: 1; @@ -51,9 +50,13 @@ #cp-app-code-container { display: none; } #cp-app-code-preview { border: 0; } } + &.cp-chat-visible { + #cp-app-code-container { + width: 35%; + } + } } #cp-app-code-preview { - flex: 1; padding: 5px 20px; overflow: auto; display: inline-block; @@ -63,6 +66,7 @@ font-family: Calibri,Ubuntu,sans-serif; word-wrap: break-word; position: relative; + flex: 1; media-tag { * { max-width:100%; diff --git a/www/common/common-constants.js b/www/common/common-constants.js index 908134bec..c3eb447a2 100644 --- a/www/common/common-constants.js +++ b/www/common/common-constants.js @@ -13,6 +13,8 @@ define(function () { storageKey: 'filesData', tokenKey: 'loginToken', displayPadCreationScreen: 'displayPadCreationScreen', - deprecatedKey: 'deprecated' + deprecatedKey: 'deprecated', + // Sub + plan: 'CryptPad_plan' }; }); diff --git a/www/common/common-interface.js b/www/common/common-interface.js index b33767f48..2929b17d5 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -892,7 +892,7 @@ define([ h('div.cp-corner-filler', { style: "width:60px;" }), h('div.cp-corner-filler', { style: "width:40px;" }), 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), Pages.setHTML(h('div.cp-corner-footer'), footer) ]); diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js index 1c75927fc..b0d13ec81 100644 --- a/www/common/common-messaging.js +++ b/www/common/common-messaging.js @@ -150,7 +150,8 @@ define([ } cfg.friendComplete({ logText: Messages.contacts_added, - netfluxId: sender + netfluxId: sender, + friend: msgData }); var msg = ["FRIEND_REQ_ACK", chan]; var msgStr = Crypto.encrypt(JSON.stringify(msg), key); @@ -163,7 +164,7 @@ define([ if (i !== -1) { pendingRequests.splice(i, 1); } cfg.friendComplete({ logText: Messages.contacts_rejected, - netfluxId: sender + netfluxId: sender, }); cfg.updateMetadata(); return; @@ -180,7 +181,8 @@ define([ } cfg.friendComplete({ logText: Messages.contacts_added, - netfluxId: sender + netfluxId: sender, + friend: data }); }); return; diff --git a/www/common/common-messenger.js b/www/common/common-messenger.js index cd0a5626d..9b2b339c0 100644 --- a/www/common/common-messenger.js +++ b/www/common/common-messenger.js @@ -5,7 +5,9 @@ define([ '/common/common-util.js', '/common/common-realtime.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'; var Msg = { inputs: [], @@ -52,7 +54,7 @@ define([ var msgAlreadyKnown = function (channel, sig) { return channel.messages.some(function (message) { - return message[0] === sig; + return message.sig === sig; }); }; @@ -65,6 +67,7 @@ define([ update: [], friend: [], unfriend: [], + event: [] }, range_requests: {}, }; @@ -73,6 +76,12 @@ define([ messenger.handlers[type].forEach(g); }; + var emit = function (ev, data) { + eachHandler('event', function (f) { + f(ev, data); + }); + }; + messenger.on = function (type, f) { var stack = messenger.handlers[type]; if (!Array.isArray(stack)) { @@ -95,20 +104,26 @@ define([ Msg.hk = network.historyKeeper; var friends = getFriendList(proxy); - var getChannel = function (curvePublic) { - var friend = friends[curvePublic]; - if (!friend) { return; } - var chanId = friend.channel; - if (!chanId) { return; } + var getChannel = function (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] = { messages: [], cb: cb, - curvePublic: curvePublic, - sig: sig, + chanId: chanId, }; }; @@ -120,24 +135,22 @@ define([ 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(hash) !== 'string') { - // FIXME hash is not necessarily defined. - // What does this mean? - console.error("not sure what to do here"); - return; + // Channel is empty! + return void cb(void 0, []); } - var chan = getChannel(curvePublic); + var chan = getChannel(chanId); if (typeof(chan) === 'undefined') { console.error("chan is undefined. we're going to have a problem here"); return; } var txid = Util.uid(); - initRangeRequest(txid, curvePublic, hash, cb); + initRangeRequest(txid, chanId, cb); var msg = [ 'GET_HISTORY_RANGE', chan.id, { from: hash, count: count, @@ -151,38 +164,80 @@ define([ }); }; - var getCurveForChannel = function (id) { + /*var getCurveForChannel = function (id) { var channel = channels[id]; if (!channel) { return; } 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) { - var friend = friends[curvePublic]; - if (!friend) { return void cb('NO_SUCH_FRIEND'); } - cb(void 0, friend.lastKnownHash); + messenger.setChannelHead = function (id, hash, cb) { + var channel = getChannel(id); + if (channel.isFriendChat) { + 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) { - var friend = friends[curvePublic]; - if (!friend) { return void cb('NO_SUCH_FRIEND'); } - friend.lastKnownHash = hash; - cb(); + // Make sure the data we have about our friends are up-to-date when we see them online + var checkFriendData = function (curve, data, channel) { + if (curve === proxy.curvePublic) { return; } + var friend = getFriend(proxy, curve); + 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 var onIdMessage = function (msg, sender) { - var channel; - var isId = Object.keys(channels).some(function (chanId) { - if (channels[chanId].userList.indexOf(sender) !== -1) { - channel = channels[chanId]; - return true; - } - }); + var channel, parsed0; - 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) { return void console.error("Failed to decrypt message"); @@ -206,20 +261,26 @@ define([ // the sender field. This is to prevent replay attacks. if (parsed[2] !== sender || !parsed[1]) { return; } channel.mapId[sender] = parsed[1]; + checkFriendData(parsed[1].curvePublic, parsed[1], channel.id); eachHandler('join', function (f) { f(parsed[1], channel.id); }); if (parsed[0] !== Types.mapId) { return; } // Don't send your key if it's already an ACK // 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 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 channel = getChannel(curvePublic); + var orderMessages = function (channel, new_messages) { var messages = channel.messages; // TODO improve performance, guarantee correct ordering @@ -236,9 +297,9 @@ define([ }; var pushMsg = function (channel, cryptMsg) { - var msg = channel.encryptor.decrypt(cryptMsg); var sig = cryptMsg.slice(0, 64); if (msgAlreadyKnown(channel, sig)) { return; } + var msg = channel.encryptor.decrypt(cryptMsg); var parsedMsg = JSON.parse(msg); var curvePublic; @@ -250,43 +311,38 @@ define([ author: parsedMsg[1], time: parsedMsg[2], text: parsedMsg[3], + channel: channel.id, + name: parsedMsg[4] // Display name for multi-user rooms // this makes debugging a whole lot easier - curve: getCurveForChannel(channel.id), + //curve: getCurveForChannel(channel.id), }; channel.messages.push(res); - eachHandler('message', function (f) { - f(res); - }); + if (!joining[channel.id]) { + // Channel is ready + eachHandler('message', function (f) { + f(res); + }); + } return true; } if (parsedMsg[0] === Types.update) { - if (parsedMsg[1] === proxy.curvePublic) { return; } - 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); - }); + checkFriendData(parsedMsg[1], parsedMsg[3], channel.id); return; } if (parsedMsg[0] === Types.unfriend) { 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); + delete channels[channel.id]; eachHandler('unfriend', function (f) { - f(curvePublic); + f(curvePublic, false); }); }); return; @@ -324,7 +380,7 @@ define([ }); }); eachHandler('update', function (f) { - f(myData, myData.curvePublic); + f(myData, ['displayName', 'profile', 'avatar']); }); friends.me = myData; } @@ -352,12 +408,26 @@ define([ 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') { req.messages.push(parsed[2]); } else if (type === 'HISTORY_RANGE_END') { // process all the messages (decrypt) - var curvePublic = req.curvePublic; - var channel = getChannel(curvePublic); + var channel = getChannel(req.chanId); var decrypted = req.messages.map(function (msg) { if (msg[2] !== 'MSG') { return; } @@ -371,6 +441,8 @@ define([ return null; } }).filter(function (decrypted) { + if (!decrypted.d || decrypted.d[0] !== Types.message) { return; } + if (msgAlreadyKnown(channel, decrypted.sig)) { return; } return decrypted; }).map(function (O) { return { @@ -379,11 +451,12 @@ define([ author: O.d[1], time: O.d[2], 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); return deleteRangeRequest(txid); } else { @@ -395,20 +468,17 @@ define([ if ((parsed.validateKey || parsed.owners) && parsed.channel) { return; } + // End of initial history if (parsed.state && parsed.state === 1 && parsed.channel) { if (channels[parsed.channel]) { // parsed.channel is Ready // channel[parsed.channel].ready(); channels[parsed.channel].ready = true; onChannelReady(parsed.channel); - var updateTypes = channels[parsed.channel].updateOnReady; - if (updateTypes) { - - //channels[parsed.channel].updateUI(updateTypes); - } } return; } + // Initial history message var chan = parsed[3]; if (!chan || !channels[chan]) { return; } pushMsg(channels[chan], parsed[4]); @@ -440,7 +510,7 @@ define([ if (!data) { // friend is not valid console.error('friend is not valid'); - return; + return void cb('INVALID_FRIEND'); } var channel = channels[data.channel]; @@ -458,12 +528,13 @@ define([ var msgStr = JSON.stringify(msg); var cryptMsg = channel.encryptor.encrypt(msgStr); - // TODO emit remove_friend event? try { channel.wc.bcast(cryptMsg).then(function () { - delete friends[curvePublic]; - delete channels[curvePublic]; - Realtime.whenRealtimeSyncs(realtime, function () { + removeFromFriendList(curvePublic, function () { + delete channels[channel.id]; + eachHandler('unfriend', function (f) { + f(curvePublic, true); + }); cb(); }); }, function (err) { @@ -476,9 +547,27 @@ define([ }; 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 = { - validateKey: keys.validateKey, + validateKey: keys ? keys.validateKey : undefined, owners: [proxy.edPublic, data.edPublic], lastKnownHash: data.lastKnownHash }; @@ -489,79 +578,88 @@ define([ }); }; - var openFriendChannel = function (data, f) { - var keys = Curve.deriveKeys(data.curvePublic, proxy.curvePrivate); - var encryptor = Curve.createEncryptor(keys); - network.join(data.channel).then(function (chan) { - var channel = channels[data.channel] = { - id: data.channel, - sending: false, - friendEd: f, - keys: keys, - curve: data.curvePublic, - encryptor: encryptor, - messages: [], - 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 openChannel = function (data) { + var keys = data.keys; + var encryptor = data.encryptor || Curve.createEncryptor(keys); + var channel = { + id: data.channel, + isFriendChat: data.isFriendChat, + isPadChat: data.isPadChat, + sending: false, + encryptor: encryptor, + messages: [], + userList: [], + mapId: {}, + }; - var msg = [Types.message, proxy.curvePublic, +new Date(), payload]; - var msgStr = JSON.stringify(msg); - var cryptMsg = channel.encryptor.encrypt(msgStr); + var onJoining = function (peer) { + if (peer === Msg.hk) { return; } + if (channel.userList.indexOf(peer) !== -1) { return; } + channel.userList.push(peer); - channel.wc.bcast(cryptMsg).then(function () { - pushMsg(channel, cryptMsg); - cb(); - }, function (err) { - cb(err); - }); - } + // Join event will be sent once we are able to ID this peer + var myData = createData(proxy); + delete myData.channel; + var msg = [Types.mapId, myData, channel.wc.myID]; + 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) { 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) { if (peer === Msg.hk) { return; } if (channel.userList.indexOf(peer) !== -1) { return; } channel.userList.push(peer); }); chan.on('join', onJoining); - chan.on('leave', function (peer) { - 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); - }); - }); + chan.on('leave', onLeaving); // FIXME don't subscribe to the channel implicitly - getChannelMessagesSince(chan, data, keys); - }, function (err) { + getChannelMessagesSince(channel, data, keys); + }; + network.join(data.channel).then(onOpen, function (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) { @@ -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(cb) !== 'function') { throw new Error('expected callback'); } @@ -585,10 +683,10 @@ define([ if (!channel) { return void cb('E_NO_CHANNEL'); } joining[channel] = cb; openFriendChannel(friend, curvePublic); - }; + };*/ - messenger.sendMessage = function (curvePublic, payload, cb) { - var channel = getChannel(curvePublic); + messenger.sendMessage = function (id, payload, cb) { + var channel = getChannel(id); if (!channel) { return void cb('NO_CHANNEL'); } if (!network.webChannels.some(function (wc) { if (wc.id === channel.wc.id) { return true; } @@ -597,6 +695,9 @@ define([ } var msg = [Types.message, proxy.curvePublic, +new Date(), payload]; + if (!channel.isFriendChat) { + msg.push(proxy[Constants.displayNameKey]); + } var msgStr = JSON.stringify(msg); var cryptMsg = channel.encryptor.encrypt(msgStr); @@ -608,18 +709,27 @@ define([ }); }; - messenger.getStatus = function (curvePublic, cb) { - var channel = getChannel(curvePublic); + messenger.getStatus = function (chanId, cb) { + // Display green status if one member is not me + var channel = getChannel(chanId); if (!channel) { return void cb('NO_SUCH_CHANNEL'); } 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); }; - messenger.getFriendInfo = function (curvePublic, cb) { + messenger.getFriendInfo = function (channel, cb) { 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'); } // this clone will be redundant when ui uses postmessage cb(void 0, clone(friend)); @@ -633,28 +743,215 @@ define([ }); }; - // TODO listen for changes to your friend list - // emit 'update' events for clients + var loadFriend = function (friend, cb) { + 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) { var curvePublic; if (o === undefined) { // new friend added 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; } - console.error(o, n, p); + if (typeof(n) === 'undefined') { + // Handled by .on('remove') + return; + } }).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) { - 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); return messenger; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 3f75d3369..3422ef6c8 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -12,10 +12,11 @@ define([ '/common/clipboard.js', '/customize/messages.js', '/customize/application_config.js', + '/customize/pages.js', '/bower_components/nthen/index.js', 'css!/customize/fonts/cptools/style.css' ], function ($, Config, Util, Hash, Language, UI, Constants, Feedback, h, MediaTag, Clipboard, - Messages, AppConfig, NThen) { + Messages, AppConfig, Pages, NThen) { var UIElements = {}; // Configure MediaTags to use our local viewer @@ -630,23 +631,25 @@ define([ if (!data.FM) { return; } var $input = $('', { 'type': 'file', - 'style': 'display: none;' + 'style': 'display: none;', + 'multiple': 'multiple' }).on('change', function (e) { - var file = e.target.files[0]; - var ev = { - target: data.target - }; - if (data.filter && !data.filter(file)) { - return; - } - if (data.transformer) { - data.transformer(file, function (newFile) { - data.FM.handleFile(newFile, ev); - if (callback) { callback(); } - }); - return; - } - data.FM.handleFile(file, ev); + var files = Util.slice(e.target.files); + files.forEach(function (file) { + var ev = { + target: data.target + }; + if (data.filter && !data.filter(file)) { + return; + } + if (data.transformer) { + data.transformer(file, function (newFile) { + data.FM.handleFile(newFile, ev); + }); + return; + } + data.FM.handleFile(file, ev); + }); if (callback) { callback(); } }); if (data.accept) { $input.attr('accept', data.accept); } @@ -1779,13 +1782,16 @@ define([ var $container = $('
'); var i = 0; - AppConfig.availablePadTypes.forEach(function (p) { + var types = AppConfig.availablePadTypes.filter(function (p) { if (p === 'drive') { return; } if (p === 'contacts') { return; } if (p === 'todo') { return; } if (p === 'file') { return; } if (!common.isLoggedIn() && AppConfig.registeredOnlyTypes && AppConfig.registeredOnlyTypes.indexOf(p) !== -1) { return; } + return true; + }); + types.forEach(function (p) { var $element = $('
  • ', { 'class': 'cp-icons-element', 'id': 'cp-newpad-icons-'+ (i++) @@ -1809,7 +1815,7 @@ define([ var selected = -1; var next = function () { - selected = ++selected % 5; + selected = ++selected % types.length; $container.find('.cp-icons-element-selected').removeClass('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(); }; + 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; UIElements.displayStorePadPopup = function (common, data) { if (storePopupState) { return; } @@ -2347,15 +2399,20 @@ define([ }); $(hide).click(function () { + UIElements.displayCrowdfunding(common); modal.delete(); }); $(store).click(function () { - modal.delete(); common.getSframeChannel().query("Q_AUTOSTORE_STORE", null, function (err, obj) { - if (err || (obj && obj.error)) { - console.error(err || obj.error); + var error = err || (obj && obj.error); + if (error) { + if (error === 'E_OVER_LIMIT') { + return void UI.warn(Messages.pinLimitReached); + } return void UI.warn(Messages.autostore_error); } + modal.delete(); + UIElements.displayCrowdfunding(common); UI.log(Messages.autostore_saved); }); }); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 2b29ff8ec..150d2a469 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -345,6 +345,9 @@ define([ }; common.getPadAttribute = function (attr, cb, href) { href = Hash.getRelativeHref(href || window.location.href); + if (!href) { + return void cb('E404'); + } postMessage("GET_PAD_ATTRIBUTE", { href: href, attr: attr, @@ -622,6 +625,12 @@ define([ messenger.setChannelHead = function (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.onJoinEvent = Util.mkEvent(); messenger.onLeaveEvent = Util.mkEvent(); @@ -1059,6 +1068,8 @@ define([ CONTACTS_UPDATE: common.messenger.onUpdateEvent.fire, CONTACTS_FRIEND: common.messenger.onFriendEvent.fire, CONTACTS_UNFRIEND: common.messenger.onUnfriendEvent.fire, + // Chat + CHAT_EVENT: common.messenger.onEvent.fire, // Pad PAD_READY: common.padRpc.onReadyEvent.fire, PAD_MESSAGE: common.padRpc.onMessageEvent.fire, @@ -1425,7 +1436,7 @@ define([ postMessage("INIT_RPC", null, waitFor(function (obj) { console.log('RPC handshake complete'); if (obj.error) { return; } - localStorage.plan = obj.plan; + localStorage[Constants.plan] = obj.plan; })); } else if (PINNING_ENABLED) { console.log('not logged in. pads will not be pinned'); diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index c224b4072..2c29afa12 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -25,9 +25,11 @@ define([ // Tasks list var checkedTaskItemPtn = /^\s*(

    )?\[[xX]\](<\/p>)?\s*/; var uncheckedTaskItemPtn = /^\s*(

    )?\[ ?\](<\/p>)?\s*/; + var bogusCheckPtn = //; renderer.listitem = function (text) { var isCheckedTaskItem = checkedTaskItemPtn.test(text); var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text); + var hasBogusInput = bogusCheckPtn.test(text); if (isCheckedTaskItem) { text = text.replace(checkedTaskItemPtn, ' ') + '\n'; @@ -36,6 +38,15 @@ define([ text = text.replace(uncheckedTaskItemPtn, ' ') + '\n'; } + if (!isCheckedTaskItem && !isUncheckedTaskItem && hasBogusInput) { + if (/checked/.test(text)) { + text = text.replace(bogusCheckPtn, + ' ') + '\n'; + } else if (/disabled/.test(text)) { + text = text.replace(bogusCheckPtn, + ' ') + '\n'; + } + } var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : ''; return '' + text + '

  • \n'; }; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 2be4ded4f..da5edd951 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -59,6 +59,9 @@ define([ obj[key] = data.value; } broadcast([clientId], "UPDATE_METADATA"); + if (Array.isArray(path) && path[0] === 'profile' && store.messenger) { + store.messenger.updateMyData(); + } onSync(cb); }; @@ -462,7 +465,7 @@ define([ if (data.password) { pad.password = data.password; } if (data.channel) { pad.channel = data.channel; } 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', { path: ['drive', UserObject.FILES_DATA] }, clientId); @@ -597,6 +600,7 @@ define([ Store.setDisplayName = function (clientId, value, cb) { store.proxy[Constants.displayNameKey] = value; broadcast([clientId], "UPDATE_METADATA"); + if (store.messenger) { store.messenger.updateMyData(); } onSync(cb); }; @@ -859,6 +863,9 @@ define([ }, pinPads: function (data, cb) { Store.pinPads(null, data, cb); }, friendComplete: function (data) { + if (data.friend && store.messenger && store.messenger.onFriendAdded) { + store.messenger.onFriendAdded(data.friend); + } postMessage(clientId, "EV_FRIEND_COMPLETE", data); }, friendRequest: function (data, cb) { @@ -892,6 +899,7 @@ define([ Store.messenger = { getFriendList: function (clientId, data, cb) { + if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); } store.messenger.getFriendList(function (e, keys) { cb({ error: e, @@ -900,6 +908,7 @@ define([ }); }, getMyInfo: function (clientId, data, cb) { + if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); } store.messenger.getMyInfo(function (e, info) { cb({ error: e, @@ -908,6 +917,7 @@ define([ }); }, getFriendInfo: function (clientId, data, cb) { + if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); } store.messenger.getFriendInfo(data, function (e, info) { cb({ error: e, @@ -916,6 +926,7 @@ define([ }); }, removeFriend: function (clientId, data, cb) { + if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); } store.messenger.removeFriend(data, function (e, info) { cb({ error: e, @@ -924,11 +935,13 @@ define([ }); }, openFriendChannel: function (clientId, data, cb) { + if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); } store.messenger.openFriendChannel(data, function (e) { cb({ error: e, }); }); }, getFriendStatus: function (clientId, data, cb) { + if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); } store.messenger.getStatus(data, function (e, online) { cb({ error: e, @@ -937,6 +950,7 @@ define([ }); }, 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) { cb({ error: e, @@ -945,6 +959,7 @@ define([ }); }, sendMessage: function (clientId, data, cb) { + if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); } store.messenger.sendMessage(data.curvePublic, data.content, function (e) { cb({ error: e, @@ -952,11 +967,17 @@ define([ }); }, setChannelHead: function (clientId, data, cb) { + if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); } store.messenger.setChannelHead(data.curvePublic, data.sig, function (e) { cb({ 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) { messengerEventClients.forEach(function (cId) { postMessage(cId, q, data); @@ -1327,41 +1347,49 @@ define([ if (messengerEventClients.indexOf(clientId) === -1) { messengerEventClients.push(clientId); } - if (!messengerEventInit) { - var messenger = store.messenger = Messenger.messenger(store); - messenger.on('message', function (message) { - sendMessengerEvent('CONTACTS_MESSAGE', message); + }; + var loadMessenger = function () { + if (AppConfig.availablePadTypes.indexOf('contacts') === -1) { return; } + 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', { - curvePublic: curvePublic, - channel: channel, - }); + }); + messenger.on('leave', function (curvePublic, channel) { + sendMessengerEvent('CONTACTS_LEAVE', { + curvePublic: curvePublic, + channel: channel, }); - messenger.on('leave', function (curvePublic, channel) { - sendMessengerEvent('CONTACTS_LEAVE', { - curvePublic: curvePublic, - channel: channel, - }); + }); + messenger.on('update', function (info, types, channel) { + sendMessengerEvent('CONTACTS_UPDATE', { + types: types, + info: info, + channel: channel }); - messenger.on('update', function (info, curvePublic) { - sendMessengerEvent('CONTACTS_UPDATE', { - curvePublic: curvePublic, - info: info, - }); + }); + messenger.on('friend', function (curvePublic) { + sendMessengerEvent('CONTACTS_FRIEND', { + curvePublic: curvePublic, }); - messenger.on('friend', function (curvePublic) { - sendMessengerEvent('CONTACTS_FRIEND', { - curvePublic: curvePublic, - }); + }); + messenger.on('unfriend', function (curvePublic, removedByMe) { + sendMessengerEvent('CONTACTS_UNFRIEND', { + curvePublic: curvePublic, + removedByMe: removedByMe }); - messenger.on('unfriend', function (curvePublic) { - sendMessengerEvent('CONTACTS_UNFRIEND', { - curvePublic: curvePublic, - }); + }); + messenger.on('event', function (ev, data) { + sendMessengerEvent('CHAT_EVENT', { + ev: ev, + data: data }); - messengerEventInit = true; - } + }); }; @@ -1450,6 +1478,7 @@ define([ }); userObject.fixFiles(); loadSharedFolders(waitFor); + loadMessenger(); }).nThen(function () { var requestLogin = function () { broadcast([], "REQUEST_LOGIN"); diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index 532b4c10e..a724fbd06 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -69,6 +69,8 @@ define([ CONTACTS_GET_MORE_HISTORY: Store.messenger.getMoreHistory, CONTACTS_SEND_MESSAGE: Store.messenger.sendMessage, CONTACTS_SET_CHANNEL_HEAD: Store.messenger.setChannelHead, + // Chat + CHAT_COMMAND: Store.messenger.execCommand, // Pad SEND_PAD_MSG: Store.sendPadMsg, JOIN_PAD: Store.joinPad, diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 7266ac0c9..3e79526d8 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -322,6 +322,8 @@ define([ if (!readOnly) { onLocal(); } evOnReady.fire(newPad); + common.openPadChat(onLocal); + UI.removeLoadingScreen(emitResize); var privateDat = cpNfInner.metadataMgr.getPrivateData(); @@ -559,6 +561,7 @@ define([ }, onLocal); var configTb = { displayed: [ + 'chat', 'userlist', 'title', 'useradmin', diff --git a/www/common/sframe-app-outer.js b/www/common/sframe-app-outer.js index b65f3f98b..cc4d5fcb3 100644 --- a/www/common/sframe-app-outer.js +++ b/www/common/sframe-app-outer.js @@ -36,7 +36,8 @@ define([ window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { SFCommonO.start({ - useCreationScreen: true + useCreationScreen: true, + messaging: true }); }); }); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 43728c55c..dcffcc606 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -262,6 +262,7 @@ define([ donateURL: Cryptpad.donateURL, upgradeURL: Cryptpad.upgradeURL }, + plan: localStorage[Utils.Constants.plan], isNewFile: isNewFile, isDeleted: isNewFile && window.location.hash.length > 0, forceCreationScreen: forceCreationScreen, @@ -370,7 +371,7 @@ define([ forceSave: true }; Cryptpad.setPadTitle(data, function (err) { - cb(err); + cb({error: err}); }); }); sframeChan.on('Q_IS_PAD_STORED', function (data, cb) { @@ -776,6 +777,22 @@ define([ 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) { sframeChan.event('EV_CONTACTS_MESSAGE', data); }); diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index a68a011fb..2b094c055 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -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 funcs.initCodeMirrorApp = callWithCommon(CodeMirror.create); @@ -517,6 +535,11 @@ define([ UI.alert(Messages.chrome68); }); + funcs.isPadStored(function (err, val) { + if (err || !val) { return; } + UIElements.displayCrowdfunding(funcs); + }); + ctx.sframeChan.ready(); cb(funcs); }); diff --git a/www/common/sframe-messenger-inner.js b/www/common/sframe-messenger-inner.js index 67532b015..31359fd3f 100644 --- a/www/common/sframe-messenger-inner.js +++ b/www/common/sframe-messenger-inner.js @@ -35,7 +35,7 @@ define([], function () { }); sFrameChan.on('EV_CONTACTS_UPDATE', function (data) { _handlers.update.forEach(function (f) { - f(data.info, data.curvePublic); + f(data.info, data.types, data.channel); }); }); sFrameChan.on('EV_CONTACTS_FRIEND', function (data) { @@ -45,7 +45,7 @@ define([], function () { }); sFrameChan.on('EV_CONTACTS_UNFRIEND', function (data) { _handlers.unfriend.forEach(function (f) { - f(data.curvePublic); + f(data.curvePublic, data.removedByMe); }); }); diff --git a/www/common/sframe-protocol.js b/www/common/sframe-protocol.js index 02390b290..391cbf96a 100644 --- a/www/common/sframe-protocol.js +++ b/www/common/sframe-protocol.js @@ -171,6 +171,11 @@ define({ 'Q_CONTACTS_SET_CHANNEL_HEAD': 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. 'EV_LOCALSTORE_PUT': true, // Put one entry in the parent sessionStorage @@ -218,6 +223,8 @@ define({ // Refresh the drive when the drive has changed ('change' or 'remove' events) 'EV_DRIVE_CHANGE': 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 'Q_REMOVE_OWNED_CHANNEL': true, diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 35fce658d..ac390400b 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -6,8 +6,11 @@ define([ '/common/common-interface.js', '/common/common-hash.js', '/common/common-feedback.js', + '/common/sframe-messenger-inner.js', + '/contacts/messenger-ui.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 Bar = { @@ -231,16 +234,15 @@ define([ var name = data.name || Messages.anonymous; var $span = $('', {'class': 'cp-avatar'}); var $rightCol = $('', {'class': 'cp-toolbar-userlist-rightcol'}); - var $nameSpan = $('', {'class': 'cp-toolbar-userlist-name'}).text(name).appendTo($rightCol); + var $nameSpan = $('', {'class': 'cp-toolbar-userlist-name'}).appendTo($rightCol); + var $nameValue = $('', { + 'class': 'cp-toolbar-userlist-name-value' + }).text(name).appendTo($nameSpan); var isMe = data.uid === user.uid; if (isMe && !priv.readOnly) { - $nameSpan.html(''); - var $nameValue = $('', { - 'class': 'cp-toolbar-userlist-name-value' - }).text(name).appendTo($nameSpan); if (!Config.disableProfile) { var $button = $('