diff --git a/.jshintignore b/.jshintignore
index 45b4087f5..c403b39b3 100644
--- a/.jshintignore
+++ b/.jshintignore
@@ -5,7 +5,7 @@ www/common/tippy/
www/common/jquery-ui/
server.js
-www/common/media-tag.js
+www/common/old-media-tag.js
www/scratch
www/common/toolbar.js
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b3b8c058..fd2c1059f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,31 @@
+# Donkey release (v2.3.0)
+
+## Goals
+
+For this release we wanted to deploy some new features related to our encrypted file functionality.
+
+## Update notes
+
+* new clientside dependencies. run `bower update`
+* new serverside APIs. Restart your server
+
+## What's new
+
+### Features
+
+* When uploading files to your CryptDrive or a pad, users will now be prompted to protect the file with a password (in addition to some random data)
+ * this adds an additional layer of security in case a third party gains access to the file's link, but not the password.
+* Users are also able to claim an encrypted file as their own, allowing them the option to delete it from the server at a later date.
+* We've refactored the Media-Tag library to be much smaller and easier to use.
+
+### Bug fixes
+
+* When setting a title for a pad which was created from a template, titles were not correctly inferred from the content of a document. This has been fixed.
+* We discovered that users who had installed _AdBlock Plus_ and configured it to **Block social media icons tracking** were unable to use the _share menu_ to construct alternative links to the same pad, but with different attributes. We have worked around the problem.
+* Admins who had configured their CryptPad instance to use custom icons for applications in the CryptDrive may have noticed that the same icons were not used on the home page. We've fixed this such that the same icons will be used everywhere
+* We have also updated the icon for the Kanban app to a more appropriate symbol
+* We found that the download button in the _file_ app was downloading the user's avatar, instead of the correct encrypted file embedded in the page. We've since fixed this
+
# Coati release (v2.2.0)
## Goals
diff --git a/customize.dist/CryptPadlogo_op5.svg b/customize.dist/CryptPadlogo_op5.svg
new file mode 100644
index 000000000..aa5acb201
--- /dev/null
+++ b/customize.dist/CryptPadlogo_op5.svg
@@ -0,0 +1 @@
+CryptPadlogo
\ No newline at end of file
diff --git a/customize.dist/images/cover-faq.jpg b/customize.dist/images/cover-faq.jpg
new file mode 100644
index 000000000..27be3af99
Binary files /dev/null and b/customize.dist/images/cover-faq.jpg differ
diff --git a/customize.dist/images/cover-features.jpg b/customize.dist/images/cover-features.jpg
new file mode 100644
index 000000000..6652c9b28
Binary files /dev/null and b/customize.dist/images/cover-features.jpg differ
diff --git a/customize.dist/images/cover-privacy.jpg b/customize.dist/images/cover-privacy.jpg
new file mode 100644
index 000000000..ff873c99d
Binary files /dev/null and b/customize.dist/images/cover-privacy.jpg differ
diff --git a/customize.dist/pages.js b/customize.dist/pages.js
index 1cd3d5f7c..300d8b9cb 100644
--- a/customize.dist/pages.js
+++ b/customize.dist/pages.js
@@ -69,7 +69,7 @@ define([
footerCol(null, [
h('div.cp-bio-foot', [
h('p', Msg.main_footerText),
- //languageSelector()
+ languageSelector()
])
], ''),
footerCol('footer_applications', [
@@ -95,7 +95,7 @@ define([
])
])
]),
- h('div.cp-version-footer', "CryptPad v2.2.0 (Coati)")
+ h('div.cp-version-footer', "CryptPad v2.3.0 (Donkey)")
]);
};
@@ -181,10 +181,10 @@ define([
]),
]),
h('div.row.align-items-center',[
- h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-2.cp-bio-avatar.cp-bio-avatar-right', [
+ h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-lg-2.cp-bio-avatar.cp-bio-avatar-right', [
h('img.img-fluid', {'src': '/customize/images/AaronMacSween.jpg'})
]),
- h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-1.cp-profile-det',[
+ h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-lg-1.cp-profile-det',[
h('h3', "Aaron MacSween"),
h('hr'),
setHTML(h('div#bioAaron'), '
Aaron transitioned into distributed systems development from a background in jazz and live stage performance. He appreciates the elegance of biological systems and functional programming, and focused on both as a student at the University of Toronto, where he studied cognitive and computer sciences. He moved to Paris in 2015 to work as a research engineer at XWiki SAS, after having dedicated significant time to various cryptography-related software projects. He spends his spare time experimenting with guitars, photography, science fiction, and spicy food.
'),
@@ -232,10 +232,10 @@ define([
]),
]),
h('div.row.align-items-center',[
- h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-2.cp-bio-avatar.cp-bio-avatar-right', [
+ h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-lg-2.cp-bio-avatar.cp-bio-avatar-right', [
h('img.img-fluid', {'src': '/customize/images/Catalin.jpg'})
]),
- h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-1.cp-profile-det',[
+ h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-lg-1.cp-profile-det',[
h('h3', "Catalin Scripcariu"),
h('hr'),
setHTML(h('div#bioCatalin'), ' Catalin is a Maths majour and has worked in B2B sales for 12 years. Design was always his passion and 3 years ago he started to dedicate himself to web design and front-end. At the beginning of 2017 he joined the XWiki, where he worked both on the business and the community side of XWiki, including the research team and CryptPad.
'),
@@ -271,113 +271,83 @@ define([
Pages['/features.html'] = function () {
return h('div#cp-main', [
infopageTopbar(),
- h('div.container.cp-container', [
- h('center', h('h1', Msg.features_title)),
- h('table#cp-features-table', [
- h('thead', h('tr', [
- h('th', Msg.features_feature),
- h('th', Msg.features_anon),
- h('th', Msg.features_registered),
- h('th', Msg.features_notes)
- ])),
- h('tbody', [
- h('tr', [
- h('td', Msg.features_f_pad),
- h('td.yes', '✔'),// u2714, u2715
- h('td.yes', '✔'),
- h('td', Msg.features_f_pad_notes)
- ]),
- h('tr', [
- h('td', Msg.features_f_history),
- h('td.yes', '✔'),
- h('td.yes', '✔'),
- h('td', Msg.features_f_history_notes)
- ]),
- h('tr', [
- h('td', Msg.features_f_export),
- h('td.yes', '✔'),
- h('td.yes', '✔'),
- h('td', Msg.features_f_export_notes)
- ]),
- h('tr', [
- h('td', Msg.features_f_todo),
- h('td.yes', '✔'),
- h('td.yes', '✔'),
- h('td')
- ]),
- h('tr', [
- h('td', Msg.features_f_viewFiles),
- h('td.yes', '✔'),
- h('td.yes', '✔'),
- h('td')
- ]),
- h('tr', [
- h('td', Msg.features_f_drive),
- h('td.part', '~'),
- h('td.yes', '✔'),
- h('td', Msg.features_f_drive_notes)
- ]),
- h('tr', [
- h('td', Msg.features_f_uploadFiles),
- h('td.no', '✕'),
- h('td.yes', '✔'),
- h('td')
- ]),
- h('tr', [
- h('td', Msg.features_f_embedFiles),
- h('td.no', '✕'),
- h('td.yes', '✔'),
- h('td')
- ]),
- h('tr', [
- h('td', Msg.features_f_multiple),
- h('td.no', '✕'),
- h('td.yes', '✔'),
- h('td', Msg.features_f_multiple_notes)
- ]),
- h('tr', [
- h('td', Msg.features_f_logoutEverywhere),
- h('td.no', '✕'),
- h('td.yes', '✔'),
- h('td', Msg.features_f_logoutEverywhere_notes || '')
- ]),
- h('tr', [
- h('td', Msg.features_f_templates),
- h('td.no', '✕'),
- h('td.yes', '✔'),
- h('td', Msg.features_f_templates_notes)
- ]),
- h('tr', [
- h('td', Msg.features_f_profile),
- h('td.no', '✕'),
- h('td.yes', '✔'),
- h('td', Msg.features_f_profile_notes)
- ]),
- h('tr', [
- h('td', Msg.features_f_tags),
- h('td.no', '✕'),
- h('td.yes', '✔'),
- h('td', Msg.features_f_tags_notes)
- ]),
- h('tr', [
- h('td', Msg.features_f_contacts),
- h('td.no', '✕'),
- h('td.yes', '✔'),
- h('td', Msg.features_f_contacts_notes)
- ]),
- h('tr', [
- h('td', Msg.features_f_storage),
- h('td.no', Msg.features_f_storage_anon),
- setHTML(h('td.yes.left'), Msg.features_f_storage_registered),
- h('td')
- ]),
- ])
+ h('div.container-fluid.cp_cont_features',[
+ h('div.container',[
+ h('center', h('h1', Msg.features_title)),
+ ]),
+ ]),
+ h('div.container',[
+ h('div.row.cp-container.cp-features-web.justify-content-sm-center',[
+ h('div.col-12.col-sm-6.cp-anon-user',[
+ h('div.card',[
+ h('div.card-body',[
+ h('h3.text-center',Msg.features_anon)
+ ]),
+ h('ul.list-group.list-group-flush', [
+ h('li.list-group-item.text-center', Msg.features_f_pad , [
+ h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_pad_notes}, h('i.fa.fa-question'))
+ ]),
+ h('li.list-group-item.text-center', Msg.features_f_history, [
+ h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_history_notes }, h('i.fa.fa-question') )
+ ]),
+ h('li.list-group-item.text-center', Msg.features_f_export, [
+ h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_export_notes }, h('i.fa.fa-question'))
+ ]),
+ h('li.list-group-item.text-center', Msg.features_f_todo),
+ h('li.list-group-item.text-center', Msg.features_f_viewFiles),
+ h('li.list-group-item.text-center', Msg.features_f_drive),
+ h('li.list-group-item.text-center', Msg.features_f_storage_anon),
+ ]),
+ ]),
+ ]),
+ h('div.col-12.col-sm-6.cp-regis-user',[
+ h('div.card',[
+ h('div.card-body',[
+ h('h3.text-center',Msg.features_registered)
+ ]),
+ h('ul.list-group.list-group-flush', [
+ h('li.list-group-item.text-center', Msg.features_f_pad, [
+ h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_pad_notes}, h('i.fa.fa-question'))
+ ]),
+ h('li.list-group-item.text-center', Msg.features_f_history, [
+ h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_history_notes }, h('i.fa.fa-question'))
+ ]),
+ h('li.list-group-item.text-center', Msg.features_f_export, [
+ h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_export_notes }, h('i.fa.fa-question'))
+ ]),
+ h('li.list-group-item.text-center', Msg.features_f_todo),
+ h('li.list-group-item.text-center', Msg.features_f_viewFiles),
+ h('li.list-group-item.text-center', Msg.features_f_drive_full),
+ h('li.list-group-item.text-center', Msg.features_f_uploadFiles),
+ h('li.list-group-item.text-center', Msg.features_f_embedFiles),
+ h('li.list-group-item.text-center', Msg.features_f_multiple, [
+ h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_multiple_notes }, h('i.fa.fa-question'))
+ ]),
+ h('li.list-group-item.text-center', Msg.features_f_logoutEverywhere),
+ h('li.list-group-item.text-center', Msg.features_f_templates, [
+ h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_templates_notes }, h('i.fa.fa-question'))
+ ]),
+ h('li.list-group-item.text-center', Msg.features_f_profile, [
+ h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_profile_notes }, h('i.fa.fa-question'))
+ ]),
+ h('li.list-group-item.text-center', Msg.features_f_tags, [
+ h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_tags_notes }, h('i.fa.fa-question'))
+ ]),
+ h('li.list-group-item.text-center', Msg.features_f_contacts, [
+ h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_contacts_notes }, h('i.fa.fa-question'))
+ ]),
+ h('li.list-group-item.text-center', setHTML(h('div'), Msg.features_f_storage_registered)),
+ ]),
+ h('div.card-body',[
+ h('div#cp-features-register', [
+ h('a', {
+ href: '/register/'
+ }, h('button.cp-features-register-button', Msg.features_f_register))
+ ]),
+ ]),
+ ]),
+ ]),
]),
- h('div#cp-features-register', [
- h('a', {
- href: '/register/'
- }, h('button.cp-features-register-button', 'Register for free'))
- ])
]),
infopageFooter()
]);
@@ -386,25 +356,35 @@ define([
Pages['/privacy.html'] = function () {
return h('div#cp-main', [
infopageTopbar(),
- h('div.container.cp-container', [
- h('center', h('h1', Msg.policy_title)),
- h('h2', Msg.policy_whatweknow),
+ h('.container-fluid.cp-privacy-top', [
+ h('div.container',[
+ h('center', h('h1', Msg.policy_title)),
+ ]),
+ ]),
+ h('div.container.cp-container.cp-privacy',[
+ h('h3', Msg.policy_whatweknow),
+ h('hr'),
setHTML(h('p'), Msg.policy_whatweknow_p1),
- h('h2', Msg.policy_howweuse),
+ h('h3', Msg.policy_howweuse),
+ h('hr'),
h('p', Msg.policy_howweuse_p1),
h('p', Msg.policy_howweuse_p2),
- h('h2', Msg.policy_whatwetell),
+ h('h3', Msg.policy_whatwetell),
+ h('hr'),
h('p', Msg.policy_whatwetell_p1),
- h('h2', Msg.policy_links),
+ h('h3', Msg.policy_links),
+ h('hr'),
h('p', Msg.policy_links_p1),
- h('h2', Msg.policy_ads),
+ h('h3', Msg.policy_ads),
+ h('hr'),
h('p', Msg.policy_ads_p1),
- h('h2', Msg.policy_choices),
+ h('h3', Msg.policy_choices),
+ h('hr'),
h('p', Msg.policy_choices_open),
setHTML(h('p'), Msg.policy_choices_vpn),
]),
@@ -425,8 +405,10 @@ define([
var question = h('p.cp-faq-questions-q#' + hash);
$(question).click(function () {
if ($(answer).is(':visible')) {
+ $(question).toggleClass('cp-active-faq');
return void $(answer).slideUp();
}
+ $(question).toggleClass('cp-active-faq');
$(answer).slideDown();
});
questions.push(h('div.cp-faq-questions-items', [
@@ -445,11 +427,15 @@ define([
}
return h('div#cp-main', [
infopageTopbar(),
- h('div.container.cp-container', [
- h('center', h('h1', Msg.faq_title)),
- h('p.cp-faq-header', h('a.nav-item.nav-link', {
+ h('div.container-fluid.cp-faq', [
+ h('div.container',[
+ h('center', h('h1', Msg.faq_title)),
+ ]),
+ ]),
+ h('div.container.cp-faq-ques-det',[
+ h('div.cp-faq-header.text-center', h('a.nav-item.nav-link', {
href: '/what-is-cryptpad.html'
- }, Msg.faq_whatis)),
+ }, setHTML(h('h4'),Msg.faq_whatis))),
h('div.cp-faq-container', categories)
]),
infopageFooter()
@@ -484,28 +470,28 @@ define([
h('div.col-12',
setHTML(h('h4.text-center'), Msg.main_about_p26)
),
- h('div.col-6.col-sm-3.col-md-3.col-lg-3',
+ h('div.col-12.col-sm-6.col-md-3.col-lg-3',
h('a.card', {href : "https://twitter.com/cryptpad"},
h('div.card-body',
setHTML(h('p'), Msg.main_about_p22)
)
)
),
- h('div.col-6.col-sm-3.col-md-3.col-lg-3',
+ h('div.col-12.col-sm-6.col-md-3.col-lg-3',
h('a.card', {href : "https://github.com/xwiki-labs/cryptpad/issues/"},
h('div.card-body',
setHTML(h('p'), Msg.main_about_p23)
)
)
),
- h('div.col-6.col-sm-3.col-md-3.col-lg-3',
+ h('div.col-12.col-sm-6.col-md-3.col-lg-3',
h('a.card', {href : "https://riot.im/app/#/room/#cryptpad:matrix.org"},
h('div.card-body',
setHTML(h('p'), Msg.main_about_p24)
)
)
),
- h('div.col-6.col-sm-3.col-md-3.col-lg-3',
+ h('div.col-12.col-sm-6.col-md-3.col-lg-3',
h('a.card', {href : "mailto:research@xwiki.com"},
h('div.card-body',
setHTML(h('p'), Msg.main_about_p25)
@@ -585,13 +571,13 @@ define([
var showingMore = false;
var icons = [
- [ 'pad', '/pad/', Msg.main_richTextPad, 'fa-file-word-o' ],
- [ 'code', '/code/', Msg.main_codePad, 'fa-file-code-o' ],
- [ 'slide', '/slide/', Msg.main_slidePad, 'fa-file-powerpoint-o' ],
- [ 'poll', '/poll/', Msg.main_pollPad, 'fa-calendar' ],
- [ 'kanban', '/kanban/', Msg.main_kanbanPad, 'fa-calendar' ],
- [ 'whiteboard', '/whiteboard/', Msg.main_whiteboardPad, 'fa-paint-brush' ],
- [ 'recent', '/drive/', Msg.main_localPads, 'fa-hdd-o' ]
+ [ 'pad', '/pad/', Msg.main_richTextPad, 'pad' ],
+ [ 'code', '/code/', Msg.main_codePad, 'code' ],
+ [ 'slide', '/slide/', Msg.main_slidePad, 'slide' ],
+ [ 'poll', '/poll/', Msg.main_pollPad, 'poll' ],
+ [ 'kanban', '/kanban/', Msg.main_kanbanPad, 'kanban' ],
+ [ 'whiteboard', '/whiteboard/', Msg.main_whiteboardPad, 'whiteboard' ],
+ [ 'recent', '/drive/', Msg.main_localPads, 'drive' ]
].filter(function (x) {
return isAvailableType(x[1]);
})
@@ -601,7 +587,7 @@ define([
return h('a', [
{ href: x[1] },
h(s, [
- h('i.fa.' + x[3]),
+ h('i.fa.' + AppConfig.applicationsIcon[x[3]]),
h('div.pad-button-text', [ h('h4', x[2]) ])
])
]);
diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less
index ebc0b1383..fd340f8d2 100644
--- a/customize.dist/src/less2/include/alertify.less
+++ b/customize.dist/src/less2/include/alertify.less
@@ -204,7 +204,16 @@
}
span.cp-password-container {
+ display: flex;
+ align-items: center;
margin-bottom: 15px;
+ justify-content: space-between;
+ & > * {
+ margin-bottom: 0 !important;
+ }
+ button {
+ margin: 0 !important;
+ }
}
input[type="checkbox"], input[type="radio"] {
@@ -220,85 +229,84 @@
}
}
- nav {
-
- text-align: right;
-
- button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button) {
-
- background-color: @colortheme_alertify-cancel;
- box-sizing: border-box;
- position: relative;
- outline: 0;
- display: inline-block;
- align-items: center;
- padding: 0 6px;
- margin: 6px 8px;
- line-height: 36px;
- min-height: 36px;
- white-space: nowrap;
- min-width: 88px;
- text-align: center;
- text-transform: uppercase;
- font-size: 14px;
- text-decoration: none;
- cursor: pointer;
- border-radius: 0;
-
- color: @alertify-btn-fg;
- border: 1px solid @colortheme_alertify-cancel-border;
-
- &.safe, &.danger {
- color: @colortheme_old-base;
- white-space: normal;
- font-weight: bold;
- }
- &.danger {
- background-color: @colortheme_alertify-red;
- border-color: @colortheme_alertify-red-border;
- color: @colortheme_alertify-red-color;
- &:hover, &:active {
- background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-red, 10%), lighten(@colortheme_alertify-red, 10%));
- }
- }
-
- &.safe {
- background-color: @colortheme_alertify-green;
- border-color: @colortheme_alertify-green-border;
- color: @colortheme_alertify-green-color;
- &:hover, &:active {
- background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-green, 10%), lighten(@colortheme_alertify-green, 10%));
- }
+ button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button) {
+
+ background-color: @colortheme_alertify-cancel;
+ box-sizing: border-box;
+ position: relative;
+ outline: 0;
+ display: inline-block;
+ align-items: center;
+ padding: 0 6px;
+ margin: 6px 8px;
+ line-height: 36px;
+ min-height: 36px;
+ white-space: nowrap;
+ min-width: 88px;
+ text-align: center;
+ text-transform: uppercase;
+ font-size: 14px;
+ text-decoration: none;
+ cursor: pointer;
+ border-radius: 0;
+
+ color: @alertify-btn-fg;
+ border: 1px solid @colortheme_alertify-cancel-border;
+
+ &.safe, &.danger {
+ color: @colortheme_old-base;
+ white-space: normal;
+ font-weight: bold;
+ }
+ &.danger {
+ background-color: @colortheme_alertify-red;
+ border-color: @colortheme_alertify-red-border;
+ color: @colortheme_alertify-red-color;
+ &:hover, &:active {
+ background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-red, 10%), lighten(@colortheme_alertify-red, 10%));
}
+ }
- &.primary {
- background-color: @colortheme_alertify-primary;
- color: @colortheme_alertify-primary-text;
- border-color: @colortheme_alertify-primary-border;
- font-weight: bold;
- &:hover, &:active {
- background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-primary, 10%), lighten(@colortheme_alertify-primary, 10%));
- }
+ &.safe {
+ background-color: @colortheme_alertify-green;
+ border-color: @colortheme_alertify-green-border;
+ color: @colortheme_alertify-green-color;
+ &:hover, &:active {
+ background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-green, 10%), lighten(@colortheme_alertify-green, 10%));
}
+ }
+ &.primary {
+ background-color: @colortheme_alertify-primary;
+ color: @colortheme_alertify-primary-text;
+ border-color: @colortheme_alertify-primary-border;
+ font-weight: bold;
&:hover, &:active {
- background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-cancel, 10%), lighten(@colortheme_alertify-cancel, 10%));
+ background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-primary, 10%), lighten(@colortheme_alertify-primary, 10%));
}
+ }
- &:focus {
- //border: 1px dotted @alertify-base;
- box-shadow: 0px 0px 5px @colortheme_alertify-primary;
- outline: none;
- }
- &::-moz-focus-inner {
- border: 0;
- }
+ &:hover, &:active {
+ background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-cancel, 10%), lighten(@colortheme_alertify-cancel, 10%));
}
- button.btn {
- margin: 6px 4px;
+ &:focus {
+ //border: 1px dotted @alertify-base;
+ box-shadow: 0px 0px 5px @colortheme_alertify-primary;
+ outline: none;
+ }
+ &::-moz-focus-inner {
+ border: 0;
}
}
+
+ button.btn {
+ margin: 6px 4px;
+ }
+
+ nav {
+ text-align: right;
+ }
}
}
diff --git a/customize.dist/src/less2/include/infopages.less b/customize.dist/src/less2/include/infopages.less
index 131877a65..4f3f6d0ad 100644
--- a/customize.dist/src/less2/include/infopages.less
+++ b/customize.dist/src/less2/include/infopages.less
@@ -161,6 +161,9 @@
background-size: contain;
height: 50px;
width: 250px;
+ @media (max-width: 326px) {
+ width: 180px;
+ }
margin-right: 0;
}
a {
diff --git a/customize.dist/src/less2/include/password-input.less b/customize.dist/src/less2/include/password-input.less
index 015f364aa..8836476fd 100644
--- a/customize.dist/src/less2/include/password-input.less
+++ b/customize.dist/src/less2/include/password-input.less
@@ -5,7 +5,6 @@
input {
flex: 1;
min-width: 0;
- margin-bottom: 0 !important; // Override margin from alertify
}
label, .fa {
margin-left: 10px;
diff --git a/customize.dist/src/less2/include/sidebar-layout.less b/customize.dist/src/less2/include/sidebar-layout.less
index 5640e014b..ef6e1189a 100644
--- a/customize.dist/src/less2/include/sidebar-layout.less
+++ b/customize.dist/src/less2/include/sidebar-layout.less
@@ -10,7 +10,7 @@
.sidebar-layout_main() {
- input[type="text"] {
+ input[type="text"], input[type="password"] {
padding-left: 10px;
}
#cp-sidebarlayout-container {
@@ -60,7 +60,7 @@
}
margin-bottom: 20px;
}
- [type="text"], button {
+ [type="text"], [type="password"], button {
vertical-align: middle;
height: 40px;
box-sizing: border-box;
diff --git a/customize.dist/src/less2/include/toolbar-history.less b/customize.dist/src/less2/include/toolbar-history.less
index abf14f3ac..31da75ad1 100644
--- a/customize.dist/src/less2/include/toolbar-history.less
+++ b/customize.dist/src/less2/include/toolbar-history.less
@@ -5,23 +5,47 @@
display: none;
text-align: center;
width: 100%;
+ padding: 10px 0;
+ align-items: center;
+ justify-content: center;
* {
font: @colortheme_app-font;
}
- .cp-toolbar-history-next {
- display: inline-block;
- vertical-align: middle;
- margin: 20px;
+ .cp-history-filler {
+ flex: 1;
}
- .cp-toolbar-history-previous {
- display: inline-block;
- vertical-align: middle;
- margin: 20px;
+ .cp-toolbar-history-close,
+ .cp-toolbar-history-revert {
+ background: white;
+ color: black;
+ //margin-top: 5px;
+ &:hover {
+ background-color: #e6e6e6;
+ }
+ }
+ .cp-toolbar-history-loadmore {
+ height: 100%;
+ color: black;
+ width: 25px;
+ position: absolute;
+ left: 0;
+ padding: 0;
+ }
+ .cp-toolbar-history-version {
+ position: absolute;
+ height: 25px;
+ line-height: 25px;
+ width: 100%;
+ text-align: center;
}
.cp-toolbar-history-goto {
display: inline-block;
vertical-align: middle;
text-align: center;
+ flex: 1;
+ flex-basis: 80%;
+ min-width: 0;
+ max-width: 600px;
input { width: 75px; }
}
.cp-toolbar-history-goto-input {
@@ -29,6 +53,30 @@
margin-left: 5px;
vertical-align: middle;
}
+ .cp-toolbar-history-bar {
+ width: 100%;
+ background: white;
+ height: 25px;
+ margin: auto;
+ position: relative;
+ }
+ .cp-toolbar-history-pos-container {
+ width: ~"calc(100% - 2px)";
+ height: 25px;
+ position: relative;
+ }
+ @pos-color: #55FF55;
+ .cp-toolbar-history-pos {
+ width: 2px;
+ height: 25px;
+ background: @pos-color;
+ &:after {
+ content: '';
+ border: 6px solid transparent;
+ border-top-color: @pos-color;
+ margin-left: -5px;
+ }
+ }
button {
color: inherit;
background-color: rgba(0,0,0,0.2);
@@ -36,14 +84,6 @@
background-color: rgba(0,0,0,0.4);
}
}
- .cp-toolbar-history-close {
- background: white;
- color: black;
- margin-top: 5px;
- &:hover {
- background-color: #e6e6e6;
- }
- }
.fa-spinner {
font-size: 66px;
}
diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less
index 602868fe4..ad696382f 100644
--- a/customize.dist/src/less2/include/toolbar.less
+++ b/customize.dist/src/less2/include/toolbar.less
@@ -22,6 +22,10 @@
@toolbar_top-height: 64px;
@toolbar_button-font: @colortheme_app-font;
+ // if we spell 'share' correctly, then adblock plus hides the share button
+ // this is a workaround
+ .fa-shhare-alt:before { content: "\f1e0"; }
+
.dropdown_main();
.ckeditor_fix();
.history_main();
diff --git a/customize.dist/src/less2/pages/page-about.less b/customize.dist/src/less2/pages/page-about.less
index 83354abd7..9850b9621 100644
--- a/customize.dist/src/less2/pages/page-about.less
+++ b/customize.dist/src/less2/pages/page-about.less
@@ -7,25 +7,25 @@
background: #fff;
}
.cp-about-intro {
- padding-top: 3em;
- padding-bottom: 3em;
- background-image: url(/customize/bkabout.jpg);
+ padding-top: 3em;
+ padding-bottom: 3em;
+ background-image: url(/customize/bkabout.jpg);
background-size: cover;
background-repeat: no-repeat;
background-position: center;
.container {
- color: #fff;
- font-family: "Open Sans";
- h1 {
- font-weight: 700;
- }
- a {
- color: #fff;
- text-decoration: underline;
- }
- p {
- padding-top: 1em;
- }
+ color: #fff;
+ font-family: "Open Sans";
+ h1 {
+ font-weight: 700;
+ }
+ a {
+ color: #fff;
+ text-decoration: underline;
+ }
+ p {
+ padding-top: 1em;
+ }
}
}
.cp-container {
diff --git a/customize.dist/src/less2/pages/page-contact.less b/customize.dist/src/less2/pages/page-contact.less
index 4d2f9ad0e..5cfe5245b 100644
--- a/customize.dist/src/less2/pages/page-contact.less
+++ b/customize.dist/src/less2/pages/page-contact.less
@@ -8,76 +8,76 @@
padding-right: 0.25em;
}
#cp-main {
- background-color: #fff;
+ background-color: #fff;
}
.cp-container {
- background: #fff;
- .cp-iconCont {
- h4 {
- margin-top: 1.5em;
- margin-bottom: 1.5em;
- }
- div {
- .card {
- padding: 4em 1em 0.5em 1em;
- box-shadow: 0 5px 15px rgba(69,145,196, 0.3);
- border-color: #fff;
- text-align: center;
- margin-bottom: 1em;
- &:hover, &:focus {
- text-decoration: none;
- transform: scale(1.05);
- }
- @media (max-width: 1200px) and (min-width: 769px) {
- min-height: 139px;
- }
- @media (max-width: 768px) and (min-width: 576px) {
- min-height: 164px;
- }
- @media (max-width: 496px) {
- min-height: 140px;
- }
- @media (max-width: 335px) {
- min-height: 162px;
- }
- }
- &:nth-child(2) {
- .card {
- background-image: url(/customize/images/twitter.svg);
- background-repeat: no-repeat;
- background-position: 50% 10%;
- background-size: 3rem;
- }
- }
- &:nth-child(3) {
- .card {
- background-image: url(/customize/images/issue.svg);
- background-repeat: no-repeat;
- background-position: 50% 10%;
- background-size: 3rem;
- }
- }
- &:nth-child(4) {
- .card {
- background-image: url(/customize/images/sayhi.svg);
- background-repeat: no-repeat;
- background-position: 50% 10%;
- background-size: 3rem;
- }
- }
- &:nth-child(5) {
- .card {
- background-image: url(/customize/images/email.svg);
- background-repeat: no-repeat;
- background-position: 50% 10%;
- background-size: 3rem;
- }
- }
- }
- }
+ background: #fff;
+ .cp-iconCont {
+ h4 {
+ margin-top: 1.5em;
+ margin-bottom: 1.5em;
+ }
+ div {
+ .card {
+ padding: 4em 1em 0.5em 1em;
+ box-shadow: 0 5px 15px rgba(69,145,196, 0.3);
+ border-color: #fff;
+ text-align: center;
+ margin-bottom: 1em;
+ &:hover, &:focus {
+ text-decoration: none;
+ transform: scale(1.05);
+ }
+ @media (max-width: 1200px) and (min-width: 769px) {
+ min-height: 139px;
+ }
+ @media (max-width: 768px) and (min-width: 576px) {
+ min-height: 164px;
+ }
+ @media (max-width: 496px) {
+ min-height: 140px;
+ }
+ @media (max-width: 335px) {
+ min-height: 162px;
+ }
+ }
+ &:nth-child(2) {
+ .card {
+ background-image: url(/customize/images/twitter.svg);
+ background-repeat: no-repeat;
+ background-position: 50% 10%;
+ background-size: 3rem;
+ }
+ }
+ &:nth-child(3) {
+ .card {
+ background-image: url(/customize/images/issue.svg);
+ background-repeat: no-repeat;
+ background-position: 50% 10%;
+ background-size: 3rem;
+ }
+ }
+ &:nth-child(4) {
+ .card {
+ background-image: url(/customize/images/sayhi.svg);
+ background-repeat: no-repeat;
+ background-position: 50% 10%;
+ background-size: 3rem;
+ }
+ }
+ &:nth-child(5) {
+ .card {
+ background-image: url(/customize/images/email.svg);
+ background-repeat: no-repeat;
+ background-position: 50% 10%;
+ background-size: 3rem;
+ }
+ }
+ }
+ }
}
.cp-contdet {
- padding-top: 3em;
+ padding-top: 3em;
padding-bottom: 3em;
background-image: url(/customize/images/bkcontact.jpg);
background-size: cover;
diff --git a/customize.dist/src/less2/pages/page-faq.less b/customize.dist/src/less2/pages/page-faq.less
index 99df97f9f..f0861a40d 100644
--- a/customize.dist/src/less2/pages/page-faq.less
+++ b/customize.dist/src/less2/pages/page-faq.less
@@ -3,20 +3,49 @@
.infopages_main();
.infopages_topbar();
+#cp-main {
+ background: #fff;
+}
+.cp-faq {
+ padding-top: 3em;
+ padding-bottom: 3em;
+ background-image: url(/customize/images/cover-faq.jpg);
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+ .container {
+ color: #fff;
+ font-family: "Open Sans";
+ }
+ h1 {
+ font-weight: 700;
+ }
+}
-.cp-faq-header {
- padding: 0;
- font-size: 1.2em;
- a {
- padding: 0;
+.cp-faq-ques-det {
+ .cp-faq-header {
+ a {
+ padding: 0;
+ h4 {
+ margin-top: 1.5rem;
+ margin-bottom: 1.5rem;
+ .cp-brand-font {
+ font-family: "Neuropolitical";
+ }
+ }
+ }
}
}
.cp-faq-container {
+ .cp-faq-questions-items {
+ background: #3a84b6;
+ color: #fff;
+ padding: 1rem 1rem 0.5rem 1rem;
+ margin-bottom: 1rem;
+ }
.cp-faq-questions-q {
- color: #3a84b6;
padding: 0;
- margin-bottom: 0;
- margin-top: 5px;
+ margin-bottom: 0.5rem;
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
@@ -24,14 +53,32 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
+ &:hover {
+ text-decoration: none;
+ }
+ &:after {
+ content: '\f067';
+ font-family: FontAwesome;
+ font-weight: normal;
+ font-style: normal;
+ float: right;
+ text-decoration: none;
+ font-size: 13px;
+ line-height: 1.5;
+ }
}
- .cp-faq-questions-q:hover {
- color: #2e688f;
- text-decoration: underline;
+ .cp-faq-questions-q.active-faq {
+ &:after {
+ content: '\f068';
+ }
}
.cp-faq-questions-a {
display: none;
- padding: 0;
+ padding: 0.5rem;
+ margin-bottom: 0.5rem;
+ background-color: #fff;
+ color: #212529;
}
+ margin-bottom: 1.5rem;
}
diff --git a/customize.dist/src/less2/pages/page-features.less b/customize.dist/src/less2/pages/page-features.less
index 37f2be245..a65eed039 100644
--- a/customize.dist/src/less2/pages/page-features.less
+++ b/customize.dist/src/less2/pages/page-features.less
@@ -3,53 +3,21 @@
.infopages_main();
.infopages_topbar();
-
-@features_th-bg: #555;
-@features_th-fg: #fff;
-@features_tr-bg-alt: #ddd;
-@features_notes: #333;
-@features_yes: #c4ffca;
-@features_no: #ffc4bc;
-@features_part: #faffd3;
-
-table#cp-features-table {
- width: 100%;
- th {
- background-color: @features_th-bg;
- color: @features_th-fg;
- padding: 10px;
- border: 1px solid @features_th-bg;
- }
- tbody {
- td {
- height: 32px;
- line-height: 32px;
- padding: 5px;
- border: 1px solid @features_th-bg;
- }
- tr:nth-child(odd) {
- background-color: @features_tr-bg-alt;
- }
- }
- td:nth-child(4) {
- color: @features_notes;
- font-size: 14px;
- font-style: italic;
- }
- td:first-child {
- font-weight: bold;
- }
- .yes, .no, .part {
- text-align: center;
- }
- .yes { background-color: @features_yes; }
- .no { background-color: @features_no; }
- .part { background-color: @features_part; }
- .left {
- text-align: left;
+#cp-main {
+ background-color: #fff;
+}
+.cp_cont_features {
+ padding-top: 3em;
+ padding-bottom: 3em;
+ background-image: url('/customize/images/cover-features.jpg');
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+ h1 {
+ font-weight: 700;
+ color: #fff;
}
}
-
#cp-features-register {
text-align: center;
padding: 20px;
@@ -62,6 +30,57 @@ table#cp-features-table {
border-radius: 0;
&:hover {
transform: scale(1.05);
+ cursor: pointer;
+ }
+ padding: 0.5em 1em;
+}
+.cp-features-web {
+ .card {
+ box-shadow: 0 5px 15px rgba(69, 145, 196, 0.3);
+ border: none;
+ }
+ h3 {
+ color: #fff;
+ }
+ .list-group {
+ li {
+ &:before {
+ content: "\f00c";
+ font-family: "FontAwesome";
+ font-size: 14px;
+ color: @cryptpad_color_blue;
+ padding-right: 10px;
+ }
+ }
+ div {
+ display: inline;
+ }
+ }
+ a.voted {
+ opacity: 0.6;
+ margin-left: 15px;
+ &:hover {
+ opacity: 1;
+ }
+ }
+ .list-group-item {
+ border-color: rgba(69, 145, 196, 0.125);
+ }
+}
+.cp-anon-user {
+ .card-body {
+ background-color: @cryptpad_color_blue;
+ }
+}
+.cp-regis-user {
+ @media (max-width:575px) {
+ margin-top: 3em;
+ }
+ .card-body {
+ &:first-of-type {
+ background: #4591C4;
+ background: -webkit-linear-gradient(to right, #FF7C4F, #4592C4); // lesshint duplicateProperty: false
+ background: linear-gradient(to right, #FF7C4F, #4592C4); // lesshint duplicateProperty: false
+ }
}
}
-
diff --git a/customize.dist/src/less2/pages/page-index.less b/customize.dist/src/less2/pages/page-index.less
index b0a6d124e..75518f5f8 100644
--- a/customize.dist/src/less2/pages/page-index.less
+++ b/customize.dist/src/less2/pages/page-index.less
@@ -26,20 +26,20 @@ body {
font-family: "Open Sans", Helvetica;
}
.cp-right {
- .cp-register-btn {
- padding: 0.5em 1em 0.7em 1em;
- border: 2px solid #fff;
- &:hover {
- transform: scale(1.05);
- }
- }
- .cp-login-btn {
- color: #fff;
- padding: 0.5em 1em 0.7em 1em;
- &:hover {
- transform: scale(1.05);
- }
- }
+ .cp-register-btn {
+ padding: 0.5em 1em 0.7em 1em;
+ border: 2px solid #fff;
+ &:hover {
+ transform: scale(1.05);
+ }
+ }
+ .cp-login-btn {
+ color: #fff;
+ padding: 0.5em 1em 0.7em 1em;
+ &:hover {
+ transform: scale(1.05);
+ }
+ }
}
.cp-title {
display: flex;
diff --git a/customize.dist/src/less2/pages/page-login.less b/customize.dist/src/less2/pages/page-login.less
index 5ad874d0f..2f77e74f4 100644
--- a/customize.dist/src/less2/pages/page-login.less
+++ b/customize.dist/src/less2/pages/page-login.less
@@ -17,7 +17,7 @@
}
}
.cp-container {
- #data {
+ #data {
background: #4591C4;
padding-top: 3em;
padding-bottom: 7em;
@@ -31,7 +31,7 @@
color: @cryptpad_header_col;
}
}
- #userForm {
+ #userForm {
padding-top: 3em;
padding-bottom: 2em;
.form-control {
@@ -47,23 +47,23 @@
}
}
.align-items-center {
- box-shadow: 0 5px 15px rgba(69,145,196, 0.3);
- background: #fff;
+ box-shadow: 0 5px 15px rgba(69,145,196, 0.3);
+ background: #fff;
}
.extra {
- margin-top: 1em;
- .login {
- background: @cryptpad_color_blue;
- color: #fff;
- padding: 10px;
+ margin-top: 1em;
+ .login {
+ background: @cryptpad_color_blue;
+ color: #fff;
+ padding: 10px;
border-radius: 0;
- &:hover {
- transform: scale(1.05);
- }
- }
+ &:hover {
+ transform: scale(1.05);
+ }
+ }
}
}
.cp-container {
- padding-top: 3em;
- min-height: 66vh;
+ padding-top: 3em;
+ min-height: 66vh;
}
diff --git a/customize.dist/src/less2/pages/page-privacy.less b/customize.dist/src/less2/pages/page-privacy.less
index a1f8b2955..cfd575173 100644
--- a/customize.dist/src/less2/pages/page-privacy.less
+++ b/customize.dist/src/less2/pages/page-privacy.less
@@ -3,3 +3,46 @@
.infopages_main();
.infopages_topbar();
+#cp-main {
+ background: #fff;
+}
+.cp-privacy-top {
+ padding-top: 3em;
+ padding-bottom: 3em;
+ background-image: url(/customize/images/cover-privacy.jpg);
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+ .container {
+ color: #fff;
+ font-family: "Open Sans";
+ h1 {
+ font-weight: 700;
+ }
+ a {
+ color: #fff;
+ text-decoration: underline;
+ }
+ p {
+ padding-top: 1em;
+ }
+ }
+}
+.cp-privacy {
+ background-image: url(/customize/CryptPadlogo_op5.svg);
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+ hr {
+ margin-left: 0;
+ width: 15rem;
+ border-top: 2px solid #4591C4;
+ }
+ h3 {
+ color: #1E1F1F;
+ font-weight: 700;
+ }
+ p {
+ color: #3F4141;
+ }
+}
diff --git a/customize.dist/src/less2/pages/page-what-is-cryptpad.less b/customize.dist/src/less2/pages/page-what-is-cryptpad.less
index 0e02ce41a..f08655b9c 100644
--- a/customize.dist/src/less2/pages/page-what-is-cryptpad.less
+++ b/customize.dist/src/less2/pages/page-what-is-cryptpad.less
@@ -5,39 +5,39 @@
.infopages_topbar();
.cp-what-is {
- padding-top: 3em;
- padding-bottom: 3em;
- background-image: url(/customize/bkwhat.jpg);
- background-size: cover;
- background-repeat: no-repeat;
- background-position: center;
- color: #fff;
- h1 {
- font-weight: 700;
- }
+ padding-top: 3em;
+ padding-bottom: 3em;
+ background-image: url(/customize/bkwhat.jpg);
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+ color: #fff;
+ h1 {
+ font-weight: 700;
+ }
}
#cp-main {
- background: #fff;
+ background: #fff;
}
.cp-container {
- padding-top: 3em;
- padding-bottom: 3em;
- h2 {
- margin-top: 0;
- font-weight: 700;
- color: @cryptpad_header_col;
- }
- p {
- color: @cryptpad_text_col;
- }
- #zeroknowledge {
- width: 65%;
- }
- .row {
- margin-bottom: 1.5em;
- }
- img {
- display: block;
- margin: 0 auto;
- }
+ padding-top: 3em;
+ padding-bottom: 3em;
+ h2 {
+ margin-top: 0;
+ font-weight: 700;
+ color: @cryptpad_header_col;
+ }
+ p {
+ color: @cryptpad_text_col;
+ }
+ #zeroknowledge {
+ width: 65%;
+ }
+ .row {
+ margin-bottom: 1.5em;
+ }
+ img {
+ display: block;
+ margin: 0 auto;
+ }
}
diff --git a/customize.dist/translations/messages.de.js b/customize.dist/translations/messages.de.js
index 06b86968c..4e68d9f29 100644
--- a/customize.dist/translations/messages.de.js
+++ b/customize.dist/translations/messages.de.js
@@ -780,7 +780,7 @@
out.faq_link = "FAQ";
out.faq_title = "Häufigste Fragen";
- out.faq_whatis = "Was ist CryptPad?";
+ out.faq_whatis = "Was ist CryptPad ?";
out.faq = {};
out.faq.keywords = {
title: 'Schlüsselkonzepte',
diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js
index e053d8fab..a936f592c 100644
--- a/customize.dist/translations/messages.fr.js
+++ b/customize.dist/translations/messages.fr.js
@@ -39,6 +39,8 @@ define(function () {
'Appuyez sur Échap pour voir le pad ou rechargez la page pour pouvoir le modifier à nouveau.';
out.errorCopy = ' Vous pouvez toujours copier son contenu ailleurs en appuyant sur Échap . Dés que vous aurez quitté la page, il sera impossible de le récupérer.';
out.errorRedirectToHome = 'Appuyez sur Échap pour retourner vers votre CryptDrive.';
+ out.newVersionError = "Une nouvelle version de CryptPad est disponible. " +
+ "Rechargez la page pour utiliser la nouvelle version, ou appuyez sur Échap pour accéder au contenu actuel en mode hors-ligne .";
out.loading = "Chargement...";
out.error = "Erreur";
@@ -230,12 +232,11 @@ define(function () {
out.historyText = "Historique";
out.historyButton = "Afficher l'historique du document";
- out.history_next = "Voir la version suivante";
- out.history_prev = "Voir la version précédente";
- out.history_goTo = "Voir la version sélectionnée";
+ out.history_next = "Version plus récente";
+ out.history_prev = "Version plus ancienne";
+ out.history_loadMore = "Charger davantage d'historique";
out.history_close = "Retour";
out.history_closeTitle = "Fermer l'historique";
- out.history_restore = "Restaurer";
out.history_restoreTitle = "Restaurer la version du document sélectionnée";
out.history_restorePrompt = "Êtes-vous sûr de vouloir remplacer la version actuelle du document par la version affichée ?";
out.history_restoreDone = "Document restauré";
@@ -616,7 +617,7 @@ define(function () {
out.upload_mustLogin = "Vous devez vous connecter pour importer un fichier";
out.download_button = "Déchiffrer et télécharger";
out.download_mt_button = "Télécharger";
- out.download_resourceNotAvailable = "Le fichier demandé n'est pas disponible...";
+ out.download_resourceNotAvailable = "Le fichier demandé n'est pas disponible... Appuyez sur Échap pour continuer.";
out.todo_title = "CryptTodo";
out.todo_newTodoNamePlaceholder = "Décrivez votre tâche...";
@@ -673,6 +674,7 @@ define(function () {
out.main_slidePad = 'Présentation Markdown';
out.main_pollPad = 'Sondage ou Planning';
out.main_whiteboardPad = 'Tableau blanc';
+ out.main_kanbanPad = 'Kanban';
out.main_localPads = 'Pads Locaux';
out.main_yourCryptDrive = 'Votre CryptDrive';
out.main_footerText = "Avec CryptPad, vous pouvez créer des documents collaboratifs rapidement pour prendre des notes à plusieurs.";
@@ -741,8 +743,8 @@ define(function () {
out.features_f_history = "Historique";
out.features_f_history_notes = "Voir et restaurer n'importe quelle version d'un pad";
out.features_f_todo = "Créer une TODO-list";
- out.features_f_drive = "CryptDrive";
- out.features_f_drive_notes = "Fonctionnalités basiques pour les utilisateurs anonymes";
+ out.features_f_drive = "Fonctionnalités CryptDrive limitées";
+ out.features_f_drive_full = "Fonctionnalités CryptDrive limitées";
out.features_f_export = "Export/Import";
out.features_f_export_notes = "Pour les pads et CryptDrive";
out.features_f_viewFiles = "Voir des fichiers";
@@ -753,7 +755,7 @@ define(function () {
out.features_f_multiple_notes = "Moyen facile de voir vos pads depuis n'importe quel appareil";
out.features_f_logoutEverywhere = "Se déconnecter partout";
out.features_f_logoutEverywhere_notes = "Se déconnecter des autres appareils utilisés";
- out.features_f_templates = "Modèles";
+ out.features_f_templates = "Utiliser les modèles";
out.features_f_templates_notes = "Créer des modèles et créer des pads basés sur ces modèles";
out.features_f_profile = "Créer un profil";
out.features_f_profile_notes = "Page personnelle contenant un avatar et une description";
@@ -764,12 +766,13 @@ define(function () {
out.features_f_storage = "Stockage";
out.features_f_storage_anon = "Pads supprimés après 3 mois";
out.features_f_storage_registered = "Gratuit: 50Mo Premium: 5Go/20Go/50Go";
+ out.features_f_register = "S'inscrire gratuitement";
// faq.html
out.faq_link = "FAQ";
out.faq_title = "Foire Aux Questions";
- out.faq_whatis = "Qu'est-ce que CryptPad ?";
+ out.faq_whatis = "Qu'est-ce que CryptPad ?";
out.faq = {};
out.faq.keywords = {
title: 'Termes spéciaux',
@@ -1113,6 +1116,16 @@ define(function () {
out.password_submit = "Valider";
out.password_show = "Afficher";
+ // Change password in pad properties
+ out.properties_addPassword = "Ajouter un mot de passe";
+ out.properties_changePassword = "Modifier le mot de passe";
+ out.properties_confirmNew = "Êtes-vous sûr ? Ajouter un mot de passe changera l'URL de ce pad. Les utilisateurs ne connaissant pas le nouveau mot de passe perdront l'accès au pad.";
+ out.properties_confirmChange = "Êtes-vous sûr ? Les utilisateurs ne connaissant pas le nouveau mot de passe perdront l'accès au pad.";
+ out.properties_passwordError = "Une erreur est survenue lors de la modification du mot de passe. Veuillez réessayer.";
+ out.properties_passwordWarning = "Le mot de passe a été modifié avec succès mais nous n'avons pas réussi à mettre à jour votre CryptDrive avec les nouvelles informations. Vous devrez peut-être supprimer manuellement l'ancienne version de ce pad. Appuyez sur OK pour recharger le pad et mettre à jour vos droits d'accès.";
+ out.properties_passwordSuccess = "Le mot de passe a été modifié avec succès. Appuyez sur OK pour mettre à jour vos droits d'accès.";
+ out.properties_changePasswordButton = "Valider";
+
// New share modal
out.share_linkCategory = "Partage";
out.share_linkAccess = "Droits d'accès";
diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js
index 8251147dd..008d53217 100644
--- a/customize.dist/translations/messages.js
+++ b/customize.dist/translations/messages.js
@@ -40,6 +40,8 @@ define(function () {
'Hit Esc to continue to view this pad, or reload to try editing again.';
out.errorCopy = ' You can still copy the content to another location by pressing Esc . Once you leave this page, it will disappear forever!';
out.errorRedirectToHome = 'Press Esc to be redirected to your CryptDrive.';
+ out.newVersionError = "A new version of CryptPad is available. " +
+ "Reload to use the new version, or press escape to access your content in offline mode .";
out.loading = "Loading...";
out.error = "Error";
@@ -232,12 +234,10 @@ define(function () {
out.historyText = "History";
out.historyButton = "Display the document history";
- out.history_next = "Go to the next version";
- out.history_prev = "Go to the previous version";
- out.history_goTo = "Go to the selected version";
- out.history_close = "Back";
+ out.history_next = "Newer version";
+ out.history_prev = "Older version";
+ out.history_loadMore = "Load more history";
out.history_closeTitle = "Close the history";
- out.history_restore = "Restore";
out.history_restoreTitle = "Restore the selected version of the document";
out.history_restorePrompt = "Are you sure you want to replace the current version of the document by the displayed one?";
out.history_restoreDone = "Document restored";
@@ -600,6 +600,15 @@ define(function () {
out.settings_templateSkip = "Skip the template selection modal";
out.settings_templateSkipHint = "When you create a new empty pad, if you have stored templates for this type of pad, a modal appears to ask if you want to use a template. Here you can choose to never show this modal and so to never use a template.";
+ out.settings_changePasswordTitle = "Change your password"; // XXX
+ out.settings_changePasswordHint = "Change your account's password without losing its data. You have to enter your existing password once, and the new password you want twice. " +
+ "We can't reset your password if you forget it so be very careful! "; // XXX
+ out.settings_changePasswordButton = "Change password"; // XXX
+ out.settings_changePasswordCurrent = "Existing password"; // XXX
+ out.settings_changePasswordNew = "New password"; // XXX
+ out.settings_changePasswordNewConfirm = "Confirm new password"; // XXX
+ out.settings_changePasswordConfirm = "Are you sure?"; // XXX
+
out.upload_title = "File upload";
out.upload_modal_title = "File upload options";
out.upload_modal_filename = "File name (extension {0} added automatically)";
@@ -620,7 +629,7 @@ define(function () {
out.upload_mustLogin = "You must be logged in to upload files";
out.download_button = "Decrypt & Download";
out.download_mt_button = "Download";
- out.download_resourceNotAvailable = "The requested resource was not available...";
+ out.download_resourceNotAvailable = "The requested resource was not available... Press Esc to continue.";
out.todo_title = "CryptTodo";
out.todo_newTodoNamePlaceholder = "Describe your task...";
@@ -678,6 +687,7 @@ define(function () {
out.main_slidePad = 'Markdown Presentation';
out.main_pollPad = 'Poll or Schedule';
out.main_whiteboardPad = 'Whiteboard';
+ out.main_kanbanPad = 'Kanban';
out.main_localPads = 'Local Pads';
out.main_yourCryptDrive = 'Your CryptDrive';
out.main_footerText = "With CryptPad, you can make quick collaborative documents for taking notes and writing down ideas together.";
@@ -746,8 +756,8 @@ define(function () {
out.features_f_history = "History";
out.features_f_history_notes = "View and restore any version of your pads";
out.features_f_todo = "Create a TODO-list";
- out.features_f_drive = "CryptDrive";
- out.features_f_drive_notes = "Basic features for anonymous users";
+ out.features_f_drive = "Limited CryptDrive functionality";
+ out.features_f_drive_full = "Complete CryptDrive functionality";
out.features_f_export = "Export/Import";
out.features_f_export_notes = "For pads and CryptDrive";
out.features_f_viewFiles = "View files";
@@ -767,14 +777,15 @@ define(function () {
out.features_f_contacts = "Contacts application";
out.features_f_contacts_notes = "Add contacts and chat with them in an encrypted session";
out.features_f_storage = "Storage";
- out.features_f_storage_anon = "Pads deleted after 3 months";
+ out.features_f_storage_anon = "Pads are deleted after 3 months";
out.features_f_storage_registered = "Free: 50MB Premium: 5GB/20GB/50GB";
+ out.features_f_register = "Register for free";
// faq.html
out.faq_link = "FAQ";
out.faq_title = "Frequently Asked Questions";
- out.faq_whatis = "What is CryptPad?";
+ out.faq_whatis = "What is CryptPad ?";
out.faq = {};
out.faq.keywords = {
title: 'Keywords',
@@ -1151,13 +1162,23 @@ define(function () {
out.creation_newPadModalDescriptionAdvanced = "You can check the box (or press Space to change its value) if you want to display the pad creation screen (for owned pads, expiring pads, etc.).";
out.creation_newPadModalAdvanced = "Display the pad creation screen";
- // Password prompt on the loadind screen
- out.password_info = "The pad you're tyring to open is protected with a password. Enter the correct password to access its content.";
+ // Password prompt on the loading screen
+ out.password_info = "The pad you're trying to open is protected with a password. Enter the correct password to access its content.";
out.password_error = "Pad not found! This error can be caused by two factors: either the password in invalid, or the pad has been deleted from the server.";
out.password_placeholder = "Type the password here...";
out.password_submit = "Submit";
out.password_show = "Show";
+ // Change password in pad properties
+ out.properties_addPassword = "Add a password";
+ out.properties_changePassword = "Change the password";
+ out.properties_confirmNew = "Are you sure? Adding a password will change this pad's URL. Users without the password will lose access to this pad";
+ out.properties_confirmChange = "Are you sure? Users without the new password will lose access to this pad";
+ out.properties_passwordError = "An error occured while trying to change the password. Please try again.";
+ out.properties_passwordWarning = "The password was successfully changed but we were unable to update your CryptDrive with the new data. You may have to remove the old version of the pad manually. Press OK to reload and update your acces rights.";
+ out.properties_passwordSuccess = "The password was successfully changed. Press OK to reload and update your access rights.";
+ out.properties_changePasswordButton = "Submit";
+
// New share modal
out.share_linkCategory = "Share link";
out.share_linkAccess = "Access rights";
diff --git a/package.json b/package.json
index 06214aab8..27cf19d81 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server",
- "version": "2.2.0",
+ "version": "2.3.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"chainpad-server": "^2.0.0",
diff --git a/rpc.js b/rpc.js
index c21bee11f..69d84b6dd 100644
--- a/rpc.js
+++ b/rpc.js
@@ -896,13 +896,13 @@ var removeOwnedBlob = function (Env, blobId, unsafeKey, cb) {
Fs.unlink(blobPath, w(function (e) {
if (e) {
w.abort();
- return void cb(e);
+ return void cb(e.code);
}
}));
}).nThen(function () {
// Delete the proof of ownership
Fs.unlink(ownPath, function (e) {
- cb(e);
+ cb(e && e.code);
});
});
};
@@ -1204,7 +1204,7 @@ var owned_upload_complete = function (Env, safeKey, id, cb) {
return void cb();
} else {
// it failed in an unexpected way. log it
- WARN(e, 'ownedUploadComplete');
+ WARN('ownedUploadComplete', e);
return void cb(e.code);
}
});
@@ -1221,13 +1221,13 @@ var owned_upload_complete = function (Env, safeKey, id, cb) {
Mkdirp(filePath, w(function (e /*, path */) {
if (e) { // does not throw error if the directory already existed
w.abort();
- return void cb(e);
+ return void cb(e.code);
}
}));
Mkdirp(ownPath, w(function (e /*, path */) {
if (e) { // does not throw error if the directory already existed
w.abort();
- return void cb(e);
+ return void cb(e.code);
}
}));
}).nThen(function (w) {
@@ -1254,12 +1254,11 @@ var owned_upload_complete = function (Env, safeKey, id, cb) {
// flow is dumb and I need to guard against this which will never happen
/*:: if (typeof(oldPath) === 'object') { throw new Error('should never happen'); } */
- Fs.rename(oldPath /* XXX */, finalPath, w(function (e) {
+ Fs.rename(oldPath, finalPath, w(function (e) {
if (e) {
// Remove the ownership file
- // XXX not needed if we have a cleanup script?
Fs.unlink(finalOwnPath, function (e) {
- WARN(e, 'Removing ownership file ownedUploadComplete');
+ WARN('E_UNLINK_OWN_FILE', e);
});
w.abort();
return void cb(e.code);
diff --git a/www/assert/main.js b/www/assert/main.js
index 657488b81..4ff862511 100644
--- a/www/assert/main.js
+++ b/www/assert/main.js
@@ -9,7 +9,8 @@ define([
'/common/common-thumbnail.js',
'/common/wire.js',
'/common/flat-dom.js',
-], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat) {
+ '/common/media-tag.js',
+], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat, MediaTag) {
window.Hyperjson = Hyperjson;
window.Sortify = Sortify;
@@ -295,6 +296,26 @@ define([
!secret.hashData.present);
}, "test support for ugly tracking query paramaters in url");
+ assert(function (cb) {
+ try {
+ MediaTag(void 0).on('progress').on('decryption');
+ return void cb(true);
+ } catch (e) {
+ console.error(e);
+ return void cb(false);
+ }
+ }, 'check that MediaTag does the right thing when passed no value');
+
+ assert(function (cb) {
+ try {
+ MediaTag(document.createElement('div')).on('progress').on('decryption');
+ return void cb(true);
+ } catch (e) {
+ console.error(e);
+ return void cb(false);
+ }
+ }, 'check that MediaTag does the right thing when passed no value');
+
assert(function (cb) {
// TODO
return cb(true);
diff --git a/www/common/LessLoader.js b/www/common/LessLoader.js
index 063b3d024..04fa68714 100644
--- a/www/common/LessLoader.js
+++ b/www/common/LessLoader.js
@@ -49,6 +49,7 @@ define([
if (ua[0].indexOf(':') === -1 && ua[0].indexOf('/') && parent) {
ua[0] = parent.replace(/\/[^\/]*$/, '/') + ua[0];
}
+ ua[0] = ua[0].replace(/^\/\.\.\//, '/');
var out = ua.join('#');
//console.log(url + " --> " + out);
return out;
@@ -91,17 +92,32 @@ define([
};
var lessEngine;
+ var tempCache = { key: Math.random() };
var getLessEngine = function (cb) {
if (lessEngine) {
cb(lessEngine);
} else {
require(['/bower_components/less/dist/less.min.js'], function (Less) {
+ if (lessEngine) { return void cb(lessEngine); }
lessEngine = Less;
var doXHR = lessEngine.FileManager.prototype.doXHR;
lessEngine.FileManager.prototype.doXHR = function (url, type, callback, errback) {
url = fixURL(url);
- //console.log("xhr: " + url);
- return doXHR(url, type, callback, errback);
+ var cached = tempCache[url];
+ if (cached && cached.res) {
+ var res = cached.res;
+ return void setTimeout(function () { callback(res[0], res[1]); });
+ }
+ if (cached) { return void cached.queue.push(callback); }
+ cached = tempCache[url] = { queue: [ callback ], res: undefined };
+ return doXHR(url, type, function (text, lastModified) {
+ cached.res = [ text, lastModified ];
+ var queue = cached.queue;
+ cached.queue = [];
+ queue.forEach(function (f) {
+ setTimeout(function () { f(text, lastModified); });
+ });
+ }, errback);
};
cb(lessEngine);
});
diff --git a/www/common/application_config_internal.js b/www/common/application_config_internal.js
index 0ab300721..ec97ac39c 100644
--- a/www/common/application_config_internal.js
+++ b/www/common/application_config_internal.js
@@ -81,7 +81,8 @@ define(function() {
whiteboard: 'fa-paint-brush',
todo: 'fa-tasks',
contacts: 'fa-users',
- kanban: 'fa-list-alt',
+ kanban: 'fa-columns',
+ drive: 'fa-hdd-o',
};
// Ability to create owned pads and expiring pads through a new pad creation screen.
diff --git a/www/common/common-interface.js b/www/common/common-interface.js
index e65742d0f..7b05d22fe 100644
--- a/www/common/common-interface.js
+++ b/www/common/common-interface.js
@@ -144,13 +144,15 @@ define([
};
dialog.frame = function (content) {
- return h('div.alertify', {
+ return $(h('div.alertify', {
tabindex: 1,
}, [
h('div.dialog', [
h('div', content),
])
- ]);
+ ])).click(function (e) {
+ e.stopPropagation();
+ })[0];
};
/**
@@ -624,6 +626,7 @@ define([
h('p.cp-loading-progress-drive'),
h('p.cp-loading-progress-pad')
]);
+ $(progress).hide();
$loading.find('.cp-loading-container').append(progress);
} else if (config.noProgress) {
$loading.find('.cp-loading-progress').remove();
@@ -645,6 +648,7 @@ define([
UI.updateLoadingProgress = function (data, isDrive) {
var $loading = $('#' + LOADING);
if (!$loading.length || loading.error) { return; }
+ $loading.find('.cp-loading-progress').show();
var $progress;
if (isDrive) {
// Drive state
diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js
index b5a12da0b..1c75927fc 100644
--- a/www/common/common-messaging.js
+++ b/www/common/common-messaging.js
@@ -89,7 +89,7 @@ define([
};
/* Used to accept friend requests within apps other than /contacts/ */
- Msg.addDirectMessageHandler = function (cfg) {
+ Msg.addDirectMessageHandler = function (cfg, href) {
var network = cfg.network;
var proxy = cfg.proxy;
if (!network) { return void console.error('Network not ready'); }
@@ -97,13 +97,12 @@ define([
var msg;
if (sender === network.historyKeeper) { return; }
try {
- var parsed = Hash.parsePadUrl(window.location.href);
+ var parsed = Hash.parsePadUrl(href);
+ var secret = Hash.getSecrets(parsed.type, parsed.hash);
if (!parsed.hashData) { return; }
- var chan = Hash.hrefToHexChannelId(window.location.href);
+ var chan = secret.channel;
// Decrypt
- var keyStr = parsed.hashData.key;
- var cryptor = Crypto.createEditCryptor(keyStr);
- var key = cryptor.cryptKey;
+ var key = secret.keys ? secret.keys.cryptKey : Hash.decodeBase64(secret.key);
var decryptMsg;
try {
decryptMsg = Crypto.decrypt(message, key);
@@ -197,15 +196,14 @@ define([
var network = cfg.network;
var netfluxId = data.netfluxId;
var parsed = Hash.parsePadUrl(data.href);
+ var secret = Hash.getSecrets(parsed.type, parsed.hash);
if (!parsed.hashData) { return; }
// Message
- var chan = Hash.hrefToHexChannelId(data.href);
+ var chan = secret.channel;
var myData = createData(cfg.proxy);
var msg = ["FRIEND_REQ", chan, myData];
// Encryption
- var keyStr = parsed.hashData.key;
- var cryptor = Crypto.createEditCryptor(keyStr);
- var key = cryptor.cryptKey;
+ var key = secret.keys ? secret.keys.cryptKey : Hash.decodeBase64(secret.key);
var msgStr = Crypto.encrypt(JSON.stringify(msg), key);
// Send encrypted message
if (pendingRequests.indexOf(netfluxId) === -1) {
diff --git a/www/common/common-thumbnail.js b/www/common/common-thumbnail.js
index bc8145d46..767b0e92a 100644
--- a/www/common/common-thumbnail.js
+++ b/www/common/common-thumbnail.js
@@ -256,7 +256,7 @@ define([
var k = getKey(parsed.type, channel);
var whenNewThumb = function () {
var secret = Hash.getSecrets('file', parsed.hash, password);
- var hexFileName = channel;
+ var hexFileName = secret.channel;
var src = Hash.getBlobPathFromHex(hexFileName);
var key = secret.keys && secret.keys.cryptKey;
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index 3c1c9c7ab..e138e7efb 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -18,8 +18,10 @@ define([
var UIElements = {};
// Configure MediaTags to use our local viewer
- if (MediaTag && MediaTag.PdfPlugin) {
- MediaTag.PdfPlugin.viewer = '/common/pdfjs/web/viewer.html';
+ if (MediaTag) {
+ MediaTag.setDefaultConfig('pdf', {
+ viewer: '/common/pdfjs/web/viewer.html'
+ });
}
UIElements.updateTags = function (common, href) {
@@ -134,12 +136,6 @@ define([
$d.append(UI.dialog.selectable(owners, {
id: 'cp-app-prop-owners',
}));
- /* TODO
- if (owned) {
- var $deleteOwned = $('button').text(Messages.fc_delete_owned).click(function () {
- });
- $d.append($deleteOwned);
- }*/
var expire = Messages.creation_expireFalse;
if (data.expire && typeof (data.expire) === "number") {
@@ -151,11 +147,12 @@ define([
id: 'cp-app-prop-expire',
}));
- if (typeof data.password !== "undefined") {
+ var hasPassword = typeof data.password !== "undefined";
+ if (hasPassword) {
$('', {'for': 'cp-app-prop-password'}).text(Messages.creation_passwordValue)
.appendTo($d);
var password = UI.passwordInput({
- id: 'cp-app-prop-expire',
+ id: 'cp-app-prop-password',
readonly: 'readonly'
});
var $pwInput = $(password).find('.cp-password-input');
@@ -165,6 +162,51 @@ define([
$d.append(password);
}
+ var parsed = Hash.parsePadUrl(data.href);
+ if (owned && parsed.hashData.type === 'pad') {
+ var sframeChan = common.getSframeChannel();
+ var changePwTitle = Messages.properties_changePassword;
+ var changePwConfirm = Messages.properties_confirmChange;
+ if (!hasPassword) {
+ changePwTitle = Messages.properties_addPassword;
+ changePwConfirm = Messages.properties_confirmNew;
+ }
+ $('', {'for': 'cp-app-prop-change-password'})
+ .text(changePwTitle).appendTo($d);
+ var newPassword = UI.passwordInput({
+ id: 'cp-app-prop-change-password',
+ style: 'flex: 1;'
+ });
+ var passwordOk = h('button', Messages.properties_changePasswordButton);
+ var changePass = h('span.cp-password-container', [
+ newPassword,
+ passwordOk
+ ]);
+ $(passwordOk).click(function () {
+ UI.confirm(changePwConfirm, function (yes) {
+ if (!yes) { return; }
+ sframeChan.query("Q_PAD_PASSWORD_CHANGE", {
+ href: data.href,
+ password: $(newPassword).val()
+ }, function (err, data) {
+ if (err || data.error) {
+ return void UI.alert(Messages.properties_passwordError);
+ }
+ UI.findOKButton().click();
+ if (data.warning) {
+ return void UI.alert(Messages.properties_passwordWarning, function () {
+ common.gotoURL(hasPassword ? undefined : data.href);
+ }, {force: true});
+ }
+ return void UI.alert(Messages.properties_passwordSuccess, function () {
+ common.gotoURL(hasPassword ? undefined : data.href);
+ }, {force: true});
+ });
+ });
+ });
+ $d.append(changePass);
+ }
+
cb(void 0, $d);
};
var getPadProperties = function (common, data, cb) {
@@ -626,7 +668,6 @@ define([
}
}
sframeChan.query('Q_SAVE_AS_TEMPLATE', {
- title: title,
toSave: toSave
}, function () {
UI.alert(Messages.templateSaved);
@@ -1035,52 +1076,6 @@ define([
// Avatars
- // Enable mediatags
- $(window.document).on('decryption', function (e) {
- var decrypted = e.originalEvent;
- if (decrypted.callback) {
- var cb = decrypted.callback;
- cb(function (mediaObject) {
- var root = mediaObject.element;
- if (!root) { return; }
-
- if (mediaObject.type === 'image') {
- $(root).data('blob', decrypted.blob);
- }
-
- if (mediaObject.type !== 'download') { return; }
-
- var metadata = decrypted.metadata;
-
- var title = '';
- var size = 0;
- if (metadata && metadata.name) {
- title = metadata.name;
- }
-
- if (decrypted.blob) {
- size = decrypted.blob.size;
- }
-
- var sizeMb = Util.bytesToMegabytes(size);
-
- var $btn = $(root).find('button');
- $btn.addClass('btn btn-success')
- .attr('type', 'download')
- .html(function () {
- var text = Messages.download_mt_button + ' ';
- if (title) {
- text += '' + Util.fixHTML(title) + ' ';
- }
- if (size) {
- text += '' + Messages._getKey('formattedMB', [sizeMb]) + ' ';
- }
- return text;
- });
- });
- }
- });
-
UIElements.displayMediatagImage = function (Common, $tag, cb) {
if (!$tag.length || !$tag.is('media-tag')) { return void cb('NOT_MEDIATAG'); }
var observer = new MutationObserver(function(mutations) {
@@ -1110,7 +1105,9 @@ define([
childList: true,
characterData: false
});
- MediaTag($tag[0]);
+ MediaTag($tag[0]).on('error', function (data) {
+ console.error(data);
+ });
};
var emoji_patt = /([\uD800-\uDBFF][\uDC00-\uDFFF])/;
diff --git a/www/common/cryptget.js b/www/common/cryptget.js
index 623f5946e..4afce7671 100644
--- a/www/common/cryptget.js
+++ b/www/common/cryptget.js
@@ -5,6 +5,7 @@ define([
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/outer/network-config.js',
+ '/bower_components/chainpad/chainpad.dist.js',
], function (Crypto, CPNetflux, Util, Hash, Realtime, NetConfig) {
var finish = function (S, err, doc) {
if (S.done) { return; }
diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js
index f91f050e8..2ea540626 100644
--- a/www/common/cryptpad-common.js
+++ b/www/common/cryptpad-common.js
@@ -7,25 +7,27 @@ define([
'/common/common-constants.js',
'/common/common-feedback.js',
'/common/outer/local-store.js',
- '/common/outer/store-rpc.js',
+ '/common/outer/worker-channel.js',
'/customize/application_config.js',
'/bower_components/nthen/index.js',
], function (Config, Messages, Util, Hash,
- Messaging, Constants, Feedback, LocalStore, AStore,
+ Messaging, Constants, Feedback, LocalStore, Channel,
AppConfig, Nthen) {
-
/* This file exposes functionality which is specific to Cryptpad, but not to
any particular pad type. This includes functions for committing metadata
about pads to your local storage for future use and improved usability.
Additionally, there is some basic functionality for import/export.
*/
- var postMessage = function (cmd, data, cb) {
- setTimeout(function () {
+ var urlArgs = Util.find(Config, ['requireConf', 'urlArgs']) || '';
+
+ var postMessage = function (/*cmd, data, cb*/) {
+ /*setTimeout(function () {
AStore.query(cmd, data, cb);
- });
+ });*/
+ console.error('NOT_READY');
};
var tryParsing = function (x) {
try { return JSON.parse(x); }
@@ -232,6 +234,12 @@ define([
});
};
+ common.writeLoginBlock = function (data, cb) {
+ postMessage('WRITE_LOGIN_BLOCK', data, function (obj) {
+ cb(obj);
+ });
+ };
+
// ANON RPC
// SFRAME: talk to anon_rpc from the iframe
@@ -446,6 +454,26 @@ define([
}).nThen(function () {
Crypt.get(parsed.hash, function (err, val) {
if (err) { throw new Error(err); }
+ try {
+ // Try to fix the title before importing the template
+ var parsed = JSON.parse(val);
+ var meta;
+ if (Array.isArray(parsed) && typeof(parsed[3]) === "object") {
+ meta = parsed[3].metadata; // pad
+ } else if (parsed.info) {
+ meta = parsed.info; // poll
+ } else {
+ meta = parsed.metadata;
+ }
+ if (typeof(meta) === "object") {
+ meta.defaultTitle = meta.title || meta.defaultTitle;
+ delete meta.users;
+ meta.title = "";
+ }
+ val = JSON.stringify(parsed);
+ } catch (e) {
+ console.log("Can't fix template title", e);
+ }
Crypt.put(parsed2.hash, val, cb, optsPut);
}, optsGet);
});
@@ -469,6 +497,13 @@ define([
if (typeof (data.title) !== "string") { return cb('Missing title'); }
if (data.title.trim() === "") { data.title = Hash.getDefaultName(parsed); }
+ if (common.initialPath) {
+ if (!data.path) {
+ data.path = common.initialPath;
+ delete common.initialPath;
+ }
+ }
+
postMessage("SET_PAD_TITLE", data, function (obj) {
if (obj && obj.error) {
console.log("unable to set pad title");
@@ -505,11 +540,17 @@ define([
// Network
common.onNetworkDisconnect = Util.mkEvent();
common.onNetworkReconnect = Util.mkEvent();
+ common.onNewVersionReconnect = Util.mkEvent();
// Messaging
var messaging = common.messaging = {};
messaging.onFriendRequest = Util.mkEvent();
messaging.onFriendComplete = Util.mkEvent();
+ messaging.addHandlers = function (href) {
+ postMessage("ADD_DIRECT_MESSAGE_HANDLERS", {
+ href: href
+ });
+ };
// Messenger
var messenger = common.messenger = {};
@@ -549,8 +590,11 @@ define([
// Pad RPC
var pad = common.padRpc = {};
- pad.joinPad = function (data, cb) {
- postMessage("JOIN_PAD", data, cb);
+ pad.joinPad = function (data) {
+ postMessage("JOIN_PAD", data);
+ };
+ pad.leavePad = function (data, cb) {
+ postMessage("LEAVE_PAD", data, cb);
};
pad.sendPadMsg = function (data, cb) {
postMessage("SEND_PAD_MSG", data, cb);
@@ -560,8 +604,94 @@ define([
pad.onJoinEvent = Util.mkEvent();
pad.onLeaveEvent = Util.mkEvent();
pad.onDisconnectEvent = Util.mkEvent();
+ pad.onConnectEvent = Util.mkEvent();
pad.onErrorEvent = Util.mkEvent();
+ common.changePadPassword = function (Crypt, href, newPassword, edPublic, cb) {
+ if (!href) { return void cb({ error: 'EINVAL_HREF' }); }
+ var parsed = Hash.parsePadUrl(href);
+ if (!parsed.hash) { return void cb({ error: 'EINVAL_HREF' }); }
+
+ var warning = false;
+ var newHash;
+ var oldChannel;
+ if (parsed.hashData.password) {
+ newHash = parsed.hash;
+ } else {
+ newHash = Hash.createRandomHash(parsed.type, newPassword);
+ }
+ var newHref = '/' + parsed.type + '/#' + newHash;
+
+ var optsGet = {};
+ var optsPut = {
+ password: newPassword
+ };
+ Nthen(function (waitFor) {
+ if (parsed.hashData && parsed.hashData.password) {
+ common.getPadAttribute('password', waitFor(function (err, password) {
+ optsGet.password = password;
+ }), href);
+ }
+ common.getPadAttribute('owners', waitFor(function (err, owners) {
+ if (!Array.isArray(owners) || owners.indexOf(edPublic) === -1) {
+ // We're not an owner, we shouldn't be able to change the password!
+ waitFor.abort();
+ return void cb({ error: 'EPERM' });
+ }
+ optsPut.owners = owners;
+ }), href);
+ common.getPadAttribute('expire', waitFor(function (err, expire) {
+ optsPut.expire = (expire - (+new Date())) / 1000; // Lifetime in seconds
+ }), href);
+ }).nThen(function (waitFor) {
+ Crypt.get(parsed.hash, waitFor(function (err, val) {
+ if (err) {
+ waitFor.abort();
+ return void cb({ error: err });
+ }
+ Crypt.put(newHash, val, waitFor(function (err) {
+ if (err) {
+ waitFor.abort();
+ return void cb({ error: err });
+ }
+ }), optsPut);
+ }), optsGet);
+ }).nThen(function (waitFor) {
+ var secret = Hash.getSecrets(parsed.type, parsed.hash, optsGet.password);
+ oldChannel = secret.channel;
+ pad.leavePad({
+ channel: oldChannel
+ }, waitFor());
+ pad.onDisconnectEvent.fire(true);
+ }).nThen(function (waitFor) {
+ common.removeOwnedChannel(oldChannel, waitFor(function (obj) {
+ if (obj && obj.error) {
+ waitFor.abort();
+ return void cb(obj);
+ }
+ }));
+ }).nThen(function (waitFor) {
+ common.setPadAttribute('password', newPassword, waitFor(function (err) {
+ if (err) { warning = true; }
+ }), href);
+ var secret = Hash.getSecrets(parsed.type, newHash, newPassword);
+ common.setPadAttribute('channel', secret.channel, waitFor(function (err) {
+ if (err) { warning = true; }
+ }), href);
+
+ if (parsed.hashData.password) { return; } // same hash
+ common.setPadAttribute('href', newHref, waitFor(function (err) {
+ if (err) { warning = true; }
+ }), href);
+ }).nThen(function () {
+ cb({
+ warning: warning,
+ hash: newHash,
+ href: newHref
+ });
+ });
+ };
+
// Loading events
common.loading = {};
common.loading.onDriveEvent = Util.mkEvent();
@@ -569,6 +699,9 @@ define([
common.getFullHistory = function (data, cb) {
postMessage("GET_FULL_HISTORY", data, cb);
};
+ common.getHistoryRange = function (data, cb) {
+ postMessage("GET_HISTORY_RANGE", data, cb);
+ };
common.getShareHashes = function (secret, cb) {
var hashes;
@@ -602,9 +735,10 @@ define([
};
var CRYPTPAD_VERSION = 'cryptpad-version';
- var updateLocalVersion = function () {
+ var currentVersion = localStorage[CRYPTPAD_VERSION];
+ var updateLocalVersion = function (newUrlArgs) {
// Check for CryptPad updates
- var urlArgs = Config.requireConf ? Config.requireConf.urlArgs : null;
+ var urlArgs = newUrlArgs || (Config.requireConf ? Config.requireConf.urlArgs : null);
if (!urlArgs) { return; }
var arr = /ver=([0-9.]+)(-[0-9]*)?/.exec(urlArgs);
var ver = arr[1];
@@ -612,14 +746,20 @@ define([
var verArr = ver.split('.');
verArr[2] = 0;
if (verArr.length !== 3) { return; }
- var stored = localStorage[CRYPTPAD_VERSION] || '0.0.0';
+ var stored = currentVersion || '0.0.0';
var storedArr = stored.split('.');
storedArr[2] = 0;
var shouldUpdate = parseInt(verArr[0]) > parseInt(storedArr[0]) ||
(parseInt(verArr[0]) === parseInt(storedArr[0]) &&
parseInt(verArr[1]) > parseInt(storedArr[1]));
if (!shouldUpdate) { return; }
+ currentVersion = ver;
localStorage[CRYPTPAD_VERSION] = ver;
+ if (newUrlArgs) {
+ // It's a reconnect
+ common.onNewVersionReconnect.fire();
+ }
+ return true;
};
var _onMetadataChanged = [];
@@ -641,100 +781,57 @@ define([
window.location.href = '/login/';
};
- common.startAccountDeletion = function (cb) {
+ common.startAccountDeletion = function (data, cb) {
// Logout other tabs
LocalStore.logout(null, true);
cb();
};
- var onMessage = function (cmd, data, cb) {
- cb = cb || function () {};
- switch (cmd) {
- case 'REQUEST_LOGIN': {
- requestLogin();
- break;
- }
- case 'UPDATE_METADATA': {
- common.changeMetadata();
- break;
- }
- case 'UPDATE_TOKEN': {
- var localToken = tryParsing(localStorage.getItem(Constants.tokenKey));
- if (localToken !== data.token) { requestLogin(); }
- break;
- }
- case 'Q_FRIEND_REQUEST': {
- common.messaging.onFriendRequest.fire(data, cb);
- break;
- }
- case 'EV_FRIEND_COMPLETE': {
- common.messaging.onFriendComplete.fire(data);
- break;
- }
- // Network
- case 'NETWORK_DISCONNECT': {
- common.onNetworkDisconnect.fire(); break;
- }
- case 'NETWORK_RECONNECT': {
- common.onNetworkReconnect.fire(data); break;
- }
- // Messenger
- case 'CONTACTS_MESSAGE': {
- common.messenger.onMessageEvent.fire(data); break;
- }
- case 'CONTACTS_JOIN': {
- common.messenger.onJoinEvent.fire(data); break;
- }
- case 'CONTACTS_LEAVE': {
- common.messenger.onLeaveEvent.fire(data); break;
- }
- case 'CONTACTS_UPDATE': {
- common.messenger.onUpdateEvent.fire(data); break;
- }
- case 'CONTACTS_FRIEND': {
- common.messenger.onFriendEvent.fire(data); break;
- }
- case 'CONTACTS_UNFRIEND': {
- common.messenger.onUnfriendEvent.fire(data); break;
- }
- // Pad
- case 'PAD_READY': {
- common.padRpc.onReadyEvent.fire(); break;
- }
- case 'PAD_MESSAGE': {
- common.padRpc.onMessageEvent.fire(data); break;
- }
- case 'PAD_JOIN': {
- common.padRpc.onJoinEvent.fire(data); break;
- }
- case 'PAD_LEAVE': {
- common.padRpc.onLeaveEvent.fire(data); break;
- }
- case 'PAD_DISCONNECT': {
- common.padRpc.onDisconnectEvent.fire(data); break;
- }
- case 'PAD_ERROR': {
- common.padRpc.onErrorEvent.fire(data); break;
- }
- // Drive
- case 'DRIVE_LOG': {
- common.drive.onLog.fire(data); break;
- }
- case 'DRIVE_CHANGE': {
- common.drive.onChange.fire(data); break;
- }
- case 'DRIVE_REMOVE': {
- common.drive.onRemove.fire(data); break;
- }
- // Account deletion
- case 'DELETE_ACCOUNT': {
- common.startAccountDeletion(cb); break;
- }
- // Loading
- case 'LOADING_DRIVE': {
- common.loading.onDriveEvent.fire(data); break;
- }
- }
+ var queries = {
+ REQUEST_LOGIN: requestLogin,
+ UPDATE_METADATA: common.changeMetadata,
+ UPDATE_TOKEN: function (data) {
+ var localToken = tryParsing(localStorage.getItem(Constants.tokenKey));
+ if (localToken !== data.token) { requestLogin(); }
+ },
+ // Messaging
+ Q_FRIEND_REQUEST: common.messaging.onFriendRequest.fire,
+ EV_FIREND_COMPLETE: common.messaging.onFriendComplete.fire,
+ // Network
+ NETWORK_DISCONNECT: common.onNetworkDisconnect.fire,
+ NETWORK_RECONNECT: function (data) {
+ require(['/api/config?' + (+new Date())], function (NewConfig) {
+ var update = updateLocalVersion(NewConfig.requireConf && NewConfig.requireConf.urlArgs);
+ if (update) {
+ postMessage('DISCONNECT');
+ return;
+ }
+ common.onNetworkReconnect.fire(data);
+ });
+ },
+ // Messenger
+ CONTACTS_MESSAGE: common.messenger.onMessageEvent.fire,
+ CONTACTS_JOIN: common.messenger.onJoinEvent.fire,
+ CONTACTS_LEAVE: common.messenger.onLeaveEvent.fire,
+ CONTACTS_UPDATE: common.messenger.onUpdateEvent.fire,
+ CONTACTS_FRIEND: common.messenger.onFriendEvent.fire,
+ CONTACTS_UNFRIEND: common.messenger.onUnfriendEvent.fire,
+ // Pad
+ PAD_READY: common.padRpc.onReadyEvent.fire,
+ PAD_MESSAGE: common.padRpc.onMessageEvent.fire,
+ PAD_JOIN: common.padRpc.onJoinEvent.fire,
+ PAD_LEAVE: common.padRpc.onLeaveEvent.fire,
+ PAD_DISCONNECT: common.padRpc.onDisconnectEvent.fire,
+ PAD_CONNECT: common.padRpc.onConnectEvent.fire,
+ PAD_ERROR: common.padRpc.onErrorEvent.fire,
+ // Drive
+ DRIVE_LOG: common.drive.onLog.fire,
+ DRIVE_CHANGE: common.drive.onChange.fire,
+ DRIVE_REMOVE: common.drive.onRemove.fire,
+ // Account deletion
+ DELETE_ACCOUNT: common.startAccountDeletion,
+ // Loading
+ LOADING_DRIVE: common.loading.onDriveEvent.fire
};
common.ready = (function () {
@@ -792,43 +889,165 @@ define([
}
}).nThen(function (waitFor) {
var cfg = {
- query: onMessage, // TODO temporary, will be replaced by a webworker channel
+ init: true,
+ //query: onMessage, // TODO temporary, will be replaced by a webworker channel
userHash: LocalStore.getUserHash(),
anonHash: LocalStore.getFSHash(),
localToken: tryParsing(localStorage.getItem(Constants.tokenKey)),
language: common.getLanguage(),
- messenger: rdyCfg.messenger,
- driveEvents: rdyCfg.driveEvents
+ messenger: rdyCfg.messenger, // Boolean
+ driveEvents: rdyCfg.driveEvents // Boolean
};
if (sessionStorage[Constants.newPadPathKey]) {
- cfg.initialPath = sessionStorage[Constants.newPadPathKey];
+ common.initialPath = sessionStorage[Constants.newPadPathKey];
delete sessionStorage[Constants.newPadPathKey];
}
- AStore.query("CONNECT", cfg, waitFor(function (data) {
- if (data.error) { throw new Error(data.error); }
- if (data.state === 'ALREADY_INIT') {
- data = data.returned;
- }
-
- if (data.anonHash && !cfg.userHash) { LocalStore.setFSHash(data.anonHash); }
-
- /*if (cfg.userHash && sessionStorage) {
- // copy User_hash into sessionStorage because cross-domain iframes
- // on safari replaces localStorage with sessionStorage or something
- sessionStorage.setItem(Constants.userHashKey, cfg.userHash);
- }*/
- if (cfg.userHash) {
- var localToken = tryParsing(localStorage.getItem(Constants.tokenKey));
- if (localToken === null) {
- // if that number hasn't been set to localStorage, do so.
- localStorage.setItem(Constants.tokenKey, data[Constants.tokenKey]);
- }
+ var channelIsReady = waitFor();
+
+ var msgEv = Util.mkEvent();
+ var postMsg, worker;
+ Nthen(function (waitFor2) {
+ if (typeof(SharedWorker) !== "undefined") {
+ worker = new SharedWorker('/common/outer/sharedworker.js?' + urlArgs);
+ worker.onerror = function (e) {
+ console.error(e);
+ };
+ worker.port.onmessage = function (ev) {
+ if (ev.data === "SW_READY") {
+ return;
+ }
+ msgEv.fire(ev);
+ };
+ postMsg = function (data) {
+ worker.port.postMessage(data);
+ };
+ postMsg('INIT');
+
+ window.addEventListener('beforeunload', function () {
+ postMsg('CLOSE');
+ });
+ } else if (false && 'serviceWorker' in navigator) {
+ var initializing = true;
+ var stopWaiting = waitFor2(); // Call this function when we're ready
+
+ postMsg = function (data) {
+ if (worker) { return void worker.postMessage(data); }
+ };
+
+ navigator.serviceWorker.register('/common/outer/serviceworker.js?' + urlArgs, {scope: '/'})
+ .then(function(reg) {
+ // Add handler for receiving messages from the service worker
+ navigator.serviceWorker.addEventListener('message', function (ev) {
+ if (initializing && ev.data === "SW_READY") {
+ initializing = false;
+ } else {
+ msgEv.fire(ev);
+ }
+ });
+
+ // Initialize the worker
+ // If it is active (probably running in another tab), just post INIT
+ if (reg.active) {
+ worker = reg.active;
+ postMsg("INIT");
+ }
+ // If it was not active, wait for the "activated" state and post INIT
+ reg.onupdatefound = function () {
+ if (initializing) {
+ var w = reg.installing;
+ var onStateChange = function () {
+ if (w.state === "activated") {
+ worker = w;
+ postMsg("INIT");
+ w.removeEventListener("statechange", onStateChange);
+ }
+ };
+ w.addEventListener('statechange', onStateChange);
+ return;
+ }
+ // New version detected (from another tab): kill?
+ console.error('New version detected: ABORT?');
+ };
+ return void stopWaiting();
+ }).catch(function(error) {
+ /**/console.log('Registration failed with ' + error);
+ });
+
+ window.addEventListener('beforeunload', function () {
+ postMsg('CLOSE');
+ });
+ } else if (Worker) {
+ worker = new Worker('/common/outer/webworker.js?' + urlArgs);
+ worker.onmessage = function (ev) {
+ msgEv.fire(ev);
+ };
+ postMsg = function (data) {
+ worker.postMessage(data);
+ };
+ } else {
+ require(['/common/outer/noworker.js'], waitFor2(function (NoWorker) {
+ NoWorker.onMessage(function (data) {
+ msgEv.fire({data: data});
+ });
+ postMsg = function (d) { setTimeout(function () { NoWorker.query(d); }); };
+ NoWorker.create();
+ }));
}
+ }).nThen(function () {
+ Channel.create(msgEv, postMsg, function (chan) {
+ console.log('Outer ready');
+ Object.keys(queries).forEach(function (q) {
+ chan.on(q, function (data, cb) {
+ try {
+ queries[q](data, cb);
+ } catch (e) {
+ console.error("Error in outer when executing query " + q);
+ console.error(e);
+ console.log(data);
+ }
+ });
+ });
+
+ postMessage = function (cmd, data, cb) {
+ cb = cb || function () {};
+ chan.query(cmd, data, function (err, data) {
+ if (err) { return void cb ({error: err}); }
+ cb(data);
+ });
+ };
+
+ console.log('Posting CONNECT');
+ postMessage('CONNECT', cfg, function (data) {
+ if (data.error) { throw new Error(data.error); }
+ if (data.state === 'ALREADY_INIT') {
+ data = data.returned;
+ }
+
+ if (data.anonHash && !cfg.userHash) { LocalStore.setFSHash(data.anonHash); }
+
+ /*if (cfg.userHash && sessionStorage) {
+ // copy User_hash into sessionStorage because cross-domain iframes
+ // on safari replaces localStorage with sessionStorage or something
+ sessionStorage.setItem(Constants.userHashKey, cfg.userHash);
+ }*/
+
+ if (cfg.userHash) {
+ var localToken = tryParsing(localStorage.getItem(Constants.tokenKey));
+ if (localToken === null) {
+ // if that number hasn't been set to localStorage, do so.
+ localStorage.setItem(Constants.tokenKey, data[Constants.tokenKey]);
+ }
+ }
+
+ initFeedback(data.feedback);
+ initialized = true;
+ channelIsReady();
+ });
+
+ }, false);
+ });
- initFeedback(data.feedback);
- initialized = true;
- }));
}).nThen(function (waitFor) {
// Load the new pad when the hash has changed
var oldHref = document.location.href;
@@ -857,9 +1076,12 @@ define([
document.location.reload();
} else if (o && !n) {
LocalStore.logout();
- postMessage("DISCONNECT");
}
});
+ LocalStore.onLogout(function () {
+ console.log('onLogout: disconnect');
+ postMessage("DISCONNECT");
+ });
if (PINNING_ENABLED && LocalStore.isLoggedIn()) {
console.log("logged in. pads will be pinned");
diff --git a/www/common/media-tag.js b/www/common/media-tag.js
index acdee91da..5b911f2b8 100644
--- a/www/common/media-tag.js
+++ b/www/common/media-tag.js
@@ -1 +1,434 @@
-!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.MediaTag=t():e.MediaTag=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=63)}([function(e,t,n){"use strict";var r={IMAGE:"image",AUDIO:"audio",VIDEO:"video",PDF:"pdf",DASH:"dash",DOWNLOAD:"download",CRYPTO:"crypto",CLEAR_KEY:"clear-key",MEDIA_OBJECT:"media-object"};e.exports=r},function(e,t,n){"use strict";var r={MATCHER:"matcher",RENDERER:"renderer",FILTER:"filter",SANITIZER:"sanitizer"};e.exports=r},function(e,t,n){"use strict";function r(e){if(e instanceof Array){var t=[];return e.forEach(function(e){if(e.mediaObject)t.push(e.mediaObject);else{new c(e,r.processingEngine).mediaObjects.forEach(function(e){t.push(r.processingEngine.start(e))})}}),t}new c(e,r.processingEngine).mediaObjects.forEach(function(e){r.processingEngine.start(e)})}var o=n(31),i=n(33),u=n(34),a=n(35),c=n(36);r.pluginStore=r.pluginStore||new u,r.uriStore=r.uriStore||new a("../plugins"),r.processingEngine=r.processingEngine||new o(r.pluginStore),r.matchingEngine=r.matchingEngine||new i(r.pluginStore,r.uriStore),r.loadingEngine=null,e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n=0&&f.mediaTypes.splice(t,1)},f.removeAllAllowedMediaTypes=function(e){e.forEach(function(e){f.removeAllowedMediaType(e)})},f.isAllowedMediaType=function(e){return f.mediaTypes.some(function(t){return t===e})},e.exports=f},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n0}}]),e}();e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u={PluginExists:function(e){function t(e){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,'Plugin with same "'+e.identifier+'" identifier found.'))}return i(t,e),t}(Error),TypeNotFound:function(e){function t(){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"Media Tag could not find the content type of an instance.}."))}return i(t,e),t}(Error),FilterExists:function(e){function t(e){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,'Filter with same "'+e.identifier+' identifier found."'))}return i(t,e),t}(Error),FetchFail:function(e){function t(e){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,'Could not fetch "'+e.url+'", received "'+e.status+": "+e.statusText+'".'))}return i(t,e),t}(Error),InvalidCryptoKey:function(e){function t(){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"Invalid cryptographic key."))}return i(t,e),t}(Error),InvalidCryptoLib:function(e){function t(){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"Invalid cryptographic algorithm name."))}return i(t,e),t}(Error),FailedCrypto:function(e){function t(e){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"Failed to decrypt file"+(e&&e.message?" "+e.message:"")+"."))}return i(t,e),t}(Error)};e.exports=u},function(e,t,n){"use strict";var r=n(21),o=n(43),i=n(44),u=n(45),a=n(46),c=n(47),s=n(48),f=n(7),l=n(49),p=n(50);r.pluginStore.store(new o),r.pluginStore.store(new i),r.pluginStore.store(new u),r.pluginStore.store(new a),r.pluginStore.store(new c),r.pluginStore.store(new s),r.pluginStore.store(new f),r.pluginStore.store(new l),r.pluginStore.store(new p);var y=n(51),h=n(52);f.functionStore.store("salsa20poly1305",y),f.functionStore.store("cryptpad",h);var b=new s(" MediaTag cannot find a plugin able to renderer your content
","Download");r.processingEngine.setDefaultPlugin(b),r.CryptoFilter=f;var d=["image/png","image/jpeg","image/jpg","image/gif","audio/mp3","audio/ogg","audio/wav","audio/webm","video/mp4","video/ogg","video/webm","application/pdf","application/dash+xml","download"];r.CryptoFilter.setAllowedMediaTypes(d);var v=n(53),g=(n(14),n(0),new v);r.PdfPlugin=a,r.PdfPlugin.viewer="/pdfjs/web/viewer.html",r.processingEngine.configure(g),e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=n(1),a=n(5),c=n(6),s=function(e){function t(e){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,u.SANITIZER,a.EVERY))}return i(t,e),t}(c);e.exports=s},function(e,t,n){"use strict";var r=n(22),o=n(23),i=n(24),u=n(25),a=n(26),c=n(27),s=n(28),f=n(29),l=n(30),p=n(2);p.pluginStore.store(new r),p.pluginStore.store(new o),p.pluginStore.store(new i),p.pluginStore.store(new u),p.pluginStore.store(new a),p.pluginStore.store(new c),p.pluginStore.store(new s),p.pluginStore.store(new f),p.pluginStore.store(new l),e.exports=p},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n=e.STACK_LIMIT)throw console.error(this.snapshots[n]),new Error("Plugin stack size exceed");if(this.snapshots[n].length>=e.SNAPSHOT_LIMIT)throw console.error(this.snapshots[n]),new Error("Plugin snapshots size exceed");var r=0;if(this.stacks[n].forEach(function(e){e.type===a.RENDERER&&r++}),r>1)throw console.error(this.snapshots[n]),new Error("More of one renderer in the stack");if(0===this.stacks[n].length&&!this.stats[n][a.RENDERER]){if(!this.defaultPlugin)throw new Error("No default plugin assignated");this.stacks[n].unshift(this.defaultPlugin)}}},{key:"return",value:function(e){var t=e.getId(),n=this.unstack(e);if(n){try{this.stats[t]||(this.stats[t]={}),this.stats[t][n.type]?this.stats[t][n.type]+=1:this.stats[t][n.type]=1}catch(e){console.error(e,this.snapshots[t])}0===this.stacks[t].length&&n.type===a.RENDERER?this.run(e):n.type!==a.SANITIZER&&this.fill(e),this.snapshot(e),this.check(e),this.run(e)}}},{key:"process",value:function(e){var t=e.getId(),n=this.stacks[t].length,r=this.stacks[t][n-1];if(!r)throw console.log(this.stacks),new Error("Impossible to run a undefined plugin");r.process(e)}},{key:"isStacked",value:function(e,t){var n=e.getId();return!(!this.stacks[n]||!this.stacks[n].includes(t))}},{key:"setDefaultPlugin",value:function(e){this.defaultPlugin=e}}]),e}();f.STACK_LIMIT=100,f.SNAPSHOT_LIMIT=100,e.exports=f},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n0&&r(e.mediaObjects[t-1],e)}},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n1?t[0]:window.location.protocol}},{key:"hostname",value:function(e){var t=e.getAttribute("src").split("://");return t.length>1?t[1].split("/")[0]:window.location.hostname}},{key:"source",value:function(e){return e.getAttribute("src")}},{key:"schemes",value:function(e){return/\w+:/.exec(e.getAttribute("src"))}},{key:"sources",value:function(e){var t=e.getAttribute("sources")||e.getAttribute("srcs");return t?JSON.parse(t):null}},{key:"actions",value:function(e){var t=e.getAttribute("actions");return t?JSON.parse(t):null}},{key:"parse",value:function(t){return{protocol:e.protocol(t),hostname:e.hostname(t),src:e.source(t),type:e.type(t),extension:e.extension(t),mime:e.mime(t),sources:e.sources(t),actions:e.actions(t)}}}]),e}();e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n1;){if("number"!=typeof e[t])throw new Error("E_UNSAFE_TYPE");if(e[t]>255)throw new Error("E_OUT_OF_BOUNDS");if(255!==e[t])return void e[t]++;if(e[t]=0,0===t)throw new Error("E_NONCE_TOO_LARGE")}}},{key:"encodePrefix",value:function(e){return[65280,255].map(function(t,n){return(e&t)>>8*(1-n)})}},{key:"decodePrefix",value:function(e){return e[0]<<8|e[1]}},{key:"joinChunks",value:function(e){return new Blob(e)}},{key:"slice",value:function(e){return Array.prototype.slice.call(e)}},{key:"getRandomKeyStr",value:function(){var e=window.nacl,t=e.randomBytes(18);return e.util.encodeBase64(t)}},{key:"getKeyFromStr",value:function(e){return window.nacl.util.decodeBase64(e)}},{key:"encrypt",value:function(){}},{key:"decrypt",value:function(t,n,r){var o=window.nacl,i=function(e){var n=new Event("decryptionProgress");n.percent=e/t.length*100,window.document.dispatchEvent(n)},u=e.createNonce(),a=0,c=t.subarray(0,2),s=e.decodePrefix(c),f={metadata:void 0},l=new Uint8Array(t.subarray(2,2+s)),p=o.secretbox.open(l,u,n);e.increment(u);try{f.metadata=JSON.parse(o.util.encodeUTF8(p))}catch(e){return r("E_METADATA_DECRYPTION")}if(!f.metadata)return r("NO_METADATA");var y=function(r){setTimeout(function(){var c=131088*a+2+s,f=c+131088;a++;var l=new Uint8Array(t.subarray(c,f)),p=o.secretbox.open(l,u,n);if(e.increment(u),!p)return void r("DECRYPTION_FAILURE");i(Math.min(f,t.length)),r(void 0,p)})},h=[];!function n(){y(function(o,i){return o?setTimeout(function(){r(o)}):i?2+s+131088*a<=t.length?(h.push(i),n()):(h.push(i),f.content=e.joinChunks(h),r(void 0,f)):void r("UNEXPECTED_ENDING")})}()}}]),e}(),l=function(){function e(){r(this,e)}return u(e,null,[{key:"getArrayBuffer",value:function(e){return fetch(e).then(function(e){if(e.ok)return e.arrayBuffer();throw new a.FetchFails}).then(function(e){return e})}},{key:"createUrl",value:function(e){return window.URL.createObjectURL(e)}},{key:"getBlobUrl",value:function(e,t){return window.URL.createObjectURL(new Blob([e],{type:t}))}},{key:"getDataUrl",value:function(e,t){return"data:"+t+";base64,"+window.nacl.util.encodeBase64(e)}}]),e}();e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n&"']/g, function (x) {
+ return ({ "<": "<", ">": ">", "&": "&", '"': """, "'": "'" })[x];
+ });
+ };
+
+
+ // Default config, can be overriden per media-tag call
+ var config = {
+ allowed: [
+ 'image/png',
+ 'image/jpeg',
+ 'image/jpg',
+ 'image/gif',
+ 'audio/mp3',
+ 'audio/ogg',
+ 'audio/wav',
+ 'audio/webm',
+ 'video/mp4',
+ 'video/ogg',
+ 'video/webm',
+ 'application/pdf',
+ //'application/dash+xml', // FIXME?
+ 'download'
+ ],
+ pdf: {},
+ download: {
+ text: "Download"
+ },
+ Plugins: {
+ image: function (metadata, url, content, cfg, cb) {
+ var img = document.createElement('img');
+ img.setAttribute('src', url);
+ img.blob = content;
+ cb(void 0, img);
+ },
+ video: function (metadata, url, content, cfg, cb) {
+ var video = document.createElement('video');
+ video.setAttribute('src', url);
+ video.setAttribute('controls', true);
+ cb(void 0, video);
+ },
+ audio: function (metadata, url, content, cfg, cb) {
+ var audio = document.createElement('audio');
+ audio.setAttribute('src', url);
+ audio.setAttribute('controls', true);
+ cb(void 0, audio);
+ },
+ pdf: function (metadata, url, content, cfg, cb) {
+ var iframe = document.createElement('iframe');
+ if (cfg.pdf.viewer) { // PDFJS
+ var viewerUrl = cfg.pdf.viewer + '?file=' + url;
+ iframe.src = viewerUrl + '#' + window.encodeURIComponent(metadata.name);
+ return void cb (void 0, iframe);
+ }
+ iframe.src = url + '#' + window.encodeURIComponent(metadata.name);
+ return void cb (void 0, iframe);
+ },
+ download: function (metadata, url, content, cfg, cb) {
+ var btn = document.createElement('button');
+ btn.setAttribute('class', 'btn btn-success');
+ btn.innerHTML = cfg.download.text + ' ' +
+ (metadata.name ? '' + fixHTML(metadata.name) + ' ' : '');
+ btn.addEventListener('click', function () {
+ saveFile(content, url, metadata.name);
+ });
+ cb(void 0, btn);
+ }
+ }
+ };
+
+
+ // Download a blob from href
+ var download = function (src, _cb) {
+ var cb = function (e, res) {
+ _cb(e, res);
+ cb = function () {};
+ };
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', src, true);
+ xhr.responseType = 'arraybuffer';
+
+ xhr.onerror = function () { return void cb("XHR_ERROR"); };
+ xhr.onload = function () {
+ // Error?
+ if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); }
+
+ var arrayBuffer = xhr.response;
+ if (arrayBuffer) { cb(null, new Uint8Array(arrayBuffer)); }
+ };
+
+ xhr.send(null);
+ };
+
+ // Decryption tools
+ var Decrypt = {
+ // Create a nonce
+ createNonce: function () {
+ var n = new Uint8Array(24);
+ for (var i = 0; i < 24; i++) { n[i] = 0; }
+ return n;
+ },
+
+ // Increment a nonce
+ increment: function (N) {
+ var l = N.length;
+ while (l-- > 1) {
+ /* .jshint probably suspects this is unsafe because we lack types
+ but as long as this is only used on nonces, it should be safe */
+ if (N[l] !== 255) { return void N[l]++; } // jshint ignore:line
+
+ // you don't need to worry about this running out.
+ // you'd need a REAAAALLY big file
+ if (l === 0) { throw new Error('E_NONCE_TOO_LARGE'); }
+
+ N[l] = 0;
+ }
+ },
+
+ decodePrefix: function (A) {
+ return (A[0] << 8) | A[1];
+ },
+ joinChunks: function (chunks) {
+ return new Blob(chunks);
+ },
+
+ // Convert a Uint8Array into Array.
+ slice: function (u8) {
+ return Array.prototype.slice.call(u8);
+ },
+
+ // Gets the key from the key string.
+ getKeyFromStr: function (str) {
+ return window.nacl.util.decodeBase64(str);
+ }
+ };
+
+ // Decrypts a Uint8Array with the given key.
+ var decrypt = function (u8, strKey, done, progressCb) {
+ var Nacl = window.nacl;
+
+ var progress = function (offset) {
+ progressCb((offset / u8.length) * 100);
+ };
+
+ var key = Decrypt.getKeyFromStr(strKey);
+ var nonce = Decrypt.createNonce();
+ var i = 0;
+ var prefix = u8.subarray(0, 2);
+ var metadataLength = Decrypt.decodePrefix(prefix);
+
+ var res = { metadata: undefined };
+
+ // Get metadata
+ var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
+ var metaChunk = Nacl.secretbox.open(metaBox, nonce, key);
+
+ Decrypt.increment(nonce);
+
+ try { res.metadata = JSON.parse(Nacl.util.encodeUTF8(metaChunk)); }
+ catch (e) { return void done('E_METADATA_DECRYPTION'); }
+
+ if (!res.metadata) { return void done('NO_METADATA'); }
+
+ var takeChunk = function (cb) {
+ setTimeout(function () {
+ var start = i * cypherChunkLength + 2 + metadataLength;
+ var end = start + cypherChunkLength;
+ i++;
+
+ // Get the chunk
+ var box = new Uint8Array(u8.subarray(start, end));
+
+ // Decrypt the chunk
+ var plaintext = Nacl.secretbox.open(box, nonce, key);
+ Decrypt.increment(nonce);
+
+ if (!plaintext) { return void cb('DECRYPTION_FAILURE'); }
+
+ progress(Math.min(end, u8.length));
+
+ cb(void 0, plaintext);
+ });
+ };
+
+ var chunks = [];
+
+ // decrypt file contents
+ var again = function () {
+ takeChunk(function (e, plaintext) {
+ if (e) { return setTimeout(function () { done(e); }); }
+
+ if (plaintext) {
+ if ((i * cypherChunkLength + 2 + metadataLength) < u8.length) { // not done
+ chunks.push(plaintext);
+ return again();
+ }
+
+ chunks.push(plaintext);
+ res.content = Decrypt.joinChunks(chunks);
+ return void done(void 0, res);
+ }
+ done('UNEXPECTED_ENDING');
+ });
+ };
+ again();
+ };
+
+ // Get type
+ var getType = function (mediaObject, metadata, cfg) {
+ var mime = metadata.type;
+ var s = metadata.type.split('/');
+ var type = s[0];
+ var extension = s[1];
+
+ mediaObject.name = metadata.name;
+ if (mime && cfg.allowed.indexOf(mime) !== -1) {
+ mediaObject.type = type;
+ mediaObject.extension = extension;
+ mediaObject.mime = mime;
+ return type;
+ } else if (cfg.allowed.indexOf('download') !== -1) {
+ mediaObject.type = type;
+ mediaObject.extension = extension;
+ mediaObject.mime = mime;
+ return 'download';
+ } else {
+ return;
+ }
+ };
+
+ // Copy attributes
+ var copyAttributes = function (origin, dest) {
+ Object.keys(origin.attributes).forEach(function (i) {
+ if (!/^data-attr/.test(origin.attributes[i].name)) { return; }
+ var name = origin.attributes[i].name.slice(10);
+ var value = origin.attributes[i].value;
+ dest.setAttribute(name, value);
+ });
+ };
+
+ // Process
+ var process = function (mediaObject, decrypted, cfg, cb) {
+ var metadata = decrypted.metadata;
+ var blob = decrypted.content;
+
+ var mediaType = getType(mediaObject, metadata, cfg);
+
+ if (mediaType === 'application') {
+ mediaType = mediaObject.extension;
+ }
+
+ if (!mediaType || !cfg.Plugins[mediaType]) {
+ return void cb('NO_PLUGIN_FOUND');
+ }
+
+ // Get blob URL
+ var url = decrypted.url;
+ if (!url && window.URL) {
+ url = decrypted.url = window.URL.createObjectURL(new Blob([blob], {
+ type: metadata.type
+ }));
+ }
+
+ cfg.Plugins[mediaType](metadata, url, blob, cfg, function (err, el) {
+ if (err || !el) { return void cb(err || 'ERR_MEDIATAG_DISPLAY'); }
+ copyAttributes(mediaObject.tag, el);
+ mediaObject.tag.innerHTML = '';
+ mediaObject.tag.appendChild(el);
+ cb();
+ });
+ };
+
+ var addMissingConfig = function (base, target) {
+ Object.keys(target).forEach(function (k) {
+ if (!target[k]) { return; }
+ // Target is an object, fix it recursively
+ if (typeof target[k] === "object" && !Array.isArray(target[k])) {
+ // Sub-object
+ if (base[k] && (typeof base[k] !== "object" || Array.isArray(base[k]))) { return; }
+ else if (base[k]) { addMissingConfig(base[k], target[k]); }
+ else {
+ base[k] = {};
+ addMissingConfig(base[k], target[k]);
+ }
+ }
+ // Target is array or immutable, copy the value if it's missing
+ if (!base[k]) {
+ base[k] = Array.isArray(target[k]) ? JSON.parse(JSON.stringify(target[k]))
+ : target[k];
+ }
+ });
+ };
+
+ // Initialize a media-tag
+ var init = function (el, cfg) {
+ cfg = cfg || {};
+
+ addMissingConfig(cfg, config);
+
+ // Handle jQuery elements
+ if (typeof(el) === "object" && el.jQuery) { el = el[0]; }
+
+ // Abort smoothly if the element is not a media-tag
+ if (!el || el.nodeName !== "MEDIA-TAG") {
+ console.error("Not a media-tag!");
+ return {
+ on: function () { return this; }
+ };
+ }
+
+ var handlers = cfg.handlers || {
+ 'progress': [],
+ 'complete': [],
+ 'error': []
+ };
+
+ var mediaObject = el._mediaObject = {
+ handlers: handlers,
+ tag: el
+ };
+
+ var emit = function (ev, data) {
+ // Check if the event name is valid
+ if (Object.keys(handlers).indexOf(ev) === -1) {
+ return void console.error("Invalid mediatag event");
+ }
+
+ // Call the handlers
+ handlers[ev].forEach(function (h) {
+ // Make sure a bad handler won't break the media-tag script
+ try {
+ h(data);
+ } catch (err) {
+ console.error(err);
+ }
+ });
+ };
+
+ mediaObject.on = function (ev, handler) {
+ // Check if the event name is valid
+ if (Object.keys(handlers).indexOf(ev) === -1) {
+ console.error("Invalid mediatag event");
+ return mediaObject;
+ }
+ // Check if the handler is valid
+ if (typeof (handler) !== "function") {
+ console.error("Handler is not a function!");
+ return mediaObject;
+ }
+ // Add the handler
+ handlers[ev].push(handler);
+ return mediaObject;
+ };
+
+ var src = el.getAttribute('src');
+ var strKey = el.getAttribute('data-crypto-key');
+ if (/^cryptpad:/.test(strKey)) {
+ strKey = strKey.slice(9);
+ }
+ var uid = [src, strKey].join('');
+
+ // End media-tag rendering: display the tag and emit the event
+ var end = function (decrypted) {
+ process(mediaObject, decrypted, cfg, function (err) {
+ if (err) { return void emit('error', err); }
+ emit('complete', decrypted);
+ });
+ };
+
+ // If we have the blob in our cache, don't download & decrypt it again, just display
+ if (cache[uid]) {
+ end(cache[uid]);
+ return mediaObject;
+ }
+
+ // Download the encrypted blob
+ download(src, function (err, u8Encrypted) {
+ if (err) {
+ return void emit('error', err);
+ }
+ // Decrypt the blob
+ decrypt(u8Encrypted, strKey, function (errDecryption, u8Decrypted) {
+ if (errDecryption) {
+ return void emit('error', errDecryption);
+ }
+ // Cache and display the decrypted blob
+ cache[uid] = u8Decrypted;
+ end(u8Decrypted);
+ }, function (progress) {
+ emit('progress', {
+ progress: progress
+ });
+ });
+ });
+
+ return mediaObject;
+ };
+
+ // Add the cache as a property of MediaTag
+ cache = init.__Cryptpad_Cache = {};
+
+ init.setDefaultConfig = function (key, value) {
+ config[key] = value;
+ };
+
+ return init;
+}));
diff --git a/www/common/mergeDrive.js b/www/common/mergeDrive.js
index f491643f9..ea4c6edc7 100644
--- a/www/common/mergeDrive.js
+++ b/www/common/mergeDrive.js
@@ -109,7 +109,7 @@ define([
pinPads: function () {} // without pinPads /outer/userObject.js won't be loaded
});
var onMigrated = function () {
- oldFo.fixFiles();
+ oldFo.fixFiles(true);
var newFo = proxyData.userObject;
var oldRecentPads = parsed.drive[newFo.FILES_DATA];
var newRecentPads = proxy.drive[newFo.FILES_DATA];
diff --git a/www/common/metadata-manager.js b/www/common/metadata-manager.js
index fed4183d3..1426bc7cf 100644
--- a/www/common/metadata-manager.js
+++ b/www/common/metadata-manager.js
@@ -64,7 +64,9 @@ define(['json.sortify'], function (Sortify) {
if (metadataObj.title !== rememberedTitle) {
rememberedTitle = metadataObj.title;
- titleChangeHandlers.forEach(function (f) { f(metadataObj.title); });
+ titleChangeHandlers.forEach(function (f) {
+ f(metadataObj.title, metadataObj.defaultTitle);
+ });
}
changeHandlers.forEach(function (f) { f(); });
diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js
index 680712f17..53b84f395 100644
--- a/www/common/outer/async-store.js
+++ b/www/common/outer/async-store.js
@@ -23,1276 +23,1517 @@ define([
Crypto, ChainPad, Listmap, nThen, Saferphore) {
var Store = {};
- var postMessage = function () {};
+ var create = function () {
+ var postMessage = function () {};
+ var broadcast = function () {};
+ var sendDriveEvent = function () {};
- var storeHash;
+ var storeHash;
- var store = window.CryptPad_AsyncStore = {};
+ var store = window.CryptPad_AsyncStore = {};
- var onSync = function (cb) {
- Realtime.whenRealtimeSyncs(store.realtime, cb);
- };
+ var onSync = function (cb) {
+ Realtime.whenRealtimeSyncs(store.realtime, cb);
+ };
- Store.get = function (key, cb) {
- cb(Util.find(store.proxy, key));
- };
- Store.set = function (data, cb) {
- var path = data.key.slice();
- var key = path.pop();
- var obj = Util.find(store.proxy, path);
- if (!obj || typeof(obj) !== "object") { return void cb({error: 'INVALID_PATH'}); }
- if (typeof data.value === "undefined") {
- delete obj[key];
- } else {
- obj[key] = data.value;
- }
- onSync(cb);
- };
+ Store.get = function (clientId, key, cb) {
+ cb(Util.find(store.proxy, key));
+ };
+ Store.set = function (clientId, data, cb) {
+ var path = data.key.slice();
+ var key = path.pop();
+ var obj = Util.find(store.proxy, path);
+ if (!obj || typeof(obj) !== "object") { return void cb({error: 'INVALID_PATH'}); }
+ if (typeof data.value === "undefined") {
+ delete obj[key];
+ } else {
+ obj[key] = data.value;
+ }
+ broadcast([clientId], "UPDATE_METADATA");
+ onSync(cb);
+ };
- Store.hasSigningKeys = function () {
- if (!store.proxy) { return; }
- return typeof(store.proxy.edPrivate) === 'string' &&
- typeof(store.proxy.edPublic) === 'string';
- };
+ Store.hasSigningKeys = function () {
+ if (!store.proxy) { return; }
+ return typeof(store.proxy.edPrivate) === 'string' &&
+ typeof(store.proxy.edPublic) === 'string';
+ };
- Store.hasCurveKeys = function () {
- if (!store.proxy) { return; }
- return typeof(store.proxy.curvePrivate) === 'string' &&
- typeof(store.proxy.curvePublic) === 'string';
- };
+ Store.hasCurveKeys = function () {
+ if (!store.proxy) { return; }
+ return typeof(store.proxy.curvePrivate) === 'string' &&
+ typeof(store.proxy.curvePublic) === 'string';
+ };
- var getUserChannelList = function () {
- // start with your userHash...
- var userHash = storeHash;
- if (!userHash) { return null; }
-
- // No password for drive
- var secret = Hash.getSecrets('drive', userHash);
- var userChannel = secret.channel;
- if (!userChannel) { return null; }
-
- // Get the list of pads' channel ID in your drive
- // This list is filtered so that it doesn't include pad owned by other users (you should
- // not pin these pads)
- var files = store.userObject.getFiles([store.userObject.FILES_DATA]);
- var edPublic = store.proxy.edPublic;
- var list = files.map(function (id) {
- var d = store.userObject.getFileData(id);
- if (d.owners && d.owners.length && edPublic &&
- d.owners.indexOf(edPublic) === -1) { return; }
- return d.channel;
- })
- .filter(function (x) { return x; });
-
- // Get the avatar
- var profile = store.proxy.profile;
- if (profile) {
- var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null;
- if (profileChan) { list.push(profileChan); }
- var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar, null) : null;
- if (avatarChan) { list.push(avatarChan); }
- }
-
- if (store.proxy.friends) {
- var fList = Messaging.getFriendChannelsList(store.proxy);
- list = list.concat(fList);
- }
-
- list.push(userChannel);
- list.sort();
-
- return list;
- };
+ var getUserChannelList = function () {
+ // start with your userHash...
+ var userHash = storeHash;
+ if (!userHash) { return null; }
- var getExpirableChannelList = function () {
- var list = [];
- store.userObject.getFiles([store.userObject.FILES_DATA]).forEach(function (id) {
- var data = store.userObject.getFileData(id);
+ // No password for drive
+ var secret = Hash.getSecrets('drive', userHash);
+ var userChannel = secret.channel;
+ if (!userChannel) { return null; }
+
+ // Get the list of pads' channel ID in your drive
+ // This list is filtered so that it doesn't include pad owned by other users (you should
+ // not pin these pads)
+ var files = store.userObject.getFiles([store.userObject.FILES_DATA]);
var edPublic = store.proxy.edPublic;
+ var list = files.map(function (id) {
+ var d = store.userObject.getFileData(id);
+ if (d.owners && d.owners.length && edPublic &&
+ d.owners.indexOf(edPublic) === -1) { return; }
+ return d.channel;
+ })
+ .filter(function (x) { return x; });
+
+ // Get the avatar
+ var profile = store.proxy.profile;
+ if (profile) {
+ var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null;
+ if (profileChan) { list.push(profileChan); }
+ var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar, null) : null;
+ if (avatarChan) { list.push(avatarChan); }
+ }
- // Push channels owned by someone else or channel that should have expired
- // because of the expiration time
- if ((data.owners && data.owners.length && data.owners.indexOf(edPublic) === -1) ||
- (data.expire && data.expire < (+new Date()))) {
- list.push(data.channel);
+ if (store.proxy.friends) {
+ var fList = Messaging.getFriendChannelsList(store.proxy);
+ list = list.concat(fList);
}
- });
- return list;
- };
- var getCanonicalChannelList = function (expirable) {
- var list = expirable ? getExpirableChannelList() : getUserChannelList();
- return Util.deduplicateString(list).sort();
- };
+ list.push(userChannel);
+ list.sort();
- //////////////////////////////////////////////////////////////////
- /////////////////////// RPC //////////////////////////////////////
- //////////////////////////////////////////////////////////////////
+ return list;
+ };
+
+ var getExpirableChannelList = function () {
+ var list = [];
+ store.userObject.getFiles([store.userObject.FILES_DATA]).forEach(function (id) {
+ var data = store.userObject.getFileData(id);
+ var edPublic = store.proxy.edPublic;
+
+ // Push channels owned by someone else or channel that should have expired
+ // because of the expiration time
+ if ((data.owners && data.owners.length && data.owners.indexOf(edPublic) === -1) ||
+ (data.expire && data.expire < (+new Date()))) {
+ list.push(data.channel);
+ }
+ });
+ return list;
+ };
- Store.pinPads = function (data, cb) {
- if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
- if (typeof(cb) !== 'function') {
- console.error('expected a callback');
- }
+ var getCanonicalChannelList = function (expirable) {
+ var list = expirable ? getExpirableChannelList() : getUserChannelList();
+ return Util.deduplicateString(list).sort();
+ };
- store.rpc.pin(data, function (e, hash) {
- if (e) { return void cb({error: e}); }
- cb({hash: hash});
- });
- };
+ //////////////////////////////////////////////////////////////////
+ /////////////////////// RPC //////////////////////////////////////
+ //////////////////////////////////////////////////////////////////
- Store.unpinPads = function (data, cb) {
- if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
+ Store.pinPads = function (clientId, data, cb) {
+ if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
+ if (typeof(cb) !== 'function') {
+ console.error('expected a callback');
+ }
- store.rpc.unpin(data, function (e, hash) {
- if (e) { return void cb({error: e}); }
- cb({hash: hash});
- });
- };
+ store.rpc.pin(data, function (e, hash) {
+ if (e) { return void cb({error: e}); }
+ cb({hash: hash});
+ });
+ };
- var account = {};
+ Store.unpinPads = function (clientId, data, cb) {
+ if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
- Store.getPinnedUsage = function (data, cb) {
- if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
+ store.rpc.unpin(data, function (e, hash) {
+ if (e) { return void cb({error: e}); }
+ cb({hash: hash});
+ });
+ };
- store.rpc.getFileListSize(function (err, bytes) {
- if (typeof(bytes) === 'number') {
- account.usage = bytes;
- }
- cb({bytes: bytes});
- });
- };
+ var account = {};
- // Update for all users from accounts and return current user limits
- Store.updatePinLimit = function (data, cb) {
- if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
- store.rpc.updatePinLimits(function (e, limit, plan, note) {
- if (e) { return void cb({error: e}); }
- account.limit = limit;
- account.plan = plan;
- account.note = note;
- cb(account);
- });
- };
- // Get current user limits
- Store.getPinLimit = function (data, cb) {
- if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
-
- var ALWAYS_REVALIDATE = true;
- if (ALWAYS_REVALIDATE || typeof(account.limit) !== 'number' ||
- typeof(account.plan) !== 'string' ||
- typeof(account.note) !== 'string') {
- return void store.rpc.getLimit(function (e, limit, plan, note) {
+ Store.getPinnedUsage = function (clientId, data, cb) {
+ if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
+
+ store.rpc.getFileListSize(function (err, bytes) {
+ if (typeof(bytes) === 'number') {
+ account.usage = bytes;
+ }
+ cb({bytes: bytes});
+ });
+ };
+
+ // Update for all users from accounts and return current user limits
+ Store.updatePinLimit = function (clientId, data, cb) {
+ if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
+ store.rpc.updatePinLimits(function (e, limit, plan, note) {
if (e) { return void cb({error: e}); }
account.limit = limit;
account.plan = plan;
account.note = note;
cb(account);
});
- }
- cb(account);
- };
+ };
+ // Get current user limits
+ Store.getPinLimit = function (clientId, data, cb) {
+ if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
+
+ var ALWAYS_REVALIDATE = true;
+ if (ALWAYS_REVALIDATE || typeof(account.limit) !== 'number' ||
+ typeof(account.plan) !== 'string' ||
+ typeof(account.note) !== 'string') {
+ return void store.rpc.getLimit(function (e, limit, plan, note) {
+ if (e) { return void cb({error: e}); }
+ account.limit = limit;
+ account.plan = plan;
+ account.note = note;
+ cb(account);
+ });
+ }
+ cb(account);
+ };
- Store.clearOwnedChannel = function (data, cb) {
- if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
- store.rpc.clearOwnedChannel(data, function (err) {
- cb({error:err});
- });
- };
+ Store.clearOwnedChannel = function (clientId, data, cb) {
+ if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
+ store.rpc.clearOwnedChannel(data, function (err) {
+ cb({error:err});
+ });
+ };
- Store.removeOwnedChannel = function (data, cb) {
- if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
- store.rpc.removeOwnedChannel(data, function (err) {
- cb({error:err});
- });
- };
+ Store.removeOwnedChannel = function (clientId, data, cb) {
+ if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
+ store.rpc.removeOwnedChannel(data, function (err) {
+ cb({error:err});
+ });
+ };
- var arePinsSynced = function (cb) {
- if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
+ var arePinsSynced = function (cb) {
+ if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
- var list = getCanonicalChannelList(false);
- var local = Hash.hashChannelList(list);
- store.rpc.getServerHash(function (e, hash) {
- if (e) { return void cb(e); }
- cb(null, hash === local);
- });
- };
+ var list = getCanonicalChannelList(false);
+ var local = Hash.hashChannelList(list);
+ store.rpc.getServerHash(function (e, hash) {
+ if (e) { return void cb(e); }
+ cb(null, hash === local);
+ });
+ };
- var resetPins = function (cb) {
- if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
+ var resetPins = function (cb) {
+ if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
- var list = getCanonicalChannelList(false);
- store.rpc.reset(list, function (e, hash) {
- if (e) { return void cb(e); }
- cb(null, hash);
- });
- };
+ var list = getCanonicalChannelList(false);
+ store.rpc.reset(list, function (e, hash) {
+ if (e) { return void cb(e); }
+ cb(null, hash);
+ });
+ };
- Store.uploadComplete = function (data, cb) {
- if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
- if (data.owned) {
- // Owned file
- store.rpc.ownedUploadComplete(data.id, function (err, res) {
+ Store.uploadComplete = function (clientId, data, cb) {
+ if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
+ if (data.owned) {
+ // Owned file
+ store.rpc.ownedUploadComplete(data.id, function (err, res) {
+ if (err) { return void cb({error:err}); }
+ cb(res);
+ });
+ return;
+ }
+ // Normal upload
+ store.rpc.uploadComplete(data.id, function (err, res) {
if (err) { return void cb({error:err}); }
cb(res);
});
- return;
- }
- // Normal upload
- store.rpc.uploadComplete(data.id, function (err, res) {
- if (err) { return void cb({error:err}); }
- cb(res);
- });
- };
-
- Store.uploadStatus = function (data, cb) {
- if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
- store.rpc.uploadStatus(data.size, function (err, res) {
- if (err) { return void cb({error:err}); }
- cb(res);
- });
- };
-
- Store.uploadCancel = function (data, cb) {
- if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
- store.rpc.uploadCancel(data.size, function (err, res) {
- if (err) { return void cb({error:err}); }
- cb(res);
- });
- };
+ };
- Store.uploadChunk = function (data, cb) {
- store.rpc.send.unauthenticated('UPLOAD', data.chunk, function (e, msg) {
- cb({
- error: e,
- msg: msg
+ Store.uploadStatus = function (clientId, data, cb) {
+ if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
+ store.rpc.uploadStatus(data.size, function (err, res) {
+ if (err) { return void cb({error:err}); }
+ cb(res);
});
- });
- };
-
- Store.initRpc = function (data, cb) {
- require(['/common/pinpad.js'], function (Pinpad) {
- Pinpad.create(store.network, store.proxy, function (e, call) {
- if (e) { return void cb({error: e}); }
+ };
- store.rpc = call;
+ Store.uploadCancel = function (clientId, data, cb) {
+ if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
+ store.rpc.uploadCancel(data.size, function (err, res) {
+ if (err) { return void cb({error:err}); }
+ cb(res);
+ });
+ };
- Store.getPinLimit(null, function (obj) {
- if (obj.error) { console.error(obj.error); }
- account.limit = obj.limit;
- account.plan = obj.plan;
- account.note = obj.note;
- cb(obj);
+ Store.uploadChunk = function (clientId, data, cb) {
+ store.rpc.send.unauthenticated('UPLOAD', data.chunk, function (e, msg) {
+ cb({
+ error: e,
+ msg: msg
});
+ });
+ };
- arePinsSynced(function (err, yes) {
- if (!yes) {
- resetPins(function (err) {
- if (err) { return console.error(err); }
- console.log('RESET DONE');
- });
- }
+ Store.writeLoginBlock = function (clientId, data, cb) {
+ store.rpc.writeLoginBlock(data, function (e, res) {
+ cb({
+ error: e,
+ data: res
});
});
- });
- };
+ };
- //////////////////////////////////////////////////////////////////
- ////////////////// ANON RPC //////////////////////////////////////
- //////////////////////////////////////////////////////////////////
- Store.anonRpcMsg = function (data, cb) {
- if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
- store.anon_rpc.send(data.msg, data.data, function (err, res) {
- if (err) { return void cb({error: err}); }
- cb(res);
- });
- };
+ Store.initRpc = function (clientId, data, cb) {
+ if (store.rpc) { return void cb(account); }
+ require(['/common/pinpad.js'], function (Pinpad) {
+ Pinpad.create(store.network, store.proxy, function (e, call) {
+ if (e) { return void cb({error: e}); }
- Store.getFileSize = function (data, cb) {
- if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
+ store.rpc = call;
- var channelId = Hash.hrefToHexChannelId(data.href, data.password);
- store.anon_rpc.send("GET_FILE_SIZE", channelId, function (e, response) {
- if (e) { return void cb({error: e}); }
- if (response && response.length && typeof(response[0]) === 'number') {
- return void cb({size: response[0]});
- } else {
- cb({error: 'INVALID_RESPONSE'});
- }
- });
- };
+ Store.getPinLimit(null, null, function (obj) {
+ if (obj.error) { console.error(obj.error); }
+ account.limit = obj.limit;
+ account.plan = obj.plan;
+ account.note = obj.note;
+ cb(obj);
+ });
- Store.isNewChannel = function (data, cb) {
- if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
- var channelId = Hash.hrefToHexChannelId(data.href, data.password);
- store.anon_rpc.send("IS_NEW_CHANNEL", channelId, function (e, response) {
- if (e) { return void cb({error: e}); }
- if (response && response.length && typeof(response[0]) === 'boolean') {
- return void cb({
- isNew: response[0]
+ arePinsSynced(function (err, yes) {
+ if (!yes) {
+ resetPins(function (err) {
+ if (err) { return console.error(err); }
+ console.log('RESET DONE');
+ });
+ }
+ });
});
- } else {
- cb({error: 'INVALID_RESPONSE'});
- }
- });
- };
+ });
+ };
- Store.getMultipleFileSize = function (data, cb) {
- if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
- if (!Array.isArray(data.files)) {
- return void cb({error: 'INVALID_FILE_LIST'});
- }
+ //////////////////////////////////////////////////////////////////
+ ////////////////// ANON RPC //////////////////////////////////////
+ //////////////////////////////////////////////////////////////////
+ Store.anonRpcMsg = function (clientId, data, cb) {
+ if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
+ store.anon_rpc.send(data.msg, data.data, function (err, res) {
+ if (err) { return void cb({error: err}); }
+ cb(res);
+ });
+ };
- store.anon_rpc.send('GET_MULTIPLE_FILE_SIZE', data.files, function (e, res) {
- if (e) { return void cb({error: e}); }
- if (res && res.length && typeof(res[0]) === 'object') {
- cb({size: res[0]});
- } else {
- cb({error: 'UNEXPECTED_RESPONSE'});
- }
- });
- };
+ Store.getFileSize = function (clientId, data, cb) {
+ if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
- Store.getDeletedPads = function (data, cb) {
- if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
- var list = getCanonicalChannelList(true);
- if (!Array.isArray(list)) {
- return void cb({error: 'INVALID_FILE_LIST'});
- }
-
- store.anon_rpc.send('GET_DELETED_PADS', list, function (e, res) {
- if (e) { return void cb({error: e}); }
- if (res && res.length && Array.isArray(res[0])) {
- cb(res[0]);
- } else {
- cb({error: 'UNEXPECTED_RESPONSE'});
+ var channelId = Hash.hrefToHexChannelId(data.href, data.password);
+ store.anon_rpc.send("GET_FILE_SIZE", channelId, function (e, response) {
+ if (e) { return void cb({error: e}); }
+ if (response && response.length && typeof(response[0]) === 'number') {
+ return void cb({size: response[0]});
+ } else {
+ cb({error: 'INVALID_RESPONSE'});
+ }
+ });
+ };
+
+ Store.isNewChannel = function (clientId, data, cb) {
+ if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
+ var channelId = Hash.hrefToHexChannelId(data.href, data.password);
+ store.anon_rpc.send("IS_NEW_CHANNEL", channelId, function (e, response) {
+ if (e) { return void cb({error: e}); }
+ if (response && response.length && typeof(response[0]) === 'boolean') {
+ return void cb({
+ isNew: response[0]
+ });
+ } else {
+ cb({error: 'INVALID_RESPONSE'});
+ }
+ });
+ };
+
+ Store.getMultipleFileSize = function (clientId, data, cb) {
+ if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
+ if (!Array.isArray(data.files)) {
+ return void cb({error: 'INVALID_FILE_LIST'});
}
- });
- };
- Store.initAnonRpc = function (data, cb) {
- require([
- '/common/rpc.js',
- ], function (Rpc) {
- Rpc.createAnonymous(store.network, function (e, call) {
+ store.anon_rpc.send('GET_MULTIPLE_FILE_SIZE', data.files, function (e, res) {
if (e) { return void cb({error: e}); }
- store.anon_rpc = call;
- cb();
+ if (res && res.length && typeof(res[0]) === 'object') {
+ cb({size: res[0]});
+ } else {
+ cb({error: 'UNEXPECTED_RESPONSE'});
+ }
});
- });
- };
+ };
- //////////////////////////////////////////////////////////////////
- /////////////////////// Store ////////////////////////////////////
- //////////////////////////////////////////////////////////////////
-
- // Get the metadata for sframe-common-outer
- Store.getMetadata = function (data, cb) {
- var disableThumbnails = Util.find(store.proxy, ['settings', 'general', 'disableThumbnails']);
- var metadata = {
- // "user" is shared with everybody via the userlist
- user: {
- name: store.proxy[Constants.displayNameKey] || "",
- uid: store.proxy.uid,
- avatar: Util.find(store.proxy, ['profile', 'avatar']),
- profile: Util.find(store.proxy, ['profile', 'view']),
- curvePublic: store.proxy.curvePublic,
- },
- // "priv" is not shared with other users but is needed by the apps
- priv: {
- edPublic: store.proxy.edPublic,
- friends: store.proxy.friends || {},
- settings: store.proxy.settings,
- thumbnails: disableThumbnails === false
+ Store.getDeletedPads = function (clientId, data, cb) {
+ if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
+ var list = getCanonicalChannelList(true);
+ if (!Array.isArray(list)) {
+ return void cb({error: 'INVALID_FILE_LIST'});
}
+
+ store.anon_rpc.send('GET_DELETED_PADS', list, function (e, res) {
+ if (e) { return void cb({error: e}); }
+ if (res && res.length && Array.isArray(res[0])) {
+ cb(res[0]);
+ } else {
+ cb({error: 'UNEXPECTED_RESPONSE'});
+ }
+ });
};
- cb(JSON.parse(JSON.stringify(metadata)));
- };
- var makePad = function (href, title) {
- var now = +new Date();
- return {
- href: href,
- atime: now,
- ctime: now,
- title: title || Hash.getDefaultName(Hash.parsePadUrl(href)),
+ Store.initAnonRpc = function (clientId, data, cb) {
+ if (store.anon_rpc) { return void cb(); }
+ require([
+ '/common/rpc.js',
+ ], function (Rpc) {
+ Rpc.createAnonymous(store.network, function (e, call) {
+ if (e) { return void cb({error: e}); }
+ store.anon_rpc = call;
+ cb();
+ });
+ });
};
- };
- Store.addPad = function (data, cb) {
- if (!data.href) { return void cb({error:'NO_HREF'}); }
- var pad = makePad(data.href, data.title);
- if (data.owners) { pad.owners = data.owners; }
- if (data.expire) { pad.expire = data.expire; }
- if (data.password) { pad.password = data.password; }
- if (data.channel) { pad.channel = data.channel; }
- store.userObject.pushData(pad, function (e, id) {
- if (e) { return void cb({error: "Error while adding a template:"+ e}); }
- var path = data.path || ['root'];
- store.userObject.add(id, path);
- onSync(cb);
- });
- };
+ //////////////////////////////////////////////////////////////////
+ /////////////////////// Store ////////////////////////////////////
+ //////////////////////////////////////////////////////////////////
+
+ // Get the metadata for sframe-common-outer
+ Store.getMetadata = function (clientId, data, cb) {
+ var disableThumbnails = Util.find(store.proxy, ['settings', 'general', 'disableThumbnails']);
+ var metadata = {
+ // "user" is shared with everybody via the userlist
+ user: {
+ name: store.proxy[Constants.displayNameKey] || "",
+ uid: store.proxy.uid,
+ avatar: Util.find(store.proxy, ['profile', 'avatar']),
+ profile: Util.find(store.proxy, ['profile', 'view']),
+ curvePublic: store.proxy.curvePublic,
+ },
+ // "priv" is not shared with other users but is needed by the apps
+ priv: {
+ edPublic: store.proxy.edPublic,
+ friends: store.proxy.friends || {},
+ settings: store.proxy.settings,
+ thumbnails: disableThumbnails === false
+ }
+ };
+ cb(JSON.parse(JSON.stringify(metadata)));
+ };
- var getOwnedPads = function () {
- var list = [];
- store.userObject.getFiles([store.userObject.FILES_DATA]).forEach(function (id) {
- var data = store.userObject.getFileData(id);
- var edPublic = store.proxy.edPublic;
+ var makePad = function (href, title) {
+ var now = +new Date();
+ return {
+ href: href,
+ atime: now,
+ ctime: now,
+ title: title || Hash.getDefaultName(Hash.parsePadUrl(href)),
+ };
+ };
- // Push channels owned by someone else or channel that should have expired
- // because of the expiration time
- if (data.owners && data.owners.length === 1 && data.owners.indexOf(edPublic) !== -1) {
- list.push(data.channel);
- }
- });
- if (store.proxy.todo) {
- // No password for todo
- list.push(Hash.hrefToHexChannelId('/todo/#' + store.proxy.todo, null));
- }
- if (store.proxy.profile && store.proxy.profile.edit) {
- // No password for profile
- list.push(Hash.hrefToHexChannelId('/profile/#' + store.proxy.profile.edit, null));
- }
- return list;
- };
- var removeOwnedPads = function (waitFor) {
- // Delete owned pads
- var ownedPads = getOwnedPads();
- var sem = Saferphore.create(10);
- ownedPads.forEach(function (c) {
- var w = waitFor();
- sem.take(function (give) {
- Store.removeOwnedChannel(c, give(function (obj) {
- if (obj && obj.error) { console.error(obj.error); }
- w();
- }));
+ Store.addPad = function (clientId, data, cb) {
+ if (!data.href) { return void cb({error:'NO_HREF'}); }
+ var pad = makePad(data.href, data.title);
+ if (data.owners) { pad.owners = data.owners; }
+ if (data.expire) { pad.expire = data.expire; }
+ if (data.password) { pad.password = data.password; }
+ if (data.channel) { pad.channel = data.channel; }
+ store.userObject.pushData(pad, function (e, id) {
+ if (e) { return void cb({error: "Error while adding a template:"+ e}); }
+ var path = data.path || ['root'];
+ store.userObject.add(id, path);
+ sendDriveEvent('DRIVE_CHANGE', {
+ path: ['drive', UserObject.FILES_DATA]
+ }, clientId);
+ onSync(cb);
});
- });
- };
+ };
- Store.deleteAccount = function (data, cb) {
- var edPublic = store.proxy.edPublic;
- // No password for drive
- var secret = Hash.getSecrets('drive', storeHash);
- Store.anonRpcMsg({
- msg: 'GET_METADATA',
- data: secret.channel
- }, function (data) {
- var metadata = data[0];
- // Owned drive
- if (metadata && metadata.owners && metadata.owners.length === 1 &&
- metadata.owners.indexOf(edPublic) !== -1) {
- nThen(function (waitFor) {
- var token = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
- store.proxy[Constants.tokenKey] = token;
- postMessage("DELETE_ACCOUNT", token, waitFor());
- }).nThen(function (waitFor) {
- removeOwnedPads(waitFor);
- }).nThen(function (waitFor) {
- // Delete Pin Store
- store.rpc.removePins(waitFor(function (err) {
- if (err) { console.error(err); }
+ var getOwnedPads = function () {
+ var list = [];
+ store.userObject.getFiles([store.userObject.FILES_DATA]).forEach(function (id) {
+ var data = store.userObject.getFileData(id);
+ var edPublic = store.proxy.edPublic;
+
+ // Push channels owned by someone else or channel that should have expired
+ // because of the expiration time
+ if (data.owners && data.owners.length === 1 && data.owners.indexOf(edPublic) !== -1) {
+ list.push(data.channel);
+ }
+ });
+ if (store.proxy.todo) {
+ // No password for todo
+ list.push(Hash.hrefToHexChannelId('/todo/#' + store.proxy.todo, null));
+ }
+ if (store.proxy.profile && store.proxy.profile.edit) {
+ // No password for profile
+ list.push(Hash.hrefToHexChannelId('/profile/#' + store.proxy.profile.edit, null));
+ }
+ return list;
+ };
+ var removeOwnedPads = function (waitFor) {
+ // Delete owned pads
+ var ownedPads = getOwnedPads();
+ var sem = Saferphore.create(10);
+ ownedPads.forEach(function (c) {
+ var w = waitFor();
+ sem.take(function (give) {
+ Store.removeOwnedChannel(null, c, give(function (obj) {
+ if (obj && obj.error) { console.error(obj.error); }
+ w();
}));
- }).nThen(function (waitFor) {
- // Delete Drive
- Store.removeOwnedChannel(secret.channel, waitFor());
- }).nThen(function () {
- store.network.disconnect();
- cb({
- state: true
- });
});
- return;
- }
+ });
+ };
- // Not owned drive
- var toSign = {
- intent: 'Please delete my account.'
- };
- toSign.drive = secret.channel;
- toSign.edPublic = edPublic;
- var signKey = Crypto.Nacl.util.decodeBase64(store.proxy.edPrivate);
- var proof = Crypto.Nacl.sign.detached(Crypto.Nacl.util.decodeUTF8(Sortify(toSign)), signKey);
+ Store.deleteAccount = function (clientId, data, cb) {
+ var edPublic = store.proxy.edPublic;
+ // No password for drive
+ var secret = Hash.getSecrets('drive', storeHash);
+ Store.anonRpcMsg(clientId, {
+ msg: 'GET_METADATA',
+ data: secret.channel
+ }, function (data) {
+ var metadata = data[0];
+ // Owned drive
+ if (metadata && metadata.owners && metadata.owners.length === 1 &&
+ metadata.owners.indexOf(edPublic) !== -1) {
+ nThen(function (waitFor) {
+ var token = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
+ store.proxy[Constants.tokenKey] = token;
+ postMessage(clientId, "DELETE_ACCOUNT", token, waitFor());
+ }).nThen(function (waitFor) {
+ removeOwnedPads(waitFor);
+ }).nThen(function (waitFor) {
+ // Delete Pin Store
+ store.rpc.removePins(waitFor(function (err) {
+ if (err) { console.error(err); }
+ }));
+ }).nThen(function (waitFor) {
+ // Delete Drive
+ Store.removeOwnedChannel(clientId, secret.channel, waitFor());
+ }).nThen(function () {
+ store.network.disconnect();
+ cb({
+ state: true
+ });
+ });
+ return;
+ }
+
+ // Not owned drive
+ var toSign = {
+ intent: 'Please delete my account.'
+ };
+ toSign.drive = secret.channel;
+ toSign.edPublic = edPublic;
+ var signKey = Crypto.Nacl.util.decodeBase64(store.proxy.edPrivate);
+ var proof = Crypto.Nacl.sign.detached(Crypto.Nacl.util.decodeUTF8(Sortify(toSign)), signKey);
- var check = Crypto.Nacl.sign.detached.verify(Crypto.Nacl.util.decodeUTF8(Sortify(toSign)),
- proof,
- Crypto.Nacl.util.decodeBase64(edPublic));
+ var check = Crypto.Nacl.sign.detached.verify(Crypto.Nacl.util.decodeUTF8(Sortify(toSign)),
+ proof,
+ Crypto.Nacl.util.decodeBase64(edPublic));
- if (!check) { console.error('signed message failed verification'); }
+ if (!check) { console.error('signed message failed verification'); }
- var proofTxt = Crypto.Nacl.util.encodeBase64(proof);
- cb({
- proof: proofTxt,
- toSign: JSON.parse(Sortify(toSign))
+ var proofTxt = Crypto.Nacl.util.encodeBase64(proof);
+ cb({
+ proof: proofTxt,
+ toSign: JSON.parse(Sortify(toSign))
+ });
});
- });
- };
+ };
- /**
- * add a "What is CryptPad?" pad in the drive
- * data
- * - driveReadme
- * - driveReadmeTitle
- */
- Store.createReadme = function (data, cb) {
- require(['/common/cryptget.js'], function (Crypt) {
- var hash = Hash.createRandomHash('pad');
- Crypt.put(hash, data.driveReadme, function (e) {
- if (e) {
- return void cb({ error: "Error while creating the default pad:"+ e});
- }
- var href = '/pad/#' + hash;
- var channel = Hash.hrefToHexChannelId(href, null);
- var fileData = {
- href: href,
- channel: channel,
- title: data.driveReadmeTitle,
- };
- Store.addPad(fileData, cb);
+ /**
+ * add a "What is CryptPad?" pad in the drive
+ * data
+ * - driveReadme
+ * - driveReadmeTitle
+ */
+ Store.createReadme = function (clientId, data, cb) {
+ require(['/common/cryptget.js'], function (Crypt) {
+ var hash = Hash.createRandomHash('pad');
+ Crypt.put(hash, data.driveReadme, function (e) {
+ if (e) {
+ return void cb({ error: "Error while creating the default pad:"+ e});
+ }
+ var href = '/pad/#' + hash;
+ var channel = Hash.hrefToHexChannelId(href, null);
+ var fileData = {
+ href: href,
+ channel: channel,
+ title: data.driveReadmeTitle,
+ };
+ Store.addPad(clientId, fileData, cb);
+ });
});
- });
- };
+ };
- /**
- * Merge the anonymous drive into the user drive at registration
- * data
- * - anonHash
- */
- Store.migrateAnonDrive = function (data, cb) {
- require(['/common/mergeDrive.js'], function (Merge) {
- var hash = data.anonHash;
- Merge.anonDriveIntoUser(store, hash, cb);
- });
- };
+ /**
+ * Merge the anonymous drive into the user drive at registration
+ * data
+ * - anonHash
+ */
+ Store.migrateAnonDrive = function (clientId, data, cb) {
+ require(['/common/mergeDrive.js'], function (Merge) {
+ var hash = data.anonHash;
+ Merge.anonDriveIntoUser(store, hash, cb);
+ });
+ };
- var getAttributeObject = function (attr) {
- if (typeof attr === "string") {
- console.error('DEPRECATED: use setAttribute with an array, not a string');
+ var getAttributeObject = function (attr) {
+ if (typeof attr === "string") {
+ console.error('DEPRECATED: use setAttribute with an array, not a string');
+ return {
+ path: ['settings'],
+ obj: store.proxy.settings,
+ key: attr
+ };
+ }
+ if (!Array.isArray(attr)) { return void console.error("Attribute must be string or array"); }
+ if (attr.length === 0) { return void console.error("Attribute can't be empty"); }
+ var obj = store.proxy.settings;
+ attr.forEach(function (el, i) {
+ if (i === attr.length-1) { return; }
+ if (!obj[el]) {
+ obj[el] = {};
+ }
+ else if (typeof obj[el] !== "object") { return void console.error("Wrong attribute"); }
+ obj = obj[el];
+ });
return {
- obj: store.proxy.settings,
- key: attr
+ path: ['settings'].concat(attr),
+ obj: obj,
+ key: attr[attr.length-1]
};
- }
- if (!Array.isArray(attr)) { return void console.error("Attribute must be string or array"); }
- if (attr.length === 0) { return void console.error("Attribute can't be empty"); }
- var obj = store.proxy.settings;
- attr.forEach(function (el, i) {
- if (i === attr.length-1) { return; }
- if (!obj[el]) {
- obj[el] = {};
- }
- else if (typeof obj[el] !== "object") { return void console.error("Wrong attribute"); }
- obj = obj[el];
- });
- return {
- obj: obj,
- key: attr[attr.length-1]
};
- };
- // Set the display name (username) in the proxy
- Store.setDisplayName = function (value, cb) {
- store.proxy[Constants.displayNameKey] = value;
- onSync(cb);
- };
+ // Set the display name (username) in the proxy
+ Store.setDisplayName = function (clientId, value, cb) {
+ store.proxy[Constants.displayNameKey] = value;
+ broadcast([clientId], "UPDATE_METADATA");
+ onSync(cb);
+ };
+
+ // Reset the drive part of the userObject (from settings)
+ Store.resetDrive = function (clientId, data, cb) {
+ nThen(function (waitFor) {
+ removeOwnedPads(waitFor);
+ }).nThen(function () {
+ store.proxy.drive = store.fo.getStructure();
+ sendDriveEvent('DRIVE_CHANGE', {
+ path: ['drive', 'filesData']
+ }, clientId);
+ onSync(cb);
+ });
+ };
- // Reset the drive part of the userObject (from settings)
- Store.resetDrive = function (data, cb) {
- nThen(function (waitFor) {
- removeOwnedPads(waitFor);
- }).nThen(function () {
- store.proxy.drive = store.fo.getStructure();
+ /**
+ * Settings & pad attributes
+ * data
+ * - href (String)
+ * - attr (Array)
+ * - value (String)
+ */
+ Store.setPadAttribute = function (clientId, data, cb) {
+ store.userObject.setPadAttribute(data.href, data.attr, data.value, function () {
+ sendDriveEvent('DRIVE_CHANGE', {
+ path: ['drive', UserObject.FILES_DATA]
+ }, clientId);
+ onSync(cb);
+ });
+ };
+ Store.getPadAttribute = function (clientId, data, cb) {
+ store.userObject.getPadAttribute(data.href, data.attr, function (err, val) {
+ if (err) { return void cb({error: err}); }
+ cb(val);
+ });
+ };
+ Store.setAttribute = function (clientId, data, cb) {
+ try {
+ var object = getAttributeObject(data.attr);
+ object.obj[object.key] = data.value;
+ } catch (e) { return void cb({error: e}); }
onSync(cb);
- });
- };
+ };
+ Store.getAttribute = function (clientId, data, cb) {
+ var object;
+ try {
+ object = getAttributeObject(data.attr);
+ } catch (e) { return void cb({error: e}); }
+ cb(object.obj[object.key]);
+ };
+
+ // Tags
+ Store.listAllTags = function (clientId, data, cb) {
+ cb(store.userObject.getTagsList());
+ };
+
+ // Templates
+ Store.getTemplates = function (clientId, data, cb) {
+ var templateFiles = store.userObject.getFiles(['template']);
+ var res = [];
+ templateFiles.forEach(function (f) {
+ var data = store.userObject.getFileData(f);
+ res.push(JSON.parse(JSON.stringify(data)));
+ });
+ cb(res);
+ };
+ Store.incrementTemplateUse = function (clientId, href) {
+ store.userObject.getPadAttribute(href, 'used', function (err, data) {
+ // This is a not critical function, abort in case of error to make sure we won't
+ // create any issue with the user object or the async store
+ if (err) { return; }
+ var used = typeof data === "number" ? ++data : 1;
+ store.userObject.setPadAttribute(href, 'used', used);
+ });
+ };
- /**
- * Settings & pad attributes
- * data
- * - href (String)
- * - attr (Array)
- * - value (String)
- */
- Store.setPadAttribute = function (data, cb) {
- store.userObject.setPadAttribute(data.href, data.attr, data.value, function () {
+ // Pads
+ Store.moveToTrash = function (clientId, data, cb) {
+ var href = Hash.getRelativeHref(data.href);
+ store.userObject.forget(href);
+ sendDriveEvent('DRIVE_CHANGE', {
+ path: ['drive', UserObject.FILES_DATA]
+ }, clientId);
onSync(cb);
- });
- };
- Store.getPadAttribute = function (data, cb) {
- store.userObject.getPadAttribute(data.href, data.attr, function (err, val) {
- if (err) { return void cb({error: err}); }
- cb(val);
- });
- };
- Store.setAttribute = function (data, cb) {
- try {
- var object = getAttributeObject(data.attr);
- object.obj[object.key] = data.value;
- } catch (e) { return void cb({error: e}); }
- onSync(cb);
- };
- Store.getAttribute = function (data, cb) {
- var object;
- try {
- object = getAttributeObject(data.attr);
- } catch (e) { return void cb({error: e}); }
- cb(object.obj[object.key]);
- };
+ };
+ Store.setPadTitle = function (clientId, data, cb) {
+ var title = data.title;
+ var href = data.href;
+ var channel = data.channel;
+ var p = Hash.parsePadUrl(href);
+ var h = p.hashData;
- // Tags
- Store.listAllTags = function (data, cb) {
- cb(store.userObject.getTagsList());
- };
+ if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); }
- // Templates
- Store.getTemplates = function (data, cb) {
- var templateFiles = store.userObject.getFiles(['template']);
- var res = [];
- templateFiles.forEach(function (f) {
- var data = store.userObject.getFileData(f);
- res.push(JSON.parse(JSON.stringify(data)));
- });
- cb(res);
- };
- Store.incrementTemplateUse = function (href) {
- store.userObject.getPadAttribute(href, 'used', function (err, data) {
- // This is a not critical function, abort in case of error to make sure we won't
- // create any issue with the user object or the async store
- if (err) { return; }
- var used = typeof data === "number" ? ++data : 1;
- store.userObject.setPadAttribute(href, 'used', used);
- });
- };
+ var channelData = Store.channels && Store.channels[channel];
- // Pads
- Store.moveToTrash = function (data, cb) {
- var href = Hash.getRelativeHref(data.href);
- store.userObject.forget(href);
- onSync(cb);
- };
- Store.setPadTitle = function (data, cb) {
- var title = data.title;
- var href = data.href;
- var channel = data.channel;
- var p = Hash.parsePadUrl(href);
- var h = p.hashData;
-
- if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); }
-
- var owners;
- if (Store.channel && Store.channel.wc && channel === Store.channel.wc.id) {
- owners = Store.channel.data.owners || undefined;
- }
-
- var expire;
- if (Store.channel && Store.channel.wc && channel === Store.channel.wc.id) {
- expire = +Store.channel.data.expire || undefined;
- }
-
- var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {};
- var isStronger;
-
- // If we don't find the new channel in our existing pads, we'll have to add the pads
- // to filesData
- var contains;
-
- // Update all pads that use the same channel but with a weaker hash
- // Edit > Edit (present) > View > View (present)
- for (var id in allPads) {
- var pad = allPads[id];
- if (!pad.href) { continue; }
-
- var p2 = Hash.parsePadUrl(pad.href);
- var h2 = p2.hashData;
-
- // Different types, proceed to the next one
- // No hash data: corrupted pad?
- if (p.type !== p2.type || !h2) { continue; }
- // Different channel: continue
- if (pad.channel !== channel) { continue; }
-
- var shouldUpdate = p.hash.replace(/\/$/, '') === p2.hash.replace(/\/$/, '');
-
- // If the hash is different but represents the same channel, check if weaker or stronger
- if (!shouldUpdate && h.version !== 0) {
- // We had view & now we have edit, update
- if (h2.mode === 'view' && h.mode === 'edit') { shouldUpdate = true; }
- // Same mode and we had present URL, update
- else if (h.mode === h2.mode && h2.present) { shouldUpdate = true; }
- // If we're here it means we have a weaker URL:
- // update the date but keep the existing hash
- else {
- pad.atime = +new Date();
- contains = true;
- continue;
- }
+ var owners;
+ if (channelData && channelData.wc && channel === channelData.wc.id) {
+ owners = channelData.data.owners || undefined;
}
- if (shouldUpdate) {
- contains = true;
- pad.atime = +new Date();
- pad.title = title;
- if (owners || h.type !== "file") {
- // OWNED_FILES
- // Never remove owner for files
- pad.owners = owners;
- }
- pad.expire = expire;
-
- // If the href is different, it means we have a stronger one
- if (href !== pad.href) { isStronger = true; }
- pad.href = href;
+ var expire;
+ if (channelData && channelData.wc && channel === channelData.wc.id) {
+ expire = +channelData.data.expire || undefined;
}
- }
- if (isStronger) {
- // If we have a stronger url, remove the possible weaker from the trash.
- // If all of the weaker ones were in the trash, add the stronger to ROOT
- store.userObject.restoreHref(href);
- }
+ var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {};
+ var isStronger;
+
+ // If we don't find the new channel in our existing pads, we'll have to add the pads
+ // to filesData
+ var contains;
+
+ // Update all pads that use the same channel but with a weaker hash
+ // Edit > Edit (present) > View > View (present)
+ for (var id in allPads) {
+ var pad = allPads[id];
+ if (!pad.href) { continue; }
+
+ var p2 = Hash.parsePadUrl(pad.href);
+ var h2 = p2.hashData;
+
+ // Different types, proceed to the next one
+ // No hash data: corrupted pad?
+ if (p.type !== p2.type || !h2) { continue; }
+ // Different channel: continue
+ if (pad.channel !== channel) { continue; }
+
+ var shouldUpdate = p.hash.replace(/\/$/, '') === p2.hash.replace(/\/$/, '');
+
+ // If the hash is different but represents the same channel, check if weaker or stronger
+ if (!shouldUpdate && h.version !== 0) {
+ // We had view & now we have edit, update
+ if (h2.mode === 'view' && h.mode === 'edit') { shouldUpdate = true; }
+ // Same mode and we had present URL, update
+ else if (h.mode === h2.mode && h2.present) { shouldUpdate = true; }
+ // If we're here it means we have a weaker URL:
+ // update the date but keep the existing hash
+ else {
+ pad.atime = +new Date();
+ contains = true;
+ continue;
+ }
+ }
- // Add the pad if it does not exist in our drive
- if (!contains) {
- Store.addPad({
- href: href,
- channel: channel,
- title: title,
- owners: owners,
- expire: expire,
- password: data.password,
- path: data.path
- }, cb);
- return;
- }
- onSync(cb);
- };
+ if (shouldUpdate) {
+ contains = true;
+ pad.atime = +new Date();
+ pad.title = title;
+ if (owners || h.type !== "file") {
+ // OWNED_FILES
+ // Never remove owner for files
+ pad.owners = owners;
+ }
+ pad.expire = expire;
- // Filepicker app
- Store.getSecureFilesList = function (query, cb) {
- var list = {};
- var hashes = [];
- var types = query.types;
- var where = query.where;
- var filter = query.filter || {};
- var isFiltered = function (type, data) {
- var filtered;
- var fType = filter.fileType || [];
- if (type === 'file' && fType.length) {
- if (!data.fileType) { return true; }
- filtered = !fType.some(function (t) {
- return data.fileType.indexOf(t) === 0;
- });
+ // If the href is different, it means we have a stronger one
+ if (href !== pad.href) { isStronger = true; }
+ pad.href = href;
+ }
}
- return filtered;
- };
- store.userObject.getFiles(where).forEach(function (id) {
- var data = store.userObject.getFileData(id);
- var parsed = Hash.parsePadUrl(data.href);
- if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) &&
- hashes.indexOf(parsed.hash) === -1 &&
- !isFiltered(parsed.type, data)) {
- hashes.push(parsed.hash);
- list[id] = data;
+
+ if (isStronger) {
+ // If we have a stronger url, remove the possible weaker from the trash.
+ // If all of the weaker ones were in the trash, add the stronger to ROOT
+ store.userObject.restoreHref(href);
}
- });
- cb(list);
- };
- Store.getPadData = function (id, cb) {
- cb(store.userObject.getFileData(id));
- };
+ // Add the pad if it does not exist in our drive
+ if (!contains) {
+ Store.addPad(clientId, {
+ href: href,
+ channel: channel,
+ title: title,
+ owners: owners,
+ expire: expire,
+ password: data.password,
+ path: data.path
+ }, cb);
+ return;
+ } else {
+ sendDriveEvent('DRIVE_CHANGE', {
+ path: ['drive', UserObject.FILES_DATA]
+ }, clientId);
+ }
+ onSync(cb);
+ };
- // Messaging (manage friends from the userlist)
- var getMessagingCfg = function () {
- return {
- proxy: store.proxy,
- realtime: store.realtime,
- network: store.network,
- updateMetadata: function () {
- postMessage("UPDATE_METADATA");
- },
- pinPads: Store.pinPads,
- friendComplete: function (data) {
- postMessage("EV_FRIEND_COMPLETE", data);
- },
- friendRequest: function (data, cb) {
- postMessage("Q_FRIEND_REQUEST", data, cb);
- },
+ // Filepicker app
+ Store.getSecureFilesList = function (clientId, query, cb) {
+ var list = {};
+ var hashes = [];
+ var types = query.types;
+ var where = query.where;
+ var filter = query.filter || {};
+ var isFiltered = function (type, data) {
+ var filtered;
+ var fType = filter.fileType || [];
+ if (type === 'file' && fType.length) {
+ if (!data.fileType) { return true; }
+ filtered = !fType.some(function (t) {
+ return data.fileType.indexOf(t) === 0;
+ });
+ }
+ return filtered;
+ };
+ store.userObject.getFiles(where).forEach(function (id) {
+ var data = store.userObject.getFileData(id);
+ var parsed = Hash.parsePadUrl(data.href);
+ if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) &&
+ hashes.indexOf(parsed.hash) === -1 &&
+ !isFiltered(parsed.type, data)) {
+ hashes.push(parsed.hash);
+ list[id] = data;
+ }
+ });
+ cb(list);
+ };
+ Store.getPadData = function (clientId, id, cb) {
+ cb(store.userObject.getFileData(id));
};
- };
- Store.inviteFromUserlist = function (data, cb) {
- var messagingCfg = getMessagingCfg();
- Messaging.inviteFromUserlist(messagingCfg, data, cb);
- };
- // Messenger
- // Get hashes for the share button
- Store.getStrongerHash = function (data, cb) {
- var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {};
+ // Messaging (manage friends from the userlist)
+ var getMessagingCfg = function (clientId) {
+ return {
+ proxy: store.proxy,
+ realtime: store.realtime,
+ network: store.network,
+ updateMetadata: function () {
+ postMessage(clientId, "UPDATE_METADATA");
+ },
+ pinPads: function (data, cb) { Store.pinPads(null, data, cb); },
+ friendComplete: function (data) {
+ postMessage(clientId, "EV_FRIEND_COMPLETE", data);
+ },
+ friendRequest: function (data, cb) {
+ postMessage(clientId, "Q_FRIEND_REQUEST", data, cb);
+ },
+ };
+ };
+ Store.inviteFromUserlist = function (clientId, data, cb) {
+ var messagingCfg = getMessagingCfg(clientId);
+ Messaging.inviteFromUserlist(messagingCfg, data, cb);
+ };
+ Store.addDirectMessageHandlers = function (clientId, data) {
+ var messagingCfg = getMessagingCfg(clientId);
+ Messaging.addDirectMessageHandler(messagingCfg, data.href);
+ };
- // If we have a stronger version in drive, add it and add a redirect button
- var stronger = Hash.findStronger(data.href, data.channel, allPads);
- if (stronger) {
- var parsed2 = Hash.parsePadUrl(stronger.href);
- return void cb(parsed2.hash);
- }
- cb();
- };
+ // Messenger
- Store.messenger = {
- getFriendList: function (data, cb) {
- store.messenger.getFriendList(function (e, keys) {
- cb({
- error: e,
- data: keys,
+ // Get hashes for the share button
+ Store.getStrongerHash = function (clientId, data, cb) {
+ var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {};
+
+ // If we have a stronger version in drive, add it and add a redirect button
+ var stronger = Hash.findStronger(data.href, data.channel, allPads);
+ if (stronger) {
+ var parsed2 = Hash.parsePadUrl(stronger.href);
+ return void cb(parsed2.hash);
+ }
+ cb();
+ };
+
+ Store.messenger = {
+ getFriendList: function (data, cb) {
+ store.messenger.getFriendList(function (e, keys) {
+ cb({
+ error: e,
+ data: keys,
+ });
});
- });
- },
- getMyInfo: function (data, cb) {
- store.messenger.getMyInfo(function (e, info) {
- cb({
- error: e,
- data: info,
+ },
+ getMyInfo: function (data, cb) {
+ store.messenger.getMyInfo(function (e, info) {
+ cb({
+ error: e,
+ data: info,
+ });
});
- });
- },
- getFriendInfo: function (data, cb) {
- store.messenger.getFriendInfo(data, function (e, info) {
- cb({
- error: e,
- data: info,
+ },
+ getFriendInfo: function (data, cb) {
+ store.messenger.getFriendInfo(data, function (e, info) {
+ cb({
+ error: e,
+ data: info,
+ });
});
- });
- },
- removeFriend: function (data, cb) {
- store.messenger.removeFriend(data, function (e, info) {
- cb({
- error: e,
- data: info,
+ },
+ removeFriend: function (data, cb) {
+ store.messenger.removeFriend(data, function (e, info) {
+ cb({
+ error: e,
+ data: info,
+ });
});
- });
- },
- openFriendChannel: function (data, cb) {
- store.messenger.openFriendChannel(data, function (e) {
- cb({ error: e, });
- });
- },
- getFriendStatus: function (data, cb) {
- store.messenger.getStatus(data, function (e, online) {
- cb({
- error: e,
- data: online,
+ },
+ openFriendChannel: function (data, cb) {
+ store.messenger.openFriendChannel(data, function (e) {
+ cb({ error: e, });
});
- });
- },
- getMoreHistory: function (data, cb) {
- store.messenger.getMoreHistory(data.curvePublic, data.sig, data.count, function (e, history) {
- cb({
- error: e,
- data: history,
+ },
+ getFriendStatus: function (data, cb) {
+ store.messenger.getStatus(data, function (e, online) {
+ cb({
+ error: e,
+ data: online,
+ });
});
- });
- },
- sendMessage: function (data, cb) {
- store.messenger.sendMessage(data.curvePublic, data.content, function (e) {
- cb({
- error: e,
+ },
+ getMoreHistory: function (data, cb) {
+ store.messenger.getMoreHistory(data.curvePublic, data.sig, data.count, function (e, history) {
+ cb({
+ error: e,
+ data: history,
+ });
});
- });
- },
- setChannelHead: function (data, cb) {
- store.messenger.setChannelHead(data.curvePublic, data.sig, function (e) {
- cb({
- error: e
+ },
+ sendMessage: function (data, cb) {
+ store.messenger.sendMessage(data.curvePublic, data.content, function (e) {
+ cb({
+ error: e,
+ });
});
- });
- }
- };
+ },
+ setChannelHead: function (data, cb) {
+ store.messenger.setChannelHead(data.curvePublic, data.sig, function (e) {
+ cb({
+ error: e
+ });
+ });
+ }
+ };
- //////////////////////////////////////////////////////////////////
- /////////////////////// PAD //////////////////////////////////////
- //////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////
+ /////////////////////// PAD //////////////////////////////////////
+ //////////////////////////////////////////////////////////////////
+
+ var channels = Store.channels = {};
+
+ Store.joinPad = function (clientId, data) {
+ var isNew = typeof channels[data.channel] === "undefined";
+ var channel = channels[data.channel] = channels[data.channel] || {
+ queue: [],
+ data: {},
+ clients: [],
+ bcast: function (cmd, data, notMe) {
+ channel.clients.forEach(function (cId) {
+ if (cId === notMe) { return; }
+ postMessage(cId, cmd, data);
+ });
+ },
+ history: [],
+ pushHistory: function (msg, isCp) {
+ if (isCp) {
+ channel.history.push('cp|' + msg);
+ var i;
+ for (i = channel.history.length - 2; i > 0; i--) {
+ if (/^cp\|/.test(channel.history[i])) { break; }
+ }
+ channel.history = channel.history.slice(i);
+ return;
+ }
+ channel.history.push(msg);
+ }
+ };
+ if (channel.clients.indexOf(clientId) === -1) {
+ channel.clients.push(clientId);
+ }
- // TODO with sharedworker
- // channel will be an object storing the webchannel associated to each browser tab
- var channel = Store.channel = {
- queue: [],
- data: {}
- };
- Store.joinPad = function (data, cb) {
- var conf = {
- onReady: function (padData) {
- channel.data = padData || {};
- postMessage("PAD_READY");
- }, // post EV_PAD_READY
- onMessage: function (user, m, validateKey) {
- postMessage("PAD_MESSAGE", {
- user: user,
- msg: m,
- validateKey: validateKey
+ if (!isNew && channel.wc) {
+ postMessage(clientId, "PAD_CONNECT", {
+ myID: channel.wc.myID,
+ id: channel.wc.id,
+ members: channel.wc.members
});
- }, // post EV_PAD_MESSAGE
- onJoin: function (m) {
- postMessage("PAD_JOIN", m);
- }, // post EV_PAD_JOIN
- onLeave: function (m) {
- postMessage("PAD_LEAVE", m);
- }, // post EV_PAD_LEAVE
- onDisconnect: function () {
- postMessage("PAD_DISCONNECT");
- }, // post EV_PAD_DISCONNECT
- onError: function (err) {
- postMessage("PAD_ERROR", err);
- }, // post EV_PAD_ERROR
- channel: data.channel,
- validateKey: data.validateKey,
- owners: data.owners,
- password: data.password,
- expire: data.expire,
- network: store.network,
- readOnly: data.readOnly,
- onConnect: function (wc, sendMessage) {
- channel.sendMessage = sendMessage;
- channel.wc = wc;
- channel.queue.forEach(function (data) {
- sendMessage(data.message);
+ channel.wc.members.forEach(function (m) {
+ postMessage(clientId, "PAD_JOIN", m);
});
- cb({
- myID: wc.myID,
- id: wc.id,
- members: wc.members
+ channel.history.forEach(function (msg) {
+ postMessage(clientId, "PAD_MESSAGE", {
+ msg: CpNfWorker.removeCp(msg),
+ user: channel.wc.myID,
+ validateKey: channel.data.validateKey
+ });
});
- }
- };
- CpNfWorker.start(conf);
- };
- Store.sendPadMsg = function (data, cb) {
- if (!channel.wc) { channel.queue.push(data); }
- channel.sendMessage(data, cb);
- };
+ postMessage(clientId, "PAD_READY");
- // TODO
- // GET_FULL_HISTORY from sframe-common-outer
- Store.getFullHistory = function (data, cb) {
- var network = store.network;
- var hkn = network.historyKeeper;
- //var crypto = Crypto.createEncryptor(data.keys);
- // Get the history messages and send them to the iframe
- var parse = function (msg) {
- try {
- return JSON.parse(msg);
- } catch (e) {
- return null;
- }
- };
- var msgs = [];
- var onMsg = function (msg) {
- var parsed = parse(msg);
- if (parsed[0] === 'FULL_HISTORY_END') {
- cb(msgs);
- return;
- }
- if (parsed[0] !== 'FULL_HISTORY') { return; }
- if (parsed[1] && parsed[1].validateKey) { // First message
return;
}
- if (parsed[1][3] !== data.channel) { return; }
- msg = parsed[1][4];
- if (msg) {
- msg = msg.replace(/cp\|(([A-Za-z0-9+\/=]+)\|)?/, '');
- //var decryptedMsg = crypto.decrypt(msg, true);
- msgs.push(msg);
- }
+ var conf = {
+ onReady: function (padData) {
+ channel.data = padData || {};
+ postMessage(clientId, "PAD_READY");
+ },
+ onMessage: function (user, m, validateKey, isCp) {
+ channel.pushHistory(m, isCp);
+ channel.bcast("PAD_MESSAGE", {
+ user: user,
+ msg: m,
+ validateKey: validateKey
+ });
+ },
+ onJoin: function (m) {
+ channel.bcast("PAD_JOIN", m);
+ },
+ onLeave: function (m) {
+ channel.bcast("PAD_LEAVE", m);
+ },
+ onDisconnect: function () {
+ channel.bcast("PAD_DISCONNECT");
+ },
+ onError: function (err) {
+ channel.bcast("PAD_ERROR", err);
+ delete channels[data.channel]; // TODO test?
+ },
+ channel: data.channel,
+ validateKey: data.validateKey,
+ owners: data.owners,
+ password: data.password,
+ expire: data.expire,
+ network: store.network,
+ //readOnly: data.readOnly,
+ onConnect: function (wc, sendMessage) {
+ channel.sendMessage = function (msg, cId, cb) {
+ // Send to server
+ sendMessage(msg, cb);
+ // Broadcast to other tabs
+ channel.pushHistory(CpNfWorker.removeCp(msg), /^cp\|/.test(msg));
+ channel.bcast("PAD_MESSAGE", {
+ user: wc.myID,
+ msg: CpNfWorker.removeCp(msg),
+ validateKey: channel.data.validateKey
+ }, cId);
+ };
+ channel.wc = wc;
+ channel.queue.forEach(function (data) {
+ channel.sendMessage(data.message, clientId);
+ });
+ channel.bcast("PAD_CONNECT", {
+ myID: wc.myID,
+ id: wc.id,
+ members: wc.members
+ });
+ }
+ };
+ CpNfWorker.start(conf);
};
- network.on('message', onMsg);
- network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', data.channel, data.validateKey]));
- };
-
- // TODO with sharedworker
- // when the tab is closed, leave the pad
-
- // Drive
- Store.userObjectCommand = function (cmdData, cb) {
- if (!cmdData || !cmdData.cmd) { return; }
- var data = cmdData.data;
- switch (cmdData.cmd) {
- case 'move':
- store.userObject.move(data.paths, data.newPath, cb); break;
- case 'restore':
- store.userObject.restore(data.path, cb); break;
- case 'addFolder':
- store.userObject.addFolder(data.path, data.name, cb); break;
- case 'delete':
- store.userObject.delete(data.paths, cb, data.nocheck, data.isOwnPadRemoved); break;
- case 'emptyTrash':
- store.userObject.emptyTrash(cb); break;
- case 'rename':
- store.userObject.rename(data.path, data.newName, cb); break;
- default:
- cb();
- }
- };
-
- //////////////////////////////////////////////////////////////////
- /////////////////////// Init /////////////////////////////////////
- //////////////////////////////////////////////////////////////////
-
- var onReady = function (returned, cb) {
- var proxy = store.proxy;
- var userObject = store.userObject = UserObject.init(proxy.drive, {
- pinPads: Store.pinPads,
- unpinPads: Store.unpinPads,
- removeOwnedChannel: Store.removeOwnedChannel,
- edPublic: store.proxy.edPublic,
- loggedIn: store.loggedIn,
- log: function (msg) {
- postMessage("DRIVE_LOG", msg);
+ Store.leavePad = function (clientId, data, cb) {
+ var channel = channels[data.channel];
+ if (!channel || !channel.wc) { return void cb ({error: 'EINVAL'}); }
+ channel.wc.leave();
+ delete channels[data.channel];
+ cb();
+ };
+ Store.sendPadMsg = function (clientId, data, cb) {
+ var msg = data.msg;
+ var channel = channels[data.channel];
+ if (!channel) {
+ return; }
+ if (!channel.wc) {
+ channel.queue.push(msg);
+ return void cb();
}
- });
- nThen(function (waitFor) {
- postMessage('LOADING_DRIVE', {
- state: 2
- });
- userObject.migrate(waitFor());
- }).nThen(function (waitFor) {
- Migrate(proxy, waitFor(), function (version, progress) {
- postMessage('LOADING_DRIVE', {
- state: 2,
- progress: progress
- });
- });
- }).nThen(function () {
- postMessage('LOADING_DRIVE', {
- state: 3
- });
- userObject.fixFiles();
+ channel.sendMessage(msg, clientId, cb);
+ };
- var requestLogin = function () {
- postMessage("REQUEST_LOGIN");
+ // GET_FULL_HISTORY from sframe-common-outer
+ Store.getFullHistory = function (clientId, data, cb) {
+ var network = store.network;
+ var hkn = network.historyKeeper;
+ //var crypto = Crypto.createEncryptor(data.keys);
+ // Get the history messages and send them to the iframe
+ var parse = function (msg) {
+ try {
+ return JSON.parse(msg);
+ } catch (e) {
+ return null;
+ }
};
-
- if (store.loggedIn) {
- /* This isn't truly secure, since anyone who can read the user's object can
- set their local loginToken to match that in the object. However, it exposes
- a UI that will work most of the time. */
-
- // every user object should have a persistent, random number
- if (typeof(proxy.loginToken) !== 'number') {
- proxy[Constants.tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
+ var msgs = [];
+ var completed = false;
+ var onMsg = function (msg) {
+ if (completed) { return; }
+ var parsed = parse(msg);
+ if (parsed[0] === 'FULL_HISTORY_END') {
+ cb(msgs);
+ completed = true;
+ return;
}
- returned[Constants.tokenKey] = proxy[Constants.tokenKey];
-
- if (store.data.localToken && store.data.localToken !== proxy[Constants.tokenKey]) {
- // the local number doesn't match that in
- // the user object, request that they reauthenticate.
- return void requestLogin();
+ if (parsed[0] !== 'FULL_HISTORY') { return; }
+ if (parsed[1] && parsed[1].validateKey) { // First message
+ return;
}
- }
+ if (parsed[1][3] !== data.channel) { return; }
+ msg = parsed[1][4];
+ if (msg) {
+ msg = msg.replace(/cp\|(([A-Za-z0-9+\/=]+)\|)?/, '');
+ //var decryptedMsg = crypto.decrypt(msg, true);
+ msgs.push(msg);
+ }
+ };
+ network.on('message', onMsg);
+ network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', data.channel, data.validateKey]));
+ };
- if (!proxy.settings || !proxy.settings.general ||
- typeof(proxy.settings.general.allowUserFeedback) !== 'boolean') {
- proxy.settings = proxy.settings || {};
- proxy.settings.general = proxy.settings.general || {};
- proxy.settings.general.allowUserFeedback = true;
- }
- returned.feedback = proxy.settings.general.allowUserFeedback;
+ Store.getHistoryRange = function (clientId, data, cb) {
+ var network = store.network;
+ var hkn = network.historyKeeper;
+ var parse = function (msg) {
+ try {
+ return JSON.parse(msg);
+ } catch (e) {
+ return null;
+ }
+ };
+ var msgs = [];
+ var first = true;
+ var fullHistory = false;
+ var completed = false;
+ var lastKnownHash;
+ var txid = Util.uid();
+
+ var onMsg = function (msg) {
+ if (completed) { return; }
+ var parsed = parse(msg);
+ if (parsed[1] !== txid) { console.log('bad txid'); return; }
+ if (parsed[0] === 'HISTORY_RANGE_END') {
+ cb({
+ messages: msgs,
+ isFull: fullHistory,
+ lastKnownHash: lastKnownHash
+ });
+ completed = true;
+ return;
+ }
+ if (parsed[0] !== 'HISTORY_RANGE') { return; }
+ if (parsed[2] && parsed[1].validateKey) { // Metadata
+ return;
+ }
+ if (parsed[2][3] !== data.channel) { return; }
+ msg = parsed[2][4];
+ if (msg) {
+ if (first) {
+ // If the first message if not a checkpoint, it means it is the first
+ // message of the pad, so we have the full history!
+ if (!/^cp\|/.test(msg)) { fullHistory = true; }
+ lastKnownHash = msg.slice(0,64);
+ first = false;
+ }
+ msg = msg.replace(/cp\|(([A-Za-z0-9+\/=]+)\|)?/, '');
+ msgs.push(msg);
+ }
+ };
- if (typeof(cb) === 'function') { cb(returned); }
+ network.on('message', onMsg);
+ network.sendto(hkn, JSON.stringify(['GET_HISTORY_RANGE', data.channel, {
+ from: data.lastKnownHash,
+ cpCount: 2,
+ txid: txid
+ }]));
+ };
- if (typeof(proxy.uid) !== 'string' || proxy.uid.length !== 32) {
- // even anonymous users should have a persistent, unique-ish id
- console.log('generating a persistent identifier');
- proxy.uid = Hash.createChannelId();
+ // Drive
+ Store.userObjectCommand = function (clientId, cmdData, cb) {
+ if (!cmdData || !cmdData.cmd) { return; }
+ var data = cmdData.data;
+ var cb2 = function (data2) {
+ var paths = data.paths || [data.path] || [];
+ paths = paths.concat(data.newPath || []);
+ paths.forEach(function (p) {
+ sendDriveEvent('DRIVE_CHANGE', {
+ //path: ['drive', UserObject.FILES_DATA]
+ path: ['drive'].concat(p)
+ }, clientId);
+ });
+ cb(data2);
+ };
+ switch (cmdData.cmd) {
+ case 'move':
+ store.userObject.move(data.paths, data.newPath, cb2); break;
+ case 'restore':
+ store.userObject.restore(data.path, cb2); break;
+ case 'addFolder':
+ store.userObject.addFolder(data.path, data.name, cb2); break;
+ case 'delete':
+ store.userObject.delete(data.paths, cb2, data.nocheck, data.isOwnPadRemoved); break;
+ case 'emptyTrash':
+ store.userObject.emptyTrash(cb2); break;
+ case 'rename':
+ store.userObject.rename(data.path, data.newName, cb2); break;
+ default:
+ cb();
}
+ };
- // if the user is logged in, but does not have signing keys...
- if (store.loggedIn && (!Store.hasSigningKeys() ||
- !Store.hasCurveKeys())) {
- return void requestLogin();
- }
+ // Clients management
+ var driveEventClients = [];
+ var messengerEventClients = [];
- proxy.on('change', [Constants.displayNameKey], function (o, n) {
- if (typeof(n) !== "string") { return; }
- postMessage("UPDATE_METADATA");
- });
- proxy.on('change', ['profile'], function () {
- // Trigger userlist update when the avatar has changed
- postMessage("UPDATE_METADATA");
- });
- proxy.on('change', ['friends'], function () {
- // Trigger userlist update when the friendlist has changed
- postMessage("UPDATE_METADATA");
- });
- proxy.on('change', ['settings'], function () {
- postMessage("UPDATE_METADATA");
- });
- proxy.on('change', [Constants.tokenKey], function () {
- postMessage("UPDATE_TOKEN", { token: proxy[Constants.tokenKey] });
- });
- });
- };
+ var dropChannel = function (chanId) {
+ if (!Store.channels[chanId]) { return; }
- var connect = function (data, cb) {
- var hash = data.userHash || data.anonHash || Hash.createRandomHash('drive');
- storeHash = hash;
- if (!hash) {
- throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...');
- }
- // No password for drive
- var secret = Hash.getSecrets('drive', hash);
- var listmapConfig = {
- data: {},
- websocketURL: NetConfig.getWebsocketURL(),
- channel: secret.channel,
- readOnly: false,
- validateKey: secret.keys.validateKey || undefined,
- crypto: Crypto.createEncryptor(secret.keys),
- userName: 'fs',
- logLevel: 1,
- ChainPad: ChainPad,
- classic: true,
- };
- var rt = window.rt = Listmap.create(listmapConfig);
- store.proxy = rt.proxy;
- store.loggedIn = typeof(data.userHash) !== "undefined";
-
- var returned = {};
- rt.proxy.on('create', function (info) {
- store.realtime = info.realtime;
- store.network = info.network;
- if (!data.userHash) {
- returned.anonHash = Hash.getEditHashFromKeys(secret);
+ if (Store.channels[chanId].wc) {
+ Store.channels[chanId].wc.leave('');
}
- }).on('ready', function () {
- if (store.userObject) { return; } // the store is already ready, it is a reconnection
- if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; }
- var drive = rt.proxy.drive;
- // Creating a new anon drive: import anon pads from localStorage
- if ((!drive[Constants.oldStorageKey] || !Array.isArray(drive[Constants.oldStorageKey]))
- && !drive['filesData']) {
- drive[Constants.oldStorageKey] = [];
+ delete Store.channels[chanId];
+ };
+ Store._removeClient = function (clientId) {
+ var driveIdx = driveEventClients.indexOf(clientId);
+ if (driveIdx !== -1) {
+ driveEventClients.splice(driveIdx, 1);
}
- postMessage('LOADING_DRIVE', { state: 1 });
- // Drive already exist: return the existing drive, don't load data from legacy store
- onReady(returned, cb);
- })
- .on('change', ['drive', 'migrate'], function () {
- var path = arguments[2];
- var value = arguments[1];
- if (path[0] === 'drive' && path[1] === "migrate" && value === 1) {
- rt.network.disconnect();
- rt.realtime.abort();
- postMessage('NETWORK_DISCONNECT');
+ var messengerIdx = messengerEventClients.indexOf(clientId);
+ if (messengerIdx !== -1) {
+ messengerEventClients.splice(messengerIdx, 1);
}
- });
-
- rt.proxy.on('disconnect', function () {
- postMessage('NETWORK_DISCONNECT');
- });
- rt.proxy.on('reconnect', function (info) {
- postMessage('NETWORK_RECONNECT', {myId: info.myId});
- });
- };
-
- /**
- * Data:
- * - userHash or anonHash
- * Todo in cb
- * - LocalStore.setFSHash if needed
- * - sessionStorage.User_Hash
- * - stuff with tokenKey
- * Event to outer
- * - requestLogin
- */
- var initialized = false;
- Store.init = function (data, callback) {
- if (initialized) {
- return void callback({
- state: 'ALREADY_INIT',
- returned: store.returned
- });
- }
- initialized = true;
- postMessage = function (cmd, d, cb) {
- setTimeout(function () {
- data.query(cmd, d, cb); // TODO temporary, will be replaced by webworker channel
+ Object.keys(Store.channels).forEach(function (chanId) {
+ var chanIdx = Store.channels[chanId].clients.indexOf(clientId);
+ if (chanIdx !== -1) {
+ Store.channels[chanId].clients.splice(chanIdx, 1);
+ }
+ if (Store.channels[chanId].clients.length === 0) {
+ dropChannel(chanId);
+ }
});
};
- store.data = data;
- connect(data, function (ret) {
- if (Object.keys(store.proxy).length === 1) {
- Feedback.send("FIRST_APP_USE", true);
- }
- store.returned = ret;
-
- callback(ret);
+ // Special events
- var messagingCfg = getMessagingCfg();
- Messaging.addDirectMessageHandler(messagingCfg);
-
- // Send events whenever there is a change or a removal in the drive
- if (data.driveEvents) {
+ var driveEventInit = false;
+ sendDriveEvent = function (q, data, sender) {
+ driveEventClients.forEach(function (cId) {
+ if (cId === sender) { return; }
+ postMessage(cId, q, data);
+ });
+ };
+ Store._subscribeToDrive = function (clientId) {
+ if (driveEventClients.indexOf(clientId) === -1) {
+ driveEventClients.push(clientId);
+ }
+ if (!driveEventInit) {
store.proxy.on('change', [], function (o, n, p) {
- postMessage('DRIVE_CHANGE', {
+ sendDriveEvent('DRIVE_CHANGE', {
old: o,
new: n,
path: p
});
});
store.proxy.on('remove', [], function (o, p) {
- postMessage('DRIVE_REMOVE', {
+ sendDriveEvent(clientId, 'DRIVE_REMOVE', {
old: o,
path: p
});
});
+ driveEventInit = true;
}
+ };
- if (data.messenger) {
+ var messengerEventInit = false;
+ var sendMessengerEvent = function (q, data) {
+ messengerEventClients.forEach(function (cId) {
+ postMessage(cId, q, data);
+ });
+ };
+ Store._subscribeToMessenger = function (clientId) {
+ if (messengerEventClients.indexOf(clientId) === -1) {
+ messengerEventClients.push(clientId);
+ }
+ if (!messengerEventInit) {
var messenger = store.messenger = Messenger.messenger(store);
messenger.on('message', function (message) {
- postMessage('CONTACTS_MESSAGE', message);
+ sendMessengerEvent('CONTACTS_MESSAGE', message);
});
messenger.on('join', function (curvePublic, channel) {
- postMessage('CONTACTS_JOIN', {
+ sendMessengerEvent('CONTACTS_JOIN', {
curvePublic: curvePublic,
channel: channel,
});
});
messenger.on('leave', function (curvePublic, channel) {
- postMessage('CONTACTS_LEAVE', {
+ sendMessengerEvent('CONTACTS_LEAVE', {
curvePublic: curvePublic,
channel: channel,
});
});
messenger.on('update', function (info, curvePublic) {
- postMessage('CONTACTS_UPDATE', {
+ sendMessengerEvent('CONTACTS_UPDATE', {
curvePublic: curvePublic,
info: info,
});
});
messenger.on('friend', function (curvePublic) {
- postMessage('CONTACTS_FRIEND', {
+ sendMessengerEvent('CONTACTS_FRIEND', {
curvePublic: curvePublic,
});
});
messenger.on('unfriend', function (curvePublic) {
- postMessage('CONTACTS_UNFRIEND', {
+ sendMessengerEvent('CONTACTS_UNFRIEND', {
curvePublic: curvePublic,
});
});
+ messengerEventInit = true;
}
- });
+ };
+
+
+ //////////////////////////////////////////////////////////////////
+ /////////////////////// Init /////////////////////////////////////
+ //////////////////////////////////////////////////////////////////
+
+ var onReady = function (clientId, returned, cb) {
+ var proxy = store.proxy;
+ var userObject = store.userObject = UserObject.init(proxy.drive, {
+ pinPads: function (data, cb) { Store.pinPads(null, data, cb); },
+ unpinPads: function (data, cb) { Store.unpinPads(null, data, cb); },
+ removeOwnedChannel: function (data, cb) { Store.removeOwnedChannel(null, data, cb); },
+ edPublic: store.proxy.edPublic,
+ loggedIn: store.loggedIn,
+ log: function (msg) {
+ // broadcast to all drive apps
+ sendDriveEvent("DRIVE_LOG", msg);
+ }
+ });
+ nThen(function (waitFor) {
+ postMessage(clientId, 'LOADING_DRIVE', {
+ state: 2
+ });
+ userObject.migrate(waitFor());
+ }).nThen(function (waitFor) {
+ Migrate(proxy, waitFor(), function (version, progress) {
+ postMessage(clientId, 'LOADING_DRIVE', {
+ state: 2,
+ progress: progress
+ });
+ });
+ }).nThen(function () {
+ postMessage(clientId, 'LOADING_DRIVE', {
+ state: 3
+ });
+ userObject.fixFiles();
+
+ var requestLogin = function () {
+ broadcast([], "REQUEST_LOGIN");
+ };
+
+ if (store.loggedIn) {
+ /* This isn't truly secure, since anyone who can read the user's object can
+ set their local loginToken to match that in the object. However, it exposes
+ a UI that will work most of the time. */
+
+ // every user object should have a persistent, random number
+ if (typeof(proxy.loginToken) !== 'number') {
+ proxy[Constants.tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
+ }
+ returned[Constants.tokenKey] = proxy[Constants.tokenKey];
+
+ if (store.data.localToken && store.data.localToken !== proxy[Constants.tokenKey]) {
+ // the local number doesn't match that in
+ // the user object, request that they reauthenticate.
+ return void requestLogin();
+ }
+ }
+
+ if (!proxy.settings || !proxy.settings.general ||
+ typeof(proxy.settings.general.allowUserFeedback) !== 'boolean') {
+ proxy.settings = proxy.settings || {};
+ proxy.settings.general = proxy.settings.general || {};
+ proxy.settings.general.allowUserFeedback = true;
+ }
+ returned.feedback = proxy.settings.general.allowUserFeedback;
+
+ if (typeof(cb) === 'function') { cb(returned); }
+
+ if (typeof(proxy.uid) !== 'string' || proxy.uid.length !== 32) {
+ // even anonymous users should have a persistent, unique-ish id
+ console.log('generating a persistent identifier');
+ proxy.uid = Hash.createChannelId();
+ }
+
+ // if the user is logged in, but does not have signing keys...
+ if (store.loggedIn && (!Store.hasSigningKeys() ||
+ !Store.hasCurveKeys())) {
+ return void requestLogin();
+ }
+
+ proxy.on('change', [Constants.displayNameKey], function (o, n) {
+ if (typeof(n) !== "string") { return; }
+ broadcast([], "UPDATE_METADATA");
+ });
+ proxy.on('change', ['profile'], function () {
+ // Trigger userlist update when the avatar has changed
+ broadcast([], "UPDATE_METADATA");
+ });
+ proxy.on('change', ['friends'], function () {
+ // Trigger userlist update when the friendlist has changed
+ broadcast([], "UPDATE_METADATA");
+ });
+ proxy.on('change', ['settings'], function () {
+ broadcast([], "UPDATE_METADATA");
+ });
+ proxy.on('change', [Constants.tokenKey], function () {
+ broadcast([], "UPDATE_TOKEN", { token: proxy[Constants.tokenKey] });
+ });
+ });
+ };
+
+ var connect = function (clientId, data, cb) {
+ var hash = data.userHash || data.anonHash || Hash.createRandomHash('drive');
+ storeHash = hash;
+ if (!hash) {
+ throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...');
+ }
+ // No password for drive
+ var secret = Hash.getSecrets('drive', hash);
+ var listmapConfig = {
+ data: {},
+ websocketURL: NetConfig.getWebsocketURL(),
+ channel: secret.channel,
+ readOnly: false,
+ validateKey: secret.keys.validateKey || undefined,
+ crypto: Crypto.createEncryptor(secret.keys),
+ userName: 'fs',
+ logLevel: 1,
+ ChainPad: ChainPad,
+ classic: true,
+ };
+ var rt = window.rt = Listmap.create(listmapConfig);
+ store.proxy = rt.proxy;
+ store.loggedIn = typeof(data.userHash) !== "undefined";
+
+ var returned = {};
+ rt.proxy.on('create', function (info) {
+ store.realtime = info.realtime;
+ store.network = info.network;
+ if (!data.userHash) {
+ returned.anonHash = Hash.getEditHashFromKeys(secret);
+ }
+ }).on('ready', function () {
+ if (store.userObject) { return; } // the store is already ready, it is a reconnection
+ if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; }
+ var drive = rt.proxy.drive;
+ // Creating a new anon drive: import anon pads from localStorage
+ if ((!drive[Constants.oldStorageKey] || !Array.isArray(drive[Constants.oldStorageKey]))
+ && !drive['filesData']) {
+ drive[Constants.oldStorageKey] = [];
+ }
+ postMessage(clientId, 'LOADING_DRIVE', { state: 1 });
+ // Drive already exist: return the existing drive, don't load data from legacy store
+ onReady(clientId, returned, cb);
+ })
+ .on('change', ['drive', 'migrate'], function () {
+ var path = arguments[2];
+ var value = arguments[1];
+ if (path[0] === 'drive' && path[1] === "migrate" && value === 1) {
+ rt.network.disconnect();
+ rt.realtime.abort();
+ broadcast([], 'NETWORK_DISCONNECT');
+ }
+ });
+
+ rt.proxy.on('disconnect', function () {
+ broadcast([], 'NETWORK_DISCONNECT');
+ });
+ rt.proxy.on('reconnect', function (info) {
+ broadcast([], 'NETWORK_RECONNECT', {myId: info.myId});
+ });
+ };
+
+ /**
+ * Data:
+ * - userHash or anonHash
+ * Todo in cb
+ * - LocalStore.setFSHash if needed
+ * - sessionStorage.User_Hash
+ * - stuff with tokenKey
+ * Event to outer
+ * - requestLogin
+ */
+ var initialized = false;
+ Store.init = function (clientId, data, callback) {
+ if (initialized) {
+ return void callback({
+ state: 'ALREADY_INIT',
+ returned: store.returned
+ });
+ }
+ initialized = true;
+ postMessage = function (clientId, cmd, d, cb) {
+ data.query(clientId, cmd, d, cb);
+ };
+ broadcast = function (excludes, cmd, d, cb) {
+ data.broadcast(excludes, cmd, d, cb);
+ };
+
+ store.data = data;
+ connect(clientId, data, function (ret) {
+ if (Object.keys(store.proxy).length === 1) {
+ Feedback.send("FIRST_APP_USE", true);
+ }
+ store.returned = ret;
+
+ callback(ret);
+ });
+ };
+
+ Store.disconnect = function () {
+ if (!store.network) { return; }
+ store.network.disconnect();
+ };
+ return Store;
};
- Store.disconnect = function () {
- if (!store.network) { return; }
- store.network.disconnect();
+ return {
+ create: create
};
- return Store;
});
diff --git a/www/common/outer/chainpad-netflux-worker.js b/www/common/outer/chainpad-netflux-worker.js
index be0030ceb..a21b9e293 100644
--- a/www/common/outer/chainpad-netflux-worker.js
+++ b/www/common/outer/chainpad-netflux-worker.js
@@ -22,6 +22,10 @@ define([], function () {
var unBencode = function (str) { return str.replace(/^\d+:/, ''); };
+ var removeCp = function (str) {
+ return str.replace(/^cp\|([A-Za-z0-9+\/=]{0,20}\|)?/, '');
+ };
+
var start = function (conf) {
var channel = conf.channel;
var validateKey = conf.validateKey;
@@ -72,7 +76,7 @@ define([], function () {
// at the beginning of each message on the server.
// We have to make sure our regex ignores this nonce using {0,20} (our IDs
// should only be 8 characters long)
- return msg.replace(/^cp\|([A-Za-z0-9+\/=]{0,20}\|)?/, '');
+ return removeCp(msg);
};
var msgOut = function (msg) {
@@ -124,6 +128,8 @@ define([], function () {
lastKnownHash = msg.slice(0,64);
+
+ var isCp = /^cp\|/.test(msg);
var message = msgIn(peer, msg);
verbose(message);
@@ -134,7 +140,7 @@ define([], function () {
message = unBencode(message);//.slice(message.indexOf(':[') + 1);
// pass the message into Chainpad
- onMessage(peer, message, validateKey);
+ onMessage(peer, message, validateKey, isCp);
//sframeChan.query('Q_RT_MESSAGE', message, function () { });
};
@@ -237,7 +243,6 @@ define([], function () {
};
network.on('disconnect', function (reason) {
- console.log('disconnect');
//if (isIntentionallyLeaving) { return; }
if (reason === "network.disconnect() called") { return; }
onDisconnect();
@@ -260,7 +265,8 @@ define([], function () {
};
return {
- start: start
+ start: start,
+ removeCp: removeCp
/*function (config) {
config.sframeChan.whenReg('EV_RT_READY', function () {
start(config);
diff --git a/www/common/outer/noworker.js b/www/common/outer/noworker.js
new file mode 100644
index 000000000..a412f2da0
--- /dev/null
+++ b/www/common/outer/noworker.js
@@ -0,0 +1,103 @@
+define([
+ '/common/common-util.js',
+ '/common/outer/worker-channel.js',
+ '/common/outer/store-rpc.js',
+], function (Util, Channel, SRpc) {
+
+ var msgEv = Util.mkEvent();
+ var sendMsg = Util.mkEvent();
+ var create = function () {
+ var Rpc = SRpc();
+
+ var postMessage = function (data) {
+ sendMsg.fire(data);
+ };
+
+ Channel.create(msgEv, postMessage, function (chan) {
+ var clientId = '1';
+ Object.keys(Rpc.queries).forEach(function (q) {
+ if (q === 'CONNECT') { return; }
+ if (q === 'JOIN_PAD') { return; }
+ if (q === 'SEND_PAD_MSG') { return; }
+ chan.on(q, function (data, cb) {
+ try {
+ Rpc.queries[q](clientId, data, cb);
+ } catch (e) {
+ console.error('Error in webworker when executing query ' + q);
+ console.error(e);
+ console.log(data);
+ }
+ });
+ });
+ chan.on('CONNECT', function (cfg, cb) {
+ // load Store here, with cfg, and pass a "query" (chan.query)
+ // cId is a clientId used in ServiceWorker or SharedWorker
+ cfg.query = function (cId, cmd, data, cb) {
+ cb = cb || function () {};
+ chan.query(cmd, data, function (err, data2) {
+ if (err) { return void cb({error: err}); }
+ cb(data2);
+ });
+ };
+ cfg.broadcast = function (excludes, cmd, data, cb) {
+ cb = cb || function () {};
+ if (excludes.indexOf(clientId) !== -1) { return; }
+ chan.query(cmd, data, function (err, data2) {
+ if (err) { return void cb({error: err}); }
+ cb(data2);
+ });
+ };
+ Rpc.queries['CONNECT'](clientId, cfg, function (data) {
+ if (data && data.state === "ALREADY_INIT") {
+ return void cb(data);
+ }
+ if (cfg.driveEvents) {
+ Rpc._subscribeToDrive(clientId);
+ }
+ if (cfg.messenger) {
+ Rpc._subscribeToMessenger(clientId);
+ }
+ cb(data);
+ });
+ });
+ var chanId;
+ chan.on('JOIN_PAD', function (data, cb) {
+ chanId = data.channel;
+ try {
+ Rpc.queries['JOIN_PAD'](clientId, data, cb);
+ } catch (e) {
+ console.error('Error in webworker when executing query JOIN_PAD');
+ console.error(e);
+ console.log(data);
+ }
+ });
+ chan.on('SEND_PAD_MSG', function (msg, cb) {
+ var data = {
+ msg: msg,
+ channel: chanId
+ };
+ try {
+ Rpc.queries['SEND_PAD_MSG'](clientId, data, cb);
+ } catch (e) {
+ console.error('Error in webworker when executing query SEND_PAD_MSG');
+ console.error(e);
+ console.log(data);
+ }
+ });
+ }, true);
+ };
+
+ return {
+ query: function (data) {
+ msgEv.fire({data: data});
+ },
+ onMessage: function (cb) {
+ sendMsg.reg(function (data) {
+ setTimeout(function () {
+ cb(data);
+ });
+ });
+ },
+ create: create
+ };
+});
diff --git a/www/common/outer/serviceworker.js b/www/common/outer/serviceworker.js
new file mode 100644
index 000000000..68afb4f32
--- /dev/null
+++ b/www/common/outer/serviceworker.js
@@ -0,0 +1,175 @@
+/* jshint ignore:start */
+importScripts('/bower_components/requirejs/require.js');
+
+window = self;
+localStorage = {
+ setItem: function (k, v) { localStorage[k] = v; },
+ getItem: function (k) { return localStorage[k]; }
+};
+
+self.tabs = {};
+
+var postMsg = function (client, data) {
+ client.postMessage(data);
+};
+
+var debug = function (msg) { console.log(msg); };
+// debug = function () {};
+
+var init = function (client, cb) {
+ debug('SW INIT');
+
+ require([
+ '/common/requireconfig.js'
+ ], function (RequireConfig) {
+ require.config(RequireConfig());
+ require([
+ '/common/common-util.js',
+ '/common/outer/worker-channel.js',
+ '/common/outer/store-rpc.js'
+ ], function (Util, Channel, SRpc) {
+ debug('SW Required ressources loaded');
+ var msgEv = Util.mkEvent();
+
+ if (!self.Rpc) {
+ self.Rpc = SRpc();
+ }
+ var Rpc = self.Rpc;
+
+ var postToClient = function (data) {
+ postMsg(client, data);
+ };
+ Channel.create(msgEv, postToClient, function (chan) {
+ debug('SW Channel created');
+
+ var clientId = client.id;
+ self.tabs[clientId].chan = chan;
+ Object.keys(Rpc.queries).forEach(function (q) {
+ if (q === 'CONNECT') { return; }
+ if (q === 'JOIN_PAD') { return; }
+ if (q === 'SEND_PAD_MSG') { return; }
+ chan.on(q, function (data, cb) {
+ try {
+ Rpc.queries[q](clientId, data, cb);
+ } catch (e) {
+ console.error('Error in webworker when executing query ' + q);
+ console.error(e);
+ console.log(data);
+ }
+ if (q === "DISCONNECT") {
+ console.log('Deleting existing store!');
+ delete self.Rpc;
+ delete self.store;
+ }
+ });
+ });
+ chan.on('CONNECT', function (cfg, cb) {
+ debug('SW Connect callback');
+ if (self.store) {
+ debug('Store already exists!');
+ if (cfg.driveEvents) {
+ Rpc._subscribeToDrive(clientId);
+ }
+ if (cfg.messenger) {
+ Rpc._subscribeToMessenger(clientId);
+ }
+ return void cb(self.store);
+ }
+
+ debug('Loading new async store');
+ // One-time initialization (init async-store)
+ cfg.query = function (cId, cmd, data, cb) {
+ cb = cb || function () {};
+ self.tabs[cId].chan.query(cmd, data, function (err, data2) {
+ if (err) { return void cb({error: err}); }
+ cb(data2);
+ });
+ };
+ cfg.broadcast = function (excludes, cmd, data, cb) {
+ cb = cb || function () {};
+ Object.keys(self.tabs).forEach(function (cId) {
+ if (excludes.indexOf(cId) !== -1) { return; }
+ self.tabs[cId].chan.query(cmd, data, function (err, data2) {
+ if (err) { return void cb({error: err}); }
+ cb(data2);
+ });
+ });
+ };
+ Rpc.queries['CONNECT'](clientId, cfg, function (data) {
+ if (cfg.driveEvents) {
+ Rpc._subscribeToDrive(clientId);
+ }
+ if (cfg.messenger) {
+ Rpc._subscribeToMessenger(clientId);
+ }
+ if (data && data.state === "ALREADY_INIT") {
+ return void cb(data.returned);
+ }
+ self.store = data;
+ cb(data);
+ });
+ });
+ chan.on('JOIN_PAD', function (data, cb) {
+ self.tabs[clientId].channelId = data.channel;
+ try {
+ Rpc.queries['JOIN_PAD'](clientId, data, cb);
+ } catch (e) {
+ console.error('Error in webworker when executing query JOIN_PAD');
+ console.error(e);
+ console.log(data);
+ }
+ });
+ chan.on('SEND_PAD_MSG', function (msg, cb) {
+ var data = {
+ msg: msg,
+ channel: self.tabs[clientId].channelId
+ };
+ try {
+ Rpc.queries['SEND_PAD_MSG'](clientId, data, cb);
+ } catch (e) {
+ console.error('Error in webworker when executing query SEND_PAD_MSG');
+ console.error(e);
+ console.log(data);
+ }
+ });
+ cb();
+ }, true);
+
+ self.tabs[client.id].msgEv = msgEv;
+
+ self.tabs[client.id].close = function () {
+ Rpc._removeClient(client.id);
+ };
+ });
+ });
+};
+
+self.addEventListener('message', function (e) {
+ var cId = e.source.id;
+ if (e.data === "INIT") {
+ if (tabs[cId]) { return; }
+ tabs[cId] = {
+ client: e.source
+ };
+ init(e.source, function () {
+ postMsg(e.source, 'SW_READY');
+ });
+ } else if (e.data === "CLOSE") {
+ if (tabs[cId] && tabs[cId].close) {
+ console.log('leave');
+ tabs[cId].close();
+ }
+ } else if (self.tabs[cId] && self.tabs[cId].msgEv) {
+ self.tabs[cId].msgEv.fire(e);
+ }
+});
+self.addEventListener('install', function (e) {
+ debug('V1 installing…');
+ self.skipWaiting();
+});
+
+self.addEventListener('activate', function (e) {
+ debug('V1 now ready to handle fetches!');
+});
+
+
diff --git a/www/common/outer/sharedworker.js b/www/common/outer/sharedworker.js
new file mode 100644
index 000000000..97686da24
--- /dev/null
+++ b/www/common/outer/sharedworker.js
@@ -0,0 +1,174 @@
+/* jshint ignore:start */
+importScripts('/bower_components/requirejs/require.js');
+
+window = self;
+localStorage = {
+ setItem: function (k, v) { localStorage[k] = v; },
+ getItem: function (k) { return localStorage[k]; }
+};
+
+self.tabs = {};
+
+var postMsg = function (client, data) {
+ client.port.postMessage(data);
+};
+
+var debug = function (msg) { console.log(msg); };
+// debug = function () {};
+
+var init = function (client, cb) {
+ debug('SharedW INIT');
+
+ require([
+ '/common/requireconfig.js'
+ ], function (RequireConfig) {
+ require.config(RequireConfig());
+ require([
+ '/common/common-util.js',
+ '/common/outer/worker-channel.js',
+ '/common/outer/store-rpc.js'
+ ], function (Util, Channel, SRpc) {
+ debug('SharedW Required ressources loaded');
+ var msgEv = Util.mkEvent();
+
+ if (!self.Rpc) {
+ self.Rpc = SRpc();
+ }
+ var Rpc = self.Rpc;
+
+ var postToClient = function (data) {
+ postMsg(client, data);
+ };
+ Channel.create(msgEv, postToClient, function (chan) {
+ debug('SharedW Channel created');
+
+ var clientId = client.id;
+ client.chan = chan;
+ Object.keys(Rpc.queries).forEach(function (q) {
+ if (q === 'CONNECT') { return; }
+ if (q === 'JOIN_PAD') { return; }
+ if (q === 'SEND_PAD_MSG') { return; }
+ chan.on(q, function (data, cb) {
+ try {
+ Rpc.queries[q](clientId, data, cb);
+ } catch (e) {
+ console.error('Error in webworker when executing query ' + q);
+ console.error(e);
+ console.log(data);
+ }
+ if (q === "DISCONNECT") {
+ console.log('Deleting existing store!');
+ delete self.Rpc;
+ delete self.store;
+ }
+ });
+ });
+ chan.on('CONNECT', function (cfg, cb) {
+ debug('SharedW connecting to store...');
+ if (self.store) {
+ debug('Store already exists!');
+ if (cfg.driveEvents) {
+ Rpc._subscribeToDrive(clientId);
+ }
+ if (cfg.messenger) {
+ Rpc._subscribeToMessenger(clientId);
+ }
+ return void cb(self.store);
+ }
+
+ debug('Loading new async store');
+ // One-time initialization (init async-store)
+ cfg.query = function (cId, cmd, data, cb) {
+ cb = cb || function () {};
+ self.tabs[cId].chan.query(cmd, data, function (err, data2) {
+ if (err) { return void cb({error: err}); }
+ cb(data2);
+ });
+ };
+ cfg.broadcast = function (excludes, cmd, data, cb) {
+ cb = cb || function () {};
+ Object.keys(self.tabs).forEach(function (cId) {
+ if (excludes.indexOf(cId) !== -1) { return; }
+ self.tabs[cId].chan.query(cmd, data, function (err, data2) {
+ if (err) { return void cb({error: err}); }
+ cb(data2);
+ });
+ });
+ };
+ Rpc.queries['CONNECT'](clientId, cfg, function (data) {
+ if (cfg.driveEvents) {
+ Rpc._subscribeToDrive(clientId);
+ }
+ if (cfg.messenger) {
+ Rpc._subscribeToMessenger(clientId);
+ }
+ if (data && data.state === "ALREADY_INIT") {
+ self.store = data.returned;
+ return void cb(data.returned);
+ }
+ self.store = data;
+ cb(data);
+ });
+ });
+ chan.on('JOIN_PAD', function (data, cb) {
+ client.channelId = data.channel;
+ try {
+ Rpc.queries['JOIN_PAD'](clientId, data, cb);
+ } catch (e) {
+ console.error('Error in webworker when executing query JOIN_PAD');
+ console.error(e);
+ console.log(data);
+ }
+ });
+ chan.on('SEND_PAD_MSG', function (msg, cb) {
+ var data = {
+ msg: msg,
+ channel: client.channelId
+ };
+ try {
+ Rpc.queries['SEND_PAD_MSG'](clientId, data, cb);
+ } catch (e) {
+ console.error('Error in webworker when executing query SEND_PAD_MSG');
+ console.error(e);
+ console.log(data);
+ }
+ });
+ cb();
+ }, true);
+
+ client.msgEv = msgEv;
+
+ client.close = function () {
+ Rpc._removeClient(client.id);
+ };
+ });
+ });
+};
+
+onconnect = function(e) {
+ debug('New SharedWorker client');
+ var port = e.ports[0];
+ var cId = Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
+ var client = self.tabs[cId] = {
+ id: cId,
+ port: port
+ };
+
+ port.onmessage = function (e) {
+ if (e.data === "INIT") {
+ if (client.init) { return; }
+ client.init = true;
+ init(client, function () {
+ postMsg(client, 'SW_READY');
+ });
+ } else if (e.data === "CLOSE") {
+ if (client && client.close) {
+ console.log('leave');
+ client.close();
+ }
+ } else if (client && client.msgEv) {
+ client.msgEv.fire(e);
+ }
+ };
+};
+
diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js
index b134435b2..972776abe 100644
--- a/www/common/outer/store-rpc.js
+++ b/www/common/outer/store-rpc.js
@@ -1,193 +1,99 @@
define([
'/common/outer/async-store.js'
-], function (Store) {
- var Rpc = {};
+], function (AStore) {
- Rpc.query = function (cmd, data, cb) {
- switch (cmd) {
- // READY
- case 'CONNECT': {
- Store.init(data, cb); break;
- }
- case 'DISCONNECT': {
- Store.disconnect(data, cb); break;
- }
- case 'CREATE_README': {
- Store.createReadme(data, cb); break;
- }
- case 'MIGRATE_ANON_DRIVE': {
- Store.migrateAnonDrive(data, cb); break;
- }
+ var create = function () {
+ var Store = AStore.create();
+
+ var Rpc = {};
+
+ var queries = Rpc.queries = {
+ // Ready
+ CONNECT: Store.init,
+ DISCONNECT: Store.disconnect,
+ CREATE_README: Store.createReadme,
+ MIGRATE_ANON_DRIVE: Store.migrateAnonDrive,
// RPC
- case 'INIT_RPC': {
- Store.initRpc(data, cb); break;
- }
- case 'UPDATE_PIN_LIMIT': {
- Store.updatePinLimit(data, cb); break;
- }
- case 'GET_PIN_LIMIT': {
- Store.getPinLimit(data, cb); break;
- }
- case 'CLEAR_OWNED_CHANNEL': {
- Store.clearOwnedChannel(data, cb); break;
- }
- case 'REMOVE_OWNED_CHANNEL': {
- Store.removeOwnedChannel(data, cb); break;
- }
- case 'UPLOAD_CHUNK': {
- Store.uploadChunk(data, cb); break;
- }
- case 'UPLOAD_COMPLETE': {
- Store.uploadComplete(data, cb); break;
- }
- case 'UPLOAD_STATUS': {
- Store.uploadStatus(data, cb); break;
- }
- case 'UPLOAD_CANCEL': {
- Store.uploadCancel(data, cb); break;
- }
- case 'PIN_PADS': {
- Store.pinPads(data, cb); break;
- }
- case 'UNPIN_PADS': {
- Store.unpinPads(data, cb); break;
- }
- case 'GET_DELETED_PADS': {
- Store.getDeletedPads(data, cb); break;
- }
- case 'GET_PINNED_USAGE': {
- Store.getPinnedUsage(data, cb); break;
- }
+ INIT_RPC: Store.initRpc,
+ UPDATE_PIN_LIMIT: Store.updatePinLimit,
+ GET_PIN_LIMIT: Store.getPinLimit,
+ CLEAR_OWNED_CHANNEL: Store.clearOwnedChannel,
+ REMOVE_OWNED_CHANNEL: Store.removeOwnedChannel,
+ UPLOAD_CHUNK: Store.uploadChunk,
+ UPLOAD_COMPLETE: Store.uploadComplete,
+ UPLOAD_STATUS: Store.uploadStatus,
+ UPLOAD_CANCEL: Store.uploadCancel,
+ WRITE_LOGIN_BLOCK: Store.writeLoginBlock,
+ PIN_PADS: Store.pinPads,
+ UNPIN_PADS: Store.unpinPads,
+ GET_DELETED_PADS: Store.getDeletedPads,
+ GET_PINNED_USAGE: Store.getPinnedUsage,
// ANON RPC
- case 'INIT_ANON_RPC': {
- Store.initAnonRpc(data, cb); break;
- }
- case 'ANON_RPC_MESSAGE': {
- Store.anonRpcMsg(data, cb); break;
- }
- case 'GET_FILE_SIZE': {
- Store.getFileSize(data, cb); break;
- }
- case 'GET_MULTIPLE_FILE_SIZE': {
- Store.getMultipleFileSize(data, cb); break;
- }
+ INIT_ANON_RPC: Store.initAnonRpc,
+ ANON_RPC_MESSAGE: Store.anonRpcMsg,
+ GET_FILE_SIZE: Store.getFileSize,
+ GET_MULTIPLE_FILE_SIZE: Store.getMultipleFileSize,
// Store
- case 'GET': {
- Store.get(data, cb); break;
- }
- case 'SET': {
- Store.set(data, cb); break;
- }
- case 'ADD_PAD': {
- Store.addPad(data, cb); break;
- }
- case 'SET_PAD_TITLE': {
- Store.setPadTitle(data, cb); break;
- }
- case 'MOVE_TO_TRASH': {
- Store.moveToTrash(data, cb); break;
- }
- case 'RESET_DRIVE': {
- Store.resetDrive(data, cb); break;
- }
- case 'GET_METADATA': {
- Store.getMetadata(data, cb); break;
- }
- case 'SET_DISPLAY_NAME': {
- Store.setDisplayName(data, cb); break;
- }
- case 'SET_PAD_ATTRIBUTE': {
- Store.setPadAttribute(data, cb); break;
- }
- case 'GET_PAD_ATTRIBUTE': {
- Store.getPadAttribute(data, cb); break;
- }
- case 'SET_ATTRIBUTE': {
- Store.setAttribute(data, cb); break;
- }
- case 'GET_ATTRIBUTE': {
- Store.getAttribute(data, cb); break;
- }
- case 'LIST_ALL_TAGS': {
- Store.listAllTags(data, cb); break;
- }
- case 'GET_TEMPLATES': {
- Store.getTemplates(data, cb); break;
- }
- case 'GET_SECURE_FILES_LIST': {
- Store.getSecureFilesList(data, cb); break;
- }
- case 'GET_PAD_DATA': {
- Store.getPadData(data, cb); break;
- }
- case 'GET_STRONGER_HASH': {
- Store.getStrongerHash(data, cb); break;
- }
- case 'INCREMENT_TEMPLATE_USE': {
- Store.incrementTemplateUse(data); break;
- }
+ GET: Store.get,
+ SET: Store.set,
+ ADD_PAD: Store.addPad,
+ SET_PAD_TITLE: Store.setPadTitle,
+ MOVE_TO_TRASH: Store.moveToTrash,
+ RESET_DRIVE: Store.resetDrive,
+ GET_METADATA: Store.getMetadata,
+ SET_DISPLAY_NAME: Store.setDisplayName,
+ SET_PAD_ATTRIBUTE: Store.setPadAttribute,
+ GET_PAD_ATTRIBUTE: Store.getPadAttribute,
+ SET_ATTRIBUTE: Store.setAttribute,
+ GET_ATTRIBUTE: Store.getAttribute,
+ LIST_ALL_TAGS: Store.listAllTags,
+ GET_TEMPLATES: Store.getTemplates,
+ GET_SECURE_FILES_LIST: Store.getSecureFilesList,
+ GET_PAD_DATA: Store.getPadData,
+ GET_STRONGER_HASH: Store.getStrongerHash,
+ INCREMENT_TEMPLATE_USE: Store.incrementTemplateUse,
// Messaging
- case 'INVITE_FROM_USERLIST': {
- Store.inviteFromUserlist(data, cb); break;
- }
+ INVITE_FROM_USERLIST: Store.inviteFromUserlist,
+ ADD_DIRECT_MESSAGE_HANDLERS: Store.addDirectMessageHandlers,
// Messenger
- case 'CONTACTS_GET_FRIEND_LIST': {
- Store.messenger.getFriendList(data, cb); break;
- }
- case 'CONTACTS_GET_MY_INFO': {
- Store.messenger.getMyInfo(data, cb); break;
- }
- case 'CONTACTS_GET_FRIEND_INFO': {
- Store.messenger.getFriendInfo(data, cb); break;
- }
- case 'CONTACTS_REMOVE_FRIEND': {
- Store.messenger.removeFriend(data, cb); break;
- }
- case 'CONTACTS_OPEN_FRIEND_CHANNEL': {
- Store.messenger.openFriendChannel(data, cb); break;
- }
- case 'CONTACTS_GET_FRIEND_STATUS': {
- Store.messenger.getFriendStatus(data, cb); break;
- }
- case 'CONTACTS_GET_MORE_HISTORY': {
- Store.messenger.getMoreHistory(data, cb); break;
- }
- case 'CONTACTS_SEND_MESSAGE': {
- Store.messenger.sendMessage(data, cb); break;
- }
- case 'CONTACTS_SET_CHANNEL_HEAD': {
- Store.messenger.setChannelHead(data, cb); break;
- }
+ CONTACTS_GET_FRIEND_LIST: Store.messenger.getFriendList,
+ CONTACTS_GET_MY_INFO: Store.messenger.getMyInfo,
+ CONTACTS_GET_FRIEND_INFO: Store.messenger.getFriendInfo,
+ CONTACTS_REMOVE_FRIEND: Store.messenger.removeFriend,
+ CONTACTS_OPEN_FRIEND_CHANNEL: Store.messenger.openFriendChannel,
+ CONTACTS_GET_FRIEND_STATUS: Store.messenger.getFriendStatus,
+ CONTACTS_GET_MORE_HISTORY: Store.messenger.getMoreHistory,
+ CONTACTS_SEND_MESSAGE: Store.messenger.sendMessage,
+ CONTACTS_SET_CHANNEL_HEAD: Store.messenger.setChannelHead,
// Pad
- case 'SEND_PAD_MSG': {
- Store.sendPadMsg(data, cb); break;
- }
- case 'JOIN_PAD': {
- Store.joinPad(data, cb); break;
- }
- case 'GET_FULL_HISTORY': {
- Store.getFullHistory(data, cb); break;
- }
+ SEND_PAD_MSG: Store.sendPadMsg,
+ JOIN_PAD: Store.joinPad,
+ LEAVE_PAD: Store.leavePad,
+ GET_FULL_HISTORY: Store.getFullHistory,
+ GET_HISTORY_RANGE: Store.getHistoryRange,
+ IS_NEW_CHANNEL: Store.isNewChannel,
// Drive
- case 'DRIVE_USEROBJECT': {
- Store.userObjectCommand(data, cb); break;
- }
- // Settings
- case 'DELETE_ACCOUNT': {
- Store.deleteAccount(data, cb); break;
- }
- case 'IS_NEW_CHANNEL': {
- Store.isNewChannel(data, cb); break;
- }
- default: {
- console.error("UNHANDLED_STORE_RPC");
+ DRIVE_USEROBJECT: Store.userObjectCommand,
+ // Settings,
+ DELETE_ACCOUNT: Store.deleteAccount,
+ };
- break;
+ Rpc.query = function (cmd, data, cb) {
+ if (queries[cmd]) {
+ queries[cmd]('0', data, cb);
+ } else {
+ console.error('UNHANDLED_STORE_RPC');
}
- }
+ };
+
+ // Internal calls
+ Rpc._removeClient = Store._removeClient;
+ Rpc._subscribeToDrive = Store._subscribeToDrive;
+ Rpc._subscribeToMessenger = Store._subscribeToMessenger;
+ return Rpc;
};
- return Rpc;
+ return create;
});
diff --git a/www/common/outer/upload.js b/www/common/outer/upload.js
index 65f674517..ef571af57 100644
--- a/www/common/outer/upload.js
+++ b/www/common/outer/upload.js
@@ -11,9 +11,7 @@ define([
var u8 = file.blob; // This is not a blob but a uint8array
var metadata = file.metadata;
- var owned = file.isOwned;
-// XXX
-owned = true;
+ var owned = file.owned;
// if it exists, path contains the new pad location in the drive
var path = file.path;
diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js
index 8b2270797..25d0629a9 100644
--- a/www/common/outer/userObject.js
+++ b/www/common/outer/userObject.js
@@ -41,6 +41,7 @@ define([
if (!attr || !attr.trim()) { return void cb("E_INVAL_ATTR"); }
var data = exp.getFileData(id);
data[attr] = clone(value);
+ console.log(data);
cb(null);
};
exp.getPadAttribute = function (href, attr, cb) {
@@ -111,7 +112,7 @@ define([
// RPC may not be responding
// Send a report that can be handled manually
console.error(obj.error);
- Feedback.send('ERROR_DELETING_OWNED_PAD=' + channelId, true);
+ Feedback.send('ERROR_DELETING_OWNED_PAD=' + channelId + '|' + obj.error, true);
}
});
}
@@ -426,7 +427,7 @@ define([
migrateToNewFormat(cb);
};
- exp.fixFiles = function () {
+ exp.fixFiles = function (silent) {
// Explore the tree and check that everything is correct:
// * 'root', 'trash', 'unsorted' and 'filesData' exist and are objects
// * ROOT: Folders are objects, files are href
@@ -435,6 +436,9 @@ define([
// - Dates (adate, cdate) can be parsed/formatted
// - All files in filesData should be either in 'root', 'trash' or 'unsorted'. If that's not the case, copy the fily to 'unsorted'
// * TEMPLATE: Contains only files (href), and does not contains files that are in ROOT
+
+ if (silent) { debug = function () {}; }
+
debug("Cleaning file system...");
var before = JSON.stringify(files);
diff --git a/www/common/outer/webworker.js b/www/common/outer/webworker.js
new file mode 100644
index 000000000..201f3db9e
--- /dev/null
+++ b/www/common/outer/webworker.js
@@ -0,0 +1,100 @@
+/* jshint ignore:start */
+importScripts('/bower_components/requirejs/require.js');
+
+window = self;
+localStorage = {
+ setItem: function (k, v) { localStorage[k] = v; },
+ getItem: function (k) { return localStorage[k]; }
+};
+
+require([
+ '/common/requireconfig.js'
+], function (RequireConfig) {
+ require.config(RequireConfig());
+ require([
+ '/common/common-util.js',
+ '/common/outer/worker-channel.js',
+ '/common/outer/store-rpc.js'
+ ], function (Util, Channel, SRpc) {
+ var msgEv = Util.mkEvent();
+
+ var Rpc = SRpc();
+
+ Channel.create(msgEv, postMessage, function (chan) {
+ var clientId = '1';
+ Object.keys(Rpc.queries).forEach(function (q) {
+ if (q === 'CONNECT') { return; }
+ if (q === 'JOIN_PAD') { return; }
+ if (q === 'SEND_PAD_MSG') { return; }
+ chan.on(q, function (data, cb) {
+ try {
+ Rpc.queries[q](clientId, data, cb);
+ } catch (e) {
+ console.error('Error in webworker when executing query ' + q);
+ console.error(e);
+ console.log(data);
+ }
+ });
+ });
+ chan.on('CONNECT', function (cfg, cb) {
+ // load Store here, with cfg, and pass a "query" (chan.query)
+ // cId is a clientId used in ServiceWorker or SharedWorker
+ cfg.query = function (cId, cmd, data, cb) {
+ cb = cb || function () {};
+ chan.query(cmd, data, function (err, data2) {
+ if (err) { return void cb({error: err}); }
+ cb(data2);
+ });
+ };
+ cfg.broadcast = function (excludes, cmd, data, cb) {
+ cb = cb || function () {};
+ if (excludes.indexOf(clientId) !== -1) { return; }
+ chan.query(cmd, data, function (err, data2) {
+ if (err) { return void cb({error: err}); }
+ cb(data2);
+ });
+ };
+ Rpc.queries['CONNECT'](clientId, cfg, function (data) {
+ if (data && data.state === "ALREADY_INIT") {
+ return void cb(data);
+ }
+ if (cfg.driveEvents) {
+ Rpc._subscribeToDrive(clientId);
+ }
+ if (cfg.messenger) {
+ Rpc._subscribeToMessenger(clientId);
+ }
+ cb(data);
+ });
+ });
+ var chanId;
+ chan.on('JOIN_PAD', function (data, cb) {
+ chanId = data.channel;
+ try {
+ Rpc.queries['JOIN_PAD'](clientId, data, cb);
+ } catch (e) {
+ console.error('Error in webworker when executing query JOIN_PAD');
+ console.error(e);
+ console.log(data);
+ }
+ });
+ chan.on('SEND_PAD_MSG', function (msg, cb) {
+ var data = {
+ msg: msg,
+ channel: chanId
+ };
+ try {
+ Rpc.queries['SEND_PAD_MSG'](clientId, data, cb);
+ } catch (e) {
+ console.error('Error in webworker when executing query SEND_PAD_MSG');
+ console.error(e);
+ console.log(data);
+ }
+ });
+ }, true);
+
+ onmessage = function (e) {
+ msgEv.fire(e);
+ };
+ });
+});
diff --git a/www/common/outer/worker-channel.js b/www/common/outer/worker-channel.js
new file mode 100644
index 000000000..30148fc40
--- /dev/null
+++ b/www/common/outer/worker-channel.js
@@ -0,0 +1,149 @@
+// This file provides the API for the channel for talking to and from the sandbox iframe.
+define([
+ //'/common/sframe-protocol.js',
+ '/common/common-util.js'
+], function (/*SFrameProtocol,*/ Util) {
+
+ var mkTxid = function () {
+ return Math.random().toString(16).replace('0.', '') + Math.random().toString(16).replace('0.', '');
+ };
+
+ var create = function (onMsg, postMsg, cb, isWorker) {
+ var chanLoaded;
+ var waitingData;
+ if (!isWorker) {
+ chanLoaded = false;
+ waitingData = [];
+ onMsg.reg(function (data) {
+ if (chanLoaded) { return; }
+ waitingData.push(data);
+ });
+ }
+
+ var evReady = Util.mkEvent(true);
+
+ var handlers = {};
+ var queries = {};
+
+ // list of handlers which are registered from the other side...
+ var insideHandlers = [];
+ var callWhenRegistered = {};
+
+ var chan = {};
+
+ // Send a query. channel.query('Q_SOMETHING', { args: "whatever" }, function (reply) { ... });
+ chan.query = function (q, content, cb) {
+ var txid = mkTxid();
+ var timeout = setTimeout(function () {
+ delete queries[txid];
+ //console.log("Timeout making query " + q);
+ }, 30000);
+ queries[txid] = function (data, msg) {
+ clearTimeout(timeout);
+ delete queries[txid];
+ cb(undefined, data.content, msg);
+ };
+ evReady.reg(function () {
+ postMsg(JSON.stringify({
+ txid: txid,
+ content: content,
+ q: q
+ }));
+ });
+ };
+
+ // Fire an event. channel.event('EV_SOMETHING', { args: "whatever" });
+ var event = chan.event = function (e, content) {
+ evReady.reg(function () {
+ postMsg(JSON.stringify({ content: content, q: e }));
+ });
+ };
+
+ // Be notified on query or event. channel.on('EV_SOMETHING', function (args, reply) { ... });
+ // If the type is a query, your handler will be invoked with a reply function that takes
+ // one argument (the content to reply with).
+ chan.on = function (queryType, handler, quiet) {
+ (handlers[queryType] = handlers[queryType] || []).push(function (data, msg) {
+ handler(data.content, function (replyContent) {
+ postMsg(JSON.stringify({
+ txid: data.txid,
+ content: replyContent
+ }));
+ }, msg);
+ });
+ if (!quiet) {
+ event('EV_REGISTER_HANDLER', queryType);
+ }
+ };
+
+ // If a particular handler is registered, call the callback immediately, otherwise it will be called
+ // when that handler is first registered.
+ // channel.whenReg('Q_SOMETHING', function () { ...query Q_SOMETHING?... });
+ chan.whenReg = function (queryType, cb, always) {
+ var reg = always;
+ if (insideHandlers.indexOf(queryType) > -1) {
+ cb();
+ } else {
+ reg = true;
+ }
+ if (reg) {
+ (callWhenRegistered[queryType] = callWhenRegistered[queryType] || []).push(cb);
+ }
+ };
+
+ // Same as whenReg except it will invoke every time there is another registration, not just once.
+ chan.onReg = function (queryType, cb) { chan.whenReg(queryType, cb, true); };
+
+ chan.on('EV_REGISTER_HANDLER', function (content) {
+ if (callWhenRegistered[content]) {
+ callWhenRegistered[content].forEach(function (f) { f(); });
+ delete callWhenRegistered[content];
+ }
+ insideHandlers.push(content);
+ });
+ chan.whenReg('EV_REGISTER_HANDLER', evReady.fire);
+
+ // Make sure both iframes are ready
+ var isReady =false;
+ chan.onReady = function (h) {
+ if (isReady) {
+ return void h();
+ }
+ if (typeof(h) !== "function") { return; }
+ chan.on('EV_RPC_READY', function () { isReady = true; h(); });
+ };
+ chan.ready = function () {
+ chan.whenReg('EV_RPC_READY', function () {
+ chan.event('EV_RPC_READY');
+ });
+ };
+
+ onMsg.reg(function (msg) {
+ var data = JSON.parse(msg.data);
+ if (typeof(data.q) === 'string' && handlers[data.q]) {
+ handlers[data.q].forEach(function (f) {
+ f(data || JSON.parse(msg.data), msg);
+ data = undefined;
+ });
+ } else if (typeof(data.q) === 'undefined' && queries[data.txid]) {
+ queries[data.txid](data, msg);
+ } else {
+ console.log("DROP Unhandled message");
+ console.log(msg.data, isWorker);
+ console.log(msg);
+ }
+ });
+ if (isWorker) {
+ evReady.fire();
+ } else {
+ chanLoaded = true;
+ waitingData.forEach(function (d) {
+ onMsg.fire(d);
+ });
+ waitingData = [];
+ }
+ cb(chan);
+ };
+
+ return { create: create };
+});
diff --git a/www/common/pinpad.js b/www/common/pinpad.js
index 957f5d1d6..1e4dd6046 100644
--- a/www/common/pinpad.js
+++ b/www/common/pinpad.js
@@ -221,6 +221,10 @@ define([
});
};
+ exp.writeLoginBlock = function (data, cb) {
+ cb();
+ };
+
cb(e, exp);
});
};
diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js
index 0e3a326d9..6bd967ffe 100644
--- a/www/common/sframe-app-framework.js
+++ b/www/common/sframe-app-framework.js
@@ -140,6 +140,11 @@ define([
toolbar.initializing();
return;
}
+ if (text) {
+ // text is a boolean here. It means we won't try to reconnect
+ toolbar.failed();
+ return;
+ }
toolbar.reconnecting();
});
break;
@@ -171,9 +176,12 @@ define([
}
};
- var contentUpdate = function (newContent) {
+ var oldContent;
+ var contentUpdate = function (newContent, waitFor) {
+ if (JSONSortify(newContent) === JSONSortify(oldContent)) { return; }
try {
- evContentUpdate.fire(newContent);
+ evContentUpdate.fire(newContent, waitFor);
+ setTimeout(function () { oldContent = newContent; });
} catch (e) {
console.log(e.stack);
UI.errorLoadingScreen(e.message);
@@ -192,46 +200,48 @@ define([
cpNfInner.metadataMgr.updateMetadata(meta);
newContent = normalize(newContent);
- contentUpdate(newContent);
-
- if (!readOnly) {
- var newContent2NoMeta = normalize(contentGetter());
- var newContent2StrNoMeta = JSONSortify(newContent2NoMeta);
- var newContentStrNoMeta = JSONSortify(newContent);
-
- if (newContent2StrNoMeta !== newContentStrNoMeta) {
- console.error("shjson2 !== shjson");
- onLocal();
-
- /* pushing back over the wire is necessary, but it can
- result in a feedback loop, which we call a browser
- fight */
- // what changed?
- var ops = ChainPad.Diff.diff(newContentStrNoMeta, newContent2StrNoMeta);
- // log the changes
- console.log(newContentStrNoMeta);
- console.log(ops);
- var sop = JSON.stringify([ newContentStrNoMeta, ops ]);
-
- var fights = window.CryptPad_fights = window.CryptPad_fights || [];
- var index = fights.indexOf(sop);
- if (index === -1) {
- fights.push(sop);
- console.log("Found a new type of browser disagreement");
- console.log("You can inspect the list in your " +
- "console at `REALTIME_MODULE.fights`");
- console.log(fights);
- } else {
- console.log("Encountered a known browser disagreement: " +
- "available at `REALTIME_MODULE.fights[%s]`", index);
+ nThen(function (waitFor) {
+ contentUpdate(newContent, waitFor);
+ }).nThen(function () {
+ if (!readOnly) {
+ var newContent2NoMeta = normalize(contentGetter());
+ var newContent2StrNoMeta = JSONSortify(newContent2NoMeta);
+ var newContentStrNoMeta = JSONSortify(newContent);
+
+ if (newContent2StrNoMeta !== newContentStrNoMeta) {
+ console.error("shjson2 !== shjson");
+ onLocal();
+
+ /* pushing back over the wire is necessary, but it can
+ result in a feedback loop, which we call a browser
+ fight */
+ // what changed?
+ var ops = ChainPad.Diff.diff(newContentStrNoMeta, newContent2StrNoMeta);
+ // log the changes
+ console.log(newContentStrNoMeta);
+ console.log(ops);
+ var sop = JSON.stringify([ newContentStrNoMeta, ops ]);
+
+ var fights = window.CryptPad_fights = window.CryptPad_fights || [];
+ var index = fights.indexOf(sop);
+ if (index === -1) {
+ fights.push(sop);
+ console.log("Found a new type of browser disagreement");
+ console.log("You can inspect the list in your " +
+ "console at `REALTIME_MODULE.fights`");
+ console.log(fights);
+ } else {
+ console.log("Encountered a known browser disagreement: " +
+ "available at `REALTIME_MODULE.fights[%s]`", index);
+ }
}
}
- }
- // Notify only when the content has changed, not when someone has joined/left
- if (JSONSortify(newContent) !== JSONSortify(oldContent)) {
- common.notify();
- }
+ // Notify only when the content has changed, not when someone has joined/left
+ if (JSONSortify(newContent) !== JSONSortify(oldContent)) {
+ common.notify();
+ }
+ });
};
var setHistoryMode = function (bool, update) {
@@ -284,62 +294,66 @@ define([
var newPad = false;
if (newContentStr === '') { newPad = true; }
- if (!newPad) {
- var newContent = JSON.parse(newContentStr);
- cpNfInner.metadataMgr.updateMetadata(extractMetadata(newContent));
- newContent = normalize(newContent);
- contentUpdate(newContent);
- } else {
- if (!cpNfInner.metadataMgr.getPrivateData().isNewFile) {
- // We're getting 'new pad' but there is an existing file
- // We don't know exactly why this can happen but under no circumstances
- // should we overwrite the content, so lets just try again.
- console.log("userDoc is '' but this is not a new pad.");
- console.log("Either this is an empty document which has not been touched");
- console.log("Or else something is terribly wrong, reloading.");
- Feedback.send("NON_EMPTY_NEWDOC");
- setTimeout(function () { common.gotoURL(); }, 1000);
- return;
+ // contentUpdate may be async so we need an nthen here
+ nThen(function (waitFor) {
+ if (!newPad) {
+ var newContent = JSON.parse(newContentStr);
+ cpNfInner.metadataMgr.updateMetadata(extractMetadata(newContent));
+ newContent = normalize(newContent);
+ contentUpdate(newContent, waitFor);
+ } else {
+ if (!cpNfInner.metadataMgr.getPrivateData().isNewFile) {
+ // We're getting 'new pad' but there is an existing file
+ // We don't know exactly why this can happen but under no circumstances
+ // should we overwrite the content, so lets just try again.
+ console.log("userDoc is '' but this is not a new pad.");
+ console.log("Either this is an empty document which has not been touched");
+ console.log("Or else something is terribly wrong, reloading.");
+ Feedback.send("NON_EMPTY_NEWDOC");
+ setTimeout(function () { common.gotoURL(); }, 1000);
+ return;
+ }
+ console.log('updating title');
+ title.updateTitle(title.defaultTitle);
+ evOnDefaultContentNeeded.fire();
+ }
+ }).nThen(function () {
+ stateChange(STATE.READY);
+ firstConnection = false;
+ if (!readOnly) { onLocal(); }
+ evOnReady.fire(newPad);
+
+ UI.removeLoadingScreen(emitResize);
+
+ var privateDat = cpNfInner.metadataMgr.getPrivateData();
+ var hash = privateDat.availableHashes.editHash ||
+ privateDat.availableHashes.viewHash;
+ var href = privateDat.pathname + '#' + hash;
+ if (AppConfig.textAnalyzer && textContentGetter) {
+ AppConfig.textAnalyzer(textContentGetter, privateDat.channel);
}
- console.log('updating title');
- title.updateTitle(title.defaultTitle);
- evOnDefaultContentNeeded.fire();
- }
- stateChange(STATE.READY);
- firstConnection = false;
- if (!readOnly) { onLocal(); }
- evOnReady.fire(newPad);
-
- UI.removeLoadingScreen(emitResize);
-
- var privateDat = cpNfInner.metadataMgr.getPrivateData();
- var hash = privateDat.availableHashes.editHash ||
- privateDat.availableHashes.viewHash;
- var href = privateDat.pathname + '#' + hash;
- if (AppConfig.textAnalyzer && textContentGetter) {
- AppConfig.textAnalyzer(textContentGetter, privateDat.channel);
- }
- if (options.thumbnail && privateDat.thumbnails) {
- if (hash) {
- options.thumbnail.href = href;
- options.thumbnail.getContent = function () {
- if (!cpNfInner.chainpad) { return; }
- return cpNfInner.chainpad.getUserDoc();
- };
- Thumb.initPadThumbnails(common, options.thumbnail);
+ if (options.thumbnail && privateDat.thumbnails) {
+ if (hash) {
+ options.thumbnail.href = href;
+ options.thumbnail.getContent = function () {
+ if (!cpNfInner.chainpad) { return; }
+ return cpNfInner.chainpad.getUserDoc();
+ };
+ Thumb.initPadThumbnails(common, options.thumbnail);
+ }
}
- }
- var skipTemp = Util.find(privateDat, ['settings', 'general', 'creation', 'noTemplate']);
- var skipCreation = Util.find(privateDat, ['settings', 'general', 'creation', 'skip']);
- if (newPad && (!AppConfig.displayCreationScreen || (!skipTemp && skipCreation))) {
- common.openTemplatePicker();
- }
+ var skipTemp = Util.find(privateDat, ['settings', 'general', 'creation', 'noTemplate']);
+ var skipCreation = Util.find(privateDat, ['settings', 'general', 'creation', 'skip']);
+ if (newPad && (!AppConfig.displayCreationScreen || (!skipTemp && skipCreation))) {
+ common.openTemplatePicker();
+ }
+ });
};
var onConnectionChange = function (info) {
if (state === STATE.DELETED) { return; }
- stateChange(info.state ? STATE.INITIALIZING : STATE.DISCONNECTED);
+ stateChange(info.state ? STATE.INITIALIZING : STATE.DISCONNECTED, info.permanent);
/*if (info.state) {
UI.findOKButton().click();
} else {
@@ -380,13 +394,19 @@ define([
common.createButton('import', true, options, function (c, f) {
if (async) {
fi(c, f, function (content) {
- contentUpdate(content);
- onLocal();
+ nThen(function (waitFor) {
+ contentUpdate(content, waitFor);
+ }).nThen(function () {
+ onLocal();
+ });
});
return;
}
- contentUpdate(fi(c, f));
- onLocal();
+ nThen(function (waitFor) {
+ contentUpdate(fi(c, f), waitFor);
+ }).nThen(function () {
+ onLocal();
+ });
})
);
};
diff --git a/www/common/sframe-chainpad-netflux-inner.js b/www/common/sframe-chainpad-netflux-inner.js
index d2b54faf2..0c44db1d4 100644
--- a/www/common/sframe-chainpad-netflux-inner.js
+++ b/www/common/sframe-chainpad-netflux-inner.js
@@ -79,10 +79,12 @@ define([
evInfiniteSpinner.fire();
}, 2000);
- sframeChan.on('EV_RT_DISCONNECT', function () {
+ sframeChan.on('EV_RT_DISCONNECT', function (isPermanent) {
isReady = false;
chainpad.abort();
- onConnectionChange({ state: false });
+ // Permanent flag is here to choose if we wnat to display
+ // "reconnecting" or "disconnected" in the toolbar state
+ onConnectionChange({ state: false, permanent: isPermanent });
});
sframeChan.on('EV_RT_ERROR', function (err) {
isReady = false;
diff --git a/www/common/sframe-chainpad-netflux-outer.js b/www/common/sframe-chainpad-netflux-outer.js
index 45c4bdabf..eaafe6656 100644
--- a/www/common/sframe-chainpad-netflux-outer.js
+++ b/www/common/sframe-chainpad-netflux-outer.js
@@ -112,8 +112,12 @@ define([], function () {
}
};
- padRpc.onDisconnectEvent.reg(function () {
- sframeChan.event('EV_RT_DISCONNECT');
+ padRpc.onDisconnectEvent.reg(function (permanent) {
+ sframeChan.event('EV_RT_DISCONNECT', permanent);
+ });
+
+ padRpc.onConnectEvent.reg(function (data) {
+ onOpen(data);
});
padRpc.onErrorEvent.reg(function (err) {
@@ -128,8 +132,6 @@ define([], function () {
owners: owners,
password: password,
expire: expire
- }, function(data) {
- onOpen(data);
});
};
diff --git a/www/common/sframe-common-history.js b/www/common/sframe-common-history.js
index 27503d07a..efaa01cc1 100644
--- a/www/common/sframe-common-history.js
+++ b/www/common/sframe-common-history.js
@@ -1,26 +1,36 @@
define([
'jquery',
'/common/common-interface.js',
+ '/bower_components/nthen/index.js',
//'/bower_components/chainpad-json-validator/json-ot.js',
'/bower_components/chainpad/chainpad.dist.js',
-], function ($, UI, ChainPad /* JsonOT */) {
+], function ($, UI, nThen, ChainPad /* JsonOT */) {
//var ChainPad = window.ChainPad;
var History = {};
- var getStates = function (rt) {
- var states = [];
- var b = rt.getAuthBlock();
- if (b) { states.unshift(b); }
- while (b.getParent()) {
- b = b.getParent();
- states.unshift(b);
+ History.create = function (common, config) {
+ if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");}
+ if (History.loading) { return void console.error("History is already being loaded..."); }
+ History.loading = true;
+ var $toolbar = config.$toolbar;
+
+ if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) {
+ throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory");
}
- return states;
- };
- var loadHistory = function (config, common, cb) {
- var createRealtime = function () {
+ var getStates = function (rt) {
+ var states = [];
+ var b = rt.getAuthBlock();
+ if (b) { states.unshift(b); }
+ while (b.getParent()) {
+ b = b.getParent();
+ states.unshift(b);
+ }
+ return states;
+ };
+
+ var createRealtime = function (config) {
return ChainPad.create({
userName: 'history',
validateContent: function (content) {
@@ -33,36 +43,45 @@ define([
}
},
initialState: '',
- //patchTransformer: ChainPad.NaiveJSONTransformer,
- //logLevel: 0,
- //transformFunction: JsonOT.validate,
logLevel: config.debug ? 2 : 0,
noPrune: true
});
};
- var realtime = createRealtime();
-
- History.readOnly = common.getMetadataMgr().getPrivateData().readOnly;
-
- /*var to = window.setTimeout(function () {
- cb('[GET_FULL_HISTORY_TIMEOUT]');
- }, 30000);*/
- common.getFullHistory(realtime, function () {
- //window.clearTimeout(to);
- cb(null, realtime);
- });
- };
+ var loadFullHistory = function (config, common, cb) {
+ var realtime = createRealtime(config);
+ common.getFullHistory(realtime, function () {
+ cb(null, realtime);
+ });
+ };
+ loadFullHistory = loadFullHistory;
- History.create = function (common, config) {
- if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");}
- if (History.loading) { return void console.error("History is already being loaded..."); }
- History.loading = true;
- var $toolbar = config.$toolbar;
+ var fillChainPad = function (realtime, messages) {
+ messages.forEach(function (m) {
+ realtime.message(m);
+ });
+ };
- if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) {
- throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory");
- }
+ var allMessages = [];
+ var lastKnownHash;
+ var isComplete = false;
+ var loadMoreHistory = function (config, common, cb) {
+ if (isComplete) { return void cb ('EFULL'); }
+ var realtime = createRealtime(config);
+ var sframeChan = common.getSframeChannel();
+
+ sframeChan.query('Q_GET_HISTORY_RANGE', {
+ lastKnownHash: lastKnownHash
+ }, function (err, data) {
+ if (err) { return void console.error(err); }
+ if (!Array.isArray(data.messages)) { return void console.error('Not an array!'); }
+ lastKnownHash = data.lastKnownHash;
+ isComplete = data.isFull;
+ Array.prototype.unshift.apply(allMessages, data.messages); // Destructive concat
+ fillChainPad(realtime, allMessages);
+ cb (null, realtime);
+ });
+ };
// config.setHistory(bool, bool)
// - bool1: history value
@@ -84,21 +103,20 @@ define([
};
config.setHistory(true);
- var onReady = function () { };
var Messages = common.Messages;
var realtime;
var states = [];
- var c = states.length - 1;
+ var c = 0;//states.length - 1;
var $hist = $toolbar.find('.cp-toolbar-history');
var $left = $toolbar.find('.cp-toolbar-leftside');
var $right = $toolbar.find('.cp-toolbar-rightside');
var $cke = $toolbar.find('.cke_toolbox_main');
- $hist.html('').show();
+ $hist.html('').css('display', 'flex');
$left.hide();
$right.hide();
$cke.hide();
@@ -107,29 +125,73 @@ define([
var onUpdate;
- var update = function () {
+ var update = function (newRt) {
+ realtime = newRt;
if (!realtime) { return []; }
states = getStates(realtime);
if (typeof onUpdate === "function") { onUpdate(); }
return states;
};
+ var $loadMore, $version, get;
+
// Get the content of the selected version, and change the version number
- var get = function (i) {
+ var loading = false;
+ var loadMore = function (cb) {
+ if (loading) { return; }
+ loading = true;
+ $loadMore.removeClass('fa fa-ellipsis-h')
+ .append($('', {'class': 'fa fa-refresh fa-spin fa-3x fa-fw'}));
+ loadMoreHistory(config, common, function (err, newRt) {
+ if (err === 'EFULL') {
+ $loadMore.off('click').hide();
+ get(c);
+ $version.show();
+ return;
+ }
+ loading = false;
+ if (err) { return void console.error(err); }
+ update(newRt);
+ $loadMore.addClass('fa fa-ellipsis-h').html('');
+ get(c);
+ if (cb) { cb(); }
+ });
+ };
+ get = function (i) {
i = parseInt(i);
if (isNaN(i)) { return; }
- if (i < 0) { i = 0; }
- if (i > states.length - 1) { i = states.length - 1; }
- var val = states[i].getContent().doc;
+ if (i > 0) { i = 0; }
+ if (i < -(states.length - 2)) { i = -(states.length - 2); }
+ if (i <= -(states.length - 11)) {
+ loadMore();
+ }
+ var idx = states.length - 1 + i;
+ var val = states[idx].getContent().doc;
c = i;
if (typeof onUpdate === "function") { onUpdate(); }
- $hist.find('.cp-toolbar-history-next, .cp-toolbar-history-previous').css('visibility', '');
- if (c === states.length - 1) { $hist.find('.cp-toolbar-history-next').css('visibility', 'hidden'); }
- if (c === 0) { $hist.find('.cp-toolbar-history-previous').css('visibility', 'hidden'); }
+ $hist.find('.cp-toolbar-history-next, .cp-toolbar-history-previous, ' +
+ '.cp-toolbar-history-fast-next, .cp-toolbar-history-fast-previous')
+ .css('visibility', '');
+ if (c === -(states.length-1)) {
+ $hist.find('.cp-toolbar-history-previous').css('visibility', 'hidden');
+ $hist.find('.cp-toolbar-history-fast-previous').css('visibility', 'hidden');
+ }
+ if (c === 0) {
+ $hist.find('.cp-toolbar-history-next').css('visibility', 'hidden');
+ $hist.find('.cp-toolbar-history-fast-next').css('visibility', 'hidden');
+ }
+ var $pos = $hist.find('.cp-toolbar-history-pos');
+ var p = 100 * (1 - (-c / (states.length-1)));
+ $pos.css('margin-left', p+'%');
+
+ // Display the version when the full history is loaded
+ // Note: the first version is always empty and probably can't be displayed, so
+ // we can consider we have only states.length - 1 versions
+ $version.text(idx + ' / ' + (states.length-1));
if (config.debug) {
- console.log(states[i]);
- var ops = states[i] && states[i].getPatch() && states[i].getPatch().operations;
+ console.log(states[idx]);
+ var ops = states[idx] && states[idx].getPatch() && states[idx].getPatch().operations;
if (Array.isArray(ops)) {
ops.forEach(function (op) { console.log(op); });
}
@@ -148,6 +210,17 @@ define([
// Create the history toolbar
var display = function () {
$hist.html('');
+
+ var $rev = $('', {
+ 'class':'cp-toolbar-history-revert buttonSuccess fa fa-check-circle-o',
+ title: Messages.history_restoreTitle
+ }).appendTo($hist);//.text(Messages.history_restore);
+ if (History.readOnly) { $rev.css('visibility', 'hidden'); }
+ $('', {'class': 'cp-history-filler'}).appendTo($hist);
+ var $fastPrev = $('', {
+ 'class': 'cp-toolbar-history-fast-previous fa fa-fast-backward buttonPrimary',
+ title: Messages.history_prev
+ }).appendTo($hist);
var $prev =$('', {
'class': 'cp-toolbar-history-previous fa fa-step-backward buttonPrimary',
title: Messages.history_prev
@@ -157,58 +230,73 @@ define([
'class': 'cp-toolbar-history-next fa fa-step-forward buttonPrimary',
title: Messages.history_next
}).appendTo($hist);
+ var $fastNext = $('', {
+ 'class': 'cp-toolbar-history-fast-next fa fa-fast-forward buttonPrimary',
+ title: Messages.history_next
+ }).appendTo($hist);
+ $('', {'class': 'cp-history-filler'}).appendTo($hist);
+ var $close = $('', {
+ 'class':'cp-toolbar-history-close fa fa-window-close',
+ title: Messages.history_closeTitle
+ }).appendTo($hist);
+
+ var $bar = $('', {'class': 'cp-toolbar-history-bar'}).appendTo($nav);
+ var $container = $('
', {'class':'cp-toolbar-history-pos-container'}).appendTo($bar);
+ $('
', {'class': 'cp-toolbar-history-pos'}).appendTo($container);
+
+ $version = $('
', {
+ 'class': 'cp-toolbar-history-version'
+ }).prependTo($bar).hide();
+ $loadMore = $('', {
+ 'class':'cp-toolbar-history-loadmore fa fa-ellipsis-h',
+ title: Messages.history_loadMore
+ }).click(function () {
+ loadMore(function () {
+ get(c);
+ });
+ }).prependTo($container);
- $('').text(Messages.history_version).appendTo($nav);
- var $cur = $(' ', {
- 'class' : 'cp-toolbar-history-goto-input',
- 'type' : 'number',
- 'min' : '1',
- 'max' : states.length
- }).val(c + 1).appendTo($nav).mousedown(function (e) {
- // stopPropagation because the event would be cancelled by the dropdown menus
+ // Load a version when clicking on the bar
+ $container.click(function (e) {
e.stopPropagation();
+ if (!$(e.target).is('.cp-toolbar-history-pos-container')) { return; }
+ var p = e.offsetX / $container.width();
+ var v = -Math.round((states.length - 1) * (1 - p));
+ render(get(v));
});
- var $label2 = $('').text(' / '+ states.length).appendTo($nav);
- $(' ').appendTo($nav);
- var $close = $('', {
- 'class':'cp-toolbar-history-close',
- title: Messages.history_closeTitle
- }).text(Messages.history_closeTitle).appendTo($nav);
- var $rev = $('', {
- 'class':'cp-toolbar-history-revert buttonSuccess',
- title: Messages.history_restoreTitle
- }).text(Messages.history_restore).appendTo($nav);
- if (History.readOnly) { $rev.hide(); }
onUpdate = function () {
- $cur.attr('max', states.length);
- $cur.val(c+1);
- $label2.text(' / ' + states.length);
+ // Called when a new version is loaded
};
+ var onKeyDown, onKeyUp;
var close = function () {
$hist.hide();
$left.show();
$right.show();
$cke.show();
$(window).trigger('resize');
+ $(window).off('keydown', onKeyDown);
+ $(window).off('keyup', onKeyUp);
};
- // Buttons actions
+ // Version buttons
$prev.click(function () { render(getPrevious()); });
$next.click(function () { render(getNext()); });
- $cur.keydown(function (e) {
+ $fastPrev.click(function () { render(getPrevious(10)); });
+ $fastNext.click(function () { render(getNext(10)); });
+ onKeyDown = function (e) {
var p = function () { e.preventDefault(); };
- if (e.which === 13) { p(); return render( get($cur.val() - 1) ); } // Enter
if ([37, 40].indexOf(e.which) >= 0) { p(); return render(getPrevious()); } // Left
if ([38, 39].indexOf(e.which) >= 0) { p(); return render(getNext()); } // Right
if (e.which === 33) { p(); return render(getNext(10)); } // PageUp
if (e.which === 34) { p(); return render(getPrevious(10)); } // PageUp
if (e.which === 27) { p(); $close.click(); }
- }).keyup(function (e) { e.stopPropagation(); }).focus();
- $cur.on('change', function () {
- render( get($cur.val() - 1) );
- });
+ };
+ onKeyUp = function (e) { e.stopPropagation(); };
+ $(window).on('keydown', onKeyDown).on('keyup', onKeyUp).focus();
+
+ // Close & restore buttons
$close.click(function () {
states = [];
close();
@@ -229,14 +317,13 @@ define([
};
// Load all the history messages into a new chainpad object
- loadHistory(config, common, function (err, newRt) {
+ loadMoreHistory(config, common, function (err, newRt) {
+ History.readOnly = common.getMetadataMgr().getPrivateData().readOnly;
History.loading = false;
if (err) { throw new Error(err); }
- realtime = newRt;
- update();
+ update(newRt);
c = states.length - 1;
display();
- onReady();
});
};
diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js
index c78bf53f9..8b30aa2e2 100644
--- a/www/common/sframe-common-outer.js
+++ b/www/common/sframe-common-outer.js
@@ -143,12 +143,11 @@ define([
// or get it from the pad attributes
var needPassword = parsed.hashData && parsed.hashData.password;
if (needPassword) {
+ // Check if we have a password, and check if it is correct (file exists).
+ // It we don't have a correct password, display the password prompt.
+ // Maybe the file has been deleted from the server or the password has been changed.
Cryptpad.getPadAttribute('password', waitFor(function (err, val) {
- if (val) {
- // We already know the password, use it!
- password = val;
- todo();
- } else {
+ var askPassword = function (wrongPasswordStored) {
// Ask for the password and check if the pad exists
// If the pad doesn't exist, it means the password isn't correct
// or the pad has been deleted
@@ -162,7 +161,14 @@ define([
cb(false);
} else {
todo();
- correctPassword();
+ if (wrongPasswordStored) {
+ // Store the correct password
+ Cryptpad.setPadAttribute('password', password, function () {
+ correctPassword();
+ }, parsed.getUrl());
+ } else {
+ correctPassword();
+ }
cb(true);
}
};
@@ -178,6 +184,19 @@ define([
Cryptpad.isNewChannel(window.location.href, password, next);
});
sframeChan.event("EV_PAD_PASSWORD");
+ };
+
+ if (val) {
+ password = val;
+ Cryptpad.getFileSize(window.location.href, password, function (e, size) {
+ if (size !== 0) {
+ return void todo();
+ }
+ // Wrong password or deleted file?
+ askPassword(true);
+ });
+ } else {
+ askPassword();
}
}), parsed.getUrl());
return;
@@ -267,6 +286,10 @@ define([
Test.registerOuter(sframeChan);
+ Cryptpad.onNewVersionReconnect.reg(function () {
+ sframeChan.event("EV_NEW_VERSION");
+ });
+
// Put in the following function the RPC queries that should also work in filepicker
var addCommonRpc = function (sframeChan) {
sframeChan.on('Q_ANON_RPC_MESSAGE', function (data, cb) {
@@ -312,7 +335,8 @@ define([
var title = currentTabTitle.replace(/\{title\}/g, currentTitle || 'CryptPad');
document.title = title;
};
- sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newTitle, cb) {
+ sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newData, cb) {
+ var newTitle = newData.title || newData.defaultTitle;
currentTitle = newTitle;
setDocumentTitle();
var data = {
@@ -396,6 +420,24 @@ define([
}));
});
});
+ sframeChan.on('Q_GET_HISTORY_RANGE', function (data, cb) {
+ var crypto = Crypto.createEncryptor(secret.keys);
+ Cryptpad.getHistoryRange({
+ channel: secret.channel,
+ validateKey: secret.keys.validateKey,
+ lastKnownHash: data.lastKnownHash
+ }, function (data) {
+ cb({
+ isFull: data.isFull,
+ messages: data.messages.map(function (msg) {
+ // The 3rd parameter "true" means we're going to skip signature validation.
+ // We don't need it since the message is already validated serverside by hk
+ return crypto.decrypt(msg, true, true);
+ }),
+ lastKnownHash: data.lastKnownHash
+ });
+ });
+ });
sframeChan.on('Q_GET_PAD_ATTRIBUTE', function (data, cb) {
var href;
@@ -614,6 +656,15 @@ define([
});
});
+ sframeChan.on('Q_PAD_PASSWORD_CHANGE', function (data, cb) {
+ var href = data.href || window.location.href;
+ Cryptpad.changePadPassword(Cryptget, href, data.password, edPublic, cb);
+ });
+
+ sframeChan.on('Q_WRITE_LOGIN_BLOCK', function (data, cb) {
+ Cryptpad.writeLoginBlock(data, cb);
+ });
+
if (cfg.addRpc) {
cfg.addRpc(sframeChan, Cryptpad, Utils);
}
@@ -699,6 +750,9 @@ define([
readOnly: readOnly,
crypto: Crypto.createEncryptor(secret.keys),
onConnect: function () {
+ var href = parsed.getUrl();
+ // Add friends requests handlers when we have the final href
+ Cryptpad.messaging.addHandlers(href);
if (window.location.hash && window.location.hash !== '#') {
window.location = parsed.getUrl({
present: parsed.hashData.present,
diff --git a/www/common/sframe-common-title.js b/www/common/sframe-common-title.js
index 317944993..dcbf6288a 100644
--- a/www/common/sframe-common-title.js
+++ b/www/common/sframe-common-title.js
@@ -42,6 +42,9 @@ define([
exp.updateTitle = function (newTitle, cb) {
cb = cb || $.noop;
if (newTitle === exp.title) { return void cb(); }
+ if (newTitle === exp.defaultTitle) {
+ newTitle = "";
+ }
metadataMgr.updateTitle(newTitle);
titleUpdated = cb;
};
@@ -51,11 +54,16 @@ define([
if ($title) {
$title.find('span.cp-toolbar-title-value').text(md.title || md.defaultTitle);
$title.find('input').val(md.title || md.defaultTitle);
+ $title.find('input').prop('placeholder', md.defaultTitle);
}
+ exp.defaultTitle = md.defaultTitle;
exp.title = md.title;
});
- metadataMgr.onTitleChange(function (title) {
- sframeChan.query('Q_SET_PAD_TITLE_IN_DRIVE', title, function (err) {
+ metadataMgr.onTitleChange(function (title, defaultTitle) {
+ sframeChan.query('Q_SET_PAD_TITLE_IN_DRIVE', {
+ title: title,
+ defaultTitle: defaultTitle
+ }, function (err) {
if (err === 'E_OVER_LIMIT') {
return void UI.alert(Messages.pinLimitNotPinned, null, true);
} else if (err) { return; }
diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js
index 572fe5f4a..5ab973476 100644
--- a/www/common/sframe-common.js
+++ b/www/common/sframe-common.js
@@ -112,11 +112,9 @@ define([
var origin = ctx.metadataMgr.getPrivateData().origin;
return '';
};
- funcs.getMediatagFromHref = function (href) {
- // XXX: Should only be used with the current href
+ funcs.getMediatagFromHref = function () {
var data = ctx.metadataMgr.getPrivateData();
- var parsed = Hash.parsePadUrl(href);
- var secret = Hash.getSecrets('file', parsed.hash, data.password);
+ var secret = Hash.getSecrets('file', data.availableHashes.fileHash, data.password);
if (secret.keys && secret.channel) {
var key = Hash.encodeBase64(secret.keys && secret.keys.cryptKey);
var hexFileName = secret.channel;
@@ -440,6 +438,15 @@ define([
UI.updateLoadingProgress(data, true);
});
+ ctx.sframeChan.on('EV_NEW_VERSION', function () {
+ var $err = $('').append(Messages.newVersionError);
+ $err.find('a').click(function () {
+ funcs.gotoURL();
+ });
+ UI.findOKButton().click();
+ UI.errorLoadingScreen($err, true, true);
+ });
+
ctx.metadataMgr.onReady(waitFor());
}).nThen(function () {
try {
diff --git a/www/common/sframe-protocol.js b/www/common/sframe-protocol.js
index fef8b1027..a23124a65 100644
--- a/www/common/sframe-protocol.js
+++ b/www/common/sframe-protocol.js
@@ -74,6 +74,9 @@ define({
// Get the user's pin limit, usage and plan
'Q_PIN_GET_USAGE': true,
+ // Write/update the login block when the account password is changed
+ 'Q_WRITE_LOGIN_BLOCK': true,
+
// Check the pin limit to determine if we can store the pad in the drive or if we should.
// display a warning
'Q_GET_PIN_LIMIT_STATUS': true,
@@ -84,6 +87,7 @@ define({
// Request the full history from the server when the users clicks on the history button.
// Callback is called when the FULL_HISTORY_END message is received in the outside.
'Q_GET_FULL_HISTORY': true,
+ 'Q_GET_HISTORY_RANGE': true,
// When a (full) history message is received from the server.
'EV_RT_HIST_MESSAGE': true,
@@ -196,7 +200,6 @@ define({
// Anonymous users can empty their drive and remove FS_hash from localStorage
'EV_BURN_ANON_DRIVE': true,
-
// Inner drive needs to send command and receive updates from the async store
'Q_DRIVE_USEROBJECT': true,
'Q_DRIVE_GETOBJECT': true,
@@ -214,6 +217,8 @@ define({
// Notifications about connection and disconnection from the network
'EV_NETWORK_DISCONNECT': true,
'EV_NETWORK_RECONNECT': true,
+ // Reload on new version
+ 'EV_NEW_VERSION': true,
// Pad creation screen: create a pad with the selected attributes (owned, expire)
'Q_CREATE_PAD': true,
@@ -224,16 +229,18 @@ define({
// The exact protocol is defined in common/test.js
'EV_TESTDATA': true,
- // Critical error outside the iframe during loading screen
- 'EV_LOADING_ERROR': true,
-
// Ask for the pad password when a pad is protected
'EV_PAD_PASSWORD': true,
'Q_PAD_PASSWORD_VALUE': true,
+ // Change pad password
+ 'Q_PAD_PASSWORD_CHANGE': true,
// Loading events to display in the loading screen
'EV_LOADING_INFO': true,
+ // Critical error outside the iframe during loading screen
+ 'EV_LOADING_ERROR': true,
// Get all existing tags
'Q_GET_ALL_TAGS': true,
+
});
diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js
index 7b9e1abaf..35fce658d 100644
--- a/www/common/toolbar3.js
+++ b/www/common/toolbar3.js
@@ -420,7 +420,7 @@ define([
var hashes = metadataMgr.getPrivateData().availableHashes;
var $shareBlock = $('
', {
- 'class': 'fa fa-share-alt cp-toolbar-share-button',
+ 'class': 'fa fa-shhare-alt cp-toolbar-share-button',
title: Messages.shareButton
});
var modal = UIElements.createShareModal({
@@ -449,7 +449,7 @@ define([
var hashes = metadataMgr.getPrivateData().availableHashes;
var $shareBlock = $('', {
- 'class': 'fa fa-share-alt cp-toolbar-share-button',
+ 'class': 'fa fa-shhare-alt cp-toolbar-share-button',
title: Messages.shareButton
});
var modal = UIElements.createFileShareModal({
diff --git a/www/file/file-crypto.js b/www/file/file-crypto.js
index 06f9804cd..1c274cbe7 100644
--- a/www/file/file-crypto.js
+++ b/www/file/file-crypto.js
@@ -65,6 +65,7 @@ define([
xhr.setRequestHeader('Range', 'bytes=0-1');
xhr.responseType = 'arraybuffer';
+ xhr.onerror= function () { return CB('XHR_ERROR'); };
xhr.onload = function () {
if (/^4/.test('' + this.status)) { return CB('XHR_ERROR'); }
var res = new Uint8Array(xhr.response);
diff --git a/www/file/inner.js b/www/file/inner.js
index 78d162f5e..e9e2d868d 100644
--- a/www/file/inner.js
+++ b/www/file/inner.js
@@ -37,6 +37,9 @@ define([
var Nacl = window.nacl;
var APP = window.APP = {};
+ MediaTag.setDefaultConfig('download', {
+ text: Messages.download_mt_button
+ });
var andThen = function (common) {
var $appContainer = $('#cp-app-file-content');
@@ -92,7 +95,9 @@ define([
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (e) {
if (e === 'XHR_ERROR') {
- return void UI.errorLoadingScreen(Messages.download_resourceNotAvailable);
+ return void UI.errorLoadingScreen(Messages.download_resourceNotAvailable, false, function () {
+ common.gotoURL('/file/');
+ });
}
return void console.error(e);
}
@@ -130,32 +135,17 @@ define([
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
var rightsideDisplayed = false;
- $(window.document).on('decryption', function (e) {
- /* FIXME
- we're listening for decryption events and assuming that only
- the main media-tag exists. In practice there is also your avatar
- and there could be other things in the future, so we should
- figure out a generic way target media-tag decryption events.
- */
- var decrypted = e.originalEvent;
- if (decrypted.callback) {
- decrypted.callback();
- }
+ MediaTag($mt[0]).on('complete', function (decrypted) {
$dlview.show();
$dlform.hide();
var $dlButton = $dlview.find('media-tag button');
if (ev) { $dlButton.click(); }
- $dlButton.addClass('btn btn-success');
- var text = Messages.download_mt_button + ' ';
- text += '' + Util.fixHTML(title) + ' ';
- text += '' + Messages._getKey('formattedMB', [sizeMb]) + ' ';
- $dlButton.html(text);
if (!rightsideDisplayed) {
toolbar.$rightside
.append(common.createButton('export', true, {}, function () {
- saveAs(decrypted.blob, decrypted.metadata.name);
+ saveAs(decrypted.content, decrypted.metadata.name);
}));
rightsideDisplayed = true;
}
@@ -178,42 +168,12 @@ define([
} else {
cb();
}
- })
- .on('decryptionError', function (e) {
- var error = e.originalEvent;
- //UI.alert(error.message);
- cb(error.message);
- })
- .on('decryptionProgress', function (e) {
- var progress = e.originalEvent;
- var p = progress.percent +'%';
+ }).on('progress', function (data) {
+ var p = data.progress +'%';
$progress.width(p);
+ }).on('error', function (err) {
+ console.error(err);
});
-
- /**
- * Allowed mime types that have to be set for a rendering after a decryption.
- *
- * @type {Array}
- */
- var allowedMediaTypes = [
- 'image/png',
- 'image/jpeg',
- 'image/jpg',
- 'image/gif',
- 'audio/mp3',
- 'audio/ogg',
- 'audio/wav',
- 'audio/webm',
- 'video/mp4',
- 'video/ogg',
- 'video/webm',
- 'application/pdf',
- 'application/dash+xml',
- 'download'
- ];
- MediaTag.CryptoFilter.setAllowedMediaTypes(allowedMediaTypes);
-
- MediaTag($mt[0]);
};
var todoBigFile = function (sizeMb) {
diff --git a/www/filepicker/app-filepicker.less b/www/filepicker/app-filepicker.less
index 952a4d857..5a80661cf 100644
--- a/www/filepicker/app-filepicker.less
+++ b/www/filepicker/app-filepicker.less
@@ -4,11 +4,15 @@
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tippy.less';
+@import (once) '../../customize/src/less2/include/checkmark.less';
+@import (once) '../../customize/src/less2/include/password-input.less';
.iconColors_main();
.fileupload_main();
.alertify_main();
.tippy_main();
+.checkmark_main(20px);
+.password_main();
#cp-filepicker-dialog {
display: none;
diff --git a/www/mediatag/media-tag.js b/www/mediatag/media-tag.js
index f240e0941..9ae19d3a8 100644
--- a/www/mediatag/media-tag.js
+++ b/www/mediatag/media-tag.js
@@ -4,7 +4,6 @@
else { this[name] = definition(); }
}('MediaTag', function() {
var cache;
- var PARANOIA = true;
var cypherChunkLength = 131088;
// Save a blob on the file system
@@ -23,6 +22,14 @@
}
};
+ var fixHTML = function (str) {
+ if (!str) { return ''; }
+ return str.replace(/[<>&"']/g, function (x) {
+ return ({ "<": "<", ">": ">", "&": "&", '"': """, "'": "'" })[x];
+ });
+ };
+
+
// Default config, can be overriden per media-tag call
var config = {
allowed: [
@@ -49,6 +56,7 @@
image: function (metadata, url, content, cfg, cb) {
var img = document.createElement('img');
img.setAttribute('src', url);
+ img.blob = content;
cb(void 0, img);
},
video: function (metadata, url, content, cfg, cb) {
@@ -75,7 +83,8 @@
},
download: function (metadata, url, content, cfg, cb) {
var btn = document.createElement('button');
- btn.innerHTML = cfg.download.text;
+ btn.innerHTML = cfg.download.text + ' ' +
+ metadata.name ? '' + fixHTML(metadata.name) + ' ' : '';
btn.addEventListener('click', function () {
saveFile(content, url, metadata.name);
});
@@ -106,38 +115,24 @@
var Decrypt = {
// Create a nonce
createNonce: function () {
- if (!Array.prototype.fill) {
- // IE support
- var arr = [];
- for (var i = 0; i < 24; i++) { arr[i] = 0; }
- return new Uint8Array(arr);
- }
- return new Uint8Array(new Array(24).fill(0));
+ var n = new Uint8Array(24);
+ for (var i = 0; i < 24; i++) { n[i] = 0; }
+ return n;
},
// Increment a nonce
- // FIXME: remove throw?
increment: function (N) {
var l = N.length;
while (l-- > 1) {
- if (PARANOIA) {
- if (typeof(N[l]) !== 'number') {
- throw new Error('E_UNSAFE_TYPE');
- }
- if (N[l] > 255) {
- throw new Error('E_OUT_OF_BOUNDS');
- }
- }
/* .jshint probably suspects this is unsafe because we lack types
but as long as this is only used on nonces, it should be safe */
if (N[l] !== 255) { return void N[l]++; } // jshint ignore:line
- N[l] = 0;
// you don't need to worry about this running out.
// you'd need a REAAAALLY big file
- if (l === 0) {
- throw new Error('E_NONCE_TOO_LARGE');
- }
+ if (l === 0) { throw new Error('E_NONCE_TOO_LARGE'); }
+
+ N[l] = 0;
}
},
@@ -153,13 +148,6 @@
return Array.prototype.slice.call(u8);
},
- // Gets the random key string.
- getRandomKeyStr: function () {
- var Nacl = window.nacl;
- var rdm = Nacl.randomBytes(18);
- return Nacl.util.encodeBase64(rdm);
- },
-
// Gets the key from the key string.
getKeyFromStr: function (str) {
return window.nacl.util.decodeBase64(str);
@@ -287,7 +275,7 @@
// Get blob URL
var url = decrypted.url;
- if (!url) {
+ if (!url && window.URL) {
url = decrypted.url = window.URL.createObjectURL(new Blob([blob], {
type: metadata.type
}));
diff --git a/www/pad/inner.js b/www/pad/inner.js
index b8199bc61..865be9ef8 100644
--- a/www/pad/inner.js
+++ b/www/pad/inner.js
@@ -601,8 +601,8 @@ define([
var $clone = $(inner).clone();
nThen(function (waitFor) {
$(inner).find('media-tag').each(function (i, el) {
- if (!$(el).data('blob')) { return; }
- Util.blobToImage($(el).data('blob'), waitFor(function (imgSrc) {
+ if (!$(el).data('blob') || !el.blob) { return; }
+ Util.blobToImage(el.blob || $(el).data('blob'), waitFor(function (imgSrc) {
$clone.find('media-tag[src="' + $(el).attr('src') + '"] img')
.attr('src', imgSrc);
$clone.find('media-tag').parent()
diff --git a/www/profile/inner.js b/www/profile/inner.js
index 40642507a..76c279ce0 100644
--- a/www/profile/inner.js
+++ b/www/profile/inner.js
@@ -43,16 +43,6 @@ define([
_onRefresh: []
};
- // Decryption event for avatar mediatag (TODO not needed anymore?)
- $(window.document).on('decryption', function (e) {
- var decrypted = e.originalEvent;
- if (decrypted.callback) { decrypted.callback(); }
- })
- .on('decryptionError', function (e) {
- var error = e.originalEvent;
- UI.alert(error.message);
- });
-
$(window).click(function () {
$('.cp-dropdown-content').hide();
});
diff --git a/www/settings/app-settings.less b/www/settings/app-settings.less
index a47dd88f6..2cfd48a65 100644
--- a/www/settings/app-settings.less
+++ b/www/settings/app-settings.less
@@ -55,6 +55,15 @@
width: @sidebar_button-width;
}
}
+ .cp-settings-change-password {
+ [type="password"], [type="text"] {
+ width: @sidebar_button-width;
+ flex: unset;
+ }
+ button {
+ margin-top: 5px;
+ }
+ }
.cp-settings-drive-backup {
button {
span.fa {
diff --git a/www/settings/inner.js b/www/settings/inner.js
index bb5fc3319..07bb4f390 100644
--- a/www/settings/inner.js
+++ b/www/settings/inner.js
@@ -9,6 +9,7 @@ define([
'/common/common-hash.js',
'/customize/messages.js',
'/common/hyperscript.js',
+ '/customize/credential.js',
'/customize/application_config.js',
'/api/config',
@@ -27,6 +28,7 @@ define([
Hash,
Messages,
h,
+ Cred,
AppConfig,
ApiConfig
)
@@ -48,6 +50,7 @@ define([
'cp-settings-resettips',
'cp-settings-thumbnails',
'cp-settings-userfeedback',
+ 'cp-settings-change-password',
'cp-settings-delete'
],
'creation': [
@@ -312,6 +315,7 @@ define([
};
create['delete'] = function () {
+ if (!common.isLoggedIn()) { return; }
var $div = $('', { 'class': 'cp-settings-delete cp-sidebarlayout-element'});
$('
', {'class': 'label'}).text(Messages.settings_deleteTitle).appendTo($div);
@@ -368,6 +372,95 @@ define([
return $div;
};
+ create['change-password'] = function () {
+ if (!common.isLoggedIn()) { return; }
+
+ var $div = $('', { 'class': 'cp-settings-change-password cp-sidebarlayout-element'});
+
+ $('
', {'class': 'label'}).text(Messages.settings_changePasswordTitle).appendTo($div);
+
+ $('', {'class': 'cp-sidebarlayout-description'})
+ .append(Messages.settings_changePasswordHint).appendTo($div);
+
+ // var publicKey = privateData.edPublic;
+
+ var form = h('div', [
+ UI.passwordInput({
+ id: 'cp-settings-change-password-current',
+ placeholder: Messages.settings_changePasswordCurrent
+ }, true),
+ h('br'),
+ UI.passwordInput({
+ id: 'cp-settings-change-password-new',
+ placeholder: Messages.settings_changePasswordNew
+ }, true),
+ UI.passwordInput({
+ id: 'cp-settings-change-password-new2',
+ placeholder: Messages.settings_changePasswordNewConfirm
+ }, true),
+ h('button.btn.btn-primary', Messages.settings_changePasswordButton)
+ ]);
+
+ $(form).appendTo($div);
+
+ var updateBlock = function (data, cb) {
+ sframeChan.query('Q_WRITE_LOGIN_BLOCK', data, function (err, obj) {
+ if (err || obj.error) { return void cb ({error: err || obj.error}); }
+ cb (obj);
+ });
+ };
+ updateBlock = updateBlock; // jshint..
+
+ var todo = function () {
+ var oldPassword = $(form).find('#cp-settings-change-password-current').val();
+ var newPassword = $(form).find('#cp-settings-change-password-new').val();
+ var newPasswordConfirm = $(form).find('#cp-settings-change-password-new2').val();
+
+ /* basic validation */
+ if (!Cred.isLongEnoughPassword(newPassword)) {
+ var warning = Messages._getKey('register_passwordTooShort', [
+ Cred.MINIMUM_PASSWORD_LENGTH
+ ]);
+ return void UI.alert(warning);
+ }
+
+ if (newPassword !== newPasswordConfirm) {
+ UI.alert(Messages.register_passwordsDontMatch);
+ return;
+ }
+
+ UI.confirm(Messages.settings_changePasswordConfirm,
+ function (yes) {
+ if (!yes) { return; }
+ // TODO
+ console.log(oldPassword, newPassword, newPasswordConfirm);
+ }, {
+ ok: Messages.register_writtenPassword,
+ cancel: Messages.register_cancel,
+ cancelClass: 'safe',
+ okClass: 'danger',
+ reverseOrder: true,
+ done: function ($dialog) {
+ $dialog.find('> div').addClass('half');
+ },
+ }, true);
+ };
+
+ $(form).find('button').click(function () {
+ todo();
+ });
+ $(form).find('input').keydown(function (e) {
+ // Save on Enter
+ if (e.which === 13) {
+ e.preventDefault();
+ e.stopPropagation();
+ todo();
+ }
+ });
+
+ return $div;
+ };
+
// Pad Creation settings
var setHTML = function (e, html) {
diff --git a/www/whiteboard/inner.js b/www/whiteboard/inner.js
index 4949343ba..bf57e89b7 100644
--- a/www/whiteboard/inner.js
+++ b/www/whiteboard/inner.js
@@ -1,81 +1,59 @@
define([
'jquery',
- '/bower_components/chainpad-crypto/crypto.js',
- '/common/toolbar3.js',
'json.sortify',
- '/common/common-util.js',
'/bower_components/nthen/index.js',
'/common/sframe-common.js',
+ '/common/sframe-app-framework.js',
+ '/common/common-util.js',
+ '/common/common-hash.js',
'/common/common-interface.js',
- '/api/config',
- '/common/common-realtime.js',
+ '/common/common-thumbnail.js',
'/customize/pages.js',
'/customize/messages.js',
- '/customize/application_config.js',
- '/common/common-thumbnail.js',
'/whiteboard/colors.js',
+ '/customize/application_config.js',
'/bower_components/chainpad/chainpad.dist.js',
'/bower_components/secure-fabric.js/dist/fabric.min.js',
- '/bower_components/file-saver/FileSaver.min.js',
-
- 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
- 'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
- 'less!/customize/src/less2/main.less',
], function (
$,
- Crypto,
- Toolbar,
- JSONSortify,
- Util,
+ Sortify,
nThen,
SFCommon,
+ Framework,
+ Util,
+ Hash,
UI,
- ApiConfig,
- CommonRealtime,
+ Thumb,
Pages,
Messages,
- AppConfig,
- Thumb,
Colors,
+ AppConfig,
ChainPad)
{
- var saveAs = window.saveAs;
var APP = window.APP = {
$: $
};
var Fabric = APP.Fabric = window.fabric;
- var stringify = function (obj) {
- return JSONSortify(obj);
- };
-
- var toolbar;
+ var verbose = function (x) { console.log(x); };
+ verbose = function () {}; // comment out to enable verbose logging
- var andThen = function (common) {
- var config = {};
- /* Initialize Fabric */
- var canvas = APP.canvas = new Fabric.Canvas('cp-app-whiteboard-canvas', {
- containerClass: 'cp-app-whiteboard-canvas-container'
- });
- var $canvas = $('canvas');
- var $controls = $('#cp-app-whiteboard-controls');
- var $canvasContainer = $('canvas').parents('.cp-app-whiteboard-canvas-container');
+ var mkControls = function (framework, canvas) {
var $pickers = $('#cp-app-whiteboard-pickers');
var $colors = $('#cp-app-whiteboard-colors');
var $cursors = $('#cp-app-whiteboard-cursors');
- var $deleteButton = $('#cp-app-whiteboard-delete');
- var $toggle = $('#cp-app-whiteboard-toggledraw');
+ var $controls = $('#cp-app-whiteboard-controls');
var $width = $('#cp-app-whiteboard-width');
var $widthLabel = $('label[for="cp-app-whiteboard-width"]');
var $opacity = $('#cp-app-whiteboard-opacity');
var $opacityLabel = $('label[for="cp-app-whiteboard-opacity"]');
+ var $toggle = $('#cp-app-whiteboard-toggledraw');
+ var $deleteButton = $('#cp-app-whiteboard-delete');
+ var metadataMgr = framework._.cpNfInner.metadataMgr;
- // Brush
-
- var readOnly = false;
var brush = {
color: '#000000',
opacity: 1
@@ -136,39 +114,6 @@ define([
updateBrushOpacity();
$opacity.on('change', updateBrushOpacity);
- var pickColor = function (current, cb) {
- var $picker = $(' ', {
- type: 'color',
- value: '#FFFFFF',
- })
- .on('change', function () {
- var color = this.value;
- cb(color);
- }).appendTo($pickers);
- setTimeout(function () {
- $picker.val(current);
- $picker.click();
- });
- };
- var setColor = function (c) {
- c = Colors.rgb2hex(c);
- brush.color = c;
- canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
- APP.$color.css({
- 'color': c,
- });
- createCursor();
- };
-
- var palette = AppConfig.whiteboardPalette || [
- 'red', 'blue', 'green', 'white', 'black', 'purple',
- 'gray', 'beige', 'brown', 'cyan', 'darkcyan', 'gold', 'yellow', 'pink'
- ];
- $('.cp-app-whiteboard-palette-color').on('click', function () {
- var color = $(this).css('background-color');
- setColor(color);
- });
-
APP.draw = true;
var toggleDrawMode = function () {
canvas.deactivateAll().renderAll();
@@ -191,74 +136,49 @@ define([
canvas.discardActiveGroup();
}
canvas.renderAll();
- config.onLocal();
+ framework.localChange();
};
$deleteButton.click(deleteSelection);
$(window).on('keyup', function (e) {
if (e.which === 46) { deleteSelection (); }
});
- var setEditable = function (bool) {
- APP.editable = bool;
- if (readOnly && bool) { return; }
- if (bool) { $controls.css('display', 'flex'); }
- else { $controls.hide(); }
- canvas.isDrawingMode = bool ? APP.draw : false;
- if (!bool) {
- canvas.deactivateAll();
- canvas.renderAll();
- }
- canvas.forEachObject(function (object) {
- object.selectable = bool;
- });
- $canvasContainer.find('canvas').css('border-color', bool? 'black': 'red');
- };
-
- var saveImage = APP.saveImage = function () {
- var defaultName = "pretty-picture.png";
- UI.prompt(Messages.exportPrompt, defaultName, function (filename) {
- if (!(typeof(filename) === 'string' && filename)) { return; }
- $canvas[0].toBlob(function (blob) {
- saveAs(blob, filename);
- });
+ var pickColor = function (current, cb) {
+ var $picker = $(' ', {
+ type: 'color',
+ value: '#FFFFFF',
+ })
+ .on('change', function () {
+ var color = this.value;
+ cb(color);
+ }).appendTo($pickers);
+ setTimeout(function () {
+ $picker.val(current);
+ $picker.click();
});
};
-
- APP.FM = common.createFileManager({});
- APP.upload = function (title) {
- var canvas = $canvas[0];
- APP.canvas.deactivateAll().renderAll();
- canvas.toBlob(function (blob) {
- blob.name = title;
- APP.FM.handleFile(blob);
+ var setColor = function (c) {
+ c = Colors.rgb2hex(c);
+ brush.color = c;
+ canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
+ APP.$color.css({
+ 'color': c,
});
+ createCursor();
};
- var initializing = true;
- var $bar = $('#cp-toolbar');
- var Title;
- var cpNfInner;
- var metadataMgr;
-
- config = {
- readOnly: readOnly,
- patchTransformer: ChainPad.NaiveJSONTransformer,
- // cryptpad debug logging (default is 1)
- // logLevel: 0,
- validateContent: function (content) {
- try {
- JSON.parse(content);
- return true;
- } catch (e) {
- console.log("Failed to parse, rejecting patch");
- return false;
- }
- }
- };
+ var palette = AppConfig.whiteboardPalette || [
+ 'red', 'blue', 'green', 'white', 'black', 'purple',
+ 'gray', 'beige', 'brown', 'cyan', 'darkcyan', 'gold', 'yellow', 'pink'
+ ];
+ $('.cp-app-whiteboard-palette-color').on('click', function () {
+ var color = $(this).css('background-color');
+ setColor(color);
+ });
var addColorToPalette = function (color, i) {
- if (readOnly) { return; }
+ if (framework.isReadOnly()) { return; }
var $color = $('', {
'class': 'cp-app-whiteboard-palette-color',
})
@@ -271,7 +191,7 @@ define([
})
.on('dblclick', function (e) {
e.preventDefault();
- if (!APP.editable) { return; }
+ if (framework.isLocked()) { return; }
pickColor(Colors.rgb2hex($color.css('background-color')), function (c) {
$color.css({
'background-color': c,
@@ -287,7 +207,7 @@ define([
var first = true;
var updatePalette = function (newPalette) {
- if (first || stringify(palette) !== stringify(newPalette)) {
+ if (first || Sortify(palette) !== Sortify(newPalette)) {
palette = newPalette;
$colors.html('
');
palette.forEach(addColorToPalette);
@@ -299,7 +219,7 @@ define([
var metadata = JSON.parse(JSON.stringify(metadataMgr.getMetadata()));
metadata.palette = newPalette;
metadataMgr.updateMetadata(metadata);
- config.onLocal();
+ framework.localChange();
};
var makeColorButton = function ($container) {
@@ -312,7 +232,7 @@ define([
return;
}
- var $color = APP.$color = common.createButton(null, true, {
+ var $color = APP.$color = framework._.sfCommon.createButton(null, true, {
icon: 'fa-square',
title: Messages.canvas_chooseColor,
name: 'color',
@@ -331,33 +251,73 @@ define([
return $color;
};
- var stringifyInner = function (textValue) {
- var obj = {
- content: textValue,
- metadata: metadataMgr.getMetadataLazy()
- };
- // stringify the json and send it into chainpad
- return stringify(obj);
+ updateLocalPalette(palette);
+
+ metadataMgr.onChange(function () {
+ var md = metadataMgr.getMetadata();
+ if (md.palette) {
+ updateLocalPalette(md.palette);
+ }
+ });
+
+ return {
+ palette: palette,
+ makeColorButton: makeColorButton,
+ updateLocalPalette: updateLocalPalette,
};
+ };
+
+ var mkHelpMenu = function (framework) {
+ var $appContainer = $('#cp-app-whiteboard-container');
+ var helpMenu = framework._.sfCommon.createHelpMenu(['whiteboard']);
+ $appContainer.prepend(helpMenu.menu);
+ framework._.toolbar.$drawer.append(helpMenu.button);
+ };
+
+ // Start of the main loop
+ var andThen2 = function (framework) {
+ var canvas = APP.canvas = new Fabric.Canvas('cp-app-whiteboard-canvas', {
+ containerClass: 'cp-app-whiteboard-canvas-container'
+ });
+ var $canvas = $('canvas');
+ var $canvasContainer = $('canvas').parents('.cp-app-whiteboard-canvas-container');
+ var $controls = $('#cp-app-whiteboard-controls');
+ var metadataMgr = framework._.cpNfInner.metadataMgr;
- var onLocal = config.onLocal = function () {
- if (initializing) { return; }
- if (readOnly) { return; }
-
- var content = stringifyInner(canvas.toDatalessJSON());
-
- try {
- APP.realtime.contentUpdate(content);
- } catch (e) {
- APP.unrecoverable = true;
- setEditable(false);
- APP.toolbar.errorState(true, e.message);
- var msg = Messages.chainpadError;
- UI.errorLoadingScreen(msg, true, true);
- console.error(e);
+ var setEditable = function (bool) {
+ if (bool) { $controls.css('display', 'flex'); }
+ else { $controls.hide(); }
+
+ canvas.isDrawingMode = bool ? APP.draw : false;
+ if (!bool) {
+ canvas.deactivateAll();
+ canvas.renderAll();
}
+ canvas.forEachObject(function (object) {
+ object.selectable = bool;
+ });
+ $canvasContainer.find('canvas').css('border-color', bool? 'black': 'red');
};
+ mkHelpMenu(framework);
+
+ var controls = mkControls(framework, canvas);
+
+ // ---------------------------------------------
+ // Whiteboard custom buttons
+ // ---------------------------------------------
+
+ var $rightside = framework._.toolbar.$rightside;
+
+ APP.FM = framework._.sfCommon.createFileManager({});
+ APP.upload = function (title) {
+ var canvas = $canvas[0];
+ APP.canvas.deactivateAll().renderAll();
+ canvas.toBlob(function (blob) {
+ blob.name = title;
+ APP.FM.handleFile(blob);
+ });
+ };
var addImageToCanvas = function (img) {
var w = img.width;
var h = img.height;
@@ -370,313 +330,159 @@ define([
}
var cImg = new Fabric.Image(img, { left:0, top:0, angle:0, });
APP.canvas.add(cImg);
- onLocal();
+ framework.localChange();
};
- var initThumbnails = function () {
- var oldThumbnailState;
- var privateDat = metadataMgr.getPrivateData();
- if (!privateDat.thumbnails) { return; }
- var hash = privateDat.availableHashes.editHash ||
- privateDat.availableHashes.viewHash;
- var href = privateDat.pathname + '#' + hash;
- var mkThumbnail = function () {
- if (!hash) { return; }
- if (initializing) { return; }
- if (!APP.realtime) { return; }
- var content = APP.realtime.getUserDoc();
- if (content === oldThumbnailState) { return; }
- var D = Thumb.getResizedDimensions($canvas[0], 'pad');
- Thumb.fromCanvas($canvas[0], D, function (err, b64) {
- oldThumbnailState = content;
- Thumb.setPadThumbnail(common, href, privateDat.channel, b64);
- });
+ // Export to drive as PNG
+ framework._.sfCommon.createButton('savetodrive', true, {}).click(function () {
+ var defaultName = framework._.title.getTitle();
+ UI.prompt(Messages.exportPrompt, defaultName + '.png', function (name) {
+ if (name === null || !name.trim()) { return; }
+ APP.upload(name);
+ });
+ }).appendTo($rightside);
+
+ // Embed image
+ var onUpload = function (e) {
+ var file = e.target.files[0];
+ var reader = new FileReader();
+ reader.onload = function () {
+ var img = new Image();
+ img.onload = function () {
+ addImageToCanvas(img);
+ };
+ img.src = reader.result;
};
- window.setInterval(mkThumbnail, Thumb.UPDATE_INTERVAL);
- window.setTimeout(mkThumbnail, Thumb.UPDATE_FIRST);
+ reader.readAsDataURL(file);
};
-
- config.onInit = function (info) {
- updateLocalPalette(palette);
- readOnly = metadataMgr.getPrivateData().readOnly;
-
- Title = common.createTitle({});
-
- var configTb = {
- displayed: [
- 'userlist',
- 'title',
- 'useradmin',
- 'spinner',
- 'newpad',
- 'share',
- 'limit',
- 'unpinnedWarning'
- ],
- title: Title.getTitleConfig(),
- metadataMgr: metadataMgr,
- readOnly: readOnly,
- realtime: info.realtime,
- sfCommon: common,
- $container: $bar,
- $contentContainer: $('#cp-app-whiteboard-canvas-area')
+ framework._.sfCommon.createButton('', true, {
+ title: Messages.canvas_imageEmbed,
+ icon: 'fa-file-image-o',
+ name: 'embedImage'
+ }).click(function () {
+ $(' ', {type:'file'}).on('change', onUpload).click();
+ }).appendTo($rightside);
+
+ if (framework._.sfCommon.isLoggedIn()) {
+ var fileDialogCfg = {
+ onSelect: function (data) {
+ if (data.type === 'file') {
+ var mt = ' ';
+ framework._.sfCommon.displayMediatagImage($(mt), function (err, $image) {
+ Util.blobURLToImage($image.attr('src'), function (imgSrc) {
+ var img = new Image();
+ img.onload = function () { addImageToCanvas(img); };
+ img.src = imgSrc;
+ });
+ });
+ return;
+ }
+ }
};
- toolbar = APP.toolbar = Toolbar.create(configTb);
- Title.setToolbar(toolbar);
-
- var $rightside = toolbar.$rightside;
- var $drawer = toolbar.$drawer;
-
- /* save as template */
- if (!metadataMgr.getPrivateData().isTemplate) {
- var templateObj = {
- rt: info.realtime,
- getTitle: function () { return metadataMgr.getMetadata().title; }
+ framework._.sfCommon.initFilePicker(fileDialogCfg);
+ framework._.sfCommon.createButton('mediatag', true).click(function () {
+ var pickerCfg = {
+ types: ['file'],
+ where: ['root'],
+ filter: {
+ fileType: ['image/']
+ }
};
- var $templateButton = common.createButton('template', true, templateObj);
- $rightside.append($templateButton);
- }
-
- /* add an export button */
- var $export = common.createButton('export', true, {}, saveImage);
- $drawer.append($export);
-
- if (common.isLoggedIn()) {
- common.createButton('savetodrive', true, {}, function () {})
- .click(function () {
- UI.prompt(Messages.exportPrompt, document.title + '.png',
- function (name) {
- if (name === null || !name.trim()) { return; }
- APP.upload(name);
- });
- }).appendTo($rightside);
-
- common.createButton('hashtag', true).appendTo($rightside);
- }
-
- var $forget = common.createButton('forget', true, {}, function (err) {
- if (err) { return; }
- setEditable(false);
- });
- $rightside.append($forget);
-
- var $properties = common.createButton('properties', true);
- toolbar.$drawer.append($properties);
-
- var $appContainer = $('#cp-app-whiteboard-container');
- var helpMenu = common.createHelpMenu(['whiteboard']);
- $appContainer.prepend(helpMenu.menu);
- toolbar.$drawer.append(helpMenu.button);
-
- if (!readOnly) {
- makeColorButton($rightside);
-
- // Embed image
- var onUpload = function (e) {
- var file = e.target.files[0];
- var reader = new FileReader();
- reader.onload = function () {
- var img = new Image();
- img.onload = function () {
- addImageToCanvas(img);
- };
- img.src = reader.result;
- };
- reader.readAsDataURL(file);
- };
- common.createButton('', true, {
- title: Messages.canvas_imageEmbed,
- icon: 'fa-file-image-o',
- name: 'embedImage'
- }).click(function () {
- $(' ', {type:'file'}).on('change', onUpload).click();
- }).appendTo($rightside);
-
- if (common.isLoggedIn()) {
- var fileDialogCfg = {
- onSelect: function (data) {
- if (data.type === 'file') {
- var mt = ' ';
- common.displayMediatagImage($(mt), function (err, $image) {
- Util.blobURLToImage($image.attr('src'), function (imgSrc) {
- var img = new Image();
- img.onload = function () { addImageToCanvas(img); };
- img.src = imgSrc;
- });
- });
- return;
- }
- }
- };
- common.initFilePicker(fileDialogCfg);
- APP.$mediaTagButton = common.createButton('mediatag', true).click(function () {
- var pickerCfg = {
- types: ['file'],
- where: ['root'],
- filter: {
- fileType: ['image/']
- }
- };
- common.openFilePicker(pickerCfg);
- }).appendTo($rightside);
- }
- } else {
- $colors.hide();
- $controls.hide();
- }
-
- metadataMgr.onChange(function () {
- var md = metadataMgr.getMetadata();
- if (md.palette) {
- updateLocalPalette(md.palette);
- }
- });
- };
-
- config.onReady = function (info) {
- if (APP.realtime !== info.realtime) {
- APP.realtime = info.realtime;
- }
-
- var userDoc = APP.realtime.getUserDoc();
- var isNew = false;
- var newDoc = '';
- if (userDoc === "" || userDoc === "{}") { isNew = true; }
+ framework._.sfCommon.openFilePicker(pickerCfg);
+ }).appendTo($rightside);
+ }
- if (userDoc !== "") {
- var hjson = JSON.parse(userDoc);
-
- if (hjson && hjson.metadata) {
- metadataMgr.updateMetadata(hjson.metadata);
- }
- if (typeof (hjson) !== 'object' || Array.isArray(hjson) ||
- (hjson.metadata && typeof(hjson.metadata.type) !== 'undefined' &&
- hjson.metadata.type !== 'whiteboard')) {
- var errorText = Messages.typeError;
- UI.errorLoadingScreen(errorText);
- throw new Error(errorText);
- }
- newDoc = hjson.content;
- } else {
- Title.updateTitle(Title.defaultTitle);
- }
+ if (framework.isReadOnly()) {
+ setEditable(false);
+ } else {
+ controls.makeColorButton($rightside);
+ }
- nThen(function (waitFor) {
- if (newDoc) {
- canvas.loadFromJSON(newDoc, waitFor(function () {
- console.log('loaded');
- canvas.renderAll();
- }));
- }
- }).nThen(function () {
- setEditable(!readOnly);
- initializing = false;
- config.onLocal();
- UI.removeLoadingScreen();
+ $('#cp-app-whiteboard-clear').on('click', function () {
+ canvas.clear();
+ framework.localChange();
+ });
- initThumbnails();
+ // ---------------------------------------------
+ // End custom
+ // ---------------------------------------------
- if (readOnly) { return; }
+ framework.onEditableChange(function () {
+ var locked = framework.isLocked() || framework.isReadOnly();
+ setEditable(!locked);
+ });
- var privateDat = metadataMgr.getPrivateData();
- var skipTemp = Util.find(privateDat,
- ['settings', 'general', 'creation', 'noTemplate']);
- var skipCreation = Util.find(privateDat, ['settings', 'general', 'creation', 'skip']);
- if (isNew && (!AppConfig.displayCreationScreen || (!skipTemp && skipCreation))) {
- common.openTemplatePicker();
- }
+ framework.setFileExporter('png', function (cb) {
+ $canvas[0].toBlob(function (blob) {
+ cb(blob);
});
+ });
- };
-
- config.onRemote = function () {
- if (initializing) { return; }
- var userDoc = APP.realtime.getUserDoc();
-
- var json = JSON.parse(userDoc);
- var remoteDoc = json.content;
+ framework.setNormalizer(function (c) {
+ return {
+ content: c.content
+ };
+ });
- canvas.loadFromJSON(remoteDoc, function () {
+ framework.onContentUpdate(function (newContent, waitFor) {
+ var content = newContent.content;
+ canvas.loadFromJSON(content, waitFor(function () {
canvas.renderAll();
- if (json.metadata) {
- metadataMgr.updateMetadata(json.metadata);
- }
- });
-
- var content = canvas.toDatalessJSON();
- if (content !== remoteDoc) { common.notify(); }
- if (readOnly) { setEditable(false); }
- };
-
- config.onAbort = function () {
- if (APP.unrecoverable) { return; }
- // inform of network disconnect
- setEditable(false);
- toolbar.failed();
- UI.alert(Messages.common_connectionLost, undefined, true);
- };
-
- config.onConnectionChange = function (info) {
- if (APP.unrecoverable) { return; }
- setEditable(info.state);
- if (info.state) {
- initializing = true;
- //UI.findOKButton().click();
- } else {
- //UI.alert(Messages.common_connectionLost, undefined, true);
- }
- };
-
- config.onError = function (err) {
- common.onServerError(err, toolbar, function () {
- APP.unrecoverable = true;
- setEditable(false);
- });
- };
-
- cpNfInner = common.startRealtime(config);
- metadataMgr = cpNfInner.metadataMgr;
-
- cpNfInner.onInfiniteSpinner(function () {
- if (APP.unrecoverable) { return; }
- setEditable(false);
- UI.confirm(Messages.realtime_unrecoverableError, function (yes) {
- if (!yes) { return; }
- common.gotoURL();
- });
+ }));
});
- canvas.on('mouse:up', onLocal);
-
- $('#cp-app-whiteboard-clear').on('click', function () {
- canvas.clear();
- onLocal();
+ framework.setContentGetter(function () {
+ var content = canvas.toDatalessJSON();
+ return {
+ content: content
+ };
});
- $('#save').on('click', function () {
- saveImage();
+ framework.onReady(function () {
+ var oldThumbnailState;
+ var privateDat = metadataMgr.getPrivateData();
+ if (!privateDat.thumbnails) { return; }
+ var hash = privateDat.availableHashes.editHash ||
+ privateDat.availableHashes.viewHash;
+ var href = privateDat.pathname + '#' + hash;
+ var mkThumbnail = function () {
+ if (!hash) { return; }
+ if (framework.getState() !== 'READY') { return; }
+ if (!framework._.cpNfInner.chainpad) { return; }
+ var content = framework._.cpNfInner.chainpad.getUserDoc();
+ if (content === oldThumbnailState) { return; }
+ var D = Thumb.getResizedDimensions($canvas[0], 'pad');
+ Thumb.fromCanvas($canvas[0], D, function (err, b64) {
+ oldThumbnailState = content;
+ Thumb.setPadThumbnail(framework._.sfCommon, href, privateDat.channel, b64);
+ });
+ };
+ window.setInterval(mkThumbnail, Thumb.UPDATE_INTERVAL);
+ window.setTimeout(mkThumbnail, Thumb.UPDATE_FIRST);
});
- common.onLogout(function () { setEditable(false); });
+ canvas.on('mouse:up', framework.localChange);
+ framework.start();
};
var main = function () {
- var common;
-
+ // var framework;
nThen(function (waitFor) {
$(waitFor(function () {
- UI.addLoadingScreen();
var $div = $('').append(Pages['/whiteboard/']());
$('body').append($div.html());
}));
- SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
}).nThen(function (waitFor) {
- common.getSframeChannel().onReady(waitFor());
- }).nThen(function (waitFor) {
- common.handleNewFile(waitFor);
- }).nThen(function (/*waitFor*/) {
- andThen(common);
+
+ // Framework initialization
+ Framework.create({
+ patchTransformer: ChainPad.NaiveJSONTransformer,
+ toolbarContainer: '#cp-toolbar',
+ contentContainer: '#cp-app-whiteboard-canvas-area',
+ }, waitFor(function (framework) {
+ andThen2(framework);
+ }));
});
};
main();
diff --git a/www/worker/inner.js b/www/worker/inner.js
index 667f09221..20992b023 100644
--- a/www/worker/inner.js
+++ b/www/worker/inner.js
@@ -51,6 +51,8 @@ define([
if (!window.Worker) {
return void $container.text("WebWorkers not supported by your browser");
}
+ /*
+ // Shared worker
console.log('ready');
var myWorker = new SharedWorker('/worker/worker.js');
console.log(myWorker);
@@ -65,7 +67,73 @@ define([
}
$container.append(' ');
$container.append(e.data);
- };
+ };*/
+
+ // Service worker
+ if ('serviceWorker' in navigator) {
+ console.log('here');
+ var initializing = true;
+ var worker;
+ var postMessage = function (data) {
+ console.log(data, navigator.serviceWorker);
+ if (worker) {
+ return void worker.postMessage(data);
+ }
+ console.log('NOT READY');
+ /*if (navigator.serviceWorker && navigator.serviceWorker.controller) {
+ navigator.serviceWorker.controller.postMessage(data);
+ }*/
+ };
+ navigator.serviceWorker.register('/worker/sw.js', {scope: '/'})
+ .then(function(reg) {
+ console.log(reg);
+ console.log('Registration succeeded. Scope is ' + reg.scope);
+ $container.append(' ');
+ $container.append('Registered! (scope: ' + reg.scope +')');
+ reg.onupdatefound = function () {
+ if (initializing) {
+ var w = reg.installing;
+ var onStateChange = function () {
+ if (w.state === "activated") {
+ console.log(w);
+ worker = w;
+ postMessage("INIT");
+ w.removeEventListener("statechange", onStateChange);
+ }
+ };
+ w.addEventListener('statechange', onStateChange);
+ return;
+ }
+ console.log('new SW version found!');
+ // KILL EVERYTHING
+ UI.confirm("New version detected, you have to reload", function (yes) {
+ if (yes) { common.gotoURL(); }
+ });
+ };
+ // Here we add the event listener for receiving messages
+ navigator.serviceWorker.addEventListener('message', function (e) {
+ var data = e.data;
+ if (data && data.state === "READY") {
+ initializing = false;
+ $container.append('
sw.js ready');
+ postMessage(["Hello worker"]);
+ return;
+ }
+ $container.append(' ');
+ $container.append(e.data);
+ });
+ if (reg.active) {
+ worker = reg.active;
+ postMessage("INIT");
+ }
+ }).catch(function(error) {
+ console.log('Registration failed with ' + error);
+ $container.append('Registration error: ' + error);
+ });
+ } else {
+ console.log('NO SERVICE WORKER');
+ }
+
$container.append(' inner.js ready');
});
});
diff --git a/www/worker/sw.js b/www/worker/sw.js
new file mode 100644
index 000000000..4daebc47f
--- /dev/null
+++ b/www/worker/sw.js
@@ -0,0 +1,69 @@
+/* jshint ignore:start */
+var id;
+//= Math.floor(Math.random()*100000);
+
+var postMsg = function (client, data) {
+ client.postMessage(data);
+};
+
+var broadcast = function (data, excludes) {
+ // Loop over all available clients
+ clients.matchAll().then(function (clients) {
+ clients.forEach(function (client) {
+ if (excludes.indexOf(client.id) === -1) {
+ postMsg(client, data);
+ }
+ })
+ })
+};
+var sendTo = function (data, clientId){
+ clients.matchAll().then(function (clients) {
+ clients.some(function (client) {
+ if (client.id === clientId) {
+ postMsg(client, data)
+ }
+ })
+ })
+};
+var getClients = function () {
+ clients.matchAll().then(function (clients) {
+ var cl = clients.map(function (c) {
+ console.log(JSON.stringify(c));
+ return c.id;
+ });
+ console.log(cl);
+ });
+};
+
+
+
+self.addEventListener('message', function (e) {
+ console.log(clients);
+ console.log('worker received');
+ console.log(e.data);
+ console.log(e.source);
+ var cId = e.source.id;
+ if (e.data === "INIT") {
+ if (!id) {
+ id = Math.floor(Math.random()*100000);
+ }
+ broadcast(cId + ' has joined!', [cId]);
+ postMsg(e.source, {state: 'READY'});
+ postMsg(e.source, "Welcome to SW " + id + "!");
+ postMsg(e.source, "You are identified as " + cId);
+ } else {
+ console.log(e.data);
+ postMsg(e.source, 'Yo (Re: '+e.data+')');
+ }
+});
+self.addEventListener('install', function (e) {
+ console.log(e);
+ console.log('V1 installing…');
+ self.skipWaiting();
+});
+
+self.addEventListener('activate', function (e) {
+ console.log(e);
+ console.log('V1 now ready to handle fetches!');
+});
+