diff --git a/.jshintignore b/.jshintignore index c2037807f..45011b5dc 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1,16 +1,11 @@ node_modules/ www/bower_components/ -www/code/codemirror* -www/common/chainpad.js -storage/kad.js -www/common/otaml.js +www/common/pdfjs/ server.js -NetFluxWebsocketSrv.js -NetFluxWebsocketServer.js -WebRTCSrv.js www/common/media-tag.js www/scratch www/common/toolbar.js www/common/hyperscript.js +www/common/tippy.min.js diff --git a/config.example.js b/config.example.js index da000a519..081f2a04b 100644 --- a/config.example.js +++ b/config.example.js @@ -25,7 +25,7 @@ module.exports = { "default-src 'none'", "style-src 'unsafe-inline' 'self'", "script-src 'self'", - "font-src 'self'", + "font-src 'self' data:", /* child-src is used to restrict iframes to a set of allowed domains. * connect-src is used to restrict what domains can connect to the websocket. @@ -67,7 +67,7 @@ module.exports = { "connect-src 'self' ws: wss:", // (insecure remote) images are included by users of the wysiwyg who embed photos in their pads - "img-src *", + "img-src * blob:", ].join('; '), httpPort: 3000, diff --git a/customize.dist/about.html b/customize.dist/about.html index 5593e41b9..03032969b 100644 --- a/customize.dist/about.html +++ b/customize.dist/about.html @@ -2,7 +2,7 @@ - Cryptpad: Zero Knowledge, Collaborative Real Time Editing + CryptPad: Zero Knowledge, Collaborative Real Time Editing diff --git a/customize.dist/application_config.js b/customize.dist/application_config.js index af2474775..a3da8eb3f 100644 --- a/customize.dist/application_config.js +++ b/customize.dist/application_config.js @@ -4,8 +4,8 @@ define(function() { /* Select the buttons displayed on the main page to create new collaborative sessions * Existing types : pad, code, poll, slide */ - config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'whiteboard', 'file']; - config.registeredOnlyTypes = ['file']; + config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'whiteboard', 'file', 'contacts']; + config.registeredOnlyTypes = ['file', 'contacts']; /* Cryptpad apps use a common API to display notifications to users * by default, notifications are hidden after 5 seconds diff --git a/customize.dist/contact.html b/customize.dist/contact.html index 5593e41b9..03032969b 100644 --- a/customize.dist/contact.html +++ b/customize.dist/contact.html @@ -2,7 +2,7 @@ - Cryptpad: Zero Knowledge, Collaborative Real Time Editing + CryptPad: Zero Knowledge, Collaborative Real Time Editing diff --git a/customize.dist/header.js b/customize.dist/header.js index 7a2ac9295..ecef95f56 100644 --- a/customize.dist/header.js +++ b/customize.dist/header.js @@ -34,7 +34,8 @@ define([ // User admin menu var $userMenu = $('#user-menu'); var userMenuCfg = { - $initBlock: $userMenu + $initBlock: $userMenu, + 'static': true }; var $userAdmin = Cryptpad.createUserAdminMenu(userMenuCfg); $userAdmin.find('button').addClass('btn').addClass('btn-secondary'); diff --git a/customize.dist/index.html b/customize.dist/index.html index 5593e41b9..03032969b 100644 --- a/customize.dist/index.html +++ b/customize.dist/index.html @@ -2,7 +2,7 @@ - Cryptpad: Zero Knowledge, Collaborative Real Time Editing + CryptPad: Zero Knowledge, Collaborative Real Time Editing diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 67759a15e..216dd4bbe 100644 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -46,6 +46,7 @@ define(req, function($, Default, Language) { } messages._languages = map; + messages._languageUsed = language; messages._checkTranslationState = function (cb) { if (typeof(cb) !== "function") { return; } diff --git a/customize.dist/pages.js b/customize.dist/pages.js index ce25d010b..9384ca067 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -162,7 +162,7 @@ define([ ]) ]), h('button.btn.btn-secondary.login.half.first', Msg.login_login), - h('button.btn.btn-success.register.half.first', Msg.login_register), + h('button.btn.btn-success.register.half', Msg.login_register), h('p.separator', Msg.login_orNoLogin), h('p#buttons.buttons'), h('p.driveLink', [ @@ -287,18 +287,10 @@ define([ }; loadingScreen = loadingScreen; // TODO use this - Pages['/settings/'] = Pages['/settings/index.html'] = function () { - return h('div#container'); - }; - Pages['/user/'] = Pages['/user/index.html'] = function () { return h('div#container'); }; - Pages['/profile/'] = Pages['/profile/index.html'] = function () { - return h('div#container'); - }; - Pages['/register/'] = Pages['/register/index.html'] = function () { return [h('div#main', [ h('div.mainOverlay'), @@ -372,7 +364,7 @@ define([ h('button.btn.btn-primary.login.first', Msg.login_login), h('div.extra', [ h('p', Msg.login_notRegistered), - h('button#register.btn.btn-success.register.first', Msg.login_register) + h('button#register.btn.btn-success.register', Msg.login_register) ]) ]) ]) @@ -498,6 +490,10 @@ define([ return loadingScreen(); }; + Pages['/contacts/'] = Pages['/contacts/index.html'] = function () { + return loadingScreen(); + }; + Pages['/pad/'] = Pages['/pad/index.html'] = function () { return loadingScreen(); }; @@ -510,5 +506,25 @@ define([ return loadingScreen(); }; + Pages['/invite/'] = Pages['/invite/index.html'] = function () { + return loadingScreen(); + }; + + Pages['/settings/'] = Pages['/settings/index.html'] = function () { + return [ + h('div#toolbar'), + h('div#container'), + loadingScreen() + ]; + }; + + Pages['/profile/'] = Pages['/profile/index.html'] = function () { + return [ + h('div#toolbar'), + h('div#container'), + loadingScreen() + ]; + }; + return Pages; }); diff --git a/customize.dist/privacy.html b/customize.dist/privacy.html index 5593e41b9..03032969b 100644 --- a/customize.dist/privacy.html +++ b/customize.dist/privacy.html @@ -2,7 +2,7 @@ - Cryptpad: Zero Knowledge, Collaborative Real Time Editing + CryptPad: Zero Knowledge, Collaborative Real Time Editing diff --git a/customize.dist/src/less/cryptpad.less b/customize.dist/src/less/cryptpad.less index 17cb35d56..18948e96a 100644 --- a/customize.dist/src/less/cryptpad.less +++ b/customize.dist/src/less/cryptpad.less @@ -19,7 +19,6 @@ html.cp, .cp body { background-color: @page-white; //@base; color: @fore; - font-family: Ubuntu,Georgia,Cambria,serif; height: 100%; } .fa { @@ -28,9 +27,19 @@ html.cp, .cp body { .cp { +// add font for tooltips +.tippy-popper { + font: normal normal normal 16px Arial, Helvetica, Tahoma, Verdana, Sans-Serif; +} + // override bootstrap colors .btn-primary { background-color: @cp-blue; + &:hover { + color: #fff; + background-color: #025aa5; + border-color: #01549b; + } } body { @@ -223,13 +232,13 @@ body.html { margin: 0 auto; } .app-row { - display: inline-block; - white-space: nowrap; - width: 700px; + display: flex; + justify-content: center; + flex-flow: row wrap; max-width: 100%; margin: 0 auto; @media screen and (max-width: 1399px) { - display: block; + display: flex; } img { @media screen and (max-width: @media-not-big) { @@ -425,6 +434,8 @@ noscript { label { margin-bottom: 0; + margin-left: 5px; + vertical-align: middle; } button { @@ -432,7 +443,7 @@ noscript { width: 100%; cursor: pointer; &.half { - width: ~"calc(50% - 2px)"; + width: ~"calc(50% - 10px)"; &:not(.first) { float: right; } @@ -569,10 +580,12 @@ noscript { /* Pin limit */ .limit-container { + display: inline-flex; + flex-flow: column-reverse; + width: 100%; + margin-top: 20px; .cryptpad-limit-bar { display: inline-block; - height: 26px; - width: 200px; max-width: 40vw; margin: 3px; box-sizing: border-box; @@ -580,8 +593,10 @@ noscript { background: white; position: relative; text-align: center; - line-height: 24px; vertical-align: middle; + width: ~"calc(100% - 6px)"; + height: 25px; + line-height: 25px; .usage { height: 100%; display: inline-block; @@ -604,12 +619,16 @@ noscript { color: black; text-shadow: 1px 0 2px white, 0 1px 2px white, -1px 0 2px white, 0 -1px 2px white; z-index: 2; - font-size: 16px; + font-size: @main-font-size; font-weight: bold; } } .upgrade { - margin-left: 10px; + padding: 0; + line-height: 25px; + height: 25px; + margin: 0 3px; + border-radius: 0; } } @@ -621,10 +640,10 @@ noscript { background-color: rgba(0,0,0,0.5); color: white; opacity: 0.7; - font-family: Ubuntu,Georgia,Cambria,serif; box-sizing: border-box; z-index:10000; display: none; + font-family: -apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif; #uploadStatus { width: 80vw; border: 1px solid black; diff --git a/customize.dist/src/less/mixins.less b/customize.dist/src/less/mixins.less index b65af90d8..cd900a60c 100644 --- a/customize.dist/src/less/mixins.less +++ b/customize.dist/src/less/mixins.less @@ -1,3 +1,5 @@ +@import "/customize/src/less/variables.less"; + .fontface(@family, @src, @style: normal, @weight: 400, @fmt: 'truetype'){ @font-face { font-family: @family; @@ -37,3 +39,102 @@ background: linear-gradient(@start, @end); /* Standard syntax */ } +.placeholderColor (@color) { + &::-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; + } +} + +.avatar (@width) { + &.avatar { + overflow: hidden; + text-overflow: ellipsis; + font-size: 16px; + display: flex; + align-items: center; + &.clickable { + cursor: pointer; + &:hover { + background-color: rgba(0,0,0,0.3); + } + } + .default, media-tag { + display: inline-flex; + width: @width; + height: @width; + justify-content: center; + align-items: center; + border-radius: 4px; + overflow: hidden; + box-sizing: content-box; + } + .default { + .unselectable(); + background: white; + color: black; + font-size: @width/1.2; + } + .right-col { + order: 10; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: flex; + flex-flow: column; + .name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .friend { + padding: 0; + } + } + media-tag { + min-height: @width; + min-width: @width; + max-height: @width; + max-width: @width; + img { + min-width: 100%; + min-height: 100%; + max-width: none; + max-height: none; // To override 'media-tag img' in slide.less + } + } + } +} + +.leftsideCategory { + .unselectable(); + padding: 5px 20px; + margin: 15px 0; + cursor: pointer; + height: @toolbar-line-height; + line-height: @toolbar-line-height - 10px; + .fa { + width: 25px; + } + &:hover { + background: rgba(0,0,0,0.05); + } + &.active { + background: white; + } +} diff --git a/customize.dist/src/less/sidebar-layout.less b/customize.dist/src/less/sidebar-layout.less new file mode 100644 index 000000000..02126b169 --- /dev/null +++ b/customize.dist/src/less/sidebar-layout.less @@ -0,0 +1,97 @@ +@import '/customize/src/less/variables.less'; +@import '/customize/src/less/mixins.less'; + +@leftside-bg: #eee; +@leftside-color: #000; +@rightside-color: #000; +@description-color: #777; + +@button-width: 400px; +@button-bg: #3066e5; +@button-alt-bg: #fff; +@button-red-bg: #e54e4e; + + +.cp { + input[type="text"] { + padding-left: 10px; + } + #container { + font-size: 16px; + display: flex; + flex: 1; + min-height: 0; + #leftSide { + color: @leftside-color; + width: 250px; + background: @leftside-bg; + display: flex; + flex-flow: column; + .categories { + flex: 1; + .category { + .leftsideCategory(); + } + } + } + #rightSide { + flex: 1; + padding: 5px 20px; + color: @rightside-color; + overflow: auto; + .element { + label:not(.noTitle), .label { + display: block; + font-weight: bold; + margin-bottom: 0; + } + .description { + display: block; + color: @description-color; + margin-bottom: 5px; + } + margin-bottom: 20px; + } + [type="text"], button { + vertical-align: middle; + height: 40px; + box-sizing: border-box; + } + .inputBlock { + display: inline-flex; + width: @button-width; + input { + flex: 1; + border-radius: 0.25em 0 0 0.25em; + border: 1px solid #adadad; + border-right: 0px; + } + button { + border-radius: 0 0.25em 0.25em 0; + //border: 1px solid #adadad; + border-left: 0px; + } + } + button.btn { + background-color: @button-bg; + border-color: darken(@button-bg, 10%); + color: white; + &:hover { + background-color: darken(@button-bg, 10%); + } + &.btn-danger { + background-color: @button-red-bg; + border-color: darken(@button-red-bg, 10%); + color: white; + &:hover { + background-color: darken(@button-red-bg, 10%); + } + } + } + &>div { + margin: 10px 0; + } + } + } +} + diff --git a/customize.dist/src/less/toolbar.less b/customize.dist/src/less/toolbar.less index 4edd3800c..f38a92c66 100644 --- a/customize.dist/src/less/toolbar.less +++ b/customize.dist/src/less/toolbar.less @@ -16,13 +16,18 @@ color: inherit; } + +// Classes used in common-interface.js .padColor { color: @toolbar-pad-bg; } .codeColor { color: @toolbar-code-bg; } .slideColor { color: @toolbar-slide-bg; } .pollColor { color: @toolbar-poll-bg; } .fileColor { color: @toolbar-file-bg; } +.friendsColor { color: @toolbar-friends-bg; } .whiteboardColor { color: @toolbar-whiteboard-bg; } .driveColor { color: @toolbar-drive-bg; } +.settingsColor { color: @toolbar-settings-bg; } +.profileColor { color: @toolbar-settings-bg; } .defaultColor { color: @toolbar-default-bg; } .toolbar-container { @@ -55,7 +60,7 @@ } body .userlist-drawer { - font: normal normal normal 16px Arial,Helvetica,Tahoma,Verdana,Sans-Serif; + font: normal normal normal @main-font-size Arial,Helvetica,Tahoma,Verdana,Sans-Serif; min-width: 175px; width: 175px; display: block; @@ -94,7 +99,7 @@ body .userlist-drawer { } & > p { - font: normal normal normal 16px Arial,Helvetica,Tahoma,Verdana,Sans-Serif; + font: normal normal normal @main-font-size Arial,Helvetica,Tahoma,Verdana,Sans-Serif; margin: 0; padding: 0; display: block; @@ -106,59 +111,19 @@ body .userlist-drawer { margin: 10px 0; margin-bottom: 20px; &>span { - overflow: hidden; - text-overflow: ellipsis; padding: 5px; - background: rgba(0,0,0,0.1); margin: 2px 0; - font-size: 16px; - display: inline-flex; - align-items: center; - &.clickable { - cursor: pointer; - &:hover { - background-color: rgba(0,0,0,0.3); - } - } + background: rgba(0,0,0,0.1); + .avatar(30px); .default, media-tag { - display: inline-flex; - width: 50px; - height: 50px; - justify-content: center; - align-items: center; margin-right: 5px; - border-radius: 10px / 6px; - overflow: hidden; - border: 1px solid black; - box-sizing: content-box; - } - .default { - .unselectable(); - background: white; - color: black; - font-size: 40px; - } - .name { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - media-tag { - min-height: 50px; - min-width: 50px; - max-height: 50px; - max-width: 50px; - img { - min-width: 100%; - min-height: 100%; - max-width: none; - max-height: none; // To override 'media-tag img' in slide.less - flex-shrink: 0; - } } } } + .friend { + display: inline-block; + width: 20px; + } } body { @@ -170,6 +135,11 @@ body { background-color: darken(@bgcolor, 10%); color: @color; } + .friend { + &:hover { + color: darken(@color, 15%); + } + } } .cryptpad-toolbar { background-color: @bgcolor; @@ -186,7 +156,7 @@ body { } } .cryptpad-spinner, .cryptpad-state { - font-size: 16px; + font-size: @main-font-size; color: @color; } .cryptpad-limit { @@ -299,6 +269,21 @@ body { @color: @toolbar-file-color; .addToolbarColors(@color, @bgcolor); } + &.app-contacts { + @bgcolor: @toolbar-friends-bg; + @color: @toolbar-friends-color; + .addToolbarColors(@color, @bgcolor); + } + &.app-settings { + @bgcolor: @toolbar-settings-bg; + @color: @toolbar-settings-color; + .addToolbarColors(@color, @bgcolor); + } + &.app-profile { + @bgcolor: @toolbar-profile-bg; + @color: @toolbar-profile-color; + .addToolbarColors(@color, @bgcolor); + } } @@ -333,8 +318,8 @@ body .cryptpad-toolbar { z-index: 9001; .dropdown-bar { - height: 100%; - display: inline-block; + //height: 100%; + //display: inline-block; button { height: 100%; border-radius: 0; @@ -503,7 +488,7 @@ body .cryptpad-toolbar { } button, select, .rightside-element { - height: 32px; + height: @toolbar-line-height; box-sizing: border-box; padding: 3px 10px; margin: 0; @@ -613,7 +598,7 @@ body .cryptpad-toolbar { flex: auto; width: 100%; order: 10; - height: 32px; + height: @toolbar-line-height; line-height: initial; margin: 0; .hoverable { @@ -624,24 +609,24 @@ body .cryptpad-toolbar { display: inline-block; overflow: hidden; text-overflow: ellipsis; - font-size: 16px; - height: 32px; + font-size: @main-font-size; + height: @toolbar-line-height; box-sizing: border-box; line-height: 20px; } .pencilIcon, .saveIcon { box-sizing: border-box; - height: 32px; - line-height: 16px; + height: @toolbar-line-height; + line-height: @main-font-size; display: inline-block; .fa { - font-size: 16px; + font-size: @main-font-size; } } input { - height: 32px; - font-size: 16px; + height: @toolbar-line-height; + font-size: @main-font-size; flex: 1; max-width: none; } @@ -651,6 +636,32 @@ body .cryptpad-toolbar { } } +.app-slide { + @media screen and (max-width: @media-medium-screen) { + .cryptpad-toolbar-leftside { + flex-flow: row wrap; + width: 175px; + height: auto; + .cryptpad-spinner { order: 0; } + } + .cryptpad-toolbar-rightside { + height: 2*@toolbar-line-height; + } + } + @media screen and (max-width: 320px) { + .cryptpad-toolbar-leftside { + flex-flow: row wrap; + width: 175px; + height: auto; + padding-top: @toolbar-line-height; + .cryptpad-spinner { order: 0; } + } + .cryptpad-toolbar-rightside { + height: auto; + } + } +} + .cryptpad-toolbar-top { display: flex; flex-flow: row; @@ -678,6 +689,9 @@ body .cryptpad-toolbar { line-height: 25px; white-space: nowrap; } + .pageTitle { + padding: 0 5px; + } .pencilIcon, .saveIcon { display: flex; align-items: center; @@ -755,7 +769,6 @@ body .cryptpad-toolbar { &:hover { background-color: rgba(0,0,0,0.3); } - order: 2; text-align: center; font-size: 32px; margin-left: 10px; @@ -813,17 +826,21 @@ body .cryptpad-toolbar { } .cryptpad-user { height: 100%; - display: inline-block; + display: inline-flex; order: 5; line-height: @toolbar-top-height; color: white; + .cryptpad-upgrade { order: 1; } + .cryptpad-new { order: 2; } + .cryptpad-user-dropdown { order: 3; } + .cryptpad-backup { order: 4; } &> * { display: inline-block; height: 100%; vertical-align: top; } .cryptpad-upgrade { - height: 32px; + height: @toolbar-line-height; vertical-align: middle; cursor: pointer; } @@ -856,6 +873,21 @@ body .cryptpad-toolbar { cursor: default; font-size: 32px; } + &.avatar { + .avatar(48px); + media-tag { + margin: 8px; + } + border: 0; + } + } + } + p.accountData { + &> span { + font-weight: bold; + span { + font-weight: normal; + } } } .cryptpad-backup { @@ -869,12 +901,14 @@ body .cryptpad-toolbar { } } .cryptpad-toolbar-leftside { - height: 32px; + //height: @toolbar-line-height; &:empty { height: 0; } float: left; - margin-bottom: -1px; + display: inline-flex; + align-items: center; + //margin-bottom: -1px; .cryptpad-dropdown-users { pre { /* needed for ckeditor */ @@ -890,21 +924,26 @@ body .cryptpad-toolbar { .dropdown-bar-content { margin-top: -1px; } - .limit-container a { - height: 26px; - margin: 3px 0; - line-height: 26px; - padding: 0 5px; - box-sizing: border-box; - border: 1px solid transparent; - font-size: 14px; - &:hover { - text-decoration: none; - } + + & > span { + height: @toolbar-line-height; + } + + #userButtons { order: 1; } + .shareButton { order: 2; } + .cryptpad-spinner { order: 3; } + + #userButtons button { + width: 125px; + text-align: center; + } + .shareButton button { + width: 50px; + text-align: center; } } .cryptpad-toolbar-rightside { - min-height: 32px; + min-height: @toolbar-line-height; overflow: hidden; &:empty { min-height: 0; @@ -924,7 +963,7 @@ body .cryptpad-toolbar { box-shadow: 0px 1px 5px 0px rgba(0, 0, 0, 0.2); position: absolute; right:0px; - margin-top: 32px; + margin-top: @toolbar-line-height; min-width: 50px; background: @dropdown-bg; display: flex; @@ -937,7 +976,7 @@ body .cryptpad-toolbar { &> span { box-sizing: border-box; min-width: 150px; - height: 32px; + height: @toolbar-line-height; border-radius: 0; border: 0; } @@ -1007,11 +1046,12 @@ body .cryptpad-toolbar { } } .cryptpad-spinner { - line-height: @toolbar-top-height; + line-height: @toolbar-line-height; + padding: 0 20px; &> span.fa { height: 20px; width: 20px; - margin: 8px; + //margin: 8px; line-height: 20px; font-size: 20px; text-align: center; @@ -1022,16 +1062,6 @@ body .cryptpad-toolbar { font-weight: bold; text-transform: uppercase; } -.cryptpad-user { - p.accountData { - &> span { - font-weight: bold; - span { - font-weight: normal; - } - } - } -} .cryptpad-dropdown-share { a { .fa { diff --git a/customize.dist/src/less/variables.less b/customize.dist/src/less/variables.less index 2ccebf170..c5b3f4be2 100644 --- a/customize.dist/src/less/variables.less +++ b/customize.dist/src/less/variables.less @@ -7,6 +7,8 @@ @old-base: #302B28; @old-fore: #fafafa; +@main-font-size: 16px; + @cp-green: #46E981; @cp-accent: lighten(@cp-green, 20%); @@ -73,7 +75,7 @@ // Dropdown -@dropdown-font: 16px -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; +@dropdown-font: @main-font-size -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; @dropdown-bg: #f9f9f9; @dropdown-color: black; @dropdown-bg-hover: #f1f1f1; @@ -97,8 +99,14 @@ @toolbar-drive-color: #fff; @toolbar-file-bg: #cd2532; @toolbar-file-color: #fff; +@toolbar-friends-bg: #607B8D; +@toolbar-friends-color: #fff; @toolbar-default-bg: #ddd; @toolbar-default-color: #000; +@toolbar-settings-bg: #0087ff; +@toolbar-settings-color: #fff; +@toolbar-profile-bg: #0087ff; +@toolbar-profile-color: #fff; @topbar-back: #fff; @@ -127,3 +135,5 @@ -ms-user-select: none; user-select: none; } + +@toolbar-line-height: 32px; diff --git a/customize.dist/src/template.html b/customize.dist/src/template.html index 5593e41b9..03032969b 100644 --- a/customize.dist/src/template.html +++ b/customize.dist/src/template.html @@ -2,7 +2,7 @@ - Cryptpad: Zero Knowledge, Collaborative Real Time Editing + CryptPad: Zero Knowledge, Collaborative Real Time Editing diff --git a/customize.dist/template.js b/customize.dist/template.js index ad00c5170..7fc7172ce 100644 --- a/customize.dist/template.js +++ b/customize.dist/template.js @@ -12,7 +12,7 @@ $(function () { var Messages = Cryptpad.Messages; var $body = $('body'); var isMainApp = function () { - return /^\/(pad|code|slide|poll|whiteboard|file|media|drive)\/$/.test(location.pathname); + return /^\/(pad|code|slide|poll|whiteboard|file|media|contacts|drive|settings|profile)\/$/.test(location.pathname); }; var rightLink = function (ref, loc, txt) { @@ -112,7 +112,7 @@ $(function () { ]) ]) ]), - h('div.version-footer', "CryptPad v1.10.0 (Kraken)") + h('div.version-footer', "CryptPad v1.11.0 (Lutin)") ])); var pathname = location.pathname; @@ -145,9 +145,12 @@ $(function () { } else if (/drive/.test(pathname)) { $('body').append(h('body', Pages[pathname]()).innerHTML); require(['/drive/main.js'], ready); - } else if (/file/.test(pathname)) { + } else if (/\/file\//.test(pathname)) { $('body').append(h('body', Pages[pathname]()).innerHTML); require([ '/file/main.js' ], ready); + } else if (/contacts/.test(pathname)) { + $('body').append(h('body', Pages[pathname]()).innerHTML); + require([ '/contacts/main.js' ], ready); } else if (/pad/.test(pathname)) { $('body').append(h('body', Pages[pathname]()).innerHTML); require([ '/pad/main.js' ], ready); @@ -157,6 +160,12 @@ $(function () { } else if (/slide/.test(pathname)) { $('body').append(h('body', Pages[pathname]()).innerHTML); require([ '/slide/main.js' ], ready); + } else if (/^\/settings\//.test(pathname)) { + $('body').append(h('body', Pages[pathname]()).innerHTML); + require([ '/settings/main.js', ], ready); + } else if (/^\/profile\//.test(pathname)) { + $('body').append(h('body', Pages[pathname]()).innerHTML); + require([ '/profile/main.js', ], ready); } }); @@ -170,11 +179,7 @@ $(function () { ], function () { $body.append($topbar).append($main).append($footer); - if (/^\/settings\//.test(pathname)) { - require([ '/settings/main.js', ], function () {}); - } else if (/^\/profile\//.test(pathname)) { - require([ '/profile/main.js'], function () {}); - } else if (/^\/user\//.test(pathname)) { + if (/^\/user\//.test(pathname)) { require([ '/user/main.js'], function () {}); } else if (/^\/register\//.test(pathname)) { require([ '/register/main.js' ], function () {}); @@ -183,6 +188,8 @@ $(function () { } else if (/^\/($|^\/index\.html$)/.test(pathname)) { // TODO use different top bar require([ '/customize/main.js', ], function () {}); + } else if (/invite/.test(pathname)) { + require([ '/invite/main.js'], function () {}); } else { require([ '/customize/main.js', ], function () {}); } diff --git a/customize.dist/terms.html b/customize.dist/terms.html index 5593e41b9..03032969b 100644 --- a/customize.dist/terms.html +++ b/customize.dist/terms.html @@ -2,7 +2,7 @@ - Cryptpad: Zero Knowledge, Collaborative Real Time Editing + CryptPad: Zero Knowledge, Collaborative Real Time Editing diff --git a/customize.dist/translations/messages.es.js b/customize.dist/translations/messages.es.js index ae82b0052..987cb5261 100644 --- a/customize.dist/translations/messages.es.js +++ b/customize.dist/translations/messages.es.js @@ -12,6 +12,7 @@ define(function () { out.type.poll = 'Encuesta'; out.type.slide = 'Presentación'; out.type.whiteboard = 'Pizarra'; + out.type.contacts = 'Contactos'; out.disconnected = "Desconectado"; out.synchronizing = "Sincronización"; @@ -246,7 +247,6 @@ define(function () { out.settings_backupTitle = "Copia de seguridad"; out.settings_backup = "Copia de seguridad"; out.settings_restore = "Recuparar datos"; - out.settings_resetTitle = "Limpiar tu drive"; out.settings_reset = "Quita todos los documentos de tu CryptDrive"; out.settings_resetPrompt = "Esta acción eliminará todos tus documentos.
¿Seguro que quieres continuar?
Introduce “I love CryptPad” para confirmar."; out.settings_resetDone = "¡Tu drive ahora está vacio!"; @@ -294,7 +294,7 @@ define(function () { '

', 'Esto es CryptPad, el editor collaborativo en tiempo real Zero Knowledge. Todo está guardado cuando escribes.', '
', - 'Comparte el enlace a este pad para editar con amigos o utiliza el botón  Compartir  para obtener un enlace solo lectura que permite leer pero no escribir.', + 'Comparte el enlace a este pad para editar con amigos o utiliza el botón  Compartir  para obtener un enlace solo lectura que permite leer pero no escribir.', '

', '

', @@ -485,5 +485,62 @@ define(function () { out.canvas_opacityLabel = "Opacidad: {0}"; out.canvas_widthLabel = "Talla: {0}"; + // 1.10.0 - Kraken + + out.moreActions = "Más acciones"; + out.importButton = "Importar"; + out.exportButton = "Exportar"; + out.saveTitle = "Guardar título (enter)"; + out.forgetButton = "Eliminar"; + out.printText = "Imprimir"; + out.slideOptionsText = "Opciones"; + out.historyText = "Historial"; + out.openLinkInNewTab = "Abrir enlace en pestaña nueva"; + out.profileButton = "Perfíl"; + out.profile_urlPlaceholder = "URL"; + out.profile_namePlaceholder = "Nombre mostrado en su perfíl"; + out.profile_avatar = "Imágen"; + out.profile_upload = "Subir una imágen"; + out.profile_error = "Error al crear tu perfíl: {0}"; + out.profile_register = "Tienes que registrarte para crear perfíl"; + out.profile_create = "Crear perfíl"; + out.profile_description = "Descripción"; + out.profile_fieldSaved = "Guardado: {0}"; + out.download_mt_button = "Descargar"; + out.updated_0_header_logoTitle = "Volver a tu CryptDrive"; + out.header_logoTitle = out.updated_0_header_logoTitle; + + // 1.11.0 - Lutin + + out.realtime_unrecoverableError = "El motor de tiempo real a encontrado un error. Haga clic en OK para recargar la página."; + out.typing = "Escribiendo"; + out.profile_inviteButton = "Connectar"; + out.profile_inviteButtonTitle = "Crear un enlace de invitación para este usuario."; + out.profile_inviteExplanation = "Hacer clic en OK creará un enlace de mensaje seguro que sólo {0} podrá ver.

El enlace será copiado a tu portapapeles y puede ser compartido publicamente."; + out.profile_viewMyProfile = "Ver mi perfíl"; + out.userlist_addAsFriendTitle = 'Agregar "{0}" como contacto'; + out.userlist_thisIsYou = 'Tú mismo ("{0}")'; + out.contacts_title = "Contactos"; + out.contacts_addError = "Error al agregar este contacto a la lista"; + out.contacts_added = "Invitación acceptada"; + out.contacts_rejected = "Invitación denegada"; + out.contacts_request = "{0} quiere agregarte como contacto. Acceptar?"; + out.contacts_send = "Enviar"; + out.contacts_remove = "Eliminar este contacto"; + out.contacts_confirmRemove = "Estás seguro que quieres eliminar {0} de tus contactos?"; + out.contacts_info1 = "Estos son tus contactos. De aquí, puedes:"; + out.contacts_info2 = "Hacer clic en el icono de tu contacto para chatear"; + out.contacts_info3 = "Hacer doble-clic para ver su perfil"; + out.contacts_info4 = "Cualquier participante puede eliminar definitivamente el historial de chat"; + out.settings_cat_account = "Cuenta"; + out.settings_cat_drive = "CryptDrive"; + out.settings_backupCategory = "Copia de seguridad"; + out.settings_resetNewTitle = "Limpiar CryptDrive"; + out.settings_resetButton = "Eliminar"; + out.settings_resetTipsAction = "Reiniciar"; + out.settings_userFeedbackTitle = "Feedback"; + out.settings_logoutEverywhereButton = "Cerar sesión"; + out.upload_title = "Subir archivo"; + return out; }); diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index bca33cb26..1effcfca7 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -9,10 +9,11 @@ define(function () { out.type.code = 'Code'; out.type.poll = 'Sondage'; out.type.slide = 'Présentation'; - out.type.drive = 'Drive'; + out.type.drive = 'CryptDrive'; out.type.whiteboard = "Tableau Blanc"; out.type.file = "Fichier"; out.type.media = "Média"; + out.type.contacts = "Contacts"; out.button_newpad = 'Nouveau document texte'; out.button_newcode = 'Nouvelle page de code'; @@ -34,9 +35,12 @@ define(function () { out.synced = "Tout est enregistré"; out.deleted = "Pad supprimé de votre CryptDrive"; + out.realtime_unrecoverableError = "Le moteur temps-réel a rencontré une erreur critique. Cliquez sur OK pour recharger la page."; + out.disconnected = 'Déconnecté'; out.synchronizing = 'Synchronisation'; out.reconnecting = 'Reconnexion...'; + out.typing = "Édition"; out.lag = 'Latence'; out.readonly = 'Lecture seule'; out.anonymous = "Anonyme"; @@ -252,6 +256,28 @@ define(function () { out.profile_description = "Description"; out.profile_fieldSaved = 'Nouvelle valeur enregistrée: {0}'; + out.profile_viewMyProfile = "Voir mon profil"; + + // contacts/userlist + out.userlist_addAsFriendTitle = 'Ajouter "{0}" comme contact'; + out.userlist_thisIsYou = 'Vous ("{0}")'; + out.userlist_pending = "En attente..."; + out.contacts_title = "Contacts"; + out.contacts_addError = "Erreur lors de l'ajout de ce contact dans votre liste"; + out.contacts_added = 'Invitation de contact acceptée'; + out.contacts_rejected = 'Invitation de contact rejetée'; + out.contacts_request = '{0} souhaite vous ajouter en tant que contact. Accepter ?'; + out.contacts_send = 'Envoyer'; + out.contacts_remove = 'Supprimer ce contact'; + out.contacts_confirmRemove = 'Êtes-vous sûr de voulour supprimer {0} de vos contacts ?'; + out.contacts_typeHere = "Entrez un message ici..."; + + + 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_info3 = "Double-cliquer sur son nom pour voir son profil"; + out.contacts_info4 = "Chaque participant peut nettoyer définitivement l'historique d'une discussion"; + // File manager out.fm_rootName = "Documents"; @@ -294,7 +320,7 @@ define(function () { out.updated_0_fm_info_trash = "Vider la corbeille permet de libérer de l'espace dans votre CryptDrive"; out.fm_info_trash = out.updated_0_fm_info_trash; out.fm_info_allFiles = 'Contient tous les fichiers de "Documents", "Fichiers non triés" et "Corbeille". Vous ne pouvez pas supprimer ou déplacer des fichiers depuis cet endroit.'; // Same here - out.fm_info_anonymous = 'Vous n\'êtes pas connectés, ces pads risquent donc d\'être supprimés (découvrez pourquoi). ' + + out.fm_info_anonymous = 'Vous n\'êtes pas connecté, ces pads risquent donc d\'être supprimés (découvrez pourquoi). ' + 'Inscrivez-vous ou connectez-vous pour les maintenir en vie.'; out.fm_alert_backupUrl = "Lien de secours pour ce CryptDrive.
" + "Il est fortement recommandé de garder ce lien pour vous-même.
" + @@ -375,28 +401,37 @@ define(function () { ]; // Settings + out.settings_cat_account = "Compte"; + out.settings_cat_drive = "CryptDrive"; out.settings_title = "Préférences"; out.settings_save = "Sauver"; + + out.settings_backupCategory = "Sauvegarde"; out.settings_backupTitle = "Créer ou restaurer une sauvegarde de vos données"; - out.settings_backup = "Créer une sauvegarde"; - out.settings_restore = "Restaurer une sauvegarde"; - out.settings_resetTitle = "Vider votre drive"; + out.settings_backup = "Sauvegarder"; + out.settings_restore = "Restaurer"; + + out.settings_resetNewTitle = "Vider CryptDrive"; + out.settings_resetButton = "Supprimer"; out.settings_reset = "Supprimer tous les fichiers et dossiers de votre CryptDrive"; out.settings_resetPrompt = "Cette action va supprimer tous les pads de votre drive.
"+ "Êtes-vous sûr de vouloir continuer ?
" + "Tapez “I love CryptPad” pour confirmer."; out.settings_resetDone = "Votre drive est désormais vide!"; out.settings_resetError = "Texte de vérification incorrect. Votre CryptDrive n'a pas été modifié."; - out.settings_resetTips = "Astuces et informations dans CryptDrive"; + + out.settings_resetTipsAction ="Réinitialiser"; + out.settings_resetTips = "Astuces"; out.settings_resetTipsButton = "Réinitialiser les astuces visibles dans CryptDrive"; out.settings_resetTipsDone = "Toutes les astuces sont de nouveau visibles."; - out.settings_importTitle = "Importer les pads récents de ce navigateur dans mon CryptDrive"; + out.settings_importTitle = "Importer les pads récents de ce navigateur dans votre CryptDrive"; out.settings_import = "Importer"; out.settings_importConfirm = "Êtes-vous sûr de vouloir importer les pads récents de ce navigateur dans le CryptDrive de votre compte utilisateur ?"; out.settings_importDone = "Importation terminée"; - out.settings_userFeedbackHint1 = "CryptPad peut envoyer des retours d'expérience très limités vers le serveur, de manière à nous permettre d'améliorer l'expérience des utilisateurs."; + out.settings_userFeedbackTitle = "Retour d'expérience"; + out.settings_userFeedbackHint1 = "CryptPad peut envoyer des retours d'expérience très limités vers le serveur, de manière à nous permettre d'améliorer l'expérience des utilisateurs. "; out.settings_userFeedbackHint2 = "Le contenu de vos pads et les clés de déchiffrement ne seront jamais partagés avec le serveur."; out.settings_userFeedback = "Activer l'envoi de retours d'expérience"; @@ -409,10 +444,12 @@ define(function () { out.settings_pinningError = "Un problème est survenu"; out.settings_usageAmount = "Vos pads épinglés occupent {0} Mo"; + out.settings_logoutEverywhereButton = "Se déconnecter"; out.settings_logoutEverywhereTitle = "Se déconnecter partout"; - out.settings_logoutEverywhere = "Se déconnecter de toutes les autres sessions."; + out.settings_logoutEverywhere = "Se déconnecter de force de toutes les autres sessions."; out.settings_logoutEverywhereConfirm = "Êtes-vous sûr ? Vous devrez vous reconnecter sur tous vos autres appareils."; + out.upload_title = "Hébergement de fichiers"; out.upload_serverError = "Erreur interne: impossible d'importer le fichier pour l'instant."; out.upload_uploadPending = "Vous avez déjà un fichier en cours d'importation. Souhaitez-vous l'annuler et importer ce nouveau fichier ?"; out.upload_success = "Votre fichier ({0}) a été importé avec succès et ajouté à votre CryptDrive."; @@ -518,7 +555,7 @@ define(function () { '

', 'Voici CryptPad, l\'éditeur collaboratif en temps-réel Zero Knowledge. Tout est sauvegardé dés que vous le tapez.', '
', - 'Partagez le lien vers ce pad avec des amis ou utilisez le bouton  Partager  pour obtenir le lien de lecture-seule, qui permet la lecture mais non la modification.', + 'Partagez le lien vers ce pad avec des amis ou utilisez le bouton  Partager  pour obtenir le lien de lecture-seule, qui permet la lecture mais non la modification.', '

', '

', '', diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 86ce50f9d..eff1c1eda 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -9,10 +9,11 @@ define(function () { out.type.code = 'Code'; out.type.poll = 'Poll'; out.type.slide = 'Presentation'; - out.type.drive = 'Drive'; + out.type.drive = 'CryptDrive'; out.type.whiteboard = 'Whiteboard'; out.type.file = 'File'; out.type.media = 'Media'; + out.type.contacts = 'Contacts'; out.button_newpad = 'New Rich Text pad'; out.button_newcode = 'New Code pad'; @@ -36,9 +37,12 @@ define(function () { out.synced = "Everything is saved"; out.deleted = "Pad deleted from your CryptDrive"; + out.realtime_unrecoverableError = "The realtime engine has encountered an unrecoverable error. Click OK to reload."; + out.disconnected = 'Disconnected'; out.synchronizing = 'Synchronizing'; out.reconnecting = 'Reconnecting...'; + out.typing = "Typing"; out.lag = 'Lag'; out.readonly = 'Read only'; out.anonymous = "Anonymous"; @@ -254,6 +258,30 @@ define(function () { out.profile_description = "Description"; out.profile_fieldSaved = 'New value saved: {0}'; + out.profile_inviteButton = "Connect"; + out.profile_inviteButtonTitle ='Create a link that will invite this user to connect with you.'; + out.profile_inviteExplanation = "Clicking OK will create a link to a secure messaging session that only {0} will be able to redeem.

The link will be copied to your clipboard and can be shared publicly."; + out.profile_viewMyProfile = "View my profile"; + + // contacts/userlist + out.userlist_addAsFriendTitle = 'Add "{0}" as a contact'; + out.userlist_thisIsYou = 'This is you ("{0}")'; + out.userlist_pending = "Pending..."; + out.contacts_title = "Contacts"; + out.contacts_addError = 'Error while adding that contact to the list'; + out.contacts_added = 'Contact invite accepted.'; + out.contacts_rejected = 'Contact invite rejected'; + out.contacts_request = '{0} would like to add you as a contact. Accept?'; + out.contacts_send = 'Send'; + 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_info1 = "These are your contacts. From here, you can:"; + out.contacts_info2 = "Click your contact's icon to chat with them"; + out.contacts_info3 = "Double-click their icon to view their profile"; + out.contacts_info4 = "Either participant can clear permanently a chat history"; + // File manager out.fm_rootName = "Documents"; @@ -380,28 +408,37 @@ define(function () { out.register_alreadyRegistered = "This user already exists, do you want to log in?"; // Settings + out.settings_cat_account = "Account"; + out.settings_cat_drive = "CryptDrive"; out.settings_title = "Settings"; out.settings_save = "Save"; + + out.settings_backupCategory = "Backup"; out.settings_backupTitle = "Backup or restore all your data"; out.settings_backup = "Backup"; out.settings_restore = "Restore"; - out.settings_resetTitle = "Clean your drive"; + + out.settings_resetNewTitle = "Clean CryptDrive"; + out.settings_resetButton = "Remove"; out.settings_reset = "Remove all the files and folders from your CryptDrive"; out.settings_resetPrompt = "This action will remove all the pads from your drive.
"+ "Are you sure you want to continue?
" + "Type “I love CryptPad” to confirm."; out.settings_resetDone = "Your drive is now empty!"; out.settings_resetError = "Incorrect verification text. Your CryptDrive has not been changed."; - out.settings_resetTips = "Tips in CryptDrive"; + + out.settings_resetTipsAction = "Reset"; + out.settings_resetTips = "Tips"; out.settings_resetTipsButton = "Reset the available tips in CryptDrive"; out.settings_resetTipsDone = "All the tips are now visible again."; - out.settings_importTitle = "Import this browser's recent pads in my CryptDrive"; + out.settings_importTitle = "Import this browser's recent pads in your CryptDrive"; out.settings_import = "Import"; out.settings_importConfirm = "Are you sure you want to import recent pads from this browser to your user account's CryptDrive?"; out.settings_importDone = "Import completed"; - out.settings_userFeedbackHint1 = "CryptPad provides some very basic feedback to the server, to let us know how to improve your experience."; + out.settings_userFeedbackTitle = "Feedback"; + out.settings_userFeedbackHint1 = "CryptPad provides some very basic feedback to the server, to let us know how to improve your experience. "; out.settings_userFeedbackHint2 = "Your pad's content will never be shared with the server."; out.settings_userFeedback = "Enable user feedback"; @@ -414,10 +451,12 @@ define(function () { out.settings_pinningError = "Something went wrong"; out.settings_usageAmount = "Your pinned pads occupy {0}MB"; + out.settings_logoutEverywhereButton = "Log out"; out.settings_logoutEverywhereTitle = "Log out everywhere"; - out.settings_logoutEverywhere = "Log out of all other web sessions"; + out.settings_logoutEverywhere = "Force log out of all other web sessions"; out.settings_logoutEverywhereConfirm = "Are you sure? You will need to log in with all your devices."; + out.upload_title = "File upload"; out.upload_serverError = "Server Error: unable to upload your file at this time."; out.upload_uploadPending = "You already have an upload in progress. Cancel it and upload your new file?"; out.upload_success = "Your file ({0}) has been successfully uploaded and added to your drive."; @@ -528,7 +567,7 @@ define(function () { '

', 'This is CryptPad, the Zero Knowledge realtime collaborative editor. Everything is saved as you type.', '
', - 'Share the link to this pad to edit with friends or use the  Share  button to share a read-only link which allows viewing but not editing.', + 'Share the link to this pad to edit with friends or use the  Share  button to share a read-only link which allows viewing but not editing.', '

', '

', diff --git a/customize.dist/translations/messages.ro.js b/customize.dist/translations/messages.ro.js index c921581b4..0eeb8f7f2 100644 --- a/customize.dist/translations/messages.ro.js +++ b/customize.dist/translations/messages.ro.js @@ -331,7 +331,7 @@ define(function () { out.header_france = "With \"love\" from \"Franța\"/ by \"XWiki"; out.header_support = " \"OpenPaaS-ng\""; out.header_logoTitle = "Mergi la pagina principală"; - out.initialState = "

Acesta este CryptPad, editorul colaborativ bazat pe tehnologia Zero Knowledge în timp real. Totul este salvat pe măsură ce scrii.
Partajează link-ul către acest pad pentru a edita cu prieteni sau folosește  Share  butonul pentru a partaja read-only link permițând vizualizarea dar nu și editarea.

Îndrăznește, începe să scrii...

 

"; + out.initialState = "

Acesta este CryptPad, editorul colaborativ bazat pe tehnologia Zero Knowledge în timp real. Totul este salvat pe măsură ce scrii.
Partajează link-ul către acest pad pentru a edita cu prieteni sau folosește  Share  butonul pentru a partaja read-only link permițând vizualizarea dar nu și editarea.

Îndrăznește, începe să scrii...

 

"; out.codeInitialState = "/*\n Acesta este editorul colaborativ de cod bazat pe tehnologia Zero Knowledge CryptPad.\n Ce scrii aici este criptat, așa că doar oamenii care au link-ul pot să-l acceseze.\n Poți să alegi ce limbaj de programare pus n evidență și schema de culori UI n dreapta sus.\n*/"; out.slideInitialState = "# CryptSlide\n* Acesta este un editor colaborativ bazat pe tehnologia Zero Knowledge.\n* Ce scrii aici este criptat, așa că doar oamenii care au link-ul pot să-l acceseze.\n* Nici măcar serverele nu au acces la ce scrii tu.\n* Ce vezi aici, ce auzi aici, atunci când pleci, lași aici.\n\n-\n# Cum se folosește\n1. Scrie-ți conținutul slide-urilor folosind sintaxa markdown\n - Află mai multe despre sintaxa markdown [aici](http://www.markdowntutorial.com/)\n2. Separă-ți slide-urile cu -\n3. Click pe butonul \"Play\" pentru a vedea rezultatele - Slide-urile tale sunt actualizate în timp real."; out.driveReadmeTitle = "Ce este CryptDrive?"; diff --git a/customize.dist/translations/messages.zh.js b/customize.dist/translations/messages.zh.js index a67682065..ac0d9ce77 100644 --- a/customize.dist/translations/messages.zh.js +++ b/customize.dist/translations/messages.zh.js @@ -470,7 +470,7 @@ define(function () { '

', '這是 CryptPad, 零知識即時協作編輯平台,當你輸入時一切已即存好。', '
', - '分享這個工作檔案的網址連結給友人或是使用、  分享  按鈕分享唯讀的連結 其只能看不能編寫。', + '分享這個工作檔案的網址連結給友人或是使用、  分享  按鈕分享唯讀的連結 其只能看不能編寫。', '

', '

', diff --git a/package.json b/package.json index ed0f25695..02b26fc7d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "1.10.0", + "version": "1.11.0", "dependencies": { "chainpad-server": "^1.0.1", "express": "~4.10.1", diff --git a/readme.md b/readme.md index 2c5b6e895..220891324 100644 --- a/readme.md +++ b/readme.md @@ -129,7 +129,7 @@ Still there are other low-lives in the world so using CryptPad over HTTPS is pro ## Setup using Docker -See [Cryptpad-Docker](cryptpad-docker.md) +See [Cryptpad-Docker](docs/cryptpad-docker.md) ## Translations diff --git a/rpc.js b/rpc.js index 5b5d058b9..73fb43736 100644 --- a/rpc.js +++ b/rpc.js @@ -28,8 +28,8 @@ var WARN = function (e, output) { }; var isValidId = function (chan) { - return chan && chan.length && /^[a-fA-F0-9]/.test(chan) || - [32, 48].indexOf(chan.length) !== -1; + return chan && chan.length && /^[a-fA-F0-9]/.test(chan) && + [32, 48].indexOf(chan.length) > -1; }; var uint8ArrayToHex = function (a) { @@ -113,15 +113,21 @@ var isTooOld = function (time, now) { return (now - time) > 300000; }; +var expireSession = function (Sessions, key) { + var session = Sessions[key]; + if (!session) { return; } + if (session.blobstage) { + session.blobstage.close(); + } + delete Sessions[key]; +}; + var expireSessions = function (Sessions) { var now = +new Date(); Object.keys(Sessions).forEach(function (key) { var session = Sessions[key]; - if (isTooOld(Sessions[key].atime, now)) { - if (session.blobstage) { - session.blobstage.close(); - } - delete Sessions[key]; + if (session && isTooOld(session.atime, now)) { + expireSession(Sessions, key); } }); }; @@ -670,6 +676,29 @@ var makeFileStream = function (root, id, cb) { }); }; +var clearOwnedChannel = function (Env, channelId, unsafeKey, cb) { + if (typeof(channelId) !== 'string' || channelId.length !== 32) { + return cb('INVALID_ARGUMENTS'); + } + + if (!(Env.msgStore && Env.msgStore.getChannelMetadata)) { + return cb('E_NOT_IMPLEMENTED'); + } + + Env.msgStore.getChannelMetadata(channelId, function (e, metadata) { + if (e) { return cb(e); } + if (!(metadata && Array.isArray(metadata.owners))) { return void cb('E_NO_OWNERS'); } + // Confirm that the channel is owned by the user is question + if (metadata.owners.indexOf(unsafeKey) === -1) { + return void cb('INSUFFICIENT_PERMISSIONS'); + } + + return void Env.msgStore.clearChannel(channelId, function (e) { + cb(e); + }); + }); +}; + var upload = function (Env, publicKey, content, cb) { var paths = Env.paths; var dec; @@ -846,6 +875,7 @@ var isAuthenticatedCall = function (call) { 'GET_LIMIT', 'UPLOAD_COMPLETE', 'UPLOAD_CANCEL', + 'EXPIRE_SESSION', ].indexOf(call) !== -1; }; @@ -1046,7 +1076,16 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) } Respond(void 0, dict); }); - + case 'EXPIRE_SESSION': + return void setTimeout(function () { + expireSession(Sessions, safeKey); + Respond(void 0, "OK"); + }); + case 'CLEAR_OWNED_CHANNEL': + return void clearOwnedChannel(Env, msg[1], publicKey, function (e, response) { + if (e) { return void Respond(e); } + Respond(void 0, response); + }); // restricted to privileged users... case 'UPLOAD': if (!privileged) { return deny(); } diff --git a/storage/file.js b/storage/file.js index 857f147f4..31b58aa94 100644 --- a/storage/file.js +++ b/storage/file.js @@ -6,6 +6,85 @@ var mkPath = function (env, channelId) { return Path.join(env.root, channelId.slice(0, 2), channelId) + '.ndjson'; }; +var getMetadataAtPath = function (Env, path, cb) { + var remainder = ''; + var stream = Fs.createReadStream(path, 'utf8'); + var complete = function (err, data) { + var _cb = cb; + cb = undefined; + if (_cb) { _cb(err, data); } + }; + stream.on('data', function (chunk) { + if (!/\n/.test(chunk)) { + remainder += chunk; + return; + } + stream.close(); + var metadata = chunk.split('\n')[0]; + + var parsed = null; + try { + parsed = JSON.parse(metadata); + complete(void 0, parsed); + } + catch (e) { + console.log(); + console.error(e); + complete('INVALID_METADATA'); + } + }); + stream.on('end', function () { + complete(null); + }); + stream.on('error', function (e) { complete(e); }); +}; + +var getChannelMetadata = function (Env, channelId, cb) { + var path = mkPath(Env, channelId); + getMetadataAtPath(Env, path, cb); +}; + +var closeChannel = function (env, channelName, cb) { + if (!env.channels[channelName]) { return; } + try { + env.channels[channelName].writeStream.close(); + delete env.channels[channelName]; + env.openFiles--; + cb(); + } catch (err) { + cb(err); + } +}; + +var clearChannel = function (env, channelId, cb) { + var path = mkPath(env, channelId); + getMetadataAtPath(env, path, function (e, metadata) { + if (e) { return cb(e); } + if (!metadata) { + return void Fs.truncate(path, 0, function (err) { + if (err) { + return cb(err); + } + cb(void 0); + }); + } + + var len = JSON.stringify(metadata).length + 1; + + // as long as closeChannel is synchronous, this should not cause + // any race conditions. truncate ought to return faster than a channel + // can be opened and read by another user. if that turns out not to be + // the case, we'll need to implement locking. + closeChannel(env, channelId, function (err) { + if (err) { cb(err); } + Fs.truncate(path, len, function (err) { + if (err) { return cb(err); } + cb(); + }); + }); + }); +}; + var readMessages = function (path, msgHandler, cb) { var remainder = ''; var stream = Fs.createReadStream(path, 'utf8'); @@ -49,22 +128,10 @@ var checkPath = function (path, callback) { }; var removeChannel = function (env, channelName, cb) { - var filename = Path.join(env.root, channelName.slice(0, 2), channelName + '.ndjson'); + var filename = mkPath(env, channelName); Fs.unlink(filename, cb); }; -var closeChannel = function (env, channelName, cb) { - if (!env.channels[channelName]) { return; } - try { - env.channels[channelName].writeStream.close(); - delete env.channels[channelName]; - env.openFiles--; - cb(); - } catch (err) { - cb(err); - } -}; - var flushUnusedChannels = function (env, cb, frame) { var currentTime = +new Date(); @@ -260,6 +327,12 @@ module.exports.create = function (conf, cb) { getChannelSize: function (chanName, cb) { channelBytes(env, chanName, cb); }, + getChannelMetadata: function (channelName, cb) { + getChannelMetadata(env, channelName, cb); + }, + clearChannel: function (channelName, cb) { + clearChannel(env, channelName, cb); + }, }); }); setInterval(function () { diff --git a/www/code/inner.js b/www/code/inner.js index 57d154950..fa3a41f6f 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -6,6 +6,8 @@ define([ 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', 'less!/code/code.less', 'less!/customize/src/less/toolbar.less', + 'less!/customize/src/less/cryptpad.less', + 'css!cm/lib/codemirror.css', 'css!cm/addon/dialog/dialog.css', 'css!cm/addon/fold/foldgutter.css', diff --git a/www/code/main.js b/www/code/main.js index 68d9c68bc..1b6cb8997 100644 --- a/www/code/main.js +++ b/www/code/main.js @@ -364,6 +364,21 @@ define([ return; } UserList.getLastName(toolbar.$userNameButton, isNew); + var fmConfig = { + dropArea: $iframe.find('.CodeMirror'), + body: $iframe.find('body'), + onUploaded: function (ev, data) { + //var cursor = editor.getCursor(); + //var cleanName = data.name.replace(/[\[\]]/g, ''); + //var text = '!['+cleanName+']('+data.url+')'; + var parsed = Cryptpad.parsePadUrl(data.url); + var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel); + var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; + var mt = ''; + editor.replaceSelection(mt); + } + }; + APP.FM = Cryptpad.createFileManager(fmConfig); }; config.onRemote = function () { diff --git a/www/common/common-codemirror.js b/www/common/common-codemirror.js index 333059a1d..618e51dd7 100644 --- a/www/common/common-codemirror.js +++ b/www/common/common-codemirror.js @@ -100,8 +100,18 @@ define([ var text = ''; lines.some(function (line) { + // lines including a c-style comment are also valuable + var clike = /^\s*(\/\*|\/\/)(.*)?(\*\/)*$/; + if (clike.test(line)) { + line.replace(clike, function (a, one, two) { + if (!(two && two.replace)) { return; } + text = two.replace(/\*\/\s*$/, '').trim(); + }); + return true; + } + // lisps? - var lispy = /^\s*(;|#\|)(.*?)$/; + var lispy = /^\s*(;|#\|)+(.*?)$/; if (lispy.test(line)) { line.replace(lispy, function (a, one, two) { text = two; @@ -111,7 +121,7 @@ define([ // lines beginning with a hash are potentially valuable // works for markdown, python, bash, etc. - var hash = /^#(.*?)$/; + var hash = /^#+(.*?)$/; if (hash.test(line)) { line.replace(hash, function (a, one) { text = one; @@ -119,16 +129,6 @@ define([ return true; } - // lines including a c-style comment are also valuable - var clike = /^\s*(\/\*|\/\/)(.*)?(\*\/)*$/; - if (clike.test(line)) { - line.replace(clike, function (a, one, two) { - if (!(two && two.replace)) { return; } - text = two.replace(/\*\/\s*$/, '').trim(); - }); - return true; - } - // TODO make one more pass for multiline comments }); diff --git a/www/common/common-hash.js b/www/common/common-hash.js index c4f452dc7..888c0a338 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -54,7 +54,7 @@ Version 1 if (!hash) { return; } var parsed = {}; var hashArr = fixDuplicateSlashes(hash).split('/'); - if (['media', 'file', 'user'].indexOf(type) === -1) { + if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) { parsed.type = 'pad'; if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Old hash @@ -93,6 +93,16 @@ Version 1 } return parsed; } + if (['invite'].indexOf(type) !== -1) { + parsed.type = 'invite'; + if (hashArr[1] && hashArr[1] === '1') { + parsed.version = 1; + parsed.channel = hashArr[2]; + parsed.pubkey = hashArr[3].replace(/-/g, '/'); + return parsed; + } + return parsed; + } return; }; var parsePadUrl = Hash.parsePadUrl = function (href) { @@ -320,5 +330,11 @@ Version 1 return hash; }; + Hash.createInviteUrl = function (curvePublic, channel) { + channel = channel || Hash.createChannelId(); + return window.location.origin + '/invite/#/1/' + channel + + '/' + curvePublic.replace(/\//g, '-') + '/'; + }; + return Hash; }); diff --git a/www/common/common-interface.js b/www/common/common-interface.js index e6927c70d..391c334a8 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -5,8 +5,10 @@ define([ '/customize/application_config.js', '/bower_components/alertifyjs/dist/js/alertify.js', '/common/notify.js', - '/common/visible.js' -], function ($, Messages, Util, AppConfig, Alertify, Notify, Visible) { + '/common/visible.js', + '/common/tippy.min.js', + 'css!/common/tippy.css', +], function ($, Messages, Util, AppConfig, Alertify, Notify, Visible, Tippy) { var UI = {}; @@ -251,9 +253,13 @@ define([ }); } - UI.importContent = function (type, f) { + UI.importContent = function (type, f, cfg) { return function () { - var $files = $('').click(); + var $files = $('', {type:"file"}); + if (cfg && cfg.accept) { + $files.attr('accept', cfg.accept); + } + $files.click(); $files.on('change', function (e) { var file = e.target.files[0]; var reader = new FileReader(); @@ -270,6 +276,7 @@ define([ var $slideIcon = $('', {"class": "fa fa-file-powerpoint-o file icon slideColor"}); var $pollIcon = $('', {"class": "fa fa-calendar file icon pollColor"}); var $whiteboardIcon = $('', {"class": "fa fa-paint-brush whiteboardColor"}); + var $contactsIcon = $('', {"class": "fa fa-users friendsColor"}); UI.getIcon = function (type) { var $icon; @@ -280,11 +287,62 @@ define([ case 'slide': $icon = $slideIcon.clone(); break; case 'poll': $icon = $pollIcon.clone(); break; case 'whiteboard': $icon = $whiteboardIcon.clone(); break; + case 'contacts': $icon = $contactsIcon.clone(); break; default: $icon = $fileIcon.clone(); } return $icon; }; + // Tooltips + UI.addTooltips = function () { + var MutationObserver = window.MutationObserver; + var addTippy = function (el) { + if (el.nodeName === 'IFRAME') { return; } + console.log(el); + Tippy(el, { + position: 'bottom', + distance: 0, + performance: true, + delay: [500, 0] + }); + }; + var $body = $('body'); + var $padIframe = $('#pad-iframe').contents().find('body'); + $('[title]').each(function (i, el) { + addTippy(el); + }); + $('#pad-iframe').contents().find('[title]').each(function (i, el) { + addTippy(el); + }); + var observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + if (mutation.type === 'childList' && mutation.addedNodes.length) { + $body.find('[title]').each(function (i, el) { + addTippy(el); + }); + if (!$padIframe.length) { return; } + $padIframe.find('[title]').each(function (i, el) { + addTippy(el); + }); + } + }); + }); + observer.observe($('body')[0], { + attributes: false, + childList: true, + characterData: false, + subtree: true + }); + if ($('#pad-iframe').length) { + observer.observe($('#pad-iframe').contents().find('body')[0], { + attributes: false, + childList: true, + characterData: false, + subtree: true + }); + } + }; + return UI; }); diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js new file mode 100644 index 000000000..976ed706b --- /dev/null +++ b/www/common/common-messaging.js @@ -0,0 +1,761 @@ +define([ + 'jquery', + '/bower_components/chainpad-crypto/crypto.js', + '/common/curve.js', + '/bower_components/marked/marked.min.js', +], function ($, Crypto, Curve, Marked) { + var Msg = {}; + + var Types = { + message: 'MSG', + update: 'UPDATE', + unfriend: 'UNFRIEND', + mapId: 'MAP_ID', + mapIdAck: 'MAP_ID_ACK' + }; + + // TODO + // - mute a channel (hide notifications or don't open it?) + + var ready = []; + var pending = {}; + var pendingRequests = []; + + var parseMessage = function (content) { + return Marked(content); + }; + + var createData = Msg.createData = function (common, hash) { + var proxy = common.getProxy(); + return { + channel: hash || common.createChannelId(), + displayName: proxy[common.displayNameKey], + profile: proxy.profile && proxy.profile.view, + edPublic: proxy.edPublic, + curvePublic: proxy.curvePublic, + avatar: proxy.profile && proxy.profile.avatar + }; + }; + + var getFriend = function (common, pubkey) { + var proxy = common.getProxy(); + if (pubkey === proxy.curvePublic) { + var data = createData(common); + delete data.channel; + return data; + } + return proxy.friends ? proxy.friends[pubkey] : undefined; + }; + + var removeFromFriendList = Msg.removeFromFriendList = function (common, curvePublic, cb) { + var proxy = common.getProxy(); + if (!proxy.friends) { + return; + } + var friends = proxy.friends; + delete friends[curvePublic]; + common.whenRealtimeSyncs(common.getRealtime(), cb); + }; + + var getFriendList = Msg.getFriendList = function (common) { + var proxy = common.getProxy(); + if (!proxy.friends) { proxy.friends = {}; } + return proxy.friends; + }; + + Msg.getFriendChannelsList = function (common) { + var friends = getFriendList(common); + var list = []; + Object.keys(friends).forEach(function (key) { + if (key === "me") { return; } + list.push(friends[key].channel); + }); + return list; + }; + + // Messaging tools + var avatars = {}; + + var addToFriendListUI = function (common, $block, open, remove, f) { + var proxy = common.getProxy(); + var friends = proxy.friends || {}; + if (f === "me") { return; } + var data = friends[f]; + var $friend = $('

', {'class': 'friend avatar'}).appendTo($block); + $friend.data('key', f); + var $rightCol = $('', {'class': 'right-col'}); + $('', {'class': 'name'}).text(data.displayName).appendTo($rightCol); + var $remove = $('', {'class': 'remove fa fa-user-times'}).appendTo($rightCol); + $remove.attr('title', common.Messages.contacts_remove); + $friend.dblclick(function () { + if (data.profile) { + window.open('/profile/#' + data.profile); + } + }); + $friend.click(function () { + open(data.curvePublic); + }); + $remove.click(function (e) { + e.stopPropagation(); + common.confirm(common.Messages._getKey('contacts_confirmRemove', [ + common.fixHTML(data.displayName) + ]), function (yes) { + if (!yes) { return; } + remove(data.curvePublic); + }, null, true); + }); + if (data.avatar && avatars[data.avatar]) { + $friend.append(avatars[data.avatar]); + $friend.append($rightCol); + } else { + common.displayAvatar($friend, data.avatar, data.displayName, function ($img) { + if (data.avatar && $img) { + avatars[data.avatar] = $img[0].outerHTML; + } + $friend.append($rightCol); + }); + } + $('', {'class': 'status'}).appendTo($friend); + }; + Msg.getFriendListUI = function (common, open, remove) { + var proxy = common.getProxy(); + var $block = $('
'); + var friends = proxy.friends || {}; + Object.keys(friends).forEach(function (f) { + addToFriendListUI(common, $block, open, remove, f); + }); + return $block; + }; + + Msg.createOwnedChannel = function (common, channelId, validateKey, owners, cb) { + var network = common.getNetwork(); + network.join(channelId).then(function (wc) { + var cfg = { + validateKey: validateKey, + owners: owners + }; + var msg = ['GET_HISTORY', wc.id, cfg]; + network.sendto(network.historyKeeper, JSON.stringify(msg)).then(cb, function (err) { + throw new Error(err); + }); + }, function (err) { + throw new Error(err); + }); + }; + + var channels = Msg.channels = window.channels = {}; + + var pushMsg = function (common, channel, cryptMsg) { + var msg = channel.encryptor.decrypt(cryptMsg); + var parsedMsg = JSON.parse(msg); + if (parsedMsg[0] === Types.message) { + parsedMsg.shift(); + channel.messages.push([cryptMsg.slice(0,64), parsedMsg]); + return true; + } + var proxy; + if (parsedMsg[0] === Types.update) { + proxy = common.getProxy(); + if (parsedMsg[1] === common.getProxy().curvePublic) { return; } + var newdata = parsedMsg[3]; + var data = getFriend(common, parsedMsg[1]); + var types = []; + Object.keys(newdata).forEach(function (k) { + if (data[k] !== newdata[k]) { + types.push(k); + data[k] = newdata[k]; + } + }); + channel.updateUI(types); + return; + } + if (parsedMsg[0] === Types.unfriend) { + proxy = common.getProxy(); + removeFromFriendList(common, channel.friendEd, function () { + channel.wc.leave(Types.unfriend); + channel.removeUI(); + }); + return; + } + }; + + var updateMyData = function (common) { + var friends = getFriendList(common); + var mySyncData = friends.me; + var myData = createData(common); + if (!mySyncData || mySyncData.displayName !== myData.displayName + || mySyncData.profile !== myData.profile + || mySyncData.avatar !== myData.avatar) { + delete myData.channel; + Object.keys(channels).forEach(function (chan) { + var channel = channels[chan]; + var msg = [Types.update, myData.curvePublic, +new Date(), myData]; + var msgStr = JSON.stringify(msg); + var cryptMsg = channel.encryptor.encrypt(msgStr); + channel.wc.bcast(cryptMsg).then(function () { + channel.refresh(); + }, function (err) { + console.error(err); + }); + }); + friends.me = myData; + } + }; + + var onChannelReady = function (common, chanId) { + if (ready.indexOf(chanId) !== -1) { return; } + ready.push(chanId); + channels[chanId].updateStatus(); + var friends = getFriendList(common); + if (ready.length === Object.keys(friends).length) { + // All channels are ready + updateMyData(common); + } + return ready.length; + }; + + // Id message allows us to map a netfluxId with a public curve key + var onIdMessage = function (common, msg, sender) { + var channel; + var isId = Object.keys(channels).some(function (chanId) { + if (channels[chanId].userList.indexOf(sender) !== -1) { + channel = channels[chanId]; + return true; + } + }); + + if (!isId) { return; } + + var decryptedMsg = channel.encryptor.decrypt(msg); + console.log(decryptedMsg); + var parsed = JSON.parse(decryptedMsg); + if (parsed[0] !== Types.mapId && parsed[0] !== Types.mapIdAck) { return; } + if (parsed[2] !== sender || !parsed[1]) { return; } + channel.mapId[sender] = parsed[1]; + + channel.updateStatus(); + + if (parsed[0] !== Types.mapId) { return; } // Don't send your key if it's already an ACK + // Answer with your own key + var proxy = common.getProxy(); + var network = common.getNetwork(); + var rMsg = [Types.mapIdAck, proxy.curvePublic, channel.wc.myID]; + var rMsgStr = JSON.stringify(rMsg); + var cryptMsg = channel.encryptor.encrypt(rMsgStr); + network.sendto(sender, cryptMsg); + }; + var onDirectMessage = function (common, msg, sender) { + if (sender !== Msg.hk) { return void onIdMessage(common, msg, sender); } + var parsed = JSON.parse(msg); + if ((parsed.validateKey || parsed.owners) && parsed.channel) { + return; + } + if (parsed.state && parsed.state === 1 && parsed.channel) { + if (channels[parsed.channel]) { + // parsed.channel is Ready + // TODO: call a function that shows that the channel is ready? (remove a spinner, ...) + // channel[parsed.channel].ready(); + channels[parsed.channel].ready = true; + onChannelReady(common, parsed.channel); + var updateTypes = channels[parsed.channel].updateOnReady; + if (updateTypes) { + channels[parsed.channel].updateUI(updateTypes); + } + } + return; + } + var chan = parsed[3]; + if (!chan || !channels[chan]) { return; } + pushMsg(common, channels[chan], parsed[4]); + }; + var onMessage = function (common, msg, sender, chan) { + if (!channels[chan.id]) { return; } + var isMessage = pushMsg(common, channels[chan.id], msg); + if (isMessage) { + // Don't notify for your own messages + if (channels[chan.id].wc.myID !== sender) { + channels[chan.id].notify(); + } + channels[chan.id].refresh(); + } + }; + + var createChatBox = function (common, $container, curvePublic) { + var data = getFriend(common, curvePublic); + var proxy = common.getProxy(); + + var $header = $('
', {'class': 'header avatar'}).appendTo($container); +/* + var $removeHistory = $('