Merge branch 'coati' into kanban

pull/1/head
yflory 7 years ago
commit 037050d16f

@ -1,6 +1,8 @@
node_modules/ node_modules/
www/bower_components/ www/bower_components/
www/common/pdfjs/ www/common/pdfjs/
www/common/tippy/
www/common/jquery-ui/
server.js server.js
www/common/media-tag.js www/common/media-tag.js
@ -8,7 +10,6 @@ www/scratch
www/common/toolbar.js www/common/toolbar.js
www/common/hyperscript.js www/common/hyperscript.js
www/common/tippy.min.js
www/pad/wysiwygarea-plugin.js www/pad/wysiwygarea-plugin.js
www/pad/mediatag-plugin.js www/pad/mediatag-plugin.js

@ -0,0 +1,47 @@
# 1.29.0
**Goals**
For this release we wanted to direct our effort towards improving user experience issues surrounding user accounts.
**Update notes**
This release features breaking changes to some clientside dependencies. Administrators must make sure to deploy the
latest server with npm update before updating your clientside dependencies with bower update.
**What's new**
* newly registered users are now able to delete their accounts automatically, along with any personal
information which had been created:
* ToDo list data is automatically deleted, along with user profiles
* all of a user's owned pads are also removed immediately in their account deletion process
* users who predate account deletion will not benefit from automatic account deletion, since the server
does not have sufficient knowledge to guarantee that the information they could request to have deleted is strictly
their own. For this reason, we've started working on scripts for validating user requests, so as to enable manual
deletion by the server administrator.
* the script can be found in cryptpad/check-account-deletion.js, and it will be a part of an ongoing
effort to improve administrator tooling for situations like this
* users who have not logged in, but wish to use their drive now see a ghost icon which they can use to create pads.
We hope this makes it easier to get started as a new user.
* registered users who have saved templates in their drives can now use those templates at any time, rather than only
using them to create new pads
* we've updated our file encryption code such that it does not interfere with other scripts which may be running at
the same time (synchronous blocking, for those who are interested)
* we now validate message signatures clientside, except when they are coming from the history keeper because clients
trust that the server has already validated those signatures
**Bug fixes**
* we've removed some dependencies from our home page that were introduced when we updated to use bootstrap4
* we now import fontawesome as css, and not less, which saves processing time and saves room in our localStorage cache
* templates which do not have a 'type' attribute set are migrated such that the pads which are created with their
content are valid
* thumbnail creation for pads is now disabled by default, due to poor performance
* users can enable thumbnail creation in their settings page
* we've fixed a significant bug in how our server handles checkpoints (special patches in history which contain the
entire pads content)
* it was possible for two users to independently create checkpoints in close proximity while the document was in a
forked state. New users joining while the session was in this state would get stuck on one side of the fork,
and could lose data if the users on the opposing fork overrode their changes
* we've updated our tests, which have been failing for some time because their success conditions were no longer valid
* while trying to register a previously registered user, users could cancel the prompt to login as that user.
If they did so, the registration form remained locked. This has been fixed.

@ -29,10 +29,10 @@
"json.sortify": "~2.1.0", "json.sortify": "~2.1.0",
"secure-fabric.js": "secure-v1.7.9", "secure-fabric.js": "secure-v1.7.9",
"hyperjson": "~1.4.0", "hyperjson": "~1.4.0",
"chainpad-crypto": "^0.1.3", "chainpad-crypto": "^0.2.0",
"chainpad-listmap": "^0.4.2", "chainpad-listmap": "^0.5.0",
"chainpad": "^5.0.0", "chainpad": "^5.1.0",
"chainpad-netflux": "^0.6.1", "chainpad-netflux": "^0.7.0",
"file-saver": "1.3.1", "file-saver": "1.3.1",
"alertifyjs": "1.0.11", "alertifyjs": "1.0.11",
"scrypt-async": "1.2.0", "scrypt-async": "1.2.0",
@ -46,7 +46,8 @@
"localforage": "^1.5.2", "localforage": "^1.5.2",
"html2canvas": "^0.4.1", "html2canvas": "^0.4.1",
"croppie": "^2.5.0", "croppie": "^2.5.0",
"sortablejs": "#^1.6.0" "sortablejs": "#^1.6.0",
"saferphore": "^0.0.1"
}, },
"resolutions": { "resolutions": {
"bootstrap": "^v4.0.0" "bootstrap": "^v4.0.0"

@ -0,0 +1,76 @@
/* jshint esversion: 6, node: true */
const Fs = require('fs');
const nThen = require('nthen');
const Pinned = require('./pinned');
const Nacl = require('tweetnacl');
const hashesFromPinFile = (pinFile, fileName) => {
var pins = {};
pinFile.split('\n').filter((x)=>(x)).map((l) => JSON.parse(l)).forEach((l) => {
switch (l[0]) {
case 'RESET': {
pins = {};
if (l[1] && l[1].length) { l[1].forEach((x) => { pins[x] = 1; }); }
//jshint -W086
// fallthrough
}
case 'PIN': {
l[1].forEach((x) => { pins[x] = 1; });
break;
}
case 'UNPIN': {
l[1].forEach((x) => { delete pins[x]; });
break;
}
default: throw new Error(JSON.stringify(l) + ' ' + fileName);
}
});
return Object.keys(pins);
};
var escapeKeyCharacters = function (key) {
return key && key.replace && key.replace(/\//g, '-');
};
const dataIdx = process.argv.indexOf('--data');
let edPublic;
if (dataIdx === -1) {
const hasEdPublic = process.argv.indexOf('--ed');
if (hasEdPublic === -1) { return void console.error("Missing ed argument"); }
edPublic = escapeKeyCharacters(process.argv[hasEdPublic+1]);
} else {
const deleteData = JSON.parse(process.argv[dataIdx+1]);
if (!deleteData.toSign || !deleteData.proof) { return void console.error("Invalid arguments"); }
// Check sig
const ed = Nacl.util.decodeBase64(deleteData.toSign.edPublic);
const signed = Nacl.util.decodeUTF8(JSON.stringify(deleteData.toSign));
const proof = Nacl.util.decodeBase64(deleteData.proof);
if (!Nacl.sign.detached.verify(signed, proof, ed)) { return void console.error("Invalid signature"); }
edPublic = escapeKeyCharacters(deleteData.toSign.edPublic);
}
let data = [];
let pinned = [];
nThen((waitFor) => {
let f = './pins/' + edPublic.slice(0, 2) + '/' + edPublic + '.ndjson';
Fs.readFile(f, waitFor((err, content) => {
if (err) { throw err; }
pinned = hashesFromPinFile(content.toString('utf8'), f);
}));
}).nThen((waitFor) => {
Pinned.load(waitFor((d) => {
data = Object.keys(d);
}), {
exclude: [edPublic + '.ndjson']
});
}).nThen(() => {
console.log('Pads pinned by this user and not pinned by anybody else:');
pinned.forEach((p) => {
if (data.indexOf(p) === -1) {
console.log(p);
}
});
});

@ -2,7 +2,7 @@
/* /*
globals module globals module
*/ */
var domain = ' http://localhost:3000/'; var domain = 'http://localhost:3000/';
// You can `kill -USR2` the node process and it will write out a heap dump. // You can `kill -USR2` the node process and it will write out a heap dump.
// If your system doesn't support dumping, comment this out and install with // If your system doesn't support dumping, comment this out and install with
@ -12,6 +12,10 @@ var domain = ' http://localhost:3000/';
// to enable this feature, uncomment the line below: // to enable this feature, uncomment the line below:
// require('heapdump'); // require('heapdump');
// we prepend a space because every usage expects it
// requiring admins to preserve it is unnecessarily confusing
domain = ' ' + domain;
module.exports = { module.exports = {
// the address you want to bind to, :: means all ipv4 and ipv6 addresses // the address you want to bind to, :: means all ipv4 and ipv6 addresses

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@ -0,0 +1,201 @@
// dark #326599
// light #4591c4
define([], function () {
var loadingStyle = (function(){/*
#cp-loading {
transition: opacity 0.75s, visibility 0s 0.75s;
visibility: visible;
position: fixed;
z-index: 10000000;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
background: linear-gradient(to right, #326599 0%, #326599 50%, #4591c4 50%, #4591c4 100%);
color: #fafafa;
font-size: 1.5em;
opacity: 1;
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
}
#cp-loading.cp-loading-hidden {
opacity: 0;
visibility: hidden;
}
#cp-loading .cp-loading-logo {
height: 300px;
width: 300px;
margin-top: 50px;
flex: 0 1 auto;
min-height: 0;
text-align: center;
}
#cp-loading .cp-loading-logo img {
max-width: 100%;
max-height: 100%;
}
#cp-loading .cp-loading-container {
width: 700px;
max-width: 90vw;
height: 500px;
max-height: calc(100vh - 20px);
margin: 50px;
flex-shrink: 0;
display: flex;
flex-flow: column;
justify-content: center;
justify-content: space-evenly;
align-items: center;
}
@media screen and (max-height: 800px) {
#cp-loading .cp-loading-container {
height: auto;
}
}
@media screen and (max-width: 600px) {
#cp-loading .cp-loading-container {
height: auto;
}
}
#cp-loading .cp-loading-cryptofist {
margin-left: auto;
margin-right: auto;
//height: 300px;
max-width: 90vw;
max-height: 300px;
width: auto;
height: auto;
margin-bottom: 2em;
}
@media screen and (max-height: 500px) {
#cp-loading .cp-loading-logo {
display: none;
}
}
#cp-loading-message {
background: #FFF;
padding: 20px;
width: 100%;
color: #000;
text-align: center;
display: none;
}
#cp-loading-password-prompt {
font-size: 18px;
}
#cp-loading-password-prompt .cp-password-error {
color: white;
background: #9e0000;
padding: 5px;
margin-bottom: 15px;
}
#cp-loading-password-prompt .cp-password-info {
text-align: left;
margin-bottom: 15px;
}
#cp-loading-password-prompt .cp-password-form {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
}
#cp-loading-password-prompt .cp-password-form button,
#cp-loading-password-prompt .cp-password-form .cp-password-input {
background-color: #4591c4;
color: white;
border: 1px solid #4591c4;
}
#cp-loading-password-prompt .cp-password-form .cp-password-container {
flex-shrink: 1;
min-width: 0;
}
#cp-loading-password-prompt .cp-password-form input {
flex: 1;
padding: 0 5px;
min-width: 0;
text-overflow: ellipsis;
}
#cp-loading-password-prompt .cp-password-form button:hover {
background-color: #326599;
}
#cp-loading .cp-loading-spinner-container {
position: relative;
height: 100px;
}
#cp-loading .cp-loading-spinner-container > div {
height: 100px;
}
#cp-loading-tip {
position: fixed;
z-index: 10000000;
top: 80%;
left: 0;
right: 0;
text-align: center;
transition: opacity 750ms;
transition-delay: 3000ms;
}
@media screen and (max-height: 600px) {
#cp-loading-tip {
display: none;
}
}
#cp-loading-tip span {
background: #222;
color: #fafafa;
text-align: center;
font-size: 1.5em;
opacity: 0.7;
font-family: 'Open Sans', 'Helvetica Neue', sans-serif;
padding: 15px;
max-width: 60%;
display: inline-block;
}
.cp-loading-progress {
width: 100%;
margin: 20px;
}
.cp-loading-progress p {
margin: 5px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.cp-loading-progress-bar {
height: 24px;
background: white;
}
.cp-loading-progress-bar-value {
height: 100%;
background: #5cb85c;
}
*/}).toString().slice(14, -3);
var urlArgs = window.location.href.replace(/^.*\?([^\?]*)$/, function (all, x) { return x; });
var elem = document.createElement('div');
elem.setAttribute('id', 'cp-loading');
elem.innerHTML = [
'<style>',
loadingStyle,
'</style>',
'<div class="cp-loading-logo">',
'<img class="cp-loading-cryptofist" src="/customize/loading-logo.png?' + urlArgs + '">',
'</div>',
'<div class="cp-loading-container">',
'<div class="cp-loading-spinner-container">',
'<span class="fa fa-circle-o-notch fa-spin fa-4x fa-fw"></span>',
'</div>',
'<p id="cp-loading-message"></p>',
'</div>'
].join('');
return function () {
var intr;
var append = function () {
if (!document.body) { return; }
clearInterval(intr);
document.body.appendChild(elem);
};
intr = setInterval(append, 100);
append();
};
});

@ -154,6 +154,7 @@ define([
proxy.login_name = uname; proxy.login_name = uname;
proxy[Constants.displayNameKey] = uname; proxy[Constants.displayNameKey] = uname;
sessionStorage.createReadme = 1; sessionStorage.createReadme = 1;
if (!shouldImport) { proxy.version = 6; }
Feedback.send('REGISTRATION', true); Feedback.send('REGISTRATION', true);
} else { } else {
Feedback.send('LOGIN', true); Feedback.send('LOGIN', true);
@ -212,6 +213,7 @@ define([
loadingText: Messages.login_hashing, loadingText: Messages.login_hashing,
hideTips: true, hideTips: true,
}); });
// We need a setTimeout(cb, 0) otherwise the loading screen is only displayed // We need a setTimeout(cb, 0) otherwise the loading screen is only displayed
// after hashing the password // after hashing the password
window.setTimeout(function () { window.setTimeout(function () {
@ -256,7 +258,10 @@ define([
// logMeIn should reset registering = false // logMeIn should reset registering = false
UI.removeLoadingScreen(function () { UI.removeLoadingScreen(function () {
UI.confirm(Messages.register_alreadyRegistered, function (yes) { UI.confirm(Messages.register_alreadyRegistered, function (yes) {
if (!yes) { return; } if (!yes) {
hashing = false;
return;
}
proxy.login_name = uname; proxy.login_name = uname;
if (!proxy[Constants.displayNameKey]) { if (!proxy[Constants.displayNameKey]) {

@ -73,7 +73,7 @@ define([
]) ])
]) ])
]), ]),
h('div.cp-version-footer', "CryptPad v1.28.0 (toString)") h('div.cp-version-footer', "CryptPad v2.0.0 (Alpaca)")
]); ]);
}; };
@ -93,16 +93,25 @@ define([
]); ]);
} }
var button = h('button.navbar-toggler', {
'type':'button',
/*'data-toggle':'collapse',
'data-target':'#menuCollapse',
'aria-controls': 'menuCollapse',
'aria-expanded':'false',
'aria-label':'Toggle navigation'*/
}, h('i.fa.fa-bars '));
$(button).click(function () {
if ($('#menuCollapse').is(':visible')) {
return void $('#menuCollapse').slideUp();
}
$('#menuCollapse').slideDown();
});
return h('nav.navbar.navbar-expand-lg', return h('nav.navbar.navbar-expand-lg',
h('a.navbar-brand', { href: '/index.html'}), h('a.navbar-brand', { href: '/index.html'}),
h('button.navbar-toggler', { button,
'type':'button',
'data-toggle':'collapse',
'data-target':'#menuCollapse',
'aria-controls': 'menuCollapse',
'aria-expanded':'false',
'aria-label':'Toggle navigation'
}, h('i.fa.fa-bars ')),
h('div.collapse.navbar-collapse.justify-content-end#menuCollapse', [ h('div.collapse.navbar-collapse.justify-content-end#menuCollapse', [
//h('a.nav-item.nav-link', { href: '/what-is-cryptpad.html'}, Msg.topbar_whatIsCryptpad), // Moved the FAQ //h('a.nav-item.nav-link', { href: '/what-is-cryptpad.html'}, Msg.topbar_whatIsCryptpad), // Moved the FAQ
h('a.nav-item.nav-link', { href: '/faq.html'}, Msg.faq_link), h('a.nav-item.nav-link', { href: '/faq.html'}, Msg.faq_link),
@ -619,23 +628,87 @@ define([
]; ];
}; };
var loadingScreen = Pages.loadingScreen = function () { Pages.createCheckbox = function (id, labelTxt, checked, opts) {
return h('div#cp-loading', opts = opts|| {};
h('div.cp-loading-container', [ // Input properties
h('img.cp-loading-cryptofist', { var inputOpts = {
src: '/customize/cryptpad-new-logo-colors-logoonly.png?' + urlArgs type: 'checkbox',
}), id: id
h('div.cp-loading-spinner-container', };
h('span.fa.fa-circle-o-notch.fa-spin.fa-4x.fa-fw')), if (checked) { inputOpts.checked = 'checked'; }
h('p'), $.extend(inputOpts, opts.input || {});
])
); // Label properties
var labelOpts = {};
$.extend(labelOpts, opts.label || {});
if (labelOpts.class) { labelOpts.class += ' cp-checkmark'; }
// Mark properties
var markOpts = { tabindex: 0 };
$.extend(markOpts, opts.mark || {});
var input = h('input', inputOpts);
var mark = h('span.cp-checkmark-mark', markOpts);
var label = h('span.cp-checkmark-label', labelTxt);
$(mark).keydown(function (e) {
if (e.which === 32) {
e.stopPropagation();
e.preventDefault();
$(input).prop('checked', !$(input).is(':checked'));
$(input).change();
}
});
$(input).change(function () { $(mark).focus(); });
return h('label.cp-checkmark', labelOpts, [
input,
mark,
label
]);
}; };
var hiddenLoader = function () { Pages.createRadio = function (name, id, labelTxt, checked, opts) {
var loader = loadingScreen(); opts = opts|| {};
loader.style.display = 'none'; // Input properties
return loader; var inputOpts = {
type: 'radio',
id: id,
name: name
};
if (checked) { inputOpts.checked = 'checked'; }
$.extend(inputOpts, opts.input || {});
// Label properties
var labelOpts = {};
$.extend(labelOpts, opts.label || {});
if (labelOpts.class) { labelOpts.class += ' cp-checkmark'; }
// Mark properties
var markOpts = { tabindex: 0 };
$.extend(markOpts, opts.mark || {});
var input = h('input', inputOpts);
var mark = h('span.cp-radio-mark', markOpts);
var label = h('span.cp-checkmark-label', labelTxt);
$(mark).keydown(function (e) {
if (e.which === 32) {
e.stopPropagation();
e.preventDefault();
$(input).prop('checked', !$(input).is(':checked'));
$(input).change();
}
});
$(input).change(function () { $(mark).focus(); });
return h('label.cp-radio', labelOpts, [
input,
mark,
label
]);
}; };
Pages['/user/'] = Pages['/user/index.html'] = function () { Pages['/user/'] = Pages['/user/index.html'] = function () {
@ -681,27 +754,10 @@ define([
placeholder: Msg.login_confirm, placeholder: Msg.login_confirm,
}), }),
h('div.checkbox-container', [ h('div.checkbox-container', [
h('input#import-recent', { Pages.createCheckbox('import-recent', Msg.register_importRecent, true)
name: 'import-recent',
type: 'checkbox',
checked: true
}),
// hscript doesn't generate for on label for some
// reason... use jquery as a temporary fallback
setHTML($('<label for="import-recent"></label>')[0], Msg.register_importRecent)
/*h('label', {
'for': 'import-recent',
}, Msg.register_importRecent),*/
]), ]),
h('div.checkbox-container', [ h('div.checkbox-container', [
h('input#accept-terms', { $(Pages.createCheckbox('accept-terms')).find('.cp-checkmark-label').append(Msg.register_acceptTerms).parent()[0]
name: 'accept-terms',
type: 'checkbox'
}),
setHTML($('<label for="accept-terms"></label>')[0], Msg.register_acceptTerms)
/*setHTML(h('label', {
'for': 'accept-terms',
}), Msg.register_acceptTerms),*/
]), ]),
h('button#register.btn.cp-login-register', Msg.login_register) h('button#register.btn.cp-login-register', Msg.login_register)
]) ])
@ -716,7 +772,6 @@ define([
]), ]),
infopageFooter(), infopageFooter(),
hiddenLoader(),
])]; ])];
}; };
@ -743,17 +798,7 @@ define([
placeholder: Msg.login_password, placeholder: Msg.login_password,
}), }),
h('div.checkbox-container', [ h('div.checkbox-container', [
h('input#import-recent', { Pages.createCheckbox('import-recent', Msg.register_importRecent, true),
name: 'import-recent',
type: 'checkbox',
checked: true
}),
// hscript doesn't generate for on label for some
// reason... use jquery as a temporary fallback
setHTML($('<label for="import-recent"></label>')[0], Msg.register_importRecent)
/*h('label', {
'for': 'import-recent',
}, Msg.register_importRecent),*/
]), ]),
h('div.extra', [ h('div.extra', [
h('button.login.first.btn', Msg.login_login) h('button.login.first.btn', Msg.login_login)
@ -762,7 +807,6 @@ define([
]), ]),
]), ]),
infopageFooter(), infopageFooter(),
hiddenLoader(),
])]; ])];
}; };

@ -12,14 +12,11 @@
@alertify-btn-fg: @alertify-fore; @alertify-btn-fg: @alertify-fore;
@alertify-btn-bg: rgba(200, 200, 200, 0.1);
@alertify-btn-bg-hover: rgba(200, 200, 200, .3);
@alertify-bg: @colortheme_modal-dim; @alertify-bg: @colortheme_modal-dim;
@alertify-fg: @alertify-fore; @alertify-fg: @alertify-fore;
@alertify-input-bg: @colortheme_modal-input; @alertify-input-bg: @colortheme_modal-input;
@alertify-input-fg: @colortheme_modal-fg; @alertify-input-fg: @colortheme_modal-input-fg;
@alertify_padding-base: @variables_padding; @alertify_padding-base: @variables_padding;
@alertify_box-shadow: @variables_shadow; @alertify_box-shadow: @variables_shadow;
@ -34,7 +31,7 @@
} }
> * { > * {
padding: @alertify_padding-base @alertify_padding-base * 4; padding: @alertify_padding-base @alertify_padding-base * 4;
color: @alertify-fore; color: @colortheme_notification-color;
font-family: @colortheme_font; font-family: @colortheme_font;
font-size: large; font-size: large;
@ -65,6 +62,8 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: 100000; // alertify container z-index: 100000; // alertify container
font: @colortheme_app-font;
&.forefront { &.forefront {
z-index: @max-z-index; // alertify max forefront z-index: @max-z-index; // alertify max forefront
} }
@ -112,10 +111,6 @@
} }
.dialog, .alert { .dialog, .alert {
.bright {
color: @colortheme_light-base;
}
& > div { & > div {
background-color: @alertify-dialog-bg; background-color: @alertify-dialog-bg;
&.half { &.half {
@ -227,7 +222,7 @@
button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button) { button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button) {
background-color: @alertify-btn-bg; background-color: @colortheme_alertify-cancel;
box-sizing: border-box; box-sizing: border-box;
position: relative; position: relative;
outline: 0; outline: 0;
@ -247,7 +242,7 @@
border-radius: 0; border-radius: 0;
color: @alertify-btn-fg; color: @alertify-btn-fg;
border: 1px solid transparent; border: 1px solid @colortheme_alertify-cancel-border;
&.safe, &.danger { &.safe, &.danger {
color: @colortheme_old-base; color: @colortheme_old-base;
@ -256,32 +251,40 @@
} }
&.danger { &.danger {
background-color: @colortheme_alertify-red; background-color: @colortheme_alertify-red;
border-color: @colortheme_alertify-red-border;
color: @colortheme_alertify-red-color;
&:hover, &:active { &:hover, &:active {
background-color: lighten(@colortheme_alertify-red, 5%); background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-red, 10%), lighten(@colortheme_alertify-red, 10%));
} }
} }
&.safe { &.safe {
background-color: @colortheme_alertify-green; background-color: @colortheme_alertify-green;
border-color: @colortheme_alertify-green-border;
color: @colortheme_alertify-green-color;
&:hover, &:active { &:hover, &:active {
background-color: lighten(@colortheme_alertify-green, 10%); background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-green, 10%), lighten(@colortheme_alertify-green, 10%));
} }
} }
&.primary { &.primary {
background-color: @colortheme_alertify-primary; background-color: @colortheme_alertify-primary;
color: @colortheme_alertify-primary-text; color: @colortheme_alertify-primary-text;
border-color: @colortheme_alertify-primary-border;
font-weight: bold;
&:hover, &:active { &:hover, &:active {
background-color: darken(@colortheme_alertify-primary, 10%); background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-primary, 10%), lighten(@colortheme_alertify-primary, 10%));
} }
} }
&:hover, &:active { &:hover, &:active {
background-color: @alertify-btn-bg-hover; background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-cancel, 10%), lighten(@colortheme_alertify-cancel, 10%));
} }
&:focus { &:focus {
border: 1px dotted @alertify-base; //border: 1px dotted @alertify-base;
box-shadow: 0px 0px 5px @colortheme_alertify-primary;
outline: none;
} }
&::-moz-focus-inner { &::-moz-focus-inner {
border: 0; border: 0;

@ -7,6 +7,7 @@
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
position: relative; position: relative;
border: 0;
body { body {
height: 100%; height: 100%;
width: 100%; width: 100%;
@ -15,6 +16,7 @@
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
position: relative; position: relative;
border: 0;
} }
} }

@ -5,7 +5,7 @@
@width: round(@size / 8); @width: round(@size / 8);
@dim1: round(@size / 3); @dim1: round(@size / 3);
@dim2: round(2 * @size / 3); @dim2: round(2 * @size / 3);
@top: round(@size / 12); @top: round(@size / 12) - 1;
// <label.cp-checkmark><input><span.cp-checkmark-mark></span>Text</label> // <label.cp-checkmark><input><span.cp-checkmark-mark></span>Text</label>
.cp-checkmark { .cp-checkmark {
margin: 0; margin: 0;
@ -17,6 +17,10 @@
-ms-user-select: none; -ms-user-select: none;
user-select: none; user-select: none;
& > a {
margin-left: 0.25em;
}
&.cp-checkmark-secondary { &.cp-checkmark-secondary {
.cp-checkmark-mark { .cp-checkmark-mark {
&:after { &:after {
@ -26,6 +30,7 @@
input { input {
&:checked ~ .cp-checkmark-mark { &:checked ~ .cp-checkmark-mark {
background-color: @colortheme_checkmark-back2; background-color: @colortheme_checkmark-back2;
border-color: @colortheme_checkmark-back2;
} }
} }
} }
@ -37,12 +42,19 @@
display: none; display: none;
&:checked ~ .cp-checkmark-mark { &:checked ~ .cp-checkmark-mark {
background-color: @colortheme_checkmark-back1; background-color: @colortheme_checkmark-back1;
border-color: @colortheme_checkmark-back1;
&:after { &:after {
display: block; display: block;
} }
} }
} }
.cp-checkmark-label {
cursor: default;
&:empty {
display: none;
}
}
.cp-checkmark-mark { .cp-checkmark-mark {
margin-right: 10px; margin-right: 10px;
position: relative; position: relative;
@ -51,6 +63,8 @@
background-color: @colortheme_checkmark-back0; background-color: @colortheme_checkmark-back0;
display: flex; display: flex;
justify-content: center; justify-content: center;
border: 1px solid @colortheme_form-border;
flex-shrink: 0;
&:after { &:after {
content: ""; content: "";
display: none; display: none;
@ -60,6 +74,90 @@
transform: rotate(45deg); transform: rotate(45deg);
border: solid @colortheme_checkmark-col1; border: solid @colortheme_checkmark-col1;
border-width: 0 @width @width 0; border-width: 0 @width @width 0;
position: absolute;
}
&:focus {
//border-color: #FF007C !important;
box-shadow: 0px 0px 5px @colortheme_checkmark-back1;
outline: none;
}
}
}
.cp-radio {
margin: 0;
display: flex;
align-items: center;
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
&.cp-radio-secondary {
.cp-radio-mark {
&:after {
border-color: @colortheme_checkmark-col2;
}
}
input {
&:checked ~ .cp-radio-mark {
background-color: @colortheme_checkmark-back2;
}
}
}
&:hover .cp-radio-mark {
background-color: @colortheme_checkmark-back0-active;
}
input {
display: none;
&:checked ~ .cp-radio-mark {
background-color: @colortheme_checkmark-back1;
border-color: @colortheme_checkmark-back1;
&:after {
display: block;
}
}
}
.cp-checkmark-label {
cursor: default;
&:empty {
display: none;
}
}
@radio-size: @dim1 * 3;
.cp-radio-mark {
margin-right: 10px;
position: relative;
height: @radio-size;
width: @radio-size;
background-color: @colortheme_checkmark-back0;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid @colortheme_form-border;
flex-shrink: 0;
&:after {
display: none;
content: "";
border-radius: 50%;
background: white;
width: @dim1;
height: @dim1;
//transform: rotate(45deg);
//border: solid @colortheme_checkmark-col1;
//border-width: 0 @width @width 0;
}
&:focus {
//border-color: #FF007C !important;
box-shadow: 0px 0px 5px @colortheme_checkmark-back1;
outline: none;
} }
} }

@ -2,6 +2,9 @@
@colortheme_app-font-size: 16px; @colortheme_app-font-size: 16px;
@colortheme_app-font: @colortheme_app-font-size @colortheme_font; @colortheme_app-font: @colortheme_app-font-size @colortheme_font;
@colortheme_logo-1: #326599;
@colortheme_logo-2: #4591c4;
@colortheme_link-color: #0275D8; @colortheme_link-color: #0275D8;
@colortheme_link-color-visited: #005999; @colortheme_link-color-visited: #005999;
@colortheme_info-background: #fafafa; @colortheme_info-background: #fafafa;
@ -15,23 +18,42 @@
@colortheme_cp-red: #FA5858; // remove red @colortheme_cp-red: #FA5858; // remove red
@colortheme_cp-green: #46E981; @colortheme_cp-green: #46E981;
@colortheme_modal-bg: #222; @colortheme_form-border: #bbbbbb;
@colortheme_modal-fg: #fff; @colortheme_form-bg: @colortheme_logo-2;
@colortheme_modal-link: #eee; @colortheme_form-color: #ffffff;
@colortheme_form-bg-alt: #ffffff;
@colortheme_form-color-alt: @colortheme_logo-1;
@colortheme_form-warning: #f49842;
@colortheme_form-warning-hov: darken(@colortheme_form-warning, 5%);
@colortheme_modal-bg: @colortheme_form-bg-alt; // TODO Modals bg
@colortheme_modal-fg: @colortheme_form-color-alt;
@colortheme_modal-link: @colortheme_link-color;
@colortheme_modal-link-visited: lighten(@colortheme_modal-link, 10%); @colortheme_modal-link-visited: lighten(@colortheme_modal-link, 10%);
@colortheme_modal-dim: rgba(0, 0, 0, 0.4); @colortheme_modal-dim: fade(@colortheme_logo-2, 50%); // TODO transparent background behind modals
@colortheme_modal-input: @colortheme_form-bg;
@colortheme_modal-input-fg: @colortheme_form-color;
@colortheme_loading-bg: #222; @colortheme_loading-bg: @colortheme_logo-1;
@colortheme_loading-bg-alt: @colortheme_logo-2;
@colortheme_loading-color: @colortheme_old-fore; @colortheme_loading-color: @colortheme_old-fore;
@colortheme_modal-input: #111;
// TODO modals buttons
@colortheme_alertify-red: #E55236; @colortheme_alertify-red: #E55236;
@colortheme_alertify-red-color: #FFF;
@colortheme_alertify-red-border: transparent;
@colortheme_alertify-green: #77C825; @colortheme_alertify-green: #77C825;
@colortheme_alertify-primary: #fff; @colortheme_alertify-green-color: #FFF;
@colortheme_alertify-primary-text: #000; @colortheme_alertify-green-border: transparent;
@colortheme_alertify-primary: @colortheme_form-bg;
@colortheme_notification-log: rgba(0, 0, 0, 0.8); @colortheme_alertify-primary-text: @colortheme_form-color;
@colortheme_alertify-primary-border: transparent;
@colortheme_alertify-cancel: @colortheme_modal-bg;
@colortheme_alertify-cancel-border: #ccc;
@colortheme_notification-log: fade(@colortheme_logo-1, 90%);
@colortheme_notification-color: #fff;;
@colortheme_notification-warn: rgba(205, 37, 50, 0.8); @colortheme_notification-warn: rgba(205, 37, 50, 0.8);
@colortheme_dropdown-bg: #f9f9f9; @colortheme_dropdown-bg: #f9f9f9;
@ -117,9 +139,9 @@
@cryptpad_header_col: #1E1F1F; @cryptpad_header_col: #1E1F1F;
@cryptpad_text_col: #3F4141; @cryptpad_text_col: #3F4141;
@colortheme_checkmark-back0: #ffffff; @colortheme_checkmark-back0: @colortheme_form-bg-alt;
@colortheme_checkmark-back0-active: #bbbbbb; @colortheme_checkmark-back0-active: @colortheme_form-border;
@colortheme_checkmark-back1: #FF0073; @colortheme_checkmark-back1: @colortheme_form-bg;
@colortheme_checkmark-col1: #ffffff; @colortheme_checkmark-col1: @colortheme_form-color;
@colortheme_checkmark-back2: #FFFFFF; @colortheme_checkmark-back2: @colortheme_form-bg-alt;
@colortheme_checkmark-col2: #000000; @colortheme_checkmark-col2: @colortheme_form-color-alt;

@ -1,47 +1,78 @@
@import (once) "./colortheme-all.less"; @import (once) "./colortheme-all.less";
@import (once) "./tools.less"; @import (once) "./tools.less";
@import (once) "./checkmark.less";
@import (once) './icon-colors.less'; @import (once) './icon-colors.less';
.creation_main() { .creation_main(
.tippy-popper { @color: @colortheme_default-color, // Color of the text for the toolbar
z-index: 100000001 !important; @bg-color: @colortheme_default-bg, // color of the toolbar background
} @warn-color: @colortheme_default-warn, // color of the warning text in the toolbar
) {
@colortheme_creation-modal-bg: #fff;
@colortheme_creation-modal: #666;
@colortheme_creation-modal-title: @colortheme_loading-bg;
#cp-creation-container { #cp-creation-container {
position: absolute; position: absolute;
z-index: 100000000; // #loading * 10 z-index: 100000000; // #loading * 10
top: 0px; top: 0px;
background: @colortheme_loading-bg; //background: @colortheme_loading-bg;
background: linear-gradient(to right, @colortheme_loading-bg 0%, @colortheme_loading-bg 50%, @colortheme_loading-bg-alt 50%, @colortheme_loading-bg-alt 100%);
color: @colortheme_loading-color; color: @colortheme_loading-color;
display: flex; display: flex;
flex-flow: column; /* we need column so that the child can shrink vertically */ flex-flow: column; /* we need column so that the child can shrink vertically */
justify-content: center; justify-content: center;
align-items: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
.cp-creation-logo {
height: 300px;
width: 300px;
margin-top: 50px;
flex: 0 1 auto; /* allows shrink */
min-height: 0;
text-align: center;
img {
max-width: 100%;
max-height: 100%;
}
}
} }
#cp-creation { #cp-creation {
flex: 0 1 auto; /* allows shrink */
min-height: 0;
overflow: auto; overflow: auto;
text-align: center; text-align: center;
background: @colortheme_creation-modal-bg;
color: @colortheme_creation-modal;
font: @colortheme_app-font; font: @colortheme_app-font;
width: 100%;
outline: none; outline: none;
width: 700px;
max-width: 90vw;
height: 500px;
max-height: calc(~"100vh - 20px");
margin: 50px;
flex-shrink: 0;
display: flex;
flex-flow: column;
& > div { & > div {
width: 60vw; width: 100%;
max-width: 100%; max-width: 100%;
margin: 40px auto; margin: auto;
text-align: left; text-align: left;
} }
.cp-creation-create, .cp-creation-settings { .cp-creation-title {
color: @colortheme_creation-modal-title;
font-weight: bold;
margin: 15px;
}
.cp-creation-create {
margin-top: 0px; margin-top: 0px;
@creation-button: #30B239;
button { button {
.tools_unselectable(); .tools_unselectable();
padding: 15px; padding: 15px;
background: @creation-button; background: linear-gradient(to right, @colortheme_logo-2, @colortheme_logo-1);
color: #FFF; color: #FFF;
font-weight: bold; font-weight: bold;
margin: 3px 10px; margin: 3px 10px;
@ -50,15 +81,16 @@
outline: none; outline: none;
width: 100%; width: 100%;
&:hover { &:hover {
background: linear-gradient(to right, lighten(@colortheme_logo-2, 5%), lighten(@colortheme_logo-1, 5%));
//background: darken(@creation-button, 5%); //background: darken(@creation-button, 5%);
background: lighten(@creation-button, 5%); //background: lighten(@creation-button, 5%);
} }
} }
} }
.cp-creation-create { .cp-creation-create {
text-align: center; text-align: center;
margin: auto; //margin: auto;
margin-top: 20px; //margin-top: 20px;
width: 400px; width: 400px;
max-width: 100%; max-width: 100%;
button { button {
@ -70,6 +102,8 @@
display: flex; display: flex;
flex-flow: column; flex-flow: column;
align-items: center; align-items: center;
flex: 1 0 auto;
justify-content: space-evenly;
& > div { & > div {
width: 400px; width: 400px;
max-width: 100%; max-width: 100%;
@ -77,7 +111,9 @@
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
font-size: 16px; font-size: 16px;
margin: 10px 0; //margin: 10px 0;
min-height: 28px;
line-height: 28px;
label { label {
flex: 1; flex: 1;
} }
@ -88,31 +124,68 @@
padding: 0 10px; padding: 0 10px;
} }
} }
.cp-creation-help { }
font-size: 18px; .cp-creation-help, .cp-creation-warning {
color: white; font-size: 18px;
&:hover { color: @colortheme_form-warning;
color: #AAA; &:hover {
text-decoration: none; color: @colortheme_form-warning-hov;
} text-decoration: none;
} }
} }
.cp-creation-slider { .cp-creation-slider {
display: block; display: block;
overflow: hidden; overflow: hidden;
max-height: 0px; max-height: 0px;
transition: max-height 0.5s ease-in-out; max-width: 0px;
width: 100%; //margin-top: 10px;
margin-top: 10px;
&.active { &.active {
max-height: 40px; transition: max-height 0.5s ease-in-out;
max-width: unset;
max-height: 100px;
} }
} }
input, select {
font-size: 14px;
border: 1px solid @colortheme_form-border;
height: 26px;
background-color: @colortheme_form-bg;
color: @colortheme_form-color;
}
.cp-creation-expire { .cp-creation-expire {
.cp-creation-expire-picker { .cp-creation-expire-picker {
text-align: center; text-align: center;
input { input {
width: 100px; width: 50px;
margin: 0 5px;
}
select {
margin-right: 5px;
}
}
&.active {
label {
flex: unset;
}
.cp-creation-slider {
flex: 1;
}
}
}
.cp-creation-password {
.cp-creation-password-picker {
text-align: center;
width: 100%;
.cp-password-container {
input {
width: 150px;
padding: 0 5px;
}
label {
flex: unset;
}
} }
} }
} }
@ -125,31 +198,51 @@
} }
div.cp-creation-remember { div.cp-creation-remember {
margin-top: 30px;
.cp-creation-remember-help { .cp-creation-remember-help {
font-style: italic; width: 100%;
//font-style: italic;
font-size: 12px;
font-weight: bold;
color: @colortheme_form-bg;
line-height: 20px;
.fa {
margin-right: 10px;
}
} }
} }
div.cp-creation-template { div.cp-creation-template {
width: 100%; width: 100%;
background-color: darken(@colortheme_modal-bg, 3%); //flex: 1 0 auto;
padding: 20px; flex-wrap: nowrap;
margin: 30px 0; .cp-creation-template-more {
.cp-creation-title { font-size: 30px;
padding: 0 0 10px 10px; cursor: pointer;
margin: auto; margin: 0 5px;
text-align: center;
&:first-child {
left: 5px;
}
&:last-child {
right: 5px;
}
&:hover {
color: #888;
}
&.hidden {
visibility: hidden;
}
} }
} }
.cp-creation-template-container { .cp-creation-template-container {
width: 100%; width: 100%;
flex: 1;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
overflow-y: auto; //overflow-y: auto;
align-items: center; align-items: center;
.cp-creation-template-element { .cp-creation-template-element {
@darker: darken(@colortheme_modal-fg, 30%); box-shadow: 2px 2px 7px @colortheme_form-border;
width: 135px; width: 135px;
padding: 5px; padding: 5px;
margin: 5px; margin: 5px;
@ -162,19 +255,23 @@
line-height: 1em; line-height: 1em;
cursor: pointer; cursor: pointer;
background-color: #111; color: black;
color: @darker;
border: 1px solid transparent; border: 1px solid transparent;
&.cp-creation-template-selected { &.cp-creation-template-selected {
border: 1px solid white; color: @color !important;
background-color: #222; background-color: @bg-color !important;
.fa {
color: @color;
}
} }
transition: all 0.1s; transition: all 0.1s;
&:hover { &:hover {
color: @colortheme_modal-fg; //color: @colortheme_modal-fg;
background-color: @colortheme_form-border;
box-shadow: none;
} }
align-items: center; align-items: center;
@ -196,6 +293,7 @@
max-width: 100%; max-width: 100%;
} }
.fa { .fa {
color: @bg-color;
cursor: pointer; cursor: pointer;
width: 100px; width: 100px;
height: 100px; height: 100px;
@ -210,52 +308,78 @@
.cp-creation-deleted-container { .cp-creation-deleted-container {
text-align: center; text-align: center;
.cp-creation-deleted { .cp-creation-deleted {
background: #111; margin: 0 10px;
background: @colortheme_loading-bg;
color: @colortheme_loading-color;
padding: 10px; padding: 10px;
text-align: center; text-align: center;
font-weight: bold; font-weight: bold;
display: inline-block; display: inline-block;
} }
} }
}
.checkmark_main(30px); @media screen and (max-height: 700px) {
#cp-creation-container {
@media screen and (max-width: @browser_media-narrow-screen) { .cp-creation-logo {
& > div { //flex-shrink: 0;
width: 95%; display: none;
margin: 10px auto; }
} }
} }
@media screen and (max-width: @browser_media-medium-screen) { @media screen and (max-width: 500px) {
#cp-creation-form { #cp-creation {
div.cp-creation-template { #cp-creation-form {
margin: 0; & > div {
padding: 5px; width: 95%;
.cp-creation-template-container { margin: 10px auto;
.cp-creation-template-element { }
flex-flow: row; .cp-creation-expire {
margin: 1px; &.active {
padding: 5px; label {
width: 155px; flex: 1;
img {
display: none;
} }
.fa { .cp-creation-slider {
font-size: 18px; flex: unset;
width: 20px; order: 10;
height: 20px; width: 100%;
line-height: 20px;
display: inline !important;
} }
.cp-creation-template-element-name { }
margin: 0; }
margin-left: 5px; }
}
}
@media screen and (max-width: @browser_media-medium-screen) {
#cp-creation {
height: auto;
#cp-creation-form {
div.cp-creation-template {
margin: 0;
padding: 5px;
.cp-creation-template-container {
.cp-creation-template-element {
flex-flow: row;
margin: 1px;
padding: 5px;
width: 155px;
img {
display: none;
}
.fa {
font-size: 18px;
width: 20px;
height: 20px;
line-height: 20px;
display: inline !important;
}
.cp-creation-template-element-name {
margin: 0;
margin-left: 5px;
}
} }
} }
} }
} }
} }
} }
}
} }

@ -55,9 +55,23 @@
user-select: none; user-select: none;
float: none; float: none;
text-align: left; text-align: left;
font: @dropdown_font;
line-height: 1em; line-height: 1em;
align-items: center;
&:not(.fa) {
font: @dropdown_font;
}
&.fa {
font-size: 18px;
&::before {
width: 40px;
margin-left: -10px;
text-align: center;
}
* {
font: @dropdown_font;
}
}
.fa { .fa {
width: 20px; width: 20px;

@ -1,8 +1,12 @@
@import (once) "./colortheme-all.less";
@import (once) "./toolbar.less"; @import (once) "./toolbar.less";
@import (once) './fileupload.less'; @import (once) './fileupload.less';
@import (once) './alertify.less'; @import (once) './alertify.less';
@import (once) './tokenfield.less'; @import (once) './tokenfield.less';
@import (once) './creation.less'; @import (once) './creation.less';
@import (once) './tippy.less';
@import (once) "./checkmark.less";
@import (once) "./password-input.less";
.framework_main(@bg-color, @warn-color, @color) { .framework_main(@bg-color, @warn-color, @color) {
.toolbar_main( .toolbar_main(
@ -13,6 +17,33 @@
.fileupload_main(); .fileupload_main();
.alertify_main(); .alertify_main();
.tokenfield_main(); .tokenfield_main();
.creation_main(); .tippy_main();
.checkmark_main(20px);
.password_main();
.creation_main(
@bg-color: @bg-color,
@warn-color: @warn-color,
@color: @color
);
font: @colortheme_app-font;
}
.framework_min_main(
@color: @colortheme_default-color, // Color of the text for the toolbar
@bg-color: @colortheme_default-bg, // color of the toolbar background
@warn-color: @colortheme_default-warn, // color of the warning text in the toolbar
) {
.toolbar_main(
@bg-color: @bg-color,
@warn-color: @warn-color,
@color: @color
);
.fileupload_main();
.alertify_main();
.tippy_main();
.checkmark_main(20px);
.password_main();
font: @colortheme_app-font;
} }

@ -10,7 +10,6 @@
text-overflow: ellipsis; text-overflow: ellipsis;
padding-top: 5px; padding-top: 5px;
padding-bottom: 5px; padding-bottom: 5px;
border: 1px solid white;
.cp-icons-name { .cp-icons-name {
width: 100%; width: 100%;
@ -26,7 +25,7 @@
word-wrap: break-word; word-wrap: break-word;
} }
&.cp-icons-element-selected { &.cp-icons-element-selected {
background-color: white; background-color: rgba(0,0,0,0.2);
color: #666; color: #666;
} }
.fa { .fa {

@ -161,6 +161,7 @@
background-size: contain; background-size: contain;
height: 50px; height: 50px;
width: 250px; width: 250px;
margin-right: 0;
} }
a { a {
border: 2px solid transparent; border: 2px solid transparent;
@ -169,7 +170,8 @@
.nav-link { .nav-link {
padding: 0.5em 0.7em; padding: 0.5em 0.7em;
&:hover { &:hover {
transform: scale(1.05); font-size: 1.05em;
//transform: scale(1.05);
}; };
} }
.cp-register-btn { .cp-register-btn {
@ -184,9 +186,18 @@
color: #4591C4; color: #4591C4;
} }
} }
@media (max-width: 991px) { @media (max-width: 1000px) {
#menuCollapse { #menuCollapse {
text-align: right; text-align: right;
/* @media (min-width: 576px) {
top: 100%;
background: rgba(255,255,255,0.8);
position: absolute;
right: 0px;
padding: 0 20px;
z-index: 1;
}
*/
} }
.navbar-nav a { .navbar-nav a {
text-align: right !important; text-align: right !important;
@ -194,7 +205,7 @@
.cp-register-btn { .cp-register-btn {
margin-right: 13px; margin-right: 13px;
text-align: center; text-align: center;
} }
} }
//footer general styles //footer general styles

@ -57,7 +57,7 @@
input { input {
background-color: @colortheme_modal-input; background-color: @colortheme_modal-input;
color: @colortheme_modal-fg; color: @colortheme_modal-input-fg;
border: 0; border: 0;
padding: 8px 12px; padding: 8px 12px;
margin: 1em; margin: 1em;

@ -0,0 +1,13 @@
.password_main() {
.cp-password-container {
display: flex;
align-items: center;
input {
flex: 1;
min-width: 0;
}
label, .fa {
margin-left: 10px;
}
}
}

@ -52,6 +52,12 @@
margin-bottom: 0; margin-bottom: 0;
} }
} }
label.noTitle {
display: inline-flex;
.fa {
margin-left: 10px;
}
}
margin-bottom: 20px; margin-bottom: 20px;
} }
[type="text"], button { [type="text"], button {

@ -0,0 +1,14 @@
@import (once) './colortheme-all.less';
.tippy_main() {
.tippy-tooltip.cryptpad-theme {
/* Your styling here. Example: */
background-color: white;
box-shadow: 2px 2px 10px #000;
font-weight: bold;
color: #333;
[x-circle] {
background-color: unset;
}
}
}

@ -1,25 +1,31 @@
@import (once) "./tools.less"; @import (once) "./tools.less";
.tokenfield_main () { .tokenfield_main () {
.ui-autocomplete {
z-index: 100001; // alertify + 1
}
.tokenfield { .tokenfield {
.tools_unselectable(); .tools_unselectable();
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
height: auto; height: auto;
min-height: 34px; min-height: 34px;
padding-bottom: 0px; padding-bottom: 0px;
background-color: unset; background-color: unset;
border: none; border: none;
display: flex; margin: 0 10px;
flex-wrap: wrap; padding: 0;
align-items: center; width: ~"calc(100% - 20px)";
padding: 0 10px;
.token { .token {
box-sizing: border-box; box-sizing: border-box;
border-radius: 3px; border-radius: 3px;
display: inline-block; display: inline-flex;
align-items: center;
border: 1px solid #d9d9d9; border: 1px solid #d9d9d9;
background-color: #ededed; background-color: #ededed;
white-space: nowrap; white-space: nowrap;
margin: 10px 5px; margin: 2px 0;
height: 24px; height: 24px;
vertical-align: middle; vertical-align: middle;
cursor: default; cursor: default;
@ -50,7 +56,7 @@
.close { .close {
font-family: Arial; font-family: Arial;
display: inline-block; display: inline-block;
line-height: 24px; line-height: 1.49em;
font-size: 1.1em; font-size: 1.1em;
margin-left: 5px; margin-left: 5px;
float: none; float: none;
@ -73,6 +79,8 @@
margin: 0 !important; // Override alertify margin: 0 !important; // Override alertify
box-shadow: none; box-shadow: none;
max-width: 100%; max-width: 100%;
width: 100%;
min-width: 100% !important;
&:focus { &:focus {
border-color: transparent; border-color: transparent;
outline: 0; outline: 0;

@ -183,8 +183,13 @@
#cp-app-toolbar-creation-dialog.cp-modal-container { #cp-app-toolbar-creation-dialog.cp-modal-container {
.icons_main(); .icons_main();
li:hover { li {
border: 1px solid white; border: 1px solid @colortheme_modal-fg;
&:hover {
//border: 1px solid @colortheme_modal-fg;
background: @colortheme_modal-fg;
color: @colortheme_modal-bg;
}
} }
.cp-modal { .cp-modal {
display: flex; display: flex;

@ -1,73 +0,0 @@
/*
WARNING: THIS FILE DOES NOTHING
It exists only as a proposal of what CSS you should use in loading.js
The CSS inside of loading.js is precompiled in order to save 200ish milliseconds to the loading screen.
*/
@import (once) "./include/colortheme-all.less";
@import (once) "./include/browser.less";
#cp-loading {
transition: opacity 0.75s, visibility 0s 0.75s;
visibility: visible;
opacity: 1;
position: fixed;
z-index: 10000000; // #loading
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
background: @colortheme_loading-bg;
color: @colortheme_loading-color;
text-align: center;
font-size: 1.5em;
.cp-loading-container {
margin-top: 50vh;
transform: translateY(-50%);
}
.cp-loading-cryptofist {
margin-left: auto;
margin-right: auto;
height: 300px;
margin-bottom: 2em;
@media screen and (max-height: @browser_media-short-screen) {
display: none;
}
}
.cp-loading-spinner-container {
position: relative;
height: 100px;
> div {
height: 100px;
}
}
&.cp-loading-hidden {
opacity: 0;
visibility: hidden;
}
}
#cp-loading-tip {
position: fixed;
z-index: 10000000; // loading tip
top: 80%;
left: 0;
right: 0;
text-align: center;
transition: opacity 750ms;
transition-delay: 3000ms;
@media screen and (max-height: @browser_media-medium-screen) {
display: none;
}
span {
background: @colortheme_loading-bg;
color: @colortheme_loading-color;
text-align: center;
font-size: 1.5em;
opacity: 0.7;
font-family: @colortheme_font;
padding: 15px;
max-width: 60%;
display: inline-block;
}
}

@ -1,11 +1,12 @@
@import (once) "../include/infopages.less"; @import (once) "../include/infopages.less";
@import (once) "../include/colortheme-all.less"; @import (once) "../include/colortheme-all.less";
@import (once) "../include/alertify.less"; @import (once) "../include/alertify.less";
@import (once) "../loading.less"; @import (once) "../include/checkmark.less";
.infopages_main(); .infopages_main();
.infopages_topbar(); .infopages_topbar();
.alertify_main(); .alertify_main();
.checkmark_main(20px);
.form-group { .form-group {
.extra { .extra {

@ -1,11 +1,12 @@
@import (once) "../include/infopages.less"; @import (once) "../include/infopages.less";
@import (once) "../include/colortheme-all.less"; @import (once) "../include/colortheme-all.less";
@import (once) "../include/alertify.less"; @import (once) "../include/alertify.less";
@import (once) "../loading.less"; @import (once) "../include/checkmark.less";
.infopages_main(); .infopages_main();
.infopages_topbar(); .infopages_topbar();
.alertify_main(); .alertify_main();
.checkmark_main(20px);
.cp-container { .cp-container {
.form-group { .form-group {
@ -23,11 +24,7 @@
} }
margin-top: 16px; margin-top: 16px;
font-size: 1.25em; font-size: 1.25em;
min-width: 30%; // conflict? min-width: 30%;
width: 30%;
@media (max-width: 500px) {
width: 45%;
}
} }
} }
padding-bottom: 3em; padding-bottom: 3em;

@ -3,7 +3,7 @@ define([
'/common/hyperscript.js', '/common/hyperscript.js',
'/customize/pages.js', '/customize/pages.js',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
], function ($, h, Pages) { ], function ($, h, Pages) {
$(function () { $(function () {
var $body = $('body'); var $body = $('body');
@ -27,8 +27,7 @@ $(function () {
window.Tether = function () {}; window.Tether = function () {};
require([ require([
'less!/customize/src/less2/main.less', 'less!/customize/src/less2/main.less',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css'
'/bower_components/bootstrap/dist/js/bootstrap.bundle.min.js'
], function () { ], function () {
$body.append($main); $body.append($main);

File diff suppressed because it is too large Load Diff

@ -31,7 +31,7 @@ define(function () {
out.websocketError = 'Αδυναμία σύνδεσης στον διακομιστή...'; out.websocketError = 'Αδυναμία σύνδεσης στον διακομιστή...';
out.typeError = "Αυτό το pad δεν είναι συμβατό με την επιλεγμένη εφαρμογή"; out.typeError = "Αυτό το pad δεν είναι συμβατό με την επιλεγμένη εφαρμογή";
out.onLogout = 'Έχετε αποσυνδεθεί, <a href="/" target="_blank">κάντε "κλικ" εδώ</a> για να συνδεθείτε<br>ή πατήστε <em>Escape</em> για να προσπελάσετε το έγγραφο σε λειτουργία ανάγνωσης μόνο.'; out.onLogout = 'Έχετε αποσυνδεθεί, {0}κάντε "κλικ" εδώ{1} για να συνδεθείτε<br>ή πατήστε <em>Escape</em> για να προσπελάσετε το έγγραφο σε λειτουργία ανάγνωσης μόνο.';
out.wrongApp = "Αδυναμία προβολής του περιεχομένου αυτής της συνεδρίας στον περιηγητή σας. Παρακαλώ δοκιμάστε επαναφόρτωση της σελίδας."; out.wrongApp = "Αδυναμία προβολής του περιεχομένου αυτής της συνεδρίας στον περιηγητή σας. Παρακαλώ δοκιμάστε επαναφόρτωση της σελίδας.";
out.loading = "Φόρτωση..."; out.loading = "Φόρτωση...";

@ -152,7 +152,7 @@ define(function () {
out.websocketError = "Error al conectarse al servidor WebSocket"; out.websocketError = "Error al conectarse al servidor WebSocket";
out.typeError = "Este documento no es compatible con la aplicación seleccionada"; out.typeError = "Este documento no es compatible con la aplicación seleccionada";
out.onLogout = "Tu sesión está cerrada, <a href=\"/\" target=\"_blank\">haz clic aquí</a> para iniciar sesión<br>o pulsa <em>Escape</em> para acceder al documento en modo sólo lectura."; out.onLogout = "Tu sesión está cerrada, {0}haz clic aquí{1} para iniciar sesión<br>o pulsa <em>Escape</em> para acceder al documento en modo sólo lectura.";
out.loading = "Cargando..."; out.loading = "Cargando...";
out.error = "Error"; out.error = "Error";
out.language = "Idioma"; out.language = "Idioma";

@ -27,7 +27,7 @@ define(function () {
out.websocketError = 'Impossible de se connecter au serveur WebSocket...'; out.websocketError = 'Impossible de se connecter au serveur WebSocket...';
out.typeError = "Ce pad n'est pas compatible avec l'application sélectionnée"; out.typeError = "Ce pad n'est pas compatible avec l'application sélectionnée";
out.onLogout = 'Vous êtes déconnecté de votre compte utilisateur, <a href="/" target="_blank">cliquez ici</a> pour vous authentifier<br>ou appuyez sur <em>Échap</em> pour accéder au pad en mode lecture seule.'; out.onLogout = 'Vous êtes déconnecté de votre compte utilisateur, {0}cliquez ici{1} pour vous authentifier<br>ou appuyez sur <em>Échap</em> pour accéder au pad en mode lecture seule.';
out.wrongApp = "Impossible d'afficher le contenu de ce document temps-réel dans votre navigateur. Vous pouvez essayer de recharger la page."; out.wrongApp = "Impossible d'afficher le contenu de ce document temps-réel dans votre navigateur. Vous pouvez essayer de recharger la page.";
out.padNotPinned = 'Ce pad va expirer après 3 mois d\'inactivité, {0}connectez-vous{1} ou {2}enregistrez-vous{3} pour le préserver.'; out.padNotPinned = 'Ce pad va expirer après 3 mois d\'inactivité, {0}connectez-vous{1} ou {2}enregistrez-vous{3} pour le préserver.';
out.anonymousStoreDisabled = "L'administrateur de cette instance de CryptPad a désactivé le drive pour les utilisateurs non enregistrés. Vous devez vous connecter pour pouvoir utiliser CryptDrive."; out.anonymousStoreDisabled = "L'administrateur de cette instance de CryptPad a désactivé le drive pour les utilisateurs non enregistrés. Vous devez vous connecter pour pouvoir utiliser CryptDrive.";
@ -37,6 +37,7 @@ define(function () {
out.chainpadError = 'Une erreur critique est survenue lors de la mise à jour du contenu. Le pad est désormais en mode lecture seule afin de s\'assurer que vous ne perdiez pas davantage de données.<br>' + out.chainpadError = 'Une erreur critique est survenue lors de la mise à jour du contenu. Le pad est désormais en mode lecture seule afin de s\'assurer que vous ne perdiez pas davantage de données.<br>' +
'Appuyez sur <em>Échap</em> pour voir le pad ou rechargez la page pour pouvoir le modifier à nouveau.'; 'Appuyez sur <em>Échap</em> 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 <em>Échap</em>.<br> Dés que vous aurez quitté la page, il sera impossible de le récupérer.'; out.errorCopy = ' Vous pouvez toujours copier son contenu ailleurs en appuyant sur <em>Échap</em>.<br> Dés que vous aurez quitté la page, il sera impossible de le récupérer.';
out.errorRedirectToHome = 'Appuyez sur <em>Échap</em> pour retourner vers votre CryptDrive.';
out.loading = "Chargement..."; out.loading = "Chargement...";
out.error = "Erreur"; out.error = "Erreur";
@ -145,6 +146,8 @@ define(function () {
out.useTemplate = "Commencer avec un modèle?"; out.useTemplate = "Commencer avec un modèle?";
out.useTemplateOK = 'Choisir un modèle (Entrée)'; out.useTemplateOK = 'Choisir un modèle (Entrée)';
out.useTemplateCancel = 'Document vierge (Échap)'; out.useTemplateCancel = 'Document vierge (Échap)';
out.template_import = "Importer un modèle";
out.template_empty = "Aucun modèle disponible";
out.previewButtonTitle = "Afficher ou cacher la prévisualisation de Markdown"; out.previewButtonTitle = "Afficher ou cacher la prévisualisation de Markdown";
@ -364,6 +367,7 @@ define(function () {
out.fm_searchName = "Recherche"; out.fm_searchName = "Recherche";
out.fm_recentPadsName = "Pads récents"; out.fm_recentPadsName = "Pads récents";
out.fm_ownedPadsName = "Pads en votre possession"; out.fm_ownedPadsName = "Pads en votre possession";
out.fm_tagsName = "Mots-clés";
out.fm_searchPlaceholder = "Rechercher..."; out.fm_searchPlaceholder = "Rechercher...";
out.fm_newButton = "Nouveau"; out.fm_newButton = "Nouveau";
out.fm_newButtonTitle = "Créer un nouveau pad ou un dossier, importer un fichier dans le dossier courant"; out.fm_newButtonTitle = "Créer un nouveau pad ou un dossier, importer un fichier dans le dossier courant";
@ -426,6 +430,8 @@ define(function () {
out.fm_padIsOwned = "Vous êtes le propriétaire de ce pad"; out.fm_padIsOwned = "Vous êtes le propriétaire de ce pad";
out.fm_padIsOwnedOther = "Ce pad est la propriété d'un autre utilisateur"; out.fm_padIsOwnedOther = "Ce pad est la propriété d'un autre utilisateur";
out.fm_deletedPads = "Ces pads n'existent plus sur le serveur, ils ont été supprimés de votre CryptDrive: {0}"; out.fm_deletedPads = "Ces pads n'existent plus sur le serveur, ils ont été supprimés de votre CryptDrive: {0}";
out.fm_tags_name = "Mot-clé";
out.fm_tags_used = "Nombre d'utilisations";
// File - Context menu // File - Context menu
out.fc_newfolder = "Nouveau dossier"; out.fc_newfolder = "Nouveau dossier";
out.fc_rename = "Renommer"; out.fc_rename = "Renommer";
@ -546,6 +552,8 @@ define(function () {
out.settings_deleteHint = "La suppression de votre compte utilisateur est permanente. Votre CryptDrive et votre liste de pads seront supprimés du serveur. Le reste de vos pads sera supprimé après 90 jours d'inactivité si personne ne les a stockés dans leur CryptDrive."; out.settings_deleteHint = "La suppression de votre compte utilisateur est permanente. Votre CryptDrive et votre liste de pads seront supprimés du serveur. Le reste de vos pads sera supprimé après 90 jours d'inactivité si personne ne les a stockés dans leur CryptDrive.";
out.settings_deleteButton = "Supprimer votre compte"; out.settings_deleteButton = "Supprimer votre compte";
out.settings_deleteModal = "Veuillez envoyer les informations suivantes à votre administrateur CryptPad afin que vos données soient supprimées du serveur."; out.settings_deleteModal = "Veuillez envoyer les informations suivantes à votre administrateur CryptPad afin que vos données soient supprimées du serveur.";
out.settings_deleteConfirm = "Êtes-vous sûr de vouloir supprimer votre compte utilisateur ? Cette action est irréversible.";
out.settings_deleted = "Votre compte utilisateur a été supprimé. Appuyez sur OK pour être rédirigé(e) vers la page d'accueil.";
out.settings_anonymous = "Vous n'êtes pas connecté. Ces préférences seront utilisées pour ce navigateur."; out.settings_anonymous = "Vous n'êtes pas connecté. Ces préférences seront utilisées pour ce navigateur.";
out.settings_publicSigningKey = "Clé publique de signature"; out.settings_publicSigningKey = "Clé publique de signature";
@ -609,9 +617,6 @@ define(function () {
out.pad_showToolbar = "Afficher la barre d'outils"; out.pad_showToolbar = "Afficher la barre d'outils";
out.pad_hideToolbar = "Cacher la barre d'outils"; out.pad_hideToolbar = "Cacher la barre d'outils";
// general warnings
out.warn_notPinned = "Ce pad n'est stocké dans aucun CryptDrive. Il va expirer après 3 mois d'inactivité. <a href='/about.html#pinning'>En savoir plus...</a>";
// markdown toolbar // markdown toolbar
out.mdToolbar_button = "Afficher ou cacher la barre d'outils Markdown"; out.mdToolbar_button = "Afficher ou cacher la barre d'outils Markdown";
out.mdToolbar_defaultText = "Votre texte ici"; out.mdToolbar_defaultText = "Votre texte ici";
@ -950,8 +955,6 @@ define(function () {
// Header.html // Header.html
out.header_france = '<a href="http://www.xwiki.com/fr" target="_blank" rel="noopener noreferrer">Fait avec <img class="bottom-bar-heart" src="/customize/heart.png" alt="amour" /> en <img class="bottom-bar-fr" title="France" alt="France" src="/customize/fr.png" /> par <img src="/customize/logo-xwiki.png" alt="XWiki SAS" class="bottom-bar-xwiki"/></a>';
out.header_support = '<a href="http://ng.open-paas.org/" title="OpenPaaS::ng" target="_blank" rel="noopener noreferrer"> <img src="/customize/openpaasng.png" alt="OpenPaaS-ng" class="bottom-bar-openpaas" /></a>';
out.updated_0_header_logoTitle = 'Retourner vers votre CryptDrive'; out.updated_0_header_logoTitle = 'Retourner vers votre CryptDrive';
out.header_logoTitle = out.updated_0_header_logoTitle; out.header_logoTitle = out.updated_0_header_logoTitle;
out.header_homeTitle = "Aller sur la page d'accueil"; out.header_homeTitle = "Aller sur la page d'accueil";
@ -1051,7 +1054,7 @@ define(function () {
out.tips = {}; out.tips = {};
out.tips.shortcuts = "`ctrl+b`, `ctrl+i` et `ctrl+u` sont des raccourcis rapides pour mettre en gras, en italique ou souligner."; out.tips.shortcuts = "`ctrl+b`, `ctrl+i` et `ctrl+u` sont des raccourcis rapides pour mettre en gras, en italique ou souligner.";
out.tips.indent = "Dans les listes à puces ou numérotées, vous pouvez utiliser `Tab` ou `Maj+Tab` pour augmenter ou réduire rapidement l'indentation."; out.tips.indent = "Dans les listes à puces ou numérotées, vous pouvez utiliser `Tab` ou `Maj+Tab` pour augmenter ou réduire rapidement l'indentation.";
out.tips.store = "Dès que vous ouvrez un nouveau pad, il est automatiquement stocké dans votre CryptDrive si vous êtes connectés."; out.tips.store = "Dès que vous ouvrez un nouveau pad, il est automatiquement stocké dans votre CryptDrive si vous êtes connecté.";
out.tips.marker = "Vous pouvez surligner du texte dans un pad en utilisant l'option \"marker\" dans le menu déroulant des styles."; out.tips.marker = "Vous pouvez surligner du texte dans un pad en utilisant l'option \"marker\" dans le menu déroulant des styles.";
out.tips.driveUpload = "Les utilisateurs enregistrés peuvent importer des fichiers en les faisant glisser et en les déposant dans leur CryptDrive."; out.tips.driveUpload = "Les utilisateurs enregistrés peuvent importer des fichiers en les faisant glisser et en les déposant dans leur CryptDrive.";
out.tips.filenames = "Vous pouvez renommer les fichiers de votre CryptDrive, ce nom ne sera visible que par vous."; out.tips.filenames = "Vous pouvez renommer les fichiers de votre CryptDrive, ce nom ne sera visible que par vous.";
@ -1081,6 +1084,7 @@ define(function () {
out.creation_expireMonths = "Mois"; out.creation_expireMonths = "Mois";
out.creation_expire1 = "Un pad <b>illimité</b> ne sera pas supprimé du serveur à moins que son propriétaire ne le décide."; out.creation_expire1 = "Un pad <b>illimité</b> ne sera pas supprimé du serveur à moins que son propriétaire ne le décide.";
out.creation_expire2 = "Un pad <b>à durée de vie</b> sera supprimé automatiquement du serveur et du CryptDrive des utilisateurs lorsque cette durée sera dépassée."; out.creation_expire2 = "Un pad <b>à durée de vie</b> sera supprimé automatiquement du serveur et du CryptDrive des utilisateurs lorsque cette durée sera dépassée.";
out.creation_password = "Ajouter un mot de passe";
out.creation_noTemplate = "Pas de modèle"; out.creation_noTemplate = "Pas de modèle";
out.creation_newTemplate = "Nouveau modèle"; out.creation_newTemplate = "Nouveau modèle";
out.creation_create = "Créer"; out.creation_create = "Créer";
@ -1092,12 +1096,20 @@ define(function () {
out.creation_ownedByOther = "Appartient à un autre utilisateur"; out.creation_ownedByOther = "Appartient à un autre utilisateur";
out.creation_noOwner = "Pas de propriétaire"; out.creation_noOwner = "Pas de propriétaire";
out.creation_expiration = "Date d'expiration"; out.creation_expiration = "Date d'expiration";
out.creation_passwordValue = "Mot de passe";
out.creation_propertiesTitle = "Disponibilité"; out.creation_propertiesTitle = "Disponibilité";
out.creation_appMenuName = "Mode avancé (Ctrl + E)"; out.creation_appMenuName = "Mode avancé (Ctrl + E)";
out.creation_newPadModalDescription = "Cliquez sur un type de pad pour le créer. Vous pouvez aussi appuyer sur <b>Tab</b> pour sélectionner un type et appuyer sur <b>Entrée</b> pour valider."; out.creation_newPadModalDescription = "Cliquez sur un type de pad pour le créer. Vous pouvez aussi appuyer sur <b>Tab</b> pour sélectionner un type et appuyer sur <b>Entrée</b> pour valider.";
out.creation_newPadModalDescriptionAdvanced = "Cochez la case si vous souhaitez voir l'écran de création de pads (pour les pads avec propriétaire ou à durée de vie). Vous pouvez appuyer sur <b>Espace</b> pour changer sa valeur."; out.creation_newPadModalDescriptionAdvanced = "Cochez la case si vous souhaitez voir l'écran de création de pads (pour les pads avec propriétaire ou à durée de vie). Vous pouvez appuyer sur <b>Espace</b> pour changer sa valeur.";
out.creation_newPadModalAdvanced = "Afficher l'écran de création de pads"; out.creation_newPadModalAdvanced = "Afficher l'écran de création de pads";
// Password prompt on the loadind screen
out.password_info = "Le pad auquel vous essayez d'accéder est protégé par un mot de passe. Entrez le bon mot de passe pour accéder à son contenu.";
out.password_error = "Pad introuvable !<br>Cette erreur peut provenir de deux facteurs. Soit le mot de passe est faux, soit le pad a été supprimé du serveur.";
out.password_placeholder = "Tapez le mot de passe ici...";
out.password_submit = "Valider";
out.password_show = "Afficher";
// New share modal // New share modal
out.share_linkCategory = "Partage"; out.share_linkCategory = "Partage";
out.share_linkAccess = "Droits d'accès"; out.share_linkAccess = "Droits d'accès";
@ -1111,5 +1123,12 @@ define(function () {
out.share_embedCategory = "Intégration"; out.share_embedCategory = "Intégration";
out.share_mediatagCopy = "Copier le mediatag"; out.share_mediatagCopy = "Copier le mediatag";
// Loading info
out.loading_pad_1 = "Initialisation du pad";
out.loading_pad_2 = "Chargement du contenu du pad";
out.loading_drive_1 = "Chargement des données";
out.loading_drive_2 = "Mise à jour du format des données";
out.loading_drive_3 = "Vérification de l'intégrité des données";
return out; return out;
}); });

@ -30,7 +30,7 @@ define(function () {
out.websocketError = 'Unable to connect to the websocket server...'; out.websocketError = 'Unable to connect to the websocket server...';
out.typeError = "This pad is not compatible with the selected application"; out.typeError = "This pad is not compatible with the selected application";
out.onLogout = 'You are logged out, <a href="/" target="_blank">click here</a> to log in<br>or press <em>Escape</em> to access your pad in read-only mode.'; out.onLogout = 'You are logged out, {0}click here{1} to log in<br>or press <em>Escape</em> to access your pad in read-only mode.';
out.wrongApp = "Unable to display the content of that realtime session in your browser. Please try to reload that page."; out.wrongApp = "Unable to display the content of that realtime session in your browser. Please try to reload that page.";
out.padNotPinned = 'This pad will expire after 3 months of inactivity, {0}login{1} or {2}register{3} to preserve it.'; out.padNotPinned = 'This pad will expire after 3 months of inactivity, {0}login{1} or {2}register{3} to preserve it.';
out.anonymousStoreDisabled = "The webmaster of this CryptPad instance has disabled the store for anonymous users. You have to log in to be able to use CryptDrive."; out.anonymousStoreDisabled = "The webmaster of this CryptPad instance has disabled the store for anonymous users. You have to log in to be able to use CryptDrive.";
@ -40,6 +40,7 @@ define(function () {
out.chainpadError = 'A critical error occurred when updating your content. This page is in read-only mode to make sure you won\'t lose your work.<br>' + out.chainpadError = 'A critical error occurred when updating your content. This page is in read-only mode to make sure you won\'t lose your work.<br>' +
'Hit <em>Esc</em> to continue to view this pad, or reload to try editing again.'; 'Hit <em>Esc</em> 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 <em>Esc</em>.<br>Once you leave this page, it will disappear forever!'; out.errorCopy = ' You can still copy the content to another location by pressing <em>Esc</em>.<br>Once you leave this page, it will disappear forever!';
out.errorRedirectToHome = 'Press <em>Esc</em> to be redirected to your CryptDrive.';
out.loading = "Loading..."; out.loading = "Loading...";
out.error = "Error"; out.error = "Error";
@ -148,6 +149,8 @@ define(function () {
out.useTemplate = "Start with a template?"; //Would you like to "You have available templates for this type of pad. Do you want to use one?"; out.useTemplate = "Start with a template?"; //Would you like to "You have available templates for this type of pad. Do you want to use one?";
out.useTemplateOK = 'Pick a template (Enter)'; out.useTemplateOK = 'Pick a template (Enter)';
out.useTemplateCancel = 'Start fresh (Esc)'; out.useTemplateCancel = 'Start fresh (Esc)';
out.template_import = "Import a template";
out.template_empty = "No template available";
out.previewButtonTitle = "Display or hide the Markdown preview mode"; out.previewButtonTitle = "Display or hide the Markdown preview mode";
@ -370,6 +373,7 @@ define(function () {
out.fm_searchName = "Search"; out.fm_searchName = "Search";
out.fm_recentPadsName = "Recent pads"; out.fm_recentPadsName = "Recent pads";
out.fm_ownedPadsName = "Owned"; out.fm_ownedPadsName = "Owned";
out.fm_tagsName = "Tags";
out.fm_searchPlaceholder = "Search..."; out.fm_searchPlaceholder = "Search...";
out.fm_newButton = "New"; out.fm_newButton = "New";
out.fm_newButtonTitle = "Create a new pad or folder, import a file in the current folder"; out.fm_newButtonTitle = "Create a new pad or folder, import a file in the current folder";
@ -432,6 +436,8 @@ define(function () {
out.fm_padIsOwned = "You are the owner of this pad"; out.fm_padIsOwned = "You are the owner of this pad";
out.fm_padIsOwnedOther = "This pad is owned by another user"; out.fm_padIsOwnedOther = "This pad is owned by another user";
out.fm_deletedPads = "These pads no longer exist on the server, they've been removed from your CryptDrive: {0}"; out.fm_deletedPads = "These pads no longer exist on the server, they've been removed from your CryptDrive: {0}";
out.fm_tags_name = "Tag name";
out.fm_tags_used = "Number of uses";
// File - Context menu // File - Context menu
out.fc_newfolder = "New folder"; out.fc_newfolder = "New folder";
out.fc_rename = "Rename"; out.fc_rename = "Rename";
@ -555,6 +561,8 @@ define(function () {
out.settings_deleteHint = "Account deletion is permanent. Your CryptDrive and your list of pads will be deleted from the server. The rest of your pads will be deleted in 90 days if nobody else has stored them in their CryptDrive."; out.settings_deleteHint = "Account deletion is permanent. Your CryptDrive and your list of pads will be deleted from the server. The rest of your pads will be deleted in 90 days if nobody else has stored them in their CryptDrive.";
out.settings_deleteButton = "Delete your account"; out.settings_deleteButton = "Delete your account";
out.settings_deleteModal = "Share the following information with your CryptPad administrator in order to have your data removed from their server."; out.settings_deleteModal = "Share the following information with your CryptPad administrator in order to have your data removed from their server.";
out.settings_deleteConfirm = "Clicking OK will delete your account permanently. Are you sure?";
out.settings_deleted = "Your user account is now deleted. Press OK to go to the home page.";
out.settings_anonymous = "You are not logged in. Settings here are specific to this browser."; out.settings_anonymous = "You are not logged in. Settings here are specific to this browser.";
out.settings_publicSigningKey = "Public Signing Key"; out.settings_publicSigningKey = "Public Signing Key";
@ -618,9 +626,6 @@ define(function () {
out.pad_showToolbar = "Show toolbar"; out.pad_showToolbar = "Show toolbar";
out.pad_hideToolbar = "Hide toolbar"; out.pad_hideToolbar = "Hide toolbar";
// general warnings
out.warn_notPinned = "This pad is not in anyone's CryptDrive. It will expire after 3 months. <a href='/about.html#pinning'>Learn more...</a>";
// markdown toolbar // markdown toolbar
out.mdToolbar_button = "Show or hide the Markdown toolbar"; out.mdToolbar_button = "Show or hide the Markdown toolbar";
out.mdToolbar_defaultText = "Your text here"; out.mdToolbar_defaultText = "Your text here";
@ -999,9 +1004,6 @@ define(function () {
// Header.html // Header.html
out.header_france = '<a href="http://www.xwiki.com/" target="_blank" rel="noopener noreferrer">With <img class="bottom-bar-heart" src="/customize/heart.png" alt="love" /> from <img class="bottom-bar-fr" src="/customize/fr.png" title="France" alt="France"/> by <img src="/customize/logo-xwiki.png" alt="XWiki SAS" class="bottom-bar-xwiki"/></a>';
out.header_support = '<a href="http://ng.open-paas.org/" title="OpenPaaS::ng" target="_blank" rel="noopener noreferrer"> <img src="/customize/openpaasng.png" alt="OpenPaaS-ng" class="bottom-bar-openpaas" /></a>';
out.updated_0_header_logoTitle = 'Go to your CryptDrive'; out.updated_0_header_logoTitle = 'Go to your CryptDrive';
out.header_logoTitle = out.updated_0_header_logoTitle; out.header_logoTitle = out.updated_0_header_logoTitle;
out.header_homeTitle = 'Go to CryptPad homepage'; out.header_homeTitle = 'Go to CryptPad homepage';
@ -1133,6 +1135,7 @@ define(function () {
out.creation_expireMonths = "Month(s)"; out.creation_expireMonths = "Month(s)";
out.creation_expire1 = "An <b>unlimited</b> pad will not be removed from the server until its owner deletes it."; out.creation_expire1 = "An <b>unlimited</b> pad will not be removed from the server until its owner deletes it.";
out.creation_expire2 = "An <b>expiring</b> pad has a set lifetime, after which it will be automatically removed from the server and other users' CryptDrives."; out.creation_expire2 = "An <b>expiring</b> pad has a set lifetime, after which it will be automatically removed from the server and other users' CryptDrives.";
out.creation_password = "Add a password";
out.creation_noTemplate = "No template"; out.creation_noTemplate = "No template";
out.creation_newTemplate = "New template"; out.creation_newTemplate = "New template";
out.creation_create = "Create"; out.creation_create = "Create";
@ -1144,12 +1147,20 @@ define(function () {
out.creation_ownedByOther = "Owned by another user"; out.creation_ownedByOther = "Owned by another user";
out.creation_noOwner = "No owner"; out.creation_noOwner = "No owner";
out.creation_expiration = "Expiration time"; out.creation_expiration = "Expiration time";
out.creation_passwordValue = "Password";
out.creation_propertiesTitle = "Availability"; out.creation_propertiesTitle = "Availability";
out.creation_appMenuName = "Advanced mode (Ctrl + E)"; out.creation_appMenuName = "Advanced mode (Ctrl + E)";
out.creation_newPadModalDescription = "Click on a pad type to create it. You can also press <b>Tab</b> to select the type and press <b>Enter</b> to confirm."; out.creation_newPadModalDescription = "Click on a pad type to create it. You can also press <b>Tab</b> to select the type and press <b>Enter</b> to confirm.";
out.creation_newPadModalDescriptionAdvanced = "You can check the box (or press <b>Space</b> to change its value) if you want to display the pad creation screen (for owned pads, expiring pads, etc.)."; out.creation_newPadModalDescriptionAdvanced = "You can check the box (or press <b>Space</b> 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"; 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.";
out.password_error = "Pad not found!<br>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";
// New share modal // New share modal
out.share_linkCategory = "Share link"; out.share_linkCategory = "Share link";
out.share_linkAccess = "Access rights"; out.share_linkAccess = "Access rights";
@ -1163,6 +1174,12 @@ define(function () {
out.share_embedCategory = "Embed"; out.share_embedCategory = "Embed";
out.share_mediatagCopy = "Copy mediatag to clipboard"; out.share_mediatagCopy = "Copy mediatag to clipboard";
// Loading info
out.loading_pad_1 = "Initializing pad";
out.loading_pad_2 = "Loading pad content";
out.loading_drive_1 = "Loading data";
out.loading_drive_2 = "Updating data format";
out.loading_drive_3 = "Verifying data integrity";
return out; return out;
}); });

@ -38,7 +38,7 @@ define(function () {
out.websocketError = 'Incapaz de se conectar com o servidor websocket...'; out.websocketError = 'Incapaz de se conectar com o servidor websocket...';
out.typeError = "Este bloco não é compatível com a aplicação selecionada"; out.typeError = "Este bloco não é compatível com a aplicação selecionada";
out.onLogout = 'você foi desconectado, <a href="/" target="_blank">clique aqui</a> para se conectar, <br>ou pressione <em>ESC</em> para acessar seu bloco em modo somente leitura.'; out.onLogout = 'você foi desconectado, {0}clique aqui{1} para se conectar, <br>ou pressione <em>ESC</em> para acessar seu bloco em modo somente leitura.';
out.wrongApp = "Incapaz de mostrar o conteúdo em tempo real no seu navegador. Por favor tente recarregar a página."; out.wrongApp = "Incapaz de mostrar o conteúdo em tempo real no seu navegador. Por favor tente recarregar a página.";
out.loading = "Carregando..."; out.loading = "Carregando...";

@ -13,7 +13,7 @@ define(function () {
out.common_connectionLost = out.updated_0_common_connectionLost; out.common_connectionLost = out.updated_0_common_connectionLost;
out.websocketError = "Conexiune inexistentă către serverul websocket..."; out.websocketError = "Conexiune inexistentă către serverul websocket...";
out.typeError = "Această filă nu este compatibilă cu aplicația aleasă"; out.typeError = "Această filă nu este compatibilă cu aplicația aleasă";
out.onLogout = "Nu mai ești autentificat, <a href=\"/\" target=\"_blank\">apasă aici</a> să te autentifici<br>sau apasă <em>Escape</em>să accesezi fila în modul citire."; out.onLogout = "Nu mai ești autentificat, {0}apasă aici{1} să te autentifici<br>sau apasă <em>Escape</em>să accesezi fila în modul citire.";
out.wrongApp = "Momentan nu putem arăta conținutul sesiunii în timp real în fereastra ta. Te rugăm reîncarcă pagina."; out.wrongApp = "Momentan nu putem arăta conținutul sesiunii în timp real în fereastra ta. Te rugăm reîncarcă pagina.";
out.loading = "Încarcă..."; out.loading = "Încarcă...";
out.error = "Eroare"; out.error = "Eroare";

@ -31,7 +31,7 @@ define(function () {
out.websocketError = '無法連結上 websocket 伺服器...'; out.websocketError = '無法連結上 websocket 伺服器...';
out.typeError = "這個編輯檔與所選的應用程式並不相容"; out.typeError = "這個編輯檔與所選的應用程式並不相容";
out.onLogout = '你已登出, <a href="/" target="_blank">點擊這裏</a> 來登入<br>或按<em>Escape</em> 來以唯讀模型使用你的編輯檔案'; out.onLogout = '你已登出, {0}點擊這裏{1} 來登入<br>或按<em>Escape</em> 來以唯讀模型使用你的編輯檔案';
out.wrongApp = "無法在瀏覽器顯示即時期間的內容,請試著再重新載入本頁。"; out.wrongApp = "無法在瀏覽器顯示即時期間的內容,請試著再重新載入本頁。";
out.loading = "載入中..."; out.loading = "載入中...";

@ -33,7 +33,7 @@ nThen(function (waitFor) {
sem.take(function (give) { sem.take(function (give) {
Fs.unlink(f.filename, give(function (err) { Fs.unlink(f.filename, give(function (err) {
if (err) { return void console.error(err + " " + f.filename); } if (err) { return void console.error(err + " " + f.filename); }
console.log(f.filename + " " + f.size + " " + (+f.atime) + " " + (+new Date())); console.log(f.filename + " " + f.size + " " + (+f.mtime) + " " + (+new Date()));
})); }));
}); });
}); });

@ -75,6 +75,12 @@ server {
} }
location ^~ /blob/ { location ^~ /blob/ {
add_header Cache-Control max-age=31536000;
try_files $uri =404;
}
location ^~ /datastore/ {
add_header Cache-Control max-age=0;
try_files $uri =404; try_files $uri =404;
} }

@ -1,7 +1,7 @@
{ {
"name": "cryptpad", "name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server", "description": "realtime collaborative visual editor with zero knowlege server",
"version": "1.28.0", "version": "2.0.0",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"chainpad-server": "^2.0.0", "chainpad-server": "^2.0.0",

@ -15,6 +15,7 @@ const hashesFromPinFile = (pinFile, fileName) => {
switch (l[0]) { switch (l[0]) {
case 'RESET': { case 'RESET': {
pins = {}; pins = {};
if (l[1] && l[1].length) { l[1].forEach((x) => { pins[x] = 1; }); }
//jshint -W086 //jshint -W086
// fallthrough // fallthrough
} }
@ -32,7 +33,7 @@ const hashesFromPinFile = (pinFile, fileName) => {
return Object.keys(pins); return Object.keys(pins);
}; };
module.exports.load = function (cb) { module.exports.load = function (cb, config) {
nThen((waitFor) => { nThen((waitFor) => {
Fs.readdir('./pins', waitFor((err, list) => { Fs.readdir('./pins', waitFor((err, list) => {
if (err) { if (err) {
@ -49,7 +50,10 @@ module.exports.load = function (cb) {
sema.take((returnAfter) => { sema.take((returnAfter) => {
Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => { Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => {
if (err) { throw err; } if (err) { throw err; }
list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); }); list2.forEach((ff) => {
if (config && config.exclude && config.exclude.indexOf(ff) > -1) { return; }
fileList.push('./pins/' + f + '/' + ff);
});
}))); })));
}); });
}); });
@ -76,4 +80,4 @@ if (!module.parent) {
console.log(x + ' ' + JSON.stringify(data[x])); console.log(x + ' ' + JSON.stringify(data[x]));
}); });
}); });
} }

@ -3,14 +3,21 @@ const Fs = require('fs');
const Semaphore = require('saferphore'); const Semaphore = require('saferphore');
const nThen = require('nthen'); const nThen = require('nthen');
/*
takes contents of a pinFile (UTF8 string)
and the pin file's name
returns an array of of channel ids which are pinned
throw errors on pin logs with invalid pin data
*/
const hashesFromPinFile = (pinFile, fileName) => { const hashesFromPinFile = (pinFile, fileName) => {
var pins = {}; var pins = {};
pinFile.split('\n').filter((x)=>(x)).map((l) => JSON.parse(l)).forEach((l) => { pinFile.split('\n').filter((x)=>(x)).map((l) => JSON.parse(l)).forEach((l) => {
switch (l[0]) { switch (l[0]) {
case 'RESET': { case 'RESET': {
pins = {}; pins = {};
//jshint -W086 if (l[1] && l[1].length) { l[1].forEach((x) => { pins[x] = 1; }); }
// fallthrough break;
} }
case 'PIN': { case 'PIN': {
l[1].forEach((x) => { pins[x] = 1; }); l[1].forEach((x) => { pins[x] = 1; });
@ -26,6 +33,11 @@ const hashesFromPinFile = (pinFile, fileName) => {
return Object.keys(pins); return Object.keys(pins);
}; };
/*
takes an array of pinned file names
and a global map of stats indexed by public keys
returns the sum of the size of those pinned files
*/
const sizeForHashes = (hashes, dsFileStats) => { const sizeForHashes = (hashes, dsFileStats) => {
let sum = 0; let sum = 0;
hashes.forEach((h) => { hashes.forEach((h) => {
@ -39,23 +51,31 @@ const sizeForHashes = (hashes, dsFileStats) => {
return sum; return sum;
}; };
// do twenty things at a time
const sema = Semaphore.create(20); const sema = Semaphore.create(20);
let dirList; let dirList;
const fileList = []; const fileList = []; // array which we reuse for a lot of things
const dsFileStats = {}; const dsFileStats = {}; // map of stats
const out = []; const out = []; // what we return at the end
const pinned = {}; const pinned = {}; // map of pinned files
// define a function: 'load' which takes a config
// and a callback
module.exports.load = function (config, cb) { module.exports.load = function (config, cb) {
nThen((waitFor) => { nThen((waitFor) => {
// read the subdirectories in the datastore
Fs.readdir('./datastore', waitFor((err, list) => { Fs.readdir('./datastore', waitFor((err, list) => {
if (err) { throw err; } if (err) { throw err; }
dirList = list; dirList = list;
})); }));
}).nThen((waitFor) => { }).nThen((waitFor) => {
// iterate over all subdirectories
dirList.forEach((f) => { dirList.forEach((f) => {
// process twenty subdirectories simultaneously
sema.take((returnAfter) => { sema.take((returnAfter) => {
// get the list of files in every subdirectory
// and push them to 'fileList'
Fs.readdir('./datastore/' + f, waitFor(returnAfter((err, list2) => { Fs.readdir('./datastore/' + f, waitFor(returnAfter((err, list2) => {
if (err) { throw err; } if (err) { throw err; }
list2.forEach((ff) => { fileList.push('./datastore/' + f + '/' + ff); }); list2.forEach((ff) => { fileList.push('./datastore/' + f + '/' + ff); });
@ -63,14 +83,19 @@ module.exports.load = function (config, cb) {
}); });
}); });
}).nThen((waitFor) => { }).nThen((waitFor) => {
// read the subdirectories in 'blob'
Fs.readdir('./blob', waitFor((err, list) => { Fs.readdir('./blob', waitFor((err, list) => {
if (err) { throw err; } if (err) { throw err; }
// overwrite dirList
dirList = list; dirList = list;
})); }));
}).nThen((waitFor) => { }).nThen((waitFor) => {
// iterate over all subdirectories
dirList.forEach((f) => { dirList.forEach((f) => {
// process twenty subdirectories simultaneously
sema.take((returnAfter) => { sema.take((returnAfter) => {
// get the list of files in every subdirectory
// and push them to 'fileList'
Fs.readdir('./blob/' + f, waitFor(returnAfter((err, list2) => { Fs.readdir('./blob/' + f, waitFor(returnAfter((err, list2) => {
if (err) { throw err; } if (err) { throw err; }
list2.forEach((ff) => { fileList.push('./blob/' + f + '/' + ff); }); list2.forEach((ff) => { fileList.push('./blob/' + f + '/' + ff); });
@ -78,24 +103,34 @@ module.exports.load = function (config, cb) {
}); });
}); });
}).nThen((waitFor) => { }).nThen((waitFor) => {
// iterate over the fileList
fileList.forEach((f) => { fileList.forEach((f) => {
// process twenty files simultaneously
sema.take((returnAfter) => { sema.take((returnAfter) => {
// get the stats of each files
Fs.stat(f, waitFor(returnAfter((err, st) => { Fs.stat(f, waitFor(returnAfter((err, st) => {
if (err) { throw err; } if (err) { throw err; }
st.filename = f; st.filename = f;
// push them to a big map of stats
dsFileStats[f.replace(/^.*\/([^\/\.]*)(\.ndjson)?$/, (all, a) => (a))] = st; dsFileStats[f.replace(/^.*\/([^\/\.]*)(\.ndjson)?$/, (all, a) => (a))] = st;
}))); })));
}); });
}); });
}).nThen((waitFor) => { }).nThen((waitFor) => {
// read the subdirectories in the pinstore
Fs.readdir('./pins', waitFor((err, list) => { Fs.readdir('./pins', waitFor((err, list) => {
if (err) { throw err; } if (err) { throw err; }
dirList = list; dirList = list;
})); }));
}).nThen((waitFor) => { }).nThen((waitFor) => {
// set file list to an empty array
// fileList = [] ??
fileList.splice(0, fileList.length); fileList.splice(0, fileList.length);
dirList.forEach((f) => { dirList.forEach((f) => {
// process twenty directories at a time
sema.take((returnAfter) => { sema.take((returnAfter) => {
// get the list of files in every subdirectory
// and push them to 'fileList' (which is empty because we keep reusing it)
Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => { Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => {
if (err) { throw err; } if (err) { throw err; }
list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); }); list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); });
@ -103,70 +138,147 @@ module.exports.load = function (config, cb) {
}); });
}); });
}).nThen((waitFor) => { }).nThen((waitFor) => {
// iterate over the list of pin logs
fileList.forEach((f) => { fileList.forEach((f) => {
// twenty at a time
sema.take((returnAfter) => { sema.take((returnAfter) => {
// read the full content
Fs.readFile(f, waitFor(returnAfter((err, content) => { Fs.readFile(f, waitFor(returnAfter((err, content) => {
if (err) { throw err; } if (err) { throw err; }
// get the list of channels pinned by this log
const hashes = hashesFromPinFile(content.toString('utf8'), f); const hashes = hashesFromPinFile(content.toString('utf8'), f);
const size = sizeForHashes(hashes, dsFileStats);
if (config.unpinned) { if (config.unpinned) {
hashes.forEach((x) => { pinned[x] = 1; }); hashes.forEach((x) => { pinned[x] = 1; });
} else { } else {
// get the size of files pinned by this log
// but only if we're gonna use it
let size = sizeForHashes(hashes, dsFileStats);
// we will return a list of values
// [user_public_key, size_of_files_they_have_pinned]
out.push([f, Math.floor(size / (1024 * 1024))]); out.push([f, Math.floor(size / (1024 * 1024))]);
} }
}))); })));
}); });
}); });
}).nThen(() => { }).nThen(() => {
// handle all the information you've processed so far
if (config.unpinned) { if (config.unpinned) {
// the user wants data about what has not been pinned
// by default we concern ourselves with pads and files older than infinity (everything)
let before = Infinity; let before = Infinity;
// but you can override this with config
if (config.olderthan) { if (config.olderthan) {
before = config.olderthan; before = config.olderthan;
if (isNaN(before)) { // FIXME validate inputs before doing the heavy lifting
if (isNaN(before)) { // make sure the supplied value is a number
return void cb('--olderthan error [' + config.olderthan + '] not a valid date'); return void cb('--olderthan error [' + config.olderthan + '] not a valid date');
} }
} }
// you can specify a different time for blobs...
let blobsbefore = before; let blobsbefore = before;
if (config.blobsolderthan) { if (config.blobsolderthan) {
// use the supplied date if it exists
blobsbefore = config.blobsolderthan; blobsbefore = config.blobsolderthan;
if (isNaN(blobsbefore)) { if (isNaN(blobsbefore)) {
return void cb('--blobsolderthan error [' + config.blobsolderthan + '] not a valid date'); return void cb('--blobsolderthan error [' + config.blobsolderthan + '] not a valid date');
} }
} }
let files = []; let files = [];
// iterate over all the stats that you've saved
Object.keys(dsFileStats).forEach((f) => { Object.keys(dsFileStats).forEach((f) => {
// we only care about files which are not in the pin map
if (!(f in pinned)) { if (!(f in pinned)) {
// check if it's a blob or a 'pad'
const isBlob = dsFileStats[f].filename.indexOf('.ndjson') === -1; const isBlob = dsFileStats[f].filename.indexOf('.ndjson') === -1;
if ((+dsFileStats[f].atime) >= ((isBlob) ? blobsbefore : before)) { return; }
// if the mtime is newer than the specified value for its file type, ignore this file
if ((+dsFileStats[f].mtime) >= ((isBlob) ? blobsbefore : before)) { return; }
// otherwise push it to the list of files, with its filename, size, and mtime
files.push({ files.push({
filename: dsFileStats[f].filename, filename: dsFileStats[f].filename,
size: dsFileStats[f].size, size: dsFileStats[f].size,
atime: dsFileStats[f].atime mtime: dsFileStats[f].mtime
}); });
} }
}); });
// return the list of files
cb(null, files); cb(null, files);
} else { } else {
// if you're not in 'unpinned' mode, sort by size (ascending)
out.sort((a,b) => (a[1] - b[1])); out.sort((a,b) => (a[1] - b[1]));
// and return the sorted data
cb(null, out.slice()); cb(null, out.slice());
} }
}); });
}; };
// This script can be called directly on its own
// or required as part of another script
if (!module.parent) { if (!module.parent) {
let config = {}; // if no parent, it is being invoked directly
let config = {}; // build the config from command line arguments...
// --unpinned gets the list of unpinned files
// if you don't pass this, it will list the size of pinned data per user
if (process.argv.indexOf('--unpinned') > -1) { config.unpinned = true; } if (process.argv.indexOf('--unpinned') > -1) { config.unpinned = true; }
// '--olderthan' must be used in conjunction with '--unpinned'
// if you pass '--olderthan' with a string date or number, it will limit
// results only to pads older than the supplied time
// it defaults to 'infinity', or no filter at all
const ot = process.argv.indexOf('--olderthan'); const ot = process.argv.indexOf('--olderthan');
config.olderthan = ot > -1 && new Date(process.argv[ot+1]); if (ot > -1) {
config.olderthan = Number(process.argv[ot+1]) ? new Date(Number(process.argv[ot+1]))
: new Date(process.argv[ot+1]);
}
// '--blobsolderthan' must be used in conjunction with '--unpinned'
// if you pass '--blobsolderthan with a string date or number, it will limit
// results only to blobs older than the supplied time
// it defaults to using the same value passed '--olderthan'
const bot = process.argv.indexOf('--blobsolderthan'); const bot = process.argv.indexOf('--blobsolderthan');
config.blobsolderthan = bot > -1 && new Date(process.argv[bot+1]); if (bot > -1) {
config.blobsolderthan = Number(process.argv[bot+1]) ? new Date(Number(process.argv[bot+1]))
: new Date(process.argv[bot+1]);
}
// call our big function directly
// pass our constructed configuration and a callback
module.exports.load(config, function (err, data) { module.exports.load(config, function (err, data) {
if (err) { throw new Error(err); } if (err) { throw new Error(err); } // throw errors
if (!Array.isArray(data)) { return; } if (!Array.isArray(data)) { return; } // if the returned value is not an array, you're done
if (config.unpinned) { if (config.unpinned) {
data.forEach((f) => { console.log(f.filename + " " + f.size + " " + (+f.atime)); }); // display the list of unpinned files with their size and mtime
data.forEach((f) => { console.log(f.filename + " " + f.size + " " + (+f.mtime)); });
} else { } else {
// display the list of public keys and the size of the data they have pinned in megabytes
data.forEach((x) => { console.log(x[0] + ' ' + x[1] + ' MB'); }); data.forEach((x) => { console.log(x[0] + ' ' + x[1] + ' MB'); });
} }
}); });
} }
/* Example usage of this script...
# display the list of public keys and the size of the data the have pinned in megabytes
node pinneddata.js
# display the list of unpinned pads and blobs with their size and mtime
node pinneddata.js --unpinned
# display the list of unpinned pads and blobs older than 12345 with their size and mtime
node pinneddata.js --unpinned --olderthan 12345
# display the list of unpinned pads older than 12345 and unpinned blobs older than 123
# each with their size and mtime
node pinneddata.js --unpinned --olderthan 12345 --blobsolderthan 123
*/

@ -326,6 +326,24 @@ var getFileSize = function (Env, channel, cb) {
}); });
}; };
var getMetadata = function (Env, channel, cb) {
if (!isValidId(channel)) { return void cb('INVALID_CHAN'); }
if (channel.length === 32) {
if (typeof(Env.msgStore.getChannelMetadata) !== 'function') {
return cb('GET_CHANNEL_METADATA_UNSUPPORTED');
}
return void Env.msgStore.getChannelMetadata(channel, function (e, data) {
if (e) {
if (e.code === 'INVALID_METADATA') { return void cb(void 0, {}); }
return void cb(e.code);
}
cb(void 0, data);
});
}
};
var getMultipleFileSize = function (Env, channels, cb) { var getMultipleFileSize = function (Env, channels, cb) {
if (!Array.isArray(channels)) { return cb('INVALID_PIN_LIST'); } if (!Array.isArray(channels)) { return cb('INVALID_PIN_LIST'); }
if (typeof(Env.msgStore.getChannelSize) !== 'function') { if (typeof(Env.msgStore.getChannelSize) !== 'function') {
@ -1139,6 +1157,7 @@ var isNewChannel = function (Env, channel, cb) {
var isUnauthenticatedCall = function (call) { var isUnauthenticatedCall = function (call) {
return [ return [
'GET_FILE_SIZE', 'GET_FILE_SIZE',
'GET_METADATA',
'GET_MULTIPLE_FILE_SIZE', 'GET_MULTIPLE_FILE_SIZE',
'IS_CHANNEL_PINNED', 'IS_CHANNEL_PINNED',
'IS_NEW_CHANNEL', 'IS_NEW_CHANNEL',
@ -1260,12 +1279,14 @@ RPC.create = function (
} }
case 'GET_FILE_SIZE': case 'GET_FILE_SIZE':
return void getFileSize(Env, msg[1], function (e, size) { return void getFileSize(Env, msg[1], function (e, size) {
if (e) {
console.error(e);
}
WARN(e, msg[1]); WARN(e, msg[1]);
respond(e, [null, size, null]); respond(e, [null, size, null]);
}); });
case 'GET_METADATA':
return void getMetadata(Env, msg[1], function (e, data) {
WARN(e, msg[1]);
respond(e, [null, data, null]);
});
case 'GET_MULTIPLE_FILE_SIZE': case 'GET_MULTIPLE_FILE_SIZE':
return void getMultipleFileSize(Env, msg[1], function (e, dict) { return void getMultipleFileSize(Env, msg[1], function (e, dict) {
if (e) { if (e) {
@ -1458,9 +1479,9 @@ RPC.create = function (
Respond(void 0, "OK"); Respond(void 0, "OK");
}); });
case 'REMOVE_PINS': case 'REMOVE_PINS':
return void removePins(Env, safeKey, function (e, response) { return void removePins(Env, safeKey, function (e) {
if (e) { return void Respond(e); } if (e) { return void Respond(e); }
Respond(void 0, response); Respond(void 0, "OK");
}); });
// restricted to privileged users... // restricted to privileged users...
case 'UPLOAD': case 'UPLOAD':

@ -123,6 +123,9 @@ app.get(mainPagePattern, Express.static(__dirname + '/customize.dist'));
app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob')), { app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob')), {
maxAge: DEV_MODE? "0d": "365d" maxAge: DEV_MODE? "0d": "365d"
})); }));
app.use("/datastore", Express.static(Path.join(__dirname, (config.filePath || './datastore')), {
maxAge: "0d"
}));
app.use("/customize", Express.static(__dirname + '/customize')); app.use("/customize", Express.static(__dirname + '/customize'));
app.use("/customize", Express.static(__dirname + '/customize.dist')); app.use("/customize", Express.static(__dirname + '/customize.dist'));
@ -188,6 +191,7 @@ var custom_four04_path = Path.resolve(__dirname + '/customize/404.html');
var send404 = function (res, path) { var send404 = function (res, path) {
if (!path && path !== four04_path) { path = four04_path; } if (!path && path !== four04_path) { path = four04_path; }
Fs.exists(path, function (exists) { Fs.exists(path, function (exists) {
res.setHeader('Content-Type', 'text/html; charset=utf-8');
if (exists) { return Fs.createReadStream(path).pipe(res); } if (exists) { return Fs.createReadStream(path).pipe(res); }
send404(res); send404(res);
}); });
@ -250,4 +254,4 @@ var nt = nThen(function (w) {
if (config.debugReplName) { if (config.debugReplName) {
require('replify')({ name: config.debugReplName, app: debuggableStore }); require('replify')({ name: config.debugReplName, app: debuggableStore });
} }

@ -223,6 +223,33 @@ define([
hd.type === 'invite'); hd.type === 'invite');
}, "test support for invite urls"); }, "test support for invite urls");
// test support for V2
assert(function (cb) {
var parsed = Hash.parsePadUrl('/pad/#/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/');
var secret = Hash.getSecrets('pad', '/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/');
return cb(parsed.hashData.version === 2 &&
parsed.hashData.mode === "edit" &&
parsed.hashData.type === "pad" &&
parsed.hashData.key === "oRE0oLCtEXusRDyin7GyLGcS" &&
secret.channel === "d8d51b4aea863f3f050f47f8ad261753" &&
window.nacl.util.encodeBase64(secret.keys.cryptKey) === "0Ts1M6VVEozErV2Nx/LTv6Im5SCD7io2LlhasyyBPQo=" &&
secret.keys.validateKey === "f5A1FM9Gp55tnOcM75RyHD1oxBG9ZPh9WDA7qe2Fvps=" &&
!parsed.hashData.present);
}, "test support for version 2 hash failed to parse");
assert(function (cb) {
var parsed = Hash.parsePadUrl('/pad/#/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed');
var secret = Hash.getSecrets('pad', '/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed', 'pewpew');
return cb(parsed.hashData.version === 2 &&
parsed.hashData.mode === "edit" &&
parsed.hashData.type === "pad" &&
parsed.hashData.key === "HGu0tK2od-2BBnwAz2ZNS-t4" &&
secret.channel === "3fb6dc93807d903aff390b5f798c92c9" &&
window.nacl.util.encodeBase64(secret.keys.cryptKey) === "EeCkGJra8eJgVu7v4Yl2Hc3yUjrgpKpxr0Lcc3bSWVs=" &&
secret.keys.validateKey === "WGkBczJf2V6vQZfAScz8V1KY6jKdoxUCckrD+E75gGE=" &&
parsed.hashData.embed &&
parsed.hashData.password);
}, "test support for password in version 2 hash failed to parse");
assert(function (cb) { assert(function (cb) {
var url = '/pad/?utm_campaign=new_comment&utm_medium=email&utm_source=thread_mailer#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/'; var url = '/pad/?utm_campaign=new_comment&utm_medium=email&utm_source=thread_mailer#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/';
var secret = Hash.parsePadUrl(url); var secret = Hash.parsePadUrl(url);

@ -334,6 +334,7 @@ define([
//var cursor = editor.getCursor(); //var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, ''); //var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')'; //var text = '!['+cleanName+']('+data.url+')';
// PASSWORD_FILES
var parsed = Hash.parsePadUrl(data.url); var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;

@ -34,7 +34,7 @@ define([
url: '/common/feedback.html?NO_LOCALSTORAGE=' + (+new Date()), url: '/common/feedback.html?NO_LOCALSTORAGE=' + (+new Date()),
}); });
}); });
window.alert("CryptPad needs localStorage to work, try a different browser"); window.alert("CryptPad needs localStorage to work. Try changing your cookie permissions, or using a different browser");
}; };
window.onerror = function (e) { window.onerror = function (e) {

@ -19,7 +19,50 @@ define([
.decodeUTF8(JSON.stringify(list)))); .decodeUTF8(JSON.stringify(list))));
}; };
var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) { var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) {
var version = secret.version;
var data = secret.keys;
if (version === 0) {
return secret.channel + secret.key;
}
if (version === 1) {
if (!data.editKeyStr) { return; }
return '/1/edit/' + hexToBase64(secret.channel) +
'/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/';
}
if (version === 2) {
if (!data.editKeyStr) { return; }
var pass = secret.password ? 'p/' : '';
return '/2/' + secret.type + '/edit/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/' + pass;
}
};
var getViewHashFromKeys = Hash.getViewHashFromKeys = function (secret) {
var version = secret.version;
var data = secret.keys;
if (version === 0) { return; }
if (version === 1) {
if (!data.viewKeyStr) { return; }
return '/1/view/' + hexToBase64(secret.channel) +
'/'+Crypto.b64RemoveSlashes(data.viewKeyStr)+'/';
}
if (version === 2) {
if (!data.viewKeyStr) { return; }
var pass = secret.password ? 'p/' : '';
return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass;
}
};
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (secret) {
var version = secret.version;
var data = secret.keys;
if (version === 0) { return; }
if (version === 1) {
return '/1/' + hexToBase64(secret.channel) + '/' +
Crypto.b64RemoveSlashes(data.fileKeyStr) + '/';
}
};
// V1
/*var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) {
if (typeof keys === 'string') { if (typeof keys === 'string') {
return chanKey + keys; return chanKey + keys;
} }
@ -34,7 +77,7 @@ define([
}; };
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) { var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) {
return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/'; return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/';
}; };*/
Hash.getUserHrefFromKeys = function (origin, username, pubkey) { Hash.getUserHrefFromKeys = function (origin, username, pubkey) {
return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-'); return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
}; };
@ -43,6 +86,24 @@ define([
return s.replace(/\/+/g, '/'); return s.replace(/\/+/g, '/');
}; };
Hash.createChannelId = function () {
var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
throw new Error('channel ids must consist of 32 hex characters');
}
return id;
};
Hash.createRandomHash = function (type, password) {
var cryptor = Crypto.createEditCryptor2(void 0, void 0, password);
return getEditHashFromKeys({
password: Boolean(password),
version: 2,
type: type,
keys: { editKeyStr: cryptor.editKeyStr }
});
};
/* /*
Version 0 Version 0
/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy /pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy
@ -56,25 +117,56 @@ Version 1
var hashArr = fixDuplicateSlashes(hash).split('/'); var hashArr = fixDuplicateSlashes(hash).split('/');
if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) { if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) {
parsed.type = 'pad'; parsed.type = 'pad';
if (hash.slice(0,1) !== '/' && hash.length >= 56) { if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0
// Old hash // Old hash
parsed.channel = hash.slice(0, 32); parsed.channel = hash.slice(0, 32);
parsed.key = hash.slice(32, 56); parsed.key = hash.slice(32, 56);
parsed.version = 0; parsed.version = 0;
parsed.getHash = function () { return hash; };
return parsed; return parsed;
} }
if (hashArr[1] && hashArr[1] === '1') { var options;
if (hashArr[1] && hashArr[1] === '1') { // Version 1
parsed.version = 1; parsed.version = 1;
parsed.mode = hashArr[2]; parsed.mode = hashArr[2];
parsed.channel = hashArr[3]; parsed.channel = hashArr[3];
parsed.key = hashArr[4].replace(/-/g, '/'); parsed.key = Crypto.b64AddSlashes(hashArr[4]);
var options = hashArr.slice(5);
options = hashArr.slice(5);
parsed.present = options.indexOf('present') !== -1; parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1; parsed.embed = options.indexOf('embed') !== -1;
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 5).join('/') + '/';
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
return hash;
};
return parsed;
}
if (hashArr[1] && hashArr[1] === '2') { // Version 2
parsed.version = 2;
parsed.app = hashArr[2];
parsed.mode = hashArr[3];
parsed.key = hashArr[4];
options = hashArr.slice(5);
parsed.password = options.indexOf('p') !== -1;
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 5).join('/') + '/';
if (parsed.password) { hash += 'p/'; }
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
return hash;
};
return parsed; return parsed;
} }
return parsed; return parsed;
} }
parsed.getHash = function () { return hashArr.join('/'); };
if (['media', 'file'].indexOf(type) !== -1) { if (['media', 'file'].indexOf(type) !== -1) {
parsed.type = 'file'; parsed.type = 'file';
if (hashArr[1] && hashArr[1] === '1') { if (hashArr[1] && hashArr[1] === '1') {
@ -125,17 +217,9 @@ Version 1
url += ret.type + '/'; url += ret.type + '/';
if (!ret.hashData) { return url; } if (!ret.hashData) { return url; }
if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; } if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; }
if (ret.hashData.version !== 1) { return url + '#' + ret.hash; } if (ret.hashData.version === 0) { return url + '#' + ret.hash; }
url += '#/' + ret.hashData.version + var hash = ret.hashData.getHash(options);
'/' + ret.hashData.mode + url += '#' + hash;
'/' + ret.hashData.channel.replace(/\//g, '-') +
'/' + ret.hashData.key.replace(/\//g, '-') +'/';
if (options.embed) {
url += 'embed/';
}
if (options.present) {
url += 'present/';
}
return url; return url;
}; };
@ -153,12 +237,13 @@ Version 1
return ''; return '';
}); });
idx = href.indexOf('/#'); idx = href.indexOf('/#');
if (idx === -1) { return ret; }
ret.hash = href.slice(idx + 2); ret.hash = href.slice(idx + 2);
ret.hashData = parseTypeHash(ret.type, ret.hash); ret.hashData = parseTypeHash(ret.type, ret.hash);
return ret; return ret;
}; };
var getRelativeHref = Hash.getRelativeHref = function (href) { Hash.getRelativeHref = function (href) {
if (!href) { return; } if (!href) { return; }
if (href.indexOf('#') === -1) { return; } if (href.indexOf('#') === -1) { return; }
var parsed = parsePadUrl(href); var parsed = parsePadUrl(href);
@ -170,11 +255,13 @@ Version 1
* - no argument: use the URL hash or create one if it doesn't exist * - no argument: use the URL hash or create one if it doesn't exist
* - secretHash provided: use secretHash to find the keys * - secretHash provided: use secretHash to find the keys
*/ */
Hash.getSecrets = function (type, secretHash) { Hash.getSecrets = function (type, secretHash, password) {
var secret = {}; var secret = {};
var generate = function () { var generate = function () {
secret.keys = Crypto.createEditCryptor(); secret.keys = Crypto.createEditCryptor2(void 0, void 0, password);
secret.key = Crypto.createEditCryptor().editKeyStr; secret.channel = base64ToHex(secret.keys.chanId);
secret.version = 2;
secret.type = type;
}; };
if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) { if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) {
generate(); generate();
@ -191,7 +278,6 @@ Version 1
parsed = pHref.hashData; parsed = pHref.hashData;
hash = pHref.hash; hash = pHref.hash;
} }
//var parsed = parsePadUrl(window.location.href);
//var hash = secretHash || window.location.hash.slice(1); //var hash = secretHash || window.location.hash.slice(1);
if (hash.length === 0) { if (hash.length === 0) {
generate(); generate();
@ -203,9 +289,10 @@ Version 1
// Old hash // Old hash
secret.channel = parsed.channel; secret.channel = parsed.channel;
secret.key = parsed.key; secret.key = parsed.key;
} secret.version = 0;
else if (parsed.version === 1) { } else if (parsed.version === 1) {
// New hash // New hash
secret.version = 1;
if (parsed.type === "pad") { if (parsed.type === "pad") {
secret.channel = base64ToHex(parsed.channel); secret.channel = base64ToHex(parsed.channel);
if (parsed.mode === 'edit') { if (parsed.mode === 'edit') {
@ -229,49 +316,63 @@ Version 1
// version 2 hashes are to be used for encrypted blobs // version 2 hashes are to be used for encrypted blobs
throw new Error("User hashes can't be opened (yet)"); throw new Error("User hashes can't be opened (yet)");
} }
} else if (parsed.version === 2) {
// New hash
secret.version = 2;
secret.type = type;
secret.password = password;
if (parsed.type === "pad") {
if (parsed.mode === 'edit') {
secret.keys = Crypto.createEditCryptor2(parsed.key, void 0, password);
secret.channel = base64ToHex(secret.keys.chanId);
secret.key = secret.keys.editKeyStr;
if (secret.channel.length !== 32 || secret.key.length !== 24) {
throw new Error("The channel key and/or the encryption key is invalid");
}
}
else if (parsed.mode === 'view') {
secret.keys = Crypto.createViewCryptor2(parsed.key, password);
secret.channel = base64ToHex(secret.keys.chanId);
if (secret.channel.length !== 32) {
throw new Error("The channel key is invalid");
}
}
} else if (parsed.type === "file") {
throw new Error("File hashes should be version 1");
} else if (parsed.type === "user") {
throw new Error("User hashes can't be opened (yet)");
}
} }
} }
return secret; return secret;
}; };
Hash.getHashes = function (channel, secret) { Hash.getHashes = function (secret) {
var hashes = {}; var hashes = {};
if (!secret.keys) { secret = JSON.parse(JSON.stringify(secret));
if (!secret.keys && !secret.key) {
console.error('e'); console.error('e');
return hashes; return hashes;
} else if (!secret.keys) {
secret.keys = {};
} }
if (secret.keys.editKeyStr) {
hashes.editHash = getEditHashFromKeys(channel, secret.keys); if (secret.keys.editKeyStr || (secret.version === 0 && secret.key)) {
hashes.editHash = getEditHashFromKeys(secret);
} }
if (secret.keys.viewKeyStr) { if (secret.keys.viewKeyStr) {
hashes.viewHash = getViewHashFromKeys(channel, secret.keys); hashes.viewHash = getViewHashFromKeys(secret);
} }
if (secret.keys.fileKeyStr) { if (secret.keys.fileKeyStr) {
hashes.fileHash = getFileHashFromKeys(channel, secret.keys.fileKeyStr); hashes.fileHash = getFileHashFromKeys(secret);
} }
return hashes; return hashes;
}; };
var createChannelId = Hash.createChannelId = function () {
var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
throw new Error('channel ids must consist of 32 hex characters');
}
return id;
};
Hash.createRandomHash = function () {
// 16 byte channel Id
var channelId = Util.hexToBase64(createChannelId());
// 18 byte encryption key
var key = Crypto.b64RemoveSlashes(Crypto.rand64(18));
return '/1/edit/' + [channelId, key].join('/') + '/';
};
// STORAGE // STORAGE
Hash.findWeaker = function (href, recents) { Hash.findWeaker = function (href, channel, recents) {
var rHref = href || getRelativeHref(window.location.href); var parsed = parsePadUrl(href);
var parsed = parsePadUrl(rHref);
if (!parsed.hash) { return false; } if (!parsed.hash) { return false; }
var weaker; var weaker;
Object.keys(recents).some(function (id) { Object.keys(recents).some(function (id) {
@ -279,6 +380,8 @@ Version 1
var p = parsePadUrl(pad.href); var p = parsePadUrl(pad.href);
if (p.type !== parsed.type) { return; } // Not the same type if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger if (p.hash === parsed.hash) { return; } // Same hash, not stronger
if (channel !== pad.channel) { return; } // Not the same channel
var pHash = p.hashData; var pHash = p.hashData;
var parsedHash = parsed.hashData; var parsedHash = parsed.hashData;
if (!parsedHash || !pHash) { return; } if (!parsedHash || !pHash) { return; }
@ -287,18 +390,16 @@ Version 1
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; } if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
if (pHash.version !== parsedHash.version) { return; } if (pHash.version !== parsedHash.version) { return; }
if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'view' && parsedHash.mode === 'edit') { if (pHash.mode === 'view' && parsedHash.mode === 'edit') {
weaker = pad.href; weaker = pad;
return true; return true;
} }
return; return;
}); });
return weaker; return weaker;
}; };
var findStronger = Hash.findStronger = function (href, recents) { Hash.findStronger = function (href, channel, recents) {
var rHref = href || getRelativeHref(window.location.href); var parsed = parsePadUrl(href);
var parsed = parsePadUrl(rHref);
if (!parsed.hash) { return false; } if (!parsed.hash) { return false; }
// We can't have a stronger hash if we're already in edit mode // We can't have a stronger hash if we're already in edit mode
if (parsed.hashData && parsed.hashData.mode === 'edit') { return; } if (parsed.hashData && parsed.hashData.mode === 'edit') { return; }
@ -308,6 +409,8 @@ Version 1
var p = parsePadUrl(pad.href); var p = parsePadUrl(pad.href);
if (p.type !== parsed.type) { return; } // Not the same type if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger if (p.hash === parsed.hash) { return; } // Same hash, not stronger
if (channel !== pad.channel) { return; } // Not the same channel
var pHash = p.hashData; var pHash = p.hashData;
var parsedHash = parsed.hashData; var parsedHash = parsed.hashData;
if (!parsedHash || !pHash) { return; } if (!parsedHash || !pHash) { return; }
@ -316,37 +419,20 @@ Version 1
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; } if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
if (pHash.version !== parsedHash.version) { return; } if (pHash.version !== parsedHash.version) { return; }
if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'edit' && parsedHash.mode === 'view') { if (pHash.mode === 'edit' && parsedHash.mode === 'view') {
stronger = pad.href; stronger = pad;
return true; return true;
} }
return; return;
}); });
return stronger; return stronger;
}; };
Hash.isNotStrongestStored = function (href, recents) {
return findStronger(href, recents);
};
Hash.hrefToHexChannelId = function (href) { Hash.hrefToHexChannelId = function (href, password) {
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
if (!parsed || !parsed.hash) { return; } if (!parsed || !parsed.hash) { return; }
var secret = Hash.getSecrets(parsed.type, parsed.hash, password);
parsed = parsed.hashData; return secret.channel;
if (parsed.version === 0) {
return parsed.channel;
} else if (parsed.version !== 1 && parsed.version !== 2) {
console.error("parsed href had no version");
console.error(parsed);
return;
}
var channel = parsed.channel;
if (!channel) { return; }
var hex = base64ToHex(channel);
return hex;
}; };
Hash.getBlobPathFromHex = function (id) { Hash.getBlobPathFromHex = function (id) {

@ -6,15 +6,18 @@ define([
'/common/common-notifier.js', '/common/common-notifier.js',
'/customize/application_config.js', '/customize/application_config.js',
'/bower_components/alertifyjs/dist/js/alertify.js', '/bower_components/alertifyjs/dist/js/alertify.js',
'/common/tippy.min.js', '/common/tippy/tippy.min.js',
'/customize/pages.js', '/customize/pages.js',
'/common/hyperscript.js', '/common/hyperscript.js',
'/customize/loading.js',
'/common/test.js', '/common/test.js',
'/common/jquery-ui/jquery-ui.min.js',
'/bower_components/bootstrap-tokenfield/dist/bootstrap-tokenfield.js', '/bower_components/bootstrap-tokenfield/dist/bootstrap-tokenfield.js',
'css!/common/tippy.css', 'css!/common/tippy/tippy.css',
'css!/common/jquery-ui/jquery-ui.min.css'
], function ($, Messages, Util, Hash, Notifier, AppConfig, ], function ($, Messages, Util, Hash, Notifier, AppConfig,
Alertify, Tippy, Pages, h, Test) { Alertify, Tippy, Pages, h, Loading, Test) {
var UI = {}; var UI = {};
/* /*
@ -182,11 +185,17 @@ define([
]); ]);
}; };
UI.tokenField = function (target) { UI.tokenField = function (target, autocomplete) {
var t = { var t = {
element: target || h('input'), element: target || h('input'),
}; };
var $t = t.tokenfield = $(t.element).tokenfield(); var $t = t.tokenfield = $(t.element).tokenfield({
autocomplete: {
source: autocomplete,
delay: 100
},
showAutocompleteOnFocus: false
});
t.getTokens = function (ignorePending) { t.getTokens = function (ignorePending) {
var tokens = $t.tokenfield('getTokens').map(function (token) { var tokens = $t.tokenfield('getTokens').map(function (token) {
@ -209,10 +218,17 @@ define([
t.preventDuplicates = function (cb) { t.preventDuplicates = function (cb) {
$t.on('tokenfield:createtoken', function (ev) { $t.on('tokenfield:createtoken', function (ev) {
// Close the suggest list when a token is added because we're going to wipe the input
var $input = $t.closest('.tokenfield').find('.token-input');
$input.autocomplete('close');
var val; var val;
ev.attrs.value = ev.attrs.value.toLowerCase(); ev.attrs.value = ev.attrs.value.toLowerCase();
if (t.getTokens(true).some(function (t) { if (t.getTokens(true).some(function (t) {
if (t === ev.attrs.value) { return ((val = t)); } if (t === ev.attrs.value) {
ev.preventDefault();
return ((val = t));
}
})) { })) {
ev.preventDefault(); ev.preventDefault();
if (typeof(cb) === 'function') { cb(val); } if (typeof(cb) === 'function') { cb(val); }
@ -240,7 +256,7 @@ define([
return t; return t;
}; };
dialog.tagPrompt = function (tags, cb) { dialog.tagPrompt = function (tags, existing, cb) {
var input = dialog.textInput(); var input = dialog.textInput();
var tagger = dialog.frame([ var tagger = dialog.frame([
@ -254,7 +270,7 @@ define([
dialog.nav(), dialog.nav(),
]); ]);
var field = UI.tokenField(input).preventDuplicates(function (val) { var field = UI.tokenField(input, existing).preventDuplicates(function (val) {
UI.warn(Messages._getKey('tags_duplicate', [val])); UI.warn(Messages._getKey('tags_duplicate', [val]));
}); });
@ -395,7 +411,7 @@ define([
stopListening(listener); stopListening(listener);
cb(); cb();
}); });
listener = listenForKeys(close, close, ok); listener = listenForKeys(close, close);
var $ok = $(ok).click(close); var $ok = $(ok).click(close);
document.body.appendChild(frame); document.body.appendChild(frame);
@ -512,6 +528,50 @@ define([
Alertify.error(Util.fixHTML(msg)); Alertify.error(Util.fixHTML(msg));
}; };
UI.passwordInput = function (opts, displayEye) {
opts = opts || {};
var attributes = merge({
type: 'password'
}, opts);
var input = h('input.cp-password-input', attributes);
var reveal = UI.createCheckbox('cp-password-reveal', Messages.password_show);
var eye = h('span.fa.fa-eye.cp-password-reveal');
$(reveal).find('input').on('change', function () {
if($(this).is(':checked')) {
$(input).prop('type', 'text');
$(input).focus();
return;
}
$(input).prop('type', 'password');
$(input).focus();
});
$(eye).mousedown(function () {
$(input).prop('type', 'text');
$(input).focus();
}).mouseup(function(){
$(input).prop('type', 'password');
$(input).focus();
}).mouseout(function(){
$(input).prop('type', 'password');
$(input).focus();
});
if (displayEye) {
$(reveal).hide();
} else {
$(eye).hide();
}
return h('span.cp-password-container', [
input,
reveal,
eye
]);
};
/* /*
* spinner * spinner
*/ */
@ -539,48 +599,99 @@ define([
var LOADING = 'cp-loading'; var LOADING = 'cp-loading';
var getRandomTip = function () { /*var getRandomTip = function () {
if (!Messages.tips || !Object.keys(Messages.tips).length) { return ''; } if (!Messages.tips || !Object.keys(Messages.tips).length) { return ''; }
var keys = Object.keys(Messages.tips); var keys = Object.keys(Messages.tips);
var rdm = Math.floor(Math.random() * keys.length); var rdm = Math.floor(Math.random() * keys.length);
return Messages.tips[keys[rdm]]; return Messages.tips[keys[rdm]];
};*/
var loading = {
error: false,
driveState: 0,
padState: 0
}; };
UI.addLoadingScreen = function (config) { UI.addLoadingScreen = function (config) {
config = config || {}; config = config || {};
var loadingText = config.loadingText; var loadingText = config.loadingText;
var hideTips = config.hideTips || AppConfig.hideLoadingScreenTips; var todo = function () {
var hideLogo = config.hideLogo; var $loading = $('#' + LOADING); //.show();
var $loading, $container;
if ($('#' + LOADING).length) {
$loading = $('#' + LOADING); //.show();
$loading.css('display', ''); $loading.css('display', '');
$loading.removeClass('cp-loading-hidden'); $loading.removeClass('cp-loading-hidden');
$('.cp-loading-spinner-container').show(); $('.cp-loading-spinner-container').show();
if (!config.noProgress && !$loading.find('.cp-loading-progress').length) {
var progress = h('div.cp-loading-progress', [
h('p.cp-loading-progress-drive'),
h('p.cp-loading-progress-pad')
]);
$loading.find('.cp-loading-container').append(progress);
} else if (config.noProgress) {
$loading.find('.cp-loading-progress').remove();
}
if (loadingText) { if (loadingText) {
$('#' + LOADING).find('p').text(loadingText); $('#' + LOADING).find('#cp-loading-message').show().text(loadingText);
} else { } else {
$('#' + LOADING).find('p').text(''); $('#' + LOADING).find('#cp-loading-message').hide().text('');
} }
$container = $loading.find('.cp-loading-container'); loading.error = false;
};
if ($('#' + LOADING).length) {
todo();
} else { } else {
$loading = $(Pages.loadingScreen()); Loading();
$container = $loading.find('.cp-loading-container'); todo();
if (hideLogo) { }
$loading.find('img').hide(); };
UI.updateLoadingProgress = function (data, isDrive) {
var $loading = $('#' + LOADING);
if (!$loading.length || loading.error) { return; }
var $progress;
if (isDrive) {
console.log(data);
// Drive state
if (loading.driveState === -1) { return; } // Already loaded
$progress = $loading.find('.cp-loading-progress-drive');
if (!$progress.length) { return; } // Can't find the box to display data
// If state is -1, remove the box, drive is loaded
if (data.state === -1) {
loading.driveState = -1;
$progress.remove();
} else { } else {
$loading.find('img').show(); if (data.state < loading.driveState) { return; } // We should not display old data
// Update the current state
loading.driveState = data.state;
data.progress = data.progress || 100;
data.msg = Messages['loading_drive_'+data.state] || '';
$progress.html(data.msg);
if (data.progress) {
$progress.append(h('div.cp-loading-progress-bar', [
h('div.cp-loading-progress-bar-value', {style: 'width:'+data.progress+'%;'})
]));
}
}
} else {
// Pad state
if (loading.padState === -1) { return; } // Already loaded
$progress = $loading.find('.cp-loading-progress-pad');
if (!$progress.length) { return; } // Can't find the box to display data
// If state is -1, remove the box, pad is loaded
if (data.state === -1) {
loading.padState = -1;
$progress.remove();
} else {
if (data.state < loading.padState) { return; } // We should not display old data
// Update the current state
loading.padState = data.state;
data.progress = data.progress || 100;
data.msg = Messages['loading_pad_'+data.state] || '';
$progress.html(data.msg);
if (data.progress) {
$progress.append(h('div.cp-loading-progress-bar', [
h('div.cp-loading-progress-bar-value', {style: 'width:'+data.progress+'%;'})
]));
}
} }
var $spinner = $loading.find('.cp-loading-spinner-container');
$spinner.show();
$('body').append($loading);
}
if (Messages.tips && !hideTips) {
var $loadingTip = $('<div>', {'id': 'cp-loading-tip'});
$('<span>', {'class': 'tips'}).text(getRandomTip()).appendTo($loadingTip);
$loadingTip.css({
'bottom': $('body').height()/2 - $container.height()/2 + 20 + 'px'
});
$('body').append($loadingTip);
} }
}; };
UI.removeLoadingScreen = function (cb) { UI.removeLoadingScreen = function (cb) {
@ -591,7 +702,7 @@ define([
$('#' + LOADING).addClass("cp-loading-hidden"); $('#' + LOADING).addClass("cp-loading-hidden");
setTimeout(cb, 750); setTimeout(cb, 750);
//$('#' + LOADING).fadeOut(750, cb); loading.error = false;
var $tip = $('#cp-loading-tip').css('top', '') var $tip = $('#cp-loading-tip').css('top', '')
// loading.less sets transition-delay: $wait-time // loading.less sets transition-delay: $wait-time
// and transition: opacity $fadeout-time // and transition: opacity $fadeout-time
@ -605,18 +716,27 @@ define([
// jquery.fadeout can get stuck // jquery.fadeout can get stuck
}; };
UI.errorLoadingScreen = function (error, transparent, exitable) { UI.errorLoadingScreen = function (error, transparent, exitable) {
if (!$('#' + LOADING).is(':visible') || $('#' + LOADING).hasClass('cp-loading-hidden')) { var $loading = $('#' + LOADING);
if (!$loading.is(':visible') || $loading.hasClass('cp-loading-hidden')) {
UI.addLoadingScreen({hideTips: true}); UI.addLoadingScreen({hideTips: true});
} }
loading.error = true;
$loading.find('.cp-loading-progress').remove();
$('.cp-loading-spinner-container').hide(); $('.cp-loading-spinner-container').hide();
$('#cp-loading-tip').remove(); $('#cp-loading-tip').remove();
if (transparent) { $('#' + LOADING).css('opacity', 0.8); } if (transparent) { $loading.css('opacity', 0.9); }
$('#' + LOADING).find('p').html(error || Messages.error); var $error = $loading.find('#cp-loading-message').show();
if (error instanceof Element) {
$error.html('').append(error);
} else {
$error.html(error || Messages.error);
}
if (exitable) { if (exitable) {
$(window).focus(); $(window).focus();
$(window).keydown(function (e) { $(window).keydown(function (e) {
if (e.which === 27) { if (e.which === 27) {
$('#' + LOADING).hide(); $loading.hide();
loading.error = false;
if (typeof(exitable) === "function") { exitable(); } if (typeof(exitable) === "function") { exitable(); }
} }
}); });
@ -660,18 +780,40 @@ define([
}); });
}; };
var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500;
$.extend(true, Tippy.defaults, {
placement: 'bottom',
performance: true,
delay: [delay, 0],
//sticky: true,
theme: 'cryptpad',
arrow: true,
maxWidth: '200px',
flip: true,
popperOptions: {
modifiers: {
preventOverflow: { boundariesElement: 'window' }
}
},
//arrowType: 'round',
dynamicTitle: true,
arrowTransform: 'scale(2)',
zIndex: 100000001
});
UI.addTooltips = function () { UI.addTooltips = function () {
var MutationObserver = window.MutationObserver; var MutationObserver = window.MutationObserver;
var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500;
var addTippy = function (i, el) { var addTippy = function (i, el) {
if (el._tippy) { return; }
if (el.nodeName === 'IFRAME') { return; } if (el.nodeName === 'IFRAME') { return; }
Tippy(el, { var opts = {
position: 'bottom', distance: 15
distance: 0, };
performance: true, Array.prototype.slice.apply(el.attributes).filter(function (obj) {
delay: [delay, 0], return /^data-tippy-/.test(obj.name);
sticky: true }).forEach(function (obj) {
opts[obj.name.slice(11)] = obj.value;
}); });
Tippy(el, opts);
}; };
// This is the robust solution to remove dangling tooltips // This is the robust solution to remove dangling tooltips
// The mutation observer does not always find removed nodes. // The mutation observer does not always find removed nodes.
@ -720,5 +862,9 @@ define([
}); });
}; };
UI.createCheckbox = Pages.createCheckbox;
UI.createRadio = Pages.createRadio;
return UI; return UI;
}); });

@ -99,7 +99,7 @@ define([
try { try {
var parsed = Hash.parsePadUrl(window.location.href); var parsed = Hash.parsePadUrl(window.location.href);
if (!parsed.hashData) { return; } if (!parsed.hashData) { return; }
var chan = parsed.hashData.channel; var chan = Hash.hrefToHexChannelId(window.location.href);
// Decrypt // Decrypt
var keyStr = parsed.hashData.key; var keyStr = parsed.hashData.key;
var cryptor = Crypto.createEditCryptor(keyStr); var cryptor = Crypto.createEditCryptor(keyStr);
@ -113,7 +113,7 @@ define([
if (!decryptMsg) { return; } if (!decryptMsg) { return; }
// Parse // Parse
msg = JSON.parse(decryptMsg); msg = JSON.parse(decryptMsg);
if (msg[1] !== parsed.hashData.channel) { return; } if (msg[1] !== chan) { return; }
var msgData = msg[2]; var msgData = msg[2];
var msgStr; var msgStr;
if (msg[0] === "FRIEND_REQ") { if (msg[0] === "FRIEND_REQ") {
@ -199,7 +199,7 @@ define([
var parsed = Hash.parsePadUrl(data.href); var parsed = Hash.parsePadUrl(data.href);
if (!parsed.hashData) { return; } if (!parsed.hashData) { return; }
// Message // Message
var chan = parsed.hashData.channel; var chan = Hash.hrefToHexChannelId(data.href);
var myData = createData(cfg.proxy); var myData = createData(cfg.proxy);
var msg = ["FRIEND_REQ", chan, myData]; var msg = ["FRIEND_REQ", chan, myData];
// Encryption // Encryption

@ -205,7 +205,7 @@ define([
if (content === oldThumbnailState) { return; } if (content === oldThumbnailState) { return; }
oldThumbnailState = content; oldThumbnailState = content;
Thumb.fromDOM(opts, function (err, b64) { Thumb.fromDOM(opts, function (err, b64) {
Thumb.setPadThumbnail(common, opts.href, b64); Thumb.setPadThumbnail(common, opts.href, null, b64);
}); });
}; };
var nafa = Util.notAgainForAnother(mkThumbnail, Thumb.UPDATE_INTERVAL); var nafa = Util.notAgainForAnother(mkThumbnail, Thumb.UPDATE_INTERVAL);
@ -240,20 +240,22 @@ define([
Thumb.addThumbnail = function(thumb, $span, cb) { Thumb.addThumbnail = function(thumb, $span, cb) {
return addThumbnail(null, thumb, $span, cb); return addThumbnail(null, thumb, $span, cb);
}; };
var getKey = function (href) { var getKey = function (type, channel) {
var parsed = Hash.parsePadUrl(href); return 'thumbnail-' + type + '-' + channel;
return 'thumbnail-' + parsed.type + '-' + parsed.hashData.channel;
}; };
Thumb.setPadThumbnail = function (common, href, b64, cb) { Thumb.setPadThumbnail = function (common, href, channel, b64, cb) {
cb = cb || function () {}; cb = cb || function () {};
var k = getKey(href); var parsed = Hash.parsePadUrl(href);
channel = channel || common.getMetadataMgr().getPrivateData().channel;
var k = getKey(parsed.type, channel);
common.setThumbnail(k, b64, cb); common.setThumbnail(k, b64, cb);
}; };
Thumb.displayThumbnail = function (common, href, $container, cb) { Thumb.displayThumbnail = function (common, href, channel, $container, cb) {
cb = cb || function () {}; cb = cb || function () {};
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
var k = getKey(href); var k = getKey(parsed.type, channel);
var whenNewThumb = function () { var whenNewThumb = function () {
// PASSWORD_FILES
var secret = Hash.getSecrets('file', parsed.hash); var secret = Hash.getSecrets('file', parsed.hash);
var hexFileName = Util.base64ToHex(secret.channel); var hexFileName = Util.base64ToHex(secret.channel);
var src = Hash.getBlobPathFromHex(hexFileName); var src = Hash.getBlobPathFromHex(hexFileName);
@ -270,7 +272,7 @@ define([
if (!v) { if (!v) {
v = 'EMPTY'; v = 'EMPTY';
} }
Thumb.setPadThumbnail(common, href, v, function (err) { Thumb.setPadThumbnail(common, href, hexFileName, v, function (err) {
if (!metadata.thumbnail) { return; } if (!metadata.thumbnail) { return; }
addThumbnail(err, metadata.thumbnail, $container, cb); addThumbnail(err, metadata.thumbnail, $container, cb);
}); });

@ -13,8 +13,6 @@ define([
'/customize/messages.js', '/customize/messages.js',
'/customize/application_config.js', '/customize/application_config.js',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'css!/common/tippy.css',
], function ($, Config, Util, Hash, Language, UI, Constants, Feedback, h, MediaTag, Clipboard, ], function ($, Config, Util, Hash, Language, UI, Constants, Feedback, h, MediaTag, Clipboard,
Messages, AppConfig, NThen) { Messages, AppConfig, NThen) {
var UIElements = {}; var UIElements = {};
@ -25,20 +23,27 @@ define([
} }
UIElements.updateTags = function (common, href) { UIElements.updateTags = function (common, href) {
var sframeChan = common.getSframeChannel(); var existing, tags;
sframeChan.query('Q_TAGS_GET', href || null, function (err, res) { NThen(function(waitFor) {
if (err || res.error) { common.getSframeChannel().query("Q_GET_ALL_TAGS", null, waitFor(function(err, res) {
if (res.error === 'NO_ENTRY') { if (err || res.error) { return void console.error(err || res.error); }
UI.alert(Messages.tags_noentry); existing = Object.keys(res.tags).sort();
}));
}).nThen(function (waitFor) {
common.getPadAttribute('tags', waitFor(function (err, res) {
if (err) {
if (err === 'NO_ENTRY') {
UI.alert(Messages.tags_noentry);
}
waitFor.abort();
return void console.error(err);
} }
return void console.error(err || res.error); tags = res || [];
} }), href);
UI.dialog.tagPrompt(res.data, function (tags) { }).nThen(function () {
if (!Array.isArray(tags)) { return; } UI.dialog.tagPrompt(tags, existing, function (newTags) {
sframeChan.event('EV_TAGS_SET', { if (!Array.isArray(newTags)) { return; }
tags: tags, common.setPadAttribute('tags', newTags, null, href);
href: href,
});
}); });
}); });
}; };
@ -62,6 +67,10 @@ define([
var getPropertiesData = function (common, cb) { var getPropertiesData = function (common, cb) {
var data = {}; var data = {};
NThen(function (waitFor) { NThen(function (waitFor) {
common.getPadAttribute('password', waitFor(function (err, val) {
data.password = val;
}));
}).nThen(function (waitFor) {
common.getPadAttribute('href', waitFor(function (err, val) { common.getPadAttribute('href', waitFor(function (err, val) {
var base = common.getMetadataMgr().getPrivateData().origin; var base = common.getMetadataMgr().getPrivateData().origin;
@ -73,15 +82,19 @@ define([
// We're not in a read-only pad // We're not in a read-only pad
data.href = base + val; data.href = base + val;
// Get Read-only href // Get Read-only href
if (parsed.hashData.type !== "pad") { return; } if (parsed.hashData.type !== "pad") { return; }
var i = data.href.indexOf('#') + 1; var i = data.href.indexOf('#') + 1;
var hBase = data.href.slice(0, i); var hBase = data.href.slice(0, i);
var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash); var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash, data.password);
if (!hrefsecret.keys) { return; } if (!hrefsecret.keys) { return; }
var viewHash = Hash.getViewHashFromKeys(hrefsecret.channel, hrefsecret.keys); var viewHash = Hash.getViewHashFromKeys(hrefsecret);
data.roHref = hBase + viewHash; data.roHref = hBase + viewHash;
})); }));
common.getPadAttribute('channel', waitFor(function (err, val) {
data.channel = val;
}));
common.getPadAttribute('atime', waitFor(function (err, val) { common.getPadAttribute('atime', waitFor(function (err, val) {
data.atime = val; data.atime = val;
})); }));
@ -137,6 +150,22 @@ define([
$d.append(UI.dialog.selectable(expire, { $d.append(UI.dialog.selectable(expire, {
id: 'cp-app-prop-expire', id: 'cp-app-prop-expire',
})); }));
if (typeof data.password !== "undefined") {
$('<label>', {'for': 'cp-app-prop-password'}).text(Messages.creation_passwordValue)
.appendTo($d);
var password = UI.passwordInput({
id: 'cp-app-prop-expire',
readonly: 'readonly'
});
var $pwInput = $(password).find('.cp-password-input');
$pwInput.val(data.password).click(function () {
$pwInput[0].select();
});
$(password).find('.cp-checkmark').css('margin-bottom', '15px');
$d.append(password);
}
cb(void 0, $d); cb(void 0, $d);
}; };
var getPadProperties = function (common, data, cb) { var getPadProperties = function (common, data, cb) {
@ -178,7 +207,7 @@ define([
if (common.isLoggedIn() && AppConfig.enablePinning) { if (common.isLoggedIn() && AppConfig.enablePinning) {
// check the size of this file... // check the size of this file...
common.getFileSize(data.href, function (e, bytes) { common.getFileSize(data.channel, function (e, bytes) {
if (e) { if (e) {
// there was a problem with the RPC // there was a problem with the RPC
console.error(e); console.error(e);
@ -237,7 +266,11 @@ define([
var link = h('div.cp-share-modal', [ var link = h('div.cp-share-modal', [
h('label', Messages.share_linkAccess), h('label', Messages.share_linkAccess),
h('br'), h('br'),
h('input#cp-share-editable-true.cp-share-editable-value', { UI.createRadio('cp-share-editable', 'cp-share-editable-true',
Messages.share_linkEdit, true, { mark: {tabindex:1} }),
UI.createRadio('cp-share-editable', 'cp-share-editable-false',
Messages.share_linkView, false, { mark: {tabindex:1} }),
/*h('input#cp-share-editable-true.cp-share-editable-value', {
type: 'radio', type: 'radio',
name: 'cp-share-editable', name: 'cp-share-editable',
value: 1, value: 1,
@ -248,25 +281,14 @@ define([
name: 'cp-share-editable', name: 'cp-share-editable',
value: 0 value: 0
}), }),
h('label', { 'for': 'cp-share-editable-false' }, Messages.share_linkView), h('label', { 'for': 'cp-share-editable-false' }, Messages.share_linkView),*/
h('br'),
h('br'), h('br'),
h('label', Messages.share_linkOptions), h('label', Messages.share_linkOptions),
h('br'), h('br'),
h('input#cp-share-embed', { UI.createCheckbox('cp-share-embed', Messages.share_linkEmbed, false, { mark: {tabindex:1} }),
type: 'checkbox', UI.createCheckbox('cp-share-present', Messages.share_linkPresent, false, { mark: {tabindex:1} }),
name: 'cp-share-embed'
}),
h('label', { 'for': 'cp-share-embed' }, Messages.share_linkEmbed),
h('br'),
h('input#cp-share-present', {
type: 'checkbox',
name: 'cp-share-present'
}),
h('label', { 'for': 'cp-share-present' }, Messages.share_linkPresent),
h('br'), h('br'),
h('br'), UI.dialog.selectable('', { id: 'cp-share-link-preview', tabindex: 1 })
UI.dialog.selectable('', { id: 'cp-share-link-preview' })
]); ]);
if (!hashes.editHash) { if (!hashes.editHash) {
$(link).find('#cp-share-editable-false').attr('checked', true); $(link).find('#cp-share-editable-false').attr('checked', true);
@ -482,13 +504,44 @@ define([
'class': 'fa fa-upload cp-toolbar-icon-import', 'class': 'fa fa-upload cp-toolbar-icon-import',
title: Messages.importButtonTitle, title: Messages.importButtonTitle,
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.importButton)); }).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.importButton));
if (callback) { /*if (data.types) {
// New import button in the toolbar
var importFunction = {
template: function () {
UIElements.openTemplatePicker(common, true);
},
file: function (cb) {
importContent('text/plain', function (content, file) {
cb(content, file);
}, {accept: data ? data.accept : undefined})
}
};
var toImport = [];
Object.keys(data.types).forEach(function (importType) {
if (!importFunction[importType] || !data.types[importType]) { return; }
var option = h('button', importType);
$(option).click(function () {
importFunction[importType](data.types[importType]);
});
toImport.push(options);
});
button.click(common.prepareFeedback(type));
if (toImport.length === 1) {
button.click(function () { $(toImport[0]).click(); });
} else {
Cryptpad.alert(h('p.cp-import-container', toImport));
}
}
else if (callback) {*/
// Old import button, used in settings
button button
.click(common.prepareFeedback(type)) .click(common.prepareFeedback(type))
.click(importContent('text/plain', function (content, file) { .click(importContent('text/plain', function (content, file) {
callback(content, file); callback(content, file);
}, {accept: data ? data.accept : undefined})); }, {accept: data ? data.accept : undefined}));
} //}
break; break;
case 'upload': case 'upload':
button = $('<button>', { button = $('<button>', {
@ -520,6 +573,19 @@ define([
if (data.accept) { $input.attr('accept', data.accept); } if (data.accept) { $input.attr('accept', data.accept); }
button.click(function () { $input.click(); }); button.click(function () { $input.click(); });
break; break;
case 'importtemplate':
if (!AppConfig.enableTemplates) { return; }
if (!common.isLoggedIn()) { return; }
button = $('<button>', {
'class': 'fa fa-upload cp-toolbar-icon-import',
title: Messages.template_import,
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.template_import));
button
.click(common.prepareFeedback(type))
.click(function () {
UIElements.openTemplatePicker(common, true);
});
break;
case 'template': case 'template':
if (!AppConfig.enableTemplates) { return; } if (!AppConfig.enableTemplates) { return; }
if (!common.isLoggedIn()) { return; } if (!common.isLoggedIn()) { return; }
@ -584,7 +650,8 @@ define([
sframeChan.query('Q_MOVE_TO_TRASH', null, function (err) { sframeChan.query('Q_MOVE_TO_TRASH', null, function (err) {
if (err) { return void callback(err); } if (err) { return void callback(err); }
var cMsg = common.isLoggedIn() ? Messages.movedToTrash : Messages.deleted; var cMsg = common.isLoggedIn() ? Messages.movedToTrash : Messages.deleted;
UI.alert(cMsg, undefined, true); var msg = common.fixLinks($('<div>').html(cMsg));
UI.alert(msg);
callback(); callback();
return; return;
}); });
@ -890,14 +957,14 @@ define([
}; };
}; };
var setHTML = function (e, html) {
e.innerHTML = html;
return e;
};
UIElements.createHelpMenu = function (common, categories) { UIElements.createHelpMenu = function (common, categories) {
var type = common.getMetadataMgr().getMetadata().type || 'pad'; var type = common.getMetadataMgr().getMetadata().type || 'pad';
var setHTML = function (e, html) {
e.innerHTML = html;
return e;
};
var elements = []; var elements = [];
if (Messages.help && Messages.help.generic) { if (Messages.help && Messages.help.generic) {
Object.keys(Messages.help.generic).forEach(function (el) { Object.keys(Messages.help.generic).forEach(function (el) {
@ -920,18 +987,7 @@ define([
h('ul', elements) h('ul', elements)
]); ]);
var origin = common.getMetadataMgr().getPrivateData().origin || ''; common.fixLinks(text);
$(text).find('a').click(function (e) {
e.preventDefault();
e.stopPropagation();
var href = $(this).attr('href');
var absolute = /^https?:\/\//i;
if (!absolute.test(href)) {
if (href.slice(0,1) !== '/') { href = '/' + href; }
href = origin + href;
}
common.openUnsafeURL(href);
});
var closeButton = h('span.cp-help-close.fa.fa-window-close'); var closeButton = h('span.cp-help-close.fa.fa-window-close');
var $toolbarButton = common.createButton('', true, { var $toolbarButton = common.createButton('', true, {
@ -1110,12 +1166,13 @@ define([
}; };
return; return;
} }
// No password for avatars
var secret = Hash.getSecrets('file', parsed.hash); var secret = Hash.getSecrets('file', parsed.hash);
if (secret.keys && secret.channel) { if (secret.keys && secret.channel) {
var cryptKey = secret.keys && secret.keys.fileKeyStr; var cryptKey = secret.keys && secret.keys.fileKeyStr;
var hexFileName = Util.base64ToHex(secret.channel); var hexFileName = Util.base64ToHex(secret.channel);
var src = Hash.getBlobPathFromHex(hexFileName); var src = Hash.getBlobPathFromHex(hexFileName);
Common.getFileSize(href, function (e, data) { Common.getFileSize(hexFileName, function (e, data) {
if (e) { if (e) {
displayDefault(); displayDefault();
return void console.error(e); return void console.error(e);
@ -1148,7 +1205,7 @@ define([
// so we can just use those and only check for errors // so we can just use those and only check for errors
var $container = $('<span>', {'class':'cp-limit-container'}); var $container = $('<span>', {'class':'cp-limit-container'});
var todo = function (err, data) { var todo = function (err, data) {
if (err) { return void console.error(err); } if (err || !data) { return void console.error(err || 'No data'); }
var usage = data.usage; var usage = data.usage;
var limit = data.limit; var limit = data.limit;
@ -1422,50 +1479,51 @@ define([
tag: 'a', tag: 'a',
attributes: { attributes: {
'target': '_blank', 'target': '_blank',
'href': origin+'/drive/' 'href': origin+'/drive/',
'class': 'fa fa-hdd-o'
}, },
content: Messages.login_accessDrive content: h('span', Messages.login_accessDrive)
}); });
} }
// Add the change display name button if not in read only mode // Add the change display name button if not in read only mode
if (config.changeNameButtonCls && config.displayChangeName && !AppConfig.disableProfile) { if (config.changeNameButtonCls && config.displayChangeName && !AppConfig.disableProfile) {
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: {'class': config.changeNameButtonCls}, attributes: {'class': config.changeNameButtonCls + ' fa fa-user'},
content: Messages.user_rename content: h('span', Messages.user_rename)
}); });
} }
if (accountName && !AppConfig.disableProfile) { if (accountName && !AppConfig.disableProfile) {
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: {'class': 'cp-toolbar-menu-profile'}, attributes: {'class': 'cp-toolbar-menu-profile fa fa-user-circle'},
content: Messages.profileButton content: h('span', Messages.profileButton)
}); });
} }
if (padType !== 'settings') { if (padType !== 'settings') {
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: {'class': 'cp-toolbar-menu-settings'}, attributes: {'class': 'cp-toolbar-menu-settings fa fa-cog'},
content: Messages.settingsButton content: h('span', Messages.settingsButton)
}); });
} }
// Add login or logout button depending on the current status // Add login or logout button depending on the current status
if (accountName) { if (accountName) {
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: {'class': 'cp-toolbar-menu-logout'}, attributes: {'class': 'cp-toolbar-menu-logout fa fa-sign-out'},
content: Messages.logoutButton content: h('span', Messages.logoutButton)
}); });
} else { } else {
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: {'class': 'cp-toolbar-menu-login'}, attributes: {'class': 'cp-toolbar-menu-login fa fa-sign-in'},
content: Messages.login_login content: h('span', Messages.login_login)
}); });
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: {'class': 'cp-toolbar-menu-register'}, attributes: {'class': 'cp-toolbar-menu-register fa fa-user-plus'},
content: Messages.login_register content: h('span', Messages.login_register)
}); });
} }
var $icon = $('<span>', {'class': 'fa fa-user-secret'}); var $icon = $('<span>', {'class': 'fa fa-user-secret'});
@ -1521,9 +1579,8 @@ define([
UIElements.displayAvatar(Common, $avatar, url, UIElements.displayAvatar(Common, $avatar, url,
newName || Messages.anonymous, function ($img) { newName || Messages.anonymous, function ($img) {
oldUrl = url; oldUrl = url;
if ($img) { $userAdmin.find('> button').removeClass('cp-avatar');
$userAdmin.find('> button').addClass('cp-avatar'); if ($img) { $userAdmin.find('> button').addClass('cp-avatar'); }
}
loadingAvatar = false; loadingAvatar = false;
}); });
return; return;
@ -1651,14 +1708,10 @@ define([
var priv = common.getMetadataMgr().getPrivateData(); var priv = common.getMetadataMgr().getPrivateData();
var c = (priv.settings.general && priv.settings.general.creation) || {}; var c = (priv.settings.general && priv.settings.general.creation) || {};
if (AppConfig.displayCreationScreen && common.isLoggedIn() && c.skip) { if (AppConfig.displayCreationScreen && common.isLoggedIn() && c.skip) {
$advanced = $('<input>', { var $cboxLabel = $(UI.createCheckbox('cp-app-toolbar-creation-advanced',
type: 'checkbox', Messages.creation_newPadModalAdvanced, true))
checked: 'checked', .appendTo($advancedContainer);
id: 'cp-app-toolbar-creation-advanced' $advanced = $cboxLabel.find('input');
}).appendTo($advancedContainer);
$('<label>', {
for: 'cp-app-toolbar-creation-advanced'
}).text(Messages.creation_newPadModalAdvanced).appendTo($advancedContainer);
$description.append('<br>'); $description.append('<br>');
$description.append(Messages.creation_newPadModalDescriptionAdvanced); $description.append(Messages.creation_newPadModalDescriptionAdvanced);
} }
@ -1742,17 +1795,21 @@ define([
sframeChan.event("EV_FILE_PICKER_OPEN", types); sframeChan.event("EV_FILE_PICKER_OPEN", types);
}; };
UIElements.openTemplatePicker = function (common) { UIElements.openTemplatePicker = function (common, force) {
var metadataMgr = common.getMetadataMgr(); var metadataMgr = common.getMetadataMgr();
var type = metadataMgr.getMetadataLazy().type; var type = metadataMgr.getMetadataLazy().type;
var sframeChan = common.getSframeChannel(); var sframeChan = common.getSframeChannel();
var focus; var focus;
var pickerCfg = { var pickerCfgInit = {
types: [type], types: [type],
where: ['template'], where: ['template'],
hidden: true hidden: true
}; };
var pickerCfg = {
types: [type],
where: ['template'],
};
var onConfirm = function (yes) { var onConfirm = function (yes) {
if (!yes) { if (!yes) {
if (focus) { focus.focus(); } if (focus) { focus.focus(); }
@ -1780,12 +1837,15 @@ define([
sframeChan.query("Q_TEMPLATE_EXIST", type, function (err, data) { sframeChan.query("Q_TEMPLATE_EXIST", type, function (err, data) {
if (data) { if (data) {
common.openFilePicker(pickerCfg); common.openFilePicker(pickerCfgInit);
focus = document.activeElement; focus = document.activeElement;
if (force) { return void onConfirm(true); }
UI.confirm(Messages.useTemplate, onConfirm, { UI.confirm(Messages.useTemplate, onConfirm, {
ok: Messages.useTemplateOK, ok: Messages.useTemplateOK,
cancel: Messages.useTemplateCancel, cancel: Messages.useTemplateCancel,
}); });
} else if (force) {
UI.alert(Messages.template_empty);
} }
}); });
}; };
@ -1821,11 +1881,15 @@ define([
var $body = $('body'); var $body = $('body');
var $creationContainer = $('<div>', { id: 'cp-creation-container' }).appendTo($body); var $creationContainer = $('<div>', { id: 'cp-creation-container' }).appendTo($body);
var urlArgs = (Config.requireConf && Config.requireConf.urlArgs) || '';
var l = h('div.cp-creation-logo', h('img', { src: '/customize/loading-logo.png?' + urlArgs }));
$(l).appendTo($creationContainer);
var $creation = $('<div>', { id: 'cp-creation', tabindex: 1 }).appendTo($creationContainer); var $creation = $('<div>', { id: 'cp-creation', tabindex: 1 }).appendTo($creationContainer);
// Title // Title
var colorClass = 'cp-icon-color-'+type; //var colorClass = 'cp-icon-color-'+type;
$creation.append(h('h2.cp-creation-title', Messages.newButtonTitle)); //$creation.append(h('h2.cp-creation-title', Messages.newButtonTitle));
$creation.append(h('h3.cp-creation-title', Messages['button_new'+type]));
//$creation.append(h('h2.cp-creation-title.'+colorClass, Messages.newButtonTitle)); //$creation.append(h('h2.cp-creation-title.'+colorClass, Messages.newButtonTitle));
// Deleted pad warning // Deleted pad warning
@ -1837,10 +1901,11 @@ define([
var origin = common.getMetadataMgr().getPrivateData().origin; var origin = common.getMetadataMgr().getPrivateData().origin;
var createHelper = function (href, text) { var createHelper = function (href, text) {
var q = h('a.cp-creation-help.fa.fa-question', { var q = h('a.cp-creation-help.fa.fa-question-circle', {
title: text, title: text,
href: origin + href, href: origin + href,
target: "_blank" target: "_blank",
'data-tippy-placement': "right"
}); });
return q; return q;
}; };
@ -1848,30 +1913,14 @@ define([
// Owned pads // Owned pads
// Default is Owned pad // Default is Owned pad
var owned = h('div.cp-creation-owned', [ var owned = h('div.cp-creation-owned', [
h('label.cp-checkmark', [ UI.createCheckbox('cp-creation-owned', Messages.creation_owned, true),
h('input', {
type: 'checkbox',
id: 'cp-creation-owned',
checked: 'checked'
}),
h('span.cp-checkmark-mark'),
Messages.creation_owned
]),
createHelper('/faq.html#keywords-owned', Messages.creation_owned1) createHelper('/faq.html#keywords-owned', Messages.creation_owned1)
]); ]);
// Life time // Life time
var expire = h('div.cp-creation-expire', [ var expire = h('div.cp-creation-expire', [
h('label.cp-checkmark', [ UI.createCheckbox('cp-creation-expire', Messages.creation_expire, false),
h('input', { h('span.cp-creation-expire-picker.cp-creation-slider', [
type: 'checkbox',
id: 'cp-creation-expire'
}),
h('span.cp-checkmark-mark'),
Messages.creation_expire
]),
createHelper('/faq.html#keywords-expiring', Messages.creation_expire2),
h('div.cp-creation-expire-picker.cp-creation-slider', [
h('input#cp-creation-expire-val', { h('input#cp-creation-expire-val', {
type: "number", type: "number",
min: 1, min: 1,
@ -1886,106 +1935,168 @@ define([
selected: 'selected' selected: 'selected'
}, Messages.creation_expireMonths) }, Messages.creation_expireMonths)
]) ])
]) ]),
createHelper('/faq.html#keywords-expiring', Messages.creation_expire2),
]); ]);
var createDiv = h('div.cp-creation-create'); // Password
var $create = $(createDiv); var password = h('div.cp-creation-password', [
UI.createCheckbox('cp-creation-password', Messages.creation_password, false),
h('span.cp-creation-password-picker.cp-creation-slider', [
UI.passwordInput({id: 'cp-creation-password-val'})
/*h('input#cp-creation-password-val', {
type: "text" // TODO type password with click to show
}),*/
]),
//createHelper('#', "TODO: password protection adds another layer of security ........") // TODO
]);
var right = h('span.fa.fa-chevron-right.cp-creation-template-more');
var left = h('span.fa.fa-chevron-left.cp-creation-template-more');
var templates = h('div.cp-creation-template', [ var templates = h('div.cp-creation-template', [
h('h3.cp-creation-title.'+colorClass, Messages['button_new'+type]), left,
h('div.cp-creation-template-container', [ h('div.cp-creation-template-container', [
h('span.fa.fa-circle-o-notch.fa-spin.fa-4x.fa-fw') h('span.fa.fa-circle-o-notch.fa-spin.fa-4x.fa-fw')
]), ]),
createDiv right
]); ]);
var settings = h('div.cp-creation-remember', [ var settings = h('div.cp-creation-remember', [
h('label.cp-checkmark', [ UI.createCheckbox('cp-creation-remember', Messages.creation_saveSettings, false),
h('input', {
type: 'checkbox',
id: 'cp-creation-remember'
}),
h('span.cp-checkmark-mark'),
Messages.creation_saveSettings
]),
createHelper('/settings/#creation', Messages.creation_settings), createHelper('/settings/#creation', Messages.creation_settings),
h('div.cp-creation-remember-help.cp-creation-slider', Messages.creation_rememberHelp) h('div.cp-creation-remember-help.cp-creation-slider', [
h('span.fa.fa-exclamation-circle.cp-creation-warning'),
Messages.creation_rememberHelp
])
]); ]);
var createDiv = h('div.cp-creation-create');
var $create = $(createDiv);
$(h('div#cp-creation-form', [ $(h('div#cp-creation-form', [
owned, owned,
expire, expire,
password,
settings, settings,
templates templates,
createDiv
])).appendTo($creation); ])).appendTo($creation);
// Display templates // Display templates
var selected = 0;
var selected = 0; // Selected template in the list (highlighted)
var TEMPLATES_DISPLAYED = 4; // Max templates displayed per page
var next = function () {}; // Function called when pressing tab to highlight the next template
var i = 0; // Index of the first template displayed in the current page
sframeChan.query("Q_CREATE_TEMPLATES", type, function (err, res) { sframeChan.query("Q_CREATE_TEMPLATES", type, function (err, res) {
if (!res.data || !Array.isArray(res.data)) { if (!res.data || !Array.isArray(res.data)) {
return void console.error("Error: get the templates list"); return void console.error("Error: get the templates list");
} }
var data = res.data.slice().sort(function (a, b) { var allData = res.data.slice().sort(function (a, b) {
if (a.name === b.name) { return 0; } if (a.used === b.used) {
return a.name < b.name ? -1 : 1; // Sort by name
}); if (a.name === b.name) { return 0; }
data.unshift({ return a.name < b.name ? -1 : 1;
name: Messages.creation_noTemplate, }
id: 0, return b.used - a.used;
icon: h('span.fa.fa-file')
}); });
data.push({ allData.unshift({
name: Messages.creation_newTemplate, name: Messages.creation_newTemplate,
id: -1, id: -1,
icon: h('span.fa.fa-bookmark') icon: h('span.fa.fa-bookmark')
}); });
var $container = $(templates).find('.cp-creation-template-container').html(''); allData.unshift({
data.forEach(function (obj, idx) { name: Messages.creation_noTemplate,
var name = obj.name; id: 0,
var $span = $('<span>', { icon: h('span.fa.fa-file')
'class': 'cp-creation-template-element', });
'title': name, var redraw = function (index) {
}).appendTo($container); if (index < 0) { i = 0; }
$span.data('id', obj.id); else if (index > allData.length - 1) { return; }
if (idx === 0) { $span.addClass('cp-creation-template-selected'); } else { i = index; }
$span.append(obj.icon || UI.getFileIcon({type: type})); var data = allData.slice(i, i + TEMPLATES_DISPLAYED);
$('<span>', {'class': 'cp-creation-template-element-name'}).text(name) var $container = $(templates).find('.cp-creation-template-container').html('');
.appendTo($span); data.forEach(function (obj, idx) {
$span.click(function () { var name = obj.name;
$container.find('.cp-creation-template-selected') var $span = $('<span>', {
.removeClass('cp-creation-template-selected'); 'class': 'cp-creation-template-element',
$span.addClass('cp-creation-template-selected'); 'title': name,
selected = idx; }).appendTo($container);
}); $span.data('id', obj.id);
if (idx === selected) { $span.addClass('cp-creation-template-selected'); }
$span.append(obj.icon || UI.getFileIcon({type: type}));
$('<span>', {'class': 'cp-creation-template-element-name'}).text(name)
.appendTo($span);
$span.click(function () {
$container.find('.cp-creation-template-selected')
.removeClass('cp-creation-template-selected');
$span.addClass('cp-creation-template-selected');
selected = idx;
});
// Add thumbnail if it exists // Add thumbnail if it exists
if (obj.thumbnail) { if (obj.thumbnail) {
common.addThumbnail(obj.thumbnail, $span, function () {}); common.addThumbnail(obj.thumbnail, $span, function () {});
}
});
$(right).off('click').removeClass('hidden').click(function () {
selected = 0;
redraw(i + TEMPLATES_DISPLAYED);
});
if (i >= allData.length - TEMPLATES_DISPLAYED ) { $(right).addClass('hidden'); }
$(left).off('click').removeClass('hidden').click(function () {
selected = TEMPLATES_DISPLAYED - 1;
redraw(i - TEMPLATES_DISPLAYED);
});
if (i < TEMPLATES_DISPLAYED) { $(left).addClass('hidden'); }
};
redraw(0);
// Change template selection when Tab is pressed
next = function (revert) {
var max = $creation.find('.cp-creation-template-element').length;
if (selected + 1 === max && !revert) {
selected = i + TEMPLATES_DISPLAYED < allData.length ? 0 : max;
return void redraw(i + TEMPLATES_DISPLAYED);
} }
}); if (selected === 0 && revert) {
}); selected = i - TEMPLATES_DISPLAYED >= 0 ? TEMPLATES_DISPLAYED - 1 : 0;
// Change template selection when Tab is pressed return void redraw(i - TEMPLATES_DISPLAYED);
var next = function (revert) { }
var max = $creation.find('.cp-creation-template-element').length; selected = revert ?
selected = revert ? (--selected < 0 ? 0 : selected) :
(--selected < 0 ? max-1 : selected) : ++selected >= max ? max-1 : selected;
++selected % max; $creation.find('.cp-creation-template-element')
$creation.find('.cp-creation-template-element') .removeClass('cp-creation-template-selected');
.removeClass('cp-creation-template-selected'); $($creation.find('.cp-creation-template-element').get(selected))
$($creation.find('.cp-creation-template-element').get(selected)) .addClass('cp-creation-template-selected');
.addClass('cp-creation-template-selected'); };
};
});
// Display expiration form when checkbox checked // Display expiration form when checkbox checked
$creation.find('#cp-creation-expire').on('change', function () { $creation.find('#cp-creation-expire').on('change', function () {
if ($(this).is(':checked')) { if ($(this).is(':checked')) {
$creation.find('.cp-creation-expire-picker:not(.active)').addClass('active'); $creation.find('.cp-creation-expire-picker:not(.active)').addClass('active');
$creation.find('.cp-creation-expire:not(.active)').addClass('active');
$creation.find('#cp-creation-expire-val').focus(); $creation.find('#cp-creation-expire-val').focus();
return; return;
} }
$creation.find('.cp-creation-expire-picker').removeClass('active'); $creation.find('.cp-creation-expire-picker').removeClass('active');
$creation.find('.cp-creation-expire').removeClass('active');
$creation.focus();
});
// Display expiration form when checkbox checked
$creation.find('#cp-creation-password').on('change', function () {
if ($(this).is(':checked')) {
$creation.find('.cp-creation-password-picker:not(.active)').addClass('active');
$creation.find('.cp-creation-password:not(.active)').addClass('active');
$creation.find('#cp-creation-password-val').focus();
return;
}
$creation.find('.cp-creation-password-picker').removeClass('active');
$creation.find('.cp-creation-password').removeClass('active');
$creation.focus(); $creation.focus();
}); });
@ -2037,12 +2148,16 @@ define([
} }
expireVal = ($('#cp-creation-expire-val').val() || 0) * unit; expireVal = ($('#cp-creation-expire-val').val() || 0) * unit;
} }
// Password
var passwordVal = $('#cp-creation-password').is(':checked') ?
$('#cp-creation-password-val').val() : undefined;
var $template = $creation.find('.cp-creation-template-selected'); var $template = $creation.find('.cp-creation-template-selected');
var templateId = $template.data('id') || undefined; var templateId = $template.data('id') || undefined;
return { return {
owned: ownedVal, owned: ownedVal,
password: passwordVal,
expire: expireVal, expire: expireVal,
templateId: templateId templateId: templateId
}; };
@ -2112,5 +2227,38 @@ define([
(cb || function () {})(); (cb || function () {})();
}; };
UIElements.displayPasswordPrompt = function (common, isError) {
var error;
if (isError) { error = setHTML(h('p.cp-password-error'), Messages.password_error); }
var info = h('p.cp-password-info', Messages.password_info);
var password = UI.passwordInput({placeholder: Messages.password_placeholder});
var button = h('button', Messages.password_submit);
var submit = function () {
var value = $(password).find('.cp-password-input').val();
UI.addLoadingScreen();
common.getSframeChannel().query('Q_PAD_PASSWORD_VALUE', value, function (err, data) {
if (!data) {
UIElements.displayPasswordPrompt(common, true);
}
});
};
$(password).find('.cp-password-input').on('keydown', function (e) { if (e.which === 13) { submit(); } });
$(button).on('click', function () { submit(); });
var block = h('div#cp-loading-password-prompt', [
error,
info,
h('p.cp-password-form', [
password,
button
])
]);
UI.errorLoadingScreen(block);
$(password).find('.cp-password-input').focus();
};
return UIElements; return UIElements;
}); });

@ -20,9 +20,9 @@ define([
} }
}; };
var makeConfig = function (hash) { var makeConfig = function (hash, password) {
// We can't use cryptget with a file or a user so we can use 'pad' as hash type // We can't use cryptget with a file or a user so we can use 'pad' as hash type
var secret = Hash.getSecrets('pad', hash); var secret = Hash.getSecrets('pad', hash, password);
if (!secret.keys) { secret.keys = secret.key; } // support old hashses if (!secret.keys) { secret.keys = secret.key; } // support old hashses
var config = { var config = {
websocketURL: NetConfig.getWebsocketURL(), websocketURL: NetConfig.getWebsocketURL(),
@ -47,8 +47,10 @@ define([
if (typeof(cb) !== 'function') { if (typeof(cb) !== 'function') {
throw new Error('Cryptget expects a callback'); throw new Error('Cryptget expects a callback');
} }
opt = opt || {};
var config = makeConfig(hash, opt.password);
var Session = { cb: cb, }; var Session = { cb: cb, };
var config = makeConfig(hash);
config.onReady = function (info) { config.onReady = function (info) {
var rt = Session.session = info.realtime; var rt = Session.session = info.realtime;
@ -64,9 +66,11 @@ define([
if (typeof(cb) !== 'function') { if (typeof(cb) !== 'function') {
throw new Error('Cryptput expects a callback'); throw new Error('Cryptput expects a callback');
} }
opt = opt || {};
var config = makeConfig(hash); var config = makeConfig(hash, opt.password);
var Session = { cb: cb, }; var Session = { cb: cb, };
config.onReady = function (info) { config.onReady = function (info) {
var realtime = Session.session = info.realtime; var realtime = Session.session = info.realtime;
Session.network = info.network; Session.network = info.network;

@ -246,8 +246,8 @@ define([
}); });
}; };
common.getFileSize = function (href, cb) { common.getFileSize = function (href, password, cb) {
postMessage("GET_FILE_SIZE", {href: href}, function (obj) { postMessage("GET_FILE_SIZE", {href: href, password: password}, function (obj) {
if (obj && obj.error) { return void cb(obj.error); } if (obj && obj.error) { return void cb(obj.error); }
cb(undefined, obj.size); cb(undefined, obj.size);
}); });
@ -260,8 +260,8 @@ define([
}); });
}; };
common.isNewChannel = function (href, cb) { common.isNewChannel = function (href, password, cb) {
postMessage('IS_NEW_CHANNEL', {href: href}, function (obj) { postMessage('IS_NEW_CHANNEL', {href: href, password: password}, function (obj) {
if (obj.error) { return void cb(obj.error); } if (obj.error) { return void cb(obj.error); }
if (!obj) { return void cb('INVALID_RESPONSE'); } if (!obj) { return void cb('INVALID_RESPONSE'); }
cb(undefined, obj.isNew); cb(undefined, obj.isNew);
@ -395,8 +395,10 @@ define([
common.saveAsTemplate = function (Cryptput, data, cb) { common.saveAsTemplate = function (Cryptput, data, cb) {
var p = Hash.parsePadUrl(window.location.href); var p = Hash.parsePadUrl(window.location.href);
if (!p.type) { return; } if (!p.type) { return; }
var hash = Hash.createRandomHash(); // PPP: password for the new template?
var hash = Hash.createRandomHash(p.type);
var href = '/' + p.type + '/#' + hash; var href = '/' + p.type + '/#' + hash;
// PPP: add password as cryptput option
Cryptput(hash, data.toSave, function (e) { Cryptput(hash, data.toSave, function (e) {
if (e) { throw new Error(e); } if (e) { throw new Error(e); }
postMessage("ADD_PAD", { postMessage("ADD_PAD", {
@ -419,15 +421,33 @@ define([
}); });
}; };
common.useTemplate = function (href, Crypt, cb, opts) { common.useTemplate = function (href, Crypt, cb, optsPut) {
// opts is used to overrides options for chainpad-netflux in cryptput // opts is used to overrides options for chainpad-netflux in cryptput
// it allows us to add owners and expiration time if it is a new file // it allows us to add owners and expiration time if it is a new file
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
var parsed2 = Hash.parsePadUrl(window.location.href);
if(!parsed) { throw new Error("Cannot get template hash"); } if(!parsed) { throw new Error("Cannot get template hash"); }
Crypt.get(parsed.hash, function (err, val) { postMessage("INCREMENT_TEMPLATE_USE", href);
if (err) { throw new Error(err); }
var p = Hash.parsePadUrl(window.location.href); optsPut = optsPut || {};
Crypt.put(p.hash, val, cb, opts); var optsGet = {};
Nthen(function (waitFor) {
if (parsed.hashData && parsed.hashData.password) {
common.getPadAttribute('password', waitFor(function (err, password) {
optsGet.password = password;
}), href);
}
if (parsed2.hashData && parsed2.hashData.password) {
common.getPadAttribute('password', waitFor(function (err, password) {
optsPut.password = password;
}));
}
}).nThen(function () {
Crypt.get(parsed.hash, function (err, val) {
if (err) { throw new Error(err); }
Crypt.put(parsed2.hash, val, cb, optsPut);
}, optsGet);
}); });
}; };
@ -438,20 +458,18 @@ define([
}; };
// When opening a new pad or renaming it, store the new title // When opening a new pad or renaming it, store the new title
common.setPadTitle = function (title, padHref, path, cb) { common.setPadTitle = function (data, cb) {
var href = padHref || window.location.href; if (!data || typeof (data) !== "object") { return cb ('Data is not an object'); }
var href = data.href || window.location.href;
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
if (!parsed.hash) { return; } if (!parsed.hash) { return cb ('Invalid hash'); }
href = parsed.getUrl({present: parsed.present}); data.href = parsed.getUrl({present: parsed.present});
if (title === null) { return; } if (typeof (data.title) !== "string") { return cb('Missing title'); }
if (title.trim() === "") { title = Hash.getDefaultName(parsed); } if (data.title.trim() === "") { data.title = Hash.getDefaultName(parsed); }
postMessage("SET_PAD_TITLE", { postMessage("SET_PAD_TITLE", data, function (obj) {
href: href,
title: title,
path: path
}, function (obj) {
if (obj && obj.error) { if (obj && obj.error) {
console.log("unable to set pad title"); console.log("unable to set pad title");
return void cb(obj.error); return void cb(obj.error);
@ -472,10 +490,6 @@ define([
cb(void 0, data); cb(void 0, data);
}); });
}; };
// Set initial path when creating a pad from pad creation screen
common.setInitialPath = function (path) {
postMessage("SET_INITIAL_PATH", path);
};
// Messaging (manage friends from the userlist) // Messaging (manage friends from the userlist)
common.inviteFromUserlist = function (netfluxId, cb) { common.inviteFromUserlist = function (netfluxId, cb) {
@ -548,6 +562,10 @@ define([
pad.onDisconnectEvent = Util.mkEvent(); pad.onDisconnectEvent = Util.mkEvent();
pad.onErrorEvent = Util.mkEvent(); pad.onErrorEvent = Util.mkEvent();
// Loading events
common.loading = {};
common.loading.onDriveEvent = Util.mkEvent();
common.getFullHistory = function (data, cb) { common.getFullHistory = function (data, cb) {
postMessage("GET_FULL_HISTORY", data, cb); postMessage("GET_FULL_HISTORY", data, cb);
}; };
@ -555,15 +573,15 @@ define([
common.getShareHashes = function (secret, cb) { common.getShareHashes = function (secret, cb) {
var hashes; var hashes;
if (!window.location.hash) { if (!window.location.hash) {
hashes = Hash.getHashes(secret.channel, secret); hashes = Hash.getHashes(secret);
return void cb(null, hashes); return void cb(null, hashes);
} }
var parsed = Hash.parsePadUrl(window.location.href); var parsed = Hash.parsePadUrl(window.location.href);
if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); } if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); }
if (parsed.type === 'file') { secret.channel = Util.base64ToHex(secret.channel); } if (parsed.type === 'file') { secret.channel = Util.base64ToHex(secret.channel); }
hashes = Hash.getHashes(secret.channel, secret); hashes = Hash.getHashes(secret);
if (!hashes.editHash && !hashes.viewHash && parsed.hashData && !parsed.hashData.mode) { if (secret.version === 0) {
// It means we're using an old hash // It means we're using an old hash
hashes.editHash = window.location.hash.slice(1); hashes.editHash = window.location.hash.slice(1);
return void cb(null, hashes); return void cb(null, hashes);
@ -575,7 +593,8 @@ define([
} }
postMessage("GET_STRONGER_HASH", { postMessage("GET_STRONGER_HASH", {
href: window.location.href href: window.location.href,
password: secret.password
}, function (hash) { }, function (hash) {
if (hash) { hashes.editHash = hash; } if (hash) { hashes.editHash = hash; }
cb(null, hashes); cb(null, hashes);
@ -622,6 +641,12 @@ define([
window.location.href = '/login/'; window.location.href = '/login/';
}; };
common.startAccountDeletion = function (cb) {
// Logout other tabs
LocalStore.logout(null, true);
cb();
};
var onMessage = function (cmd, data, cb) { var onMessage = function (cmd, data, cb) {
cb = cb || function () {}; cb = cb || function () {};
switch (cmd) { switch (cmd) {
@ -701,6 +726,14 @@ define([
case 'DRIVE_REMOVE': { case 'DRIVE_REMOVE': {
common.drive.onRemove.fire(data); break; 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;
}
} }
}; };
@ -779,11 +812,11 @@ define([
if (data.anonHash && !cfg.userHash) { LocalStore.setFSHash(data.anonHash); } if (data.anonHash && !cfg.userHash) { LocalStore.setFSHash(data.anonHash); }
if (cfg.userHash && sessionStorage) { /*if (cfg.userHash && sessionStorage) {
// copy User_hash into sessionStorage because cross-domain iframes // copy User_hash into sessionStorage because cross-domain iframes
// on safari replaces localStorage with sessionStorage or something // on safari replaces localStorage with sessionStorage or something
sessionStorage.setItem(Constants.userHashKey, cfg.userHash); sessionStorage.setItem(Constants.userHashKey, cfg.userHash);
} }*/
if (cfg.userHash) { if (cfg.userHash) {
var localToken = tryParsing(localStorage.getItem(Constants.tokenKey)); var localToken = tryParsing(localStorage.getItem(Constants.tokenKey));
@ -802,18 +835,18 @@ define([
window.onhashchange = function (ev) { window.onhashchange = function (ev) {
if (ev && ev.reset) { oldHref = document.location.href; return; } if (ev && ev.reset) { oldHref = document.location.href; return; }
var newHref = document.location.href; var newHref = document.location.href;
var parsedOld = Hash.parsePadUrl(oldHref).hashData;
var parsedNew = Hash.parsePadUrl(newHref).hashData; // Compare the URLs without /embed and /present
if (parsedOld && parsedNew && ( var parsedOld = Hash.parsePadUrl(oldHref);
parsedOld.type !== parsedNew.type var parsedNew = Hash.parsePadUrl(newHref);
|| parsedOld.channel !== parsedNew.channel if (parsedOld.hashData && parsedNew.hashData &&
|| parsedOld.mode !== parsedNew.mode parsedOld.getUrl() !== parsedNew.getUrl()) {
|| parsedOld.key !== parsedNew.key)) { if (!parsedOld.hashData.key) { oldHref = newHref; return; }
if (!parsedOld.channel) { oldHref = newHref; return; } // If different, reload
document.location.reload(); document.location.reload();
return; return;
} }
if (parsedNew) { oldHref = newHref; } if (parsedNew.hashData) { oldHref = newHref; }
}; };
// Listen for login/logout in other tabs // Listen for login/logout in other tabs
window.addEventListener('storage', function (e) { window.addEventListener('storage', function (e) {

@ -41,6 +41,7 @@ define([
}; };
renderer.image = function (href, title, text) { renderer.image = function (href, title, text) {
if (href.slice(0,6) === '/file/') { if (href.slice(0,6) === '/file/') {
// PASSWORD_FILES
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,94 +1,3 @@
define([], function () { require(['/customize/loading.js'], function (Loading) {
var loadingStyle = (function(){/* Loading();
#cp-loading {
transition: opacity 0.75s, visibility 0s 0.75s;
visibility: visible;
position: fixed;
z-index: 10000000;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
background: #222;
color: #fafafa;
text-align: center;
font-size: 1.5em;
opacity: 1;
}
#cp-loading.cp-loading-hidden {
opacity: 0;
visibility: hidden;
}
#cp-loading .cp-loading-container {
margin-top: 50vh;
transform: translateY(-50%);
}
#cp-loading .cp-loading-cryptofist {
margin-left: auto;
margin-right: auto;
height: 300px;
margin-bottom: 2em;
}
@media screen and (max-height: 450px) {
#cp-loading .cp-loading-cryptofist {
display: none;
}
}
#cp-loading .cp-loading-spinner-container {
position: relative;
height: 100px;
}
#cp-loading .cp-loading-spinner-container > div {
height: 100px;
}
#cp-loading-tip {
position: fixed;
z-index: 10000000;
top: 80%;
left: 0;
right: 0;
text-align: center;
transition: opacity 750ms;
transition-delay: 3000ms;
}
@media screen and (max-height: 600px) {
#cp-loading-tip {
display: none;
}
}
#cp-loading-tip span {
background: #222;
color: #fafafa;
text-align: center;
font-size: 1.5em;
opacity: 0.7;
font-family: 'Open Sans', 'Helvetica Neue', sans-serif;
padding: 15px;
max-width: 60%;
display: inline-block;
}
*/}).toString().slice(14, -3);
var urlArgs = window.location.href.replace(/^.*\?([^\?]*)$/, function (all, x) { return x; });
var elem = document.createElement('div');
elem.setAttribute('id', 'cp-loading');
elem.innerHTML = [
'<style>',
loadingStyle,
'</style>',
'<div class="cp-loading-container">',
'<img class="cp-loading-cryptofist" src="/customize/cryptpad-new-logo-colors-logoonly.png?' + urlArgs + '">',
'<div class="cp-loading-spinner-container">',
'<span class="fa fa-circle-o-notch fa-spin fa-4x fa-fw"></span>',
'</div>',
'<p id="cp-loading-message"></p>',
'</div>'
].join('');
var intr;
var append = function () {
if (!document.body) { return; }
clearInterval(intr);
document.body.appendChild(elem);
};
intr = setInterval(append, 100);
append();
}); });

@ -122,21 +122,15 @@ define([
// Do not migrate a pad if we already have it, it would create a duplicate in the drive // Do not migrate a pad if we already have it, it would create a duplicate in the drive
if (newHrefs.indexOf(href) !== -1) { return; } if (newHrefs.indexOf(href) !== -1) { return; }
// If we have a stronger version, do not add the current href // If we have a stronger version, do not add the current href
if (Hash.findStronger(href, newRecentPads)) { return; } if (Hash.findStronger(href, oldRecentPads[id].channel, newRecentPads)) { return; }
// If we have a weaker version, replace the href by the new one // If we have a weaker version, replace the href by the new one
// NOTE: if that weaker version is in the trash, the strong one will be put in unsorted // NOTE: if that weaker version is in the trash, the strong one will be put in unsorted
var weaker = Hash.findWeaker(href, newRecentPads); var weaker = Hash.findWeaker(href, oldRecentPads[id].channel, newRecentPads);
if (weaker) { if (weaker) {
// Update RECENTPADS // Update RECENTPADS
newRecentPads.some(function (pad) { weaker.href = href;
if (pad.href === weaker) {
pad.href = href;
return true;
}
return;
});
// Update the file in the drive // Update the file in the drive
newFo.replace(weaker, href); newFo.replace(weaker.href, href);
return; return;
} }
// Here it means we have a new href, so we should add it to the drive at its old location // Here it means we have a new href, so we should add it to the drive at its old location

@ -34,6 +34,10 @@ define(['json.sortify'], function (Sortify) {
} }
if (!metadataObj.users) { metadataObj.users = {}; } if (!metadataObj.users) { metadataObj.users = {}; }
if (!metadataLazyObj.users) { metadataLazyObj.users = {}; } if (!metadataLazyObj.users) { metadataLazyObj.users = {}; }
if (!metadataObj.type) { metadataObj.type = meta.doc.type; }
if (!metadataLazyObj.type) { metadataLazyObj.type = meta.doc.type; }
var mdo = {}; var mdo = {};
// We don't want to add our user data to the object multiple times. // We don't want to add our user data to the object multiple times.
//var containsYou = false; //var containsYou = false;

@ -1,106 +1,152 @@
define(['/common/common-feedback.js'], function (Feedback) { define([
'/common/common-feedback.js',
'/common/common-hash.js',
'/common/common-util.js',
'/bower_components/nthen/index.js',
], function (Feedback, Hash, Util, nThen) {
// Start migration check // Start migration check
// Versions: // Versions:
// 1: migrate pad attributes // 1: migrate pad attributes
// 2: migrate indent settings (codemirror) // 2: migrate indent settings (codemirror)
return function (userObject) { return function (userObject, cb, progress) {
var version = userObject.version || 0; var version = userObject.version || 0;
// DEPRECATED nThen(function () {
// Migration 1: pad attributes moved to filesData // DEPRECATED
var migratePadAttributesToData = function () { // Migration 1: pad attributes moved to filesData
return true; var migratePadAttributesToData = function () {
}; return true;
if (version < 1) { };
migratePadAttributesToData(); if (version < 1) {
} migratePadAttributesToData();
// Migration 2: global attributes from root to 'settings' subobjects
var migrateAttributes = function () {
var drawer = 'cryptpad.userlist-drawer';
var polls = 'cryptpad.hide_poll_text';
var indentKey = 'cryptpad.indentUnit';
var useTabsKey = 'cryptpad.indentWithTabs';
var settings = userObject.settings = userObject.settings || {};
if (typeof(userObject[indentKey]) !== "undefined") {
settings.codemirror = settings.codemirror || {};
settings.codemirror.indentUnit = userObject[indentKey];
delete userObject[indentKey];
}
if (typeof(userObject[useTabsKey]) !== "undefined") {
settings.codemirror = settings.codemirror || {};
settings.codemirror.indentWithTabs = userObject[useTabsKey];
delete userObject[useTabsKey];
} }
if (typeof(userObject[drawer]) !== "undefined") { }).nThen(function () {
settings.toolbar = settings.toolbar || {}; // Migration 2: global attributes from root to 'settings' subobjects
settings.toolbar['userlist-drawer'] = userObject[drawer]; var migrateAttributes = function () {
delete userObject[drawer]; var drawer = 'cryptpad.userlist-drawer';
var polls = 'cryptpad.hide_poll_text';
var indentKey = 'cryptpad.indentUnit';
var useTabsKey = 'cryptpad.indentWithTabs';
var settings = userObject.settings = userObject.settings || {};
if (typeof(userObject[indentKey]) !== "undefined") {
settings.codemirror = settings.codemirror || {};
settings.codemirror.indentUnit = userObject[indentKey];
delete userObject[indentKey];
}
if (typeof(userObject[useTabsKey]) !== "undefined") {
settings.codemirror = settings.codemirror || {};
settings.codemirror.indentWithTabs = userObject[useTabsKey];
delete userObject[useTabsKey];
}
if (typeof(userObject[drawer]) !== "undefined") {
settings.toolbar = settings.toolbar || {};
settings.toolbar['userlist-drawer'] = userObject[drawer];
delete userObject[drawer];
}
if (typeof(userObject[polls]) !== "undefined") {
settings.poll = settings.poll || {};
settings.poll['hide-text'] = userObject[polls];
delete userObject[polls];
}
};
if (version < 2) {
migrateAttributes();
Feedback.send('Migrate-2', true);
userObject.version = version = 2;
} }
if (typeof(userObject[polls]) !== "undefined") { }).nThen(function () {
settings.poll = settings.poll || {}; // Migration 3: language from localStorage to settings
settings.poll['hide-text'] = userObject[polls]; var migrateLanguage = function () {
delete userObject[polls]; if (!localStorage.CRYPTPAD_LANG) { return; }
var l = localStorage.CRYPTPAD_LANG;
userObject.settings.language = l;
};
if (version < 3) {
migrateLanguage();
Feedback.send('Migrate-3', true);
userObject.version = version = 3;
} }
}; }).nThen(function () {
if (version < 2) { // Migration 4: allowUserFeedback to settings
migrateAttributes(); var migrateFeedback = function () {
Feedback.send('Migrate-2', true); var settings = userObject.settings = userObject.settings || {};
userObject.version = version = 2; if (typeof(userObject['allowUserFeedback']) !== "undefined") {
} settings.general = settings.general || {};
settings.general.allowUserFeedback = userObject['allowUserFeedback'];
delete userObject['allowUserFeedback'];
}
// Migration 3: language from localStorage to settings };
var migrateLanguage = function () { if (version < 4) {
if (!localStorage.CRYPTPAD_LANG) { return; } migrateFeedback();
var l = localStorage.CRYPTPAD_LANG; Feedback.send('Migrate-4', true);
userObject.settings.language = l; userObject.version = version = 4;
};
if (version < 3) {
migrateLanguage();
Feedback.send('Migrate-3', true);
userObject.version = version = 3;
}
// Migration 4: allowUserFeedback to settings
var migrateFeedback = function () {
var settings = userObject.settings = userObject.settings || {};
if (typeof(userObject['allowUserFeedback']) !== "undefined") {
settings.general = settings.general || {};
settings.general.allowUserFeedback = userObject['allowUserFeedback'];
delete userObject['allowUserFeedback'];
} }
}; }).nThen(function () {
if (version < 4) { // Migration 5: dates to Number
migrateFeedback(); var migrateDates = function () {
Feedback.send('Migrate-4', true); var data = userObject.drive && userObject.drive.filesData;
userObject.version = version = 4; if (data) {
} for (var id in data) {
if (typeof data[id].ctime !== "number") {
data[id].ctime = +new Date(data[id].ctime);
}
// Migration 5: dates to Number if (typeof data[id].atime !== "number") {
var migrateDates = function () { data[id].atime = +new Date(data[id].atime);
var data = userObject.drive && userObject.drive.filesData; }
if (data) {
for (var id in data) {
if (typeof data[id].ctime !== "number") {
data[id].ctime = +new Date(data[id].ctime);
}
if (typeof data[id].atime !== "number") {
data[id].atime = +new Date(data[id].atime);
} }
} }
};
if (version < 5) {
migrateDates();
Feedback.send('Migrate-5', true);
userObject.version = version = 5;
}
}).nThen(function (waitFor) {
var addChannelId = function () {
var data = userObject.drive.filesData;
var el, parsed;
var n = nThen(function () {});
var padsLength = Object.keys(data).length;
Object.keys(data).forEach(function (k, i) {
n = n.nThen(function (w) {
setTimeout(w(function () {
el = data[k];
parsed = Hash.parsePadUrl(el.href);
if (!el.href) { return; }
if (!el.channel) {
if (parsed.hashData && parsed.hashData.type === "file") {
// PASSWORD_FILES
el.channel = Util.base64ToHex(parsed.hashData.channel);
} else {
var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.channel = secret.channel;
}
progress(6, Math.round(100*i/padsLength));
console.log('Adding missing channel in filesData ', el.channel);
}
}));
});
});
n.nThen(waitFor());
};
if (version < 6) {
addChannelId();
Feedback.send('Migrate-6', true);
userObject.version = version = 6;
} }
}; /*}).nThen(function (waitFor) {
if (version < 5) { // Test progress bar in the loading screen
migrateDates(); var i = 0;
Feedback.send('Migrate-5', true); var w = waitFor();
userObject.version = version = 5; var it = setInterval(function () {
} i += 5;
if (i >= 100) { w(); clearInterval(it); i = 100;}
progress(0, i);
}, 500);
progress(0, 0);*/
}).nThen(function () {
cb();
});
}; };
}); });

@ -16,9 +16,11 @@ define([
'/bower_components/chainpad-crypto/crypto.js?v=0.1.5', '/bower_components/chainpad-crypto/crypto.js?v=0.1.5',
'/bower_components/chainpad/chainpad.dist.js', '/bower_components/chainpad/chainpad.dist.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js', '/bower_components/chainpad-listmap/chainpad-listmap.js',
'/bower_components/nthen/index.js',
'/bower_components/saferphore/index.js',
], function (Sortify, UserObject, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger, ], function (Sortify, UserObject, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger,
CpNfWorker, NetConfig, AppConfig, CpNfWorker, NetConfig, AppConfig,
Crypto, ChainPad, Listmap) { Crypto, ChainPad, Listmap, nThen, Saferphore) {
var Store = {}; var Store = {};
var postMessage = function () {}; var postMessage = function () {};
@ -66,8 +68,9 @@ define([
var userHash = storeHash; var userHash = storeHash;
if (!userHash) { return null; } if (!userHash) { return null; }
var userParsedHash = Hash.parseTypeHash('drive', userHash); // No password for drive
var userChannel = userParsedHash && userParsedHash.channel; var secret = Hash.getSecrets('drive', userHash);
var userChannel = secret.channel;
if (!userChannel) { return null; } if (!userChannel) { return null; }
// Get the list of pads' channel ID in your drive // Get the list of pads' channel ID in your drive
@ -79,16 +82,16 @@ define([
var d = store.userObject.getFileData(id); var d = store.userObject.getFileData(id);
if (d.owners && d.owners.length && edPublic && if (d.owners && d.owners.length && edPublic &&
d.owners.indexOf(edPublic) === -1) { return; } d.owners.indexOf(edPublic) === -1) { return; }
return Hash.hrefToHexChannelId(d.href); return d.channel;
}) })
.filter(function (x) { return x; }); .filter(function (x) { return x; });
// Get the avatar // Get the avatar
var profile = store.proxy.profile; var profile = store.proxy.profile;
if (profile) { if (profile) {
var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit) : null; var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null;
if (profileChan) { list.push(profileChan); } if (profileChan) { list.push(profileChan); }
var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar) : null; var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar, null) : null;
if (avatarChan) { list.push(avatarChan); } if (avatarChan) { list.push(avatarChan); }
} }
@ -97,7 +100,7 @@ define([
list = list.concat(fList); list = list.concat(fList);
} }
list.push(Util.base64ToHex(userChannel)); list.push(userChannel);
list.sort(); list.sort();
return list; return list;
@ -113,7 +116,7 @@ define([
// because of the expiration time // because of the expiration time
if ((data.owners && data.owners.length && data.owners.indexOf(edPublic) === -1) || if ((data.owners && data.owners.length && data.owners.indexOf(edPublic) === -1) ||
(data.expire && data.expire < (+new Date()))) { (data.expire && data.expire < (+new Date()))) {
list.push(Hash.hrefToHexChannelId(data.href)); list.push(data.channel);
} }
}); });
return list; return list;
@ -301,7 +304,7 @@ define([
Store.getFileSize = function (data, cb) { Store.getFileSize = function (data, cb) {
if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
var channelId = Hash.hrefToHexChannelId(data.href); var channelId = Hash.hrefToHexChannelId(data.href, data.password);
store.anon_rpc.send("GET_FILE_SIZE", channelId, function (e, response) { store.anon_rpc.send("GET_FILE_SIZE", channelId, function (e, response) {
if (e) { return void cb({error: e}); } if (e) { return void cb({error: e}); }
if (response && response.length && typeof(response[0]) === 'number') { if (response && response.length && typeof(response[0]) === 'number') {
@ -314,7 +317,7 @@ define([
Store.isNewChannel = function (data, cb) { Store.isNewChannel = function (data, cb) {
if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
var channelId = Hash.hrefToHexChannelId(data.href); var channelId = Hash.hrefToHexChannelId(data.href, data.password);
store.anon_rpc.send("IS_NEW_CHANNEL", channelId, function (e, response) { store.anon_rpc.send("IS_NEW_CHANNEL", channelId, function (e, response) {
if (e) { return void cb({error: e}); } if (e) { return void cb({error: e}); }
if (response && response.length && typeof(response[0]) === 'boolean') { if (response && response.length && typeof(response[0]) === 'boolean') {
@ -378,6 +381,7 @@ define([
// Get the metadata for sframe-common-outer // Get the metadata for sframe-common-outer
Store.getMetadata = function (data, cb) { Store.getMetadata = function (data, cb) {
var disableThumbnails = Util.find(store.proxy, ['settings', 'general', 'disableThumbnails']);
var metadata = { var metadata = {
// "user" is shared with everybody via the userlist // "user" is shared with everybody via the userlist
user: { user: {
@ -392,7 +396,7 @@ define([
edPublic: store.proxy.edPublic, edPublic: store.proxy.edPublic,
friends: store.proxy.friends || {}, friends: store.proxy.friends || {},
settings: store.proxy.settings, settings: store.proxy.settings,
thumbnails: !Util.find(store.proxy, ['settings', 'general', 'disableThumbnails']) thumbnails: disableThumbnails === false
} }
}; };
cb(JSON.parse(JSON.stringify(metadata))); cb(JSON.parse(JSON.stringify(metadata)));
@ -413,6 +417,8 @@ define([
var pad = makePad(data.href, data.title); var pad = makePad(data.href, data.title);
if (data.owners) { pad.owners = data.owners; } if (data.owners) { pad.owners = data.owners; }
if (data.expire) { pad.expire = data.expire; } 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) { store.userObject.pushData(pad, function (e, id) {
if (e) { return void cb({error: "Error while adding a template:"+ e}); } if (e) { return void cb({error: "Error while adding a template:"+ e}); }
var path = data.path || ['root']; var path = data.path || ['root'];
@ -421,20 +427,98 @@ define([
}); });
}; };
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(c, give(function (obj) {
if (obj && obj.error) { console.error(obj.error); }
w();
}));
});
});
};
Store.deleteAccount = function (data, cb) { Store.deleteAccount = function (data, cb) {
var toSign = { var edPublic = store.proxy.edPublic;
intent: 'Please delete my account.' // No password for drive
};
var secret = Hash.getSecrets('drive', storeHash); var secret = Hash.getSecrets('drive', storeHash);
toSign.drive = secret.channel; Store.anonRpcMsg({
toSign.edPublic = store.proxy.edPublic; msg: 'GET_METADATA',
var signKey = Crypto.Nacl.util.decodeBase64(secret.keys.signKey); data: secret.channel
console.log(Sortify(toSign)); }, function (data) {
var proof = Crypto.Nacl.sign.detached(Crypto.Nacl.util.decodeUTF8(Sortify(toSign)), signKey); var metadata = data[0];
var proofTxt = Crypto.Nacl.util.encodeBase64(proof); // Owned drive
cb({ if (metadata && metadata.owners && metadata.owners.length === 1 &&
proof: proofTxt, metadata.owners.indexOf(edPublic) !== -1) {
toSign: JSON.parse(Sortify(toSign)) 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); }
}));
}).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);
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'); }
var proofTxt = Crypto.Nacl.util.encodeBase64(proof);
cb({
proof: proofTxt,
toSign: JSON.parse(Sortify(toSign))
});
}); });
}; };
@ -446,25 +530,19 @@ define([
*/ */
Store.createReadme = function (data, cb) { Store.createReadme = function (data, cb) {
require(['/common/cryptget.js'], function (Crypt) { require(['/common/cryptget.js'], function (Crypt) {
var hash = Hash.createRandomHash(); var hash = Hash.createRandomHash('pad');
Crypt.put(hash, data.driveReadme, function (e) { Crypt.put(hash, data.driveReadme, function (e) {
if (e) { if (e) {
return void cb({ error: "Error while creating the default pad:"+ e}); return void cb({ error: "Error while creating the default pad:"+ e});
} }
var href = '/pad/#' + hash; var href = '/pad/#' + hash;
var channel = Hash.hrefToHexChannelId(href, null);
var fileData = { var fileData = {
href: href, href: href,
channel: channel,
title: data.driveReadmeTitle, title: data.driveReadmeTitle,
atime: +new Date(),
ctime: +new Date()
}; };
store.userObject.pushData(fileData, function (e, id) { Store.addPad(fileData, cb);
if (e) {
return void cb({ error: "Error while creating the default pad:"+ e});
}
store.userObject.add(id);
onSync(cb);
});
}); });
}); });
}; };
@ -515,8 +593,12 @@ define([
// Reset the drive part of the userObject (from settings) // Reset the drive part of the userObject (from settings)
Store.resetDrive = function (data, cb) { Store.resetDrive = function (data, cb) {
store.proxy.drive = store.fo.getStructure(); nThen(function (waitFor) {
onSync(cb); removeOwnedPads(waitFor);
}).nThen(function () {
store.proxy.drive = store.fo.getStructure();
onSync(cb);
});
}; };
/** /**
@ -554,18 +636,7 @@ define([
// Tags // Tags
Store.listAllTags = function (data, cb) { Store.listAllTags = function (data, cb) {
var all = []; cb(store.userObject.getTagsList());
var files = Util.find(store.proxy, ['drive', 'filesData']);
if (typeof(files) !== 'object') { return cb({error: 'invalid_drive'}); }
Object.keys(files).forEach(function (k) {
var file = files[k];
if (!Array.isArray(file.tags)) { return; }
file.tags.forEach(function (tag) {
if (all.indexOf(tag) === -1) { all.push(tag); }
});
});
cb(all);
}; };
// Templates // Templates
@ -578,6 +649,15 @@ define([
}); });
cb(res); 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);
});
};
// Pads // Pads
Store.moveToTrash = function (data, cb) { Store.moveToTrash = function (data, cb) {
@ -588,17 +668,18 @@ define([
Store.setPadTitle = function (data, cb) { Store.setPadTitle = function (data, cb) {
var title = data.title; var title = data.title;
var href = data.href; var href = data.href;
var channel = data.channel;
var p = Hash.parsePadUrl(href); var p = Hash.parsePadUrl(href);
var h = p.hashData; var h = p.hashData;
if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); } if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); }
var owners; var owners;
if (Store.channel && Store.channel.wc && Util.base64ToHex(h.channel) === Store.channel.wc.id) { if (Store.channel && Store.channel.wc && channel === Store.channel.wc.id) {
owners = Store.channel.data.owners || undefined; owners = Store.channel.data.owners || undefined;
} }
var expire; var expire;
if (Store.channel && Store.channel.wc && Util.base64ToHex(h.channel) === Store.channel.wc.id) { if (Store.channel && Store.channel.wc && channel === Store.channel.wc.id) {
expire = +Store.channel.data.expire || undefined; expire = +Store.channel.data.expire || undefined;
} }
@ -621,13 +702,13 @@ define([
// Different types, proceed to the next one // Different types, proceed to the next one
// No hash data: corrupted pad? // No hash data: corrupted pad?
if (p.type !== p2.type || !h2) { continue; } if (p.type !== p2.type || !h2) { continue; }
// Different channel: continue
if (pad.channel !== channel) { continue; }
var shouldUpdate = p.hash.replace(/\/$/, '') === p2.hash.replace(/\/$/, ''); var shouldUpdate = p.hash.replace(/\/$/, '') === p2.hash.replace(/\/$/, '');
// If the hash is different but represents the same channel, check if weaker or stronger // If the hash is different but represents the same channel, check if weaker or stronger
if (!shouldUpdate && if (!shouldUpdate && h.version !== 0) {
h.version === 1 && h2.version === 1 &&
h.channel === h2.channel) {
// We had view & now we have edit, update // We had view & now we have edit, update
if (h2.mode === 'view' && h.mode === 'edit') { shouldUpdate = true; } if (h2.mode === 'view' && h.mode === 'edit') { shouldUpdate = true; }
// Same mode and we had present URL, update // Same mode and we had present URL, update
@ -664,10 +745,12 @@ define([
if (!contains) { if (!contains) {
Store.addPad({ Store.addPad({
href: href, href: href,
channel: channel,
title: title, title: title,
owners: owners, owners: owners,
expire: expire, expire: expire,
path: data.path || (store.data && store.data.initialPath) password: data.password,
path: data.path
}, cb); }, cb);
return; return;
} }
@ -707,10 +790,7 @@ define([
Store.getPadData = function (id, cb) { Store.getPadData = function (id, cb) {
cb(store.userObject.getFileData(id)); cb(store.userObject.getFileData(id));
}; };
Store.setInitialPath = function (path) {
if (!store.data) { return; }
store.data.initialPath = path;
};
// Messaging (manage friends from the userlist) // Messaging (manage friends from the userlist)
var getMessagingCfg = function () { var getMessagingCfg = function () {
@ -742,9 +822,9 @@ define([
var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {}; var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {};
// If we have a stronger version in drive, add it and add a redirect button // If we have a stronger version in drive, add it and add a redirect button
var stronger = Hash.findStronger(data.href, allPads); var stronger = Hash.findStronger(data.href, data.channel, allPads);
if (stronger) { if (stronger) {
var parsed2 = Hash.parsePadUrl(stronger); var parsed2 = Hash.parsePadUrl(stronger.href);
return void cb(parsed2.hash); return void cb(parsed2.hash);
} }
cb(); cb();
@ -836,8 +916,12 @@ define([
channel.data = padData || {}; channel.data = padData || {};
postMessage("PAD_READY"); postMessage("PAD_READY");
}, // post EV_PAD_READY }, // post EV_PAD_READY
onMessage: function (m) { onMessage: function (user, m, validateKey) {
postMessage("PAD_MESSAGE", m); postMessage("PAD_MESSAGE", {
user: user,
msg: m,
validateKey: validateKey
});
}, // post EV_PAD_MESSAGE }, // post EV_PAD_MESSAGE
onJoin: function (m) { onJoin: function (m) {
postMessage("PAD_JOIN", m); postMessage("PAD_JOIN", m);
@ -906,7 +990,7 @@ define([
if (parsed[1][3] !== data.channel) { return; } if (parsed[1][3] !== data.channel) { return; }
msg = parsed[1][4]; msg = parsed[1][4];
if (msg) { if (msg) {
msg = msg.replace(/^cp\|/, ''); msg = msg.replace(/cp\|(([A-Za-z0-9+\/=]+)\|)?/, '');
//var decryptedMsg = crypto.decrypt(msg, true); //var decryptedMsg = crypto.decrypt(msg, true);
msgs.push(msg); msgs.push(msg);
} }
@ -956,11 +1040,24 @@ define([
postMessage("DRIVE_LOG", msg); postMessage("DRIVE_LOG", msg);
} }
}); });
var todo = function () { 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(); userObject.fixFiles();
Migrate(proxy);
var requestLogin = function () { var requestLogin = function () {
postMessage("REQUEST_LOGIN"); postMessage("REQUEST_LOGIN");
}; };
@ -1023,16 +1120,16 @@ define([
proxy.on('change', [Constants.tokenKey], function () { proxy.on('change', [Constants.tokenKey], function () {
postMessage("UPDATE_TOKEN", { token: proxy[Constants.tokenKey] }); postMessage("UPDATE_TOKEN", { token: proxy[Constants.tokenKey] });
}); });
}; });
userObject.migrate(todo);
}; };
var connect = function (data, cb) { var connect = function (data, cb) {
var hash = data.userHash || data.anonHash || Hash.createRandomHash(); var hash = data.userHash || data.anonHash || Hash.createRandomHash('drive');
storeHash = hash; storeHash = hash;
if (!hash) { if (!hash) {
throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...'); 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 secret = Hash.getSecrets('drive', hash);
var listmapConfig = { var listmapConfig = {
data: {}, data: {},
@ -1055,7 +1152,7 @@ define([
store.realtime = info.realtime; store.realtime = info.realtime;
store.network = info.network; store.network = info.network;
if (!data.userHash) { if (!data.userHash) {
returned.anonHash = Hash.getEditHashFromKeys(info.channel, secret.keys); returned.anonHash = Hash.getEditHashFromKeys(secret);
} }
}).on('ready', function () { }).on('ready', function () {
if (store.userObject) { return; } // the store is already ready, it is a reconnection if (store.userObject) { return; } // the store is already ready, it is a reconnection
@ -1066,6 +1163,7 @@ define([
&& !drive['filesData']) { && !drive['filesData']) {
drive[Constants.oldStorageKey] = []; drive[Constants.oldStorageKey] = [];
} }
postMessage('LOADING_DRIVE', { state: 1 });
// Drive already exist: return the existing drive, don't load data from legacy store // Drive already exist: return the existing drive, don't load data from legacy store
onReady(returned, cb); onReady(returned, cb);
}) })

@ -68,7 +68,7 @@ define([], function () {
// shim between chainpad and netflux // shim between chainpad and netflux
var msgIn = function (peerId, msg) { var msgIn = function (peerId, msg) {
return msg.replace(/^cp\|/, ''); return msg.replace(/^cp\|([A-Za-z0-9+\/=]+\|)?/, '');
}; };
var msgOut = function (msg) { var msgOut = function (msg) {
@ -130,7 +130,7 @@ define([], function () {
message = unBencode(message);//.slice(message.indexOf(':[') + 1); message = unBencode(message);//.slice(message.indexOf(':[') + 1);
// pass the message into Chainpad // pass the message into Chainpad
onMessage(message); onMessage(peer, message, validateKey);
//sframeChan.query('Q_RT_MESSAGE', message, function () { }); //sframeChan.query('Q_RT_MESSAGE', message, function () { });
}; };

@ -92,7 +92,7 @@ define([
}); });
}; };
var logoutHandlers = []; var logoutHandlers = [];
LocalStore.logout = function (cb) { LocalStore.logout = function (cb, isDeletion) {
[ [
Constants.userNameKey, Constants.userNameKey,
Constants.userHashKey, Constants.userHashKey,
@ -108,13 +108,15 @@ define([
// Make sure we have an FS_hash in localStorage before reloading all the tabs // Make sure we have an FS_hash in localStorage before reloading all the tabs
// so that we don't end up with tabs using different anon hashes // so that we don't end up with tabs using different anon hashes
if (!LocalStore.getFSHash()) { if (!LocalStore.getFSHash()) {
LocalStore.setFSHash(Hash.createRandomHash()); LocalStore.setFSHash(Hash.createRandomHash('drive'));
} }
eraseTempSessionValues(); eraseTempSessionValues();
logoutHandlers.forEach(function (h) { if (!isDeletion) {
if (typeof (h) === "function") { h(); } logoutHandlers.forEach(function (h) {
}); if (typeof (h) === "function") { h(); }
});
}
if (typeof(AppConfig.customizeLogout) === 'function') { if (typeof(AppConfig.customizeLogout) === 'function') {
return void AppConfig.customizeLogout(cb); return void AppConfig.customizeLogout(cb);

@ -120,12 +120,12 @@ define([
case 'GET_PAD_DATA': { case 'GET_PAD_DATA': {
Store.getPadData(data, cb); break; Store.getPadData(data, cb); break;
} }
case 'SET_INITIAL_PATH': {
Store.setInitialPath(data); break;
}
case 'GET_STRONGER_HASH': { case 'GET_STRONGER_HASH': {
Store.getStrongerHash(data, cb); break; Store.getStrongerHash(data, cb); break;
} }
case 'INCREMENT_TEMPLATE_USE': {
Store.incrementTemplateUse(data); break;
}
// Messaging // Messaging
case 'INVITE_FROM_USERLIST': { case 'INVITE_FROM_USERLIST': {
Store.inviteFromUserlist(data, cb); break; Store.inviteFromUserlist(data, cb); break;

@ -51,14 +51,28 @@ define([
var b64Key = Nacl.util.encodeBase64(key); var b64Key = Nacl.util.encodeBase64(key);
var hash = Hash.getFileHashFromKeys(id, b64Key); var secret = {
version: 1,
channel: id,
keys: {
fileKeyStr: b64Key
}
};
var hash = Hash.getFileHashFromKeys(secret);
var href = '/file/#' + hash; var href = '/file/#' + hash;
var title = metadata.name; var title = metadata.name;
if (noStore) { return void onComplete(href); } if (noStore) { return void onComplete(href); }
common.setPadTitle(title || "", href, path, function (err) { // PASSWORD_FILES
var data = {
title: title || "",
href: href,
path: path,
channel: id
};
common.setPadTitle(data, function (err) {
if (err) { return void console.error(err); } if (err) { return void console.error(err); }
onComplete(href); onComplete(href);
common.setPadAttribute('fileType', metadata.type, null, href); common.setPadAttribute('fileType', metadata.type, null, href);

@ -75,7 +75,7 @@ define([
return void todo(); return void todo();
} }
if (!pinPads) { return; } if (!pinPads) { return; }
pinPads([Hash.hrefToHexChannelId(data.href)], function (obj) { pinPads([data.channel], function (obj) {
if (obj && obj.error) { return void cb(obj.error); } if (obj && obj.error) { return void cb(obj.error); }
todo(); todo();
}); });
@ -98,7 +98,7 @@ define([
exp.getFiles([FILES_DATA]).forEach(function (id) { exp.getFiles([FILES_DATA]).forEach(function (id) {
if (filesList.indexOf(id) === -1) { if (filesList.indexOf(id) === -1) {
var fd = exp.getFileData(id); var fd = exp.getFileData(id);
var channelId = fd && fd.href && Hash.hrefToHexChannelId(fd.href); var channelId = fd.channel;
// If trying to remove an owned pad, remove it from server also // If trying to remove an owned pad, remove it from server also
if (!isOwnPadRemoved && if (!isOwnPadRemoved &&
fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) { fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) {
@ -552,32 +552,52 @@ define([
for (var id in fd) { for (var id in fd) {
id = Number(id); id = Number(id);
var el = fd[id]; var el = fd[id];
// Clean corrupted data
if (!el || typeof(el) !== "object") { if (!el || typeof(el) !== "object") {
debug("An element in filesData was not an object.", el); debug("An element in filesData was not an object.", el);
toClean.push(id); toClean.push(id);
continue; continue;
} }
// Clean missing href
if (!el.href) { if (!el.href) {
debug("Removing an element in filesData with a missing href.", el); debug("Removing an element in filesData with a missing href.", el);
toClean.push(id); toClean.push(id);
continue; continue;
} }
if (/^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); }
if (!el.ctime) { el.ctime = el.atime; }
var parsed = Hash.parsePadUrl(el.href); var parsed = Hash.parsePadUrl(el.href);
if (!el.title) { el.title = Hash.getDefaultName(parsed); } // Clean invalid hash
if (!parsed.hash) { if (!parsed.hash) {
debug("Removing an element in filesData with a invalid href.", el); debug("Removing an element in filesData with a invalid href.", el);
toClean.push(id); toClean.push(id);
continue; continue;
} }
// Clean invalid type
if (!parsed.type) { if (!parsed.type) {
debug("Removing an element in filesData with a invalid type.", el); debug("Removing an element in filesData with a invalid type.", el);
toClean.push(id); toClean.push(id);
continue; continue;
} }
// Fix href
if (/^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); }
// Fix creation time
if (!el.ctime) { el.ctime = el.atime; }
// Fix title
if (!el.title) { el.title = Hash.getDefaultName(parsed); }
// Fix channel
if (!el.channel) {
if (parsed.hashData && parsed.hashData.type === "file") {
// PASSWORD_FILES
el.channel = Util.base64ToHex(parsed.hashData.channel);
} else {
var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.channel = secret.channel;
}
console.log('Adding missing channel in filesData ', el.channel);
}
if ((loggedIn || config.testMode) && rootFiles.indexOf(id) === -1) { if ((loggedIn || config.testMode) && rootFiles.indexOf(id) === -1) {
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el); debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el);
var newName = Hash.createChannelId(); var newName = Hash.createChannelId();

@ -165,6 +165,17 @@ define([
}); });
}; };
exp.removePins = function (cb) {
rpc.send('REMOVE_PINS', undefined, function (e, response) {
if (e) { return void cb(e); }
if (response && response.length && response[0] === "OK") {
cb();
} else {
cb('INVALID_RESPONSE');
}
});
};
exp.uploadComplete = function (cb) { exp.uploadComplete = function (cb) {
rpc.send('UPLOAD_COMPLETE', null, function (e, res) { rpc.send('UPLOAD_COMPLETE', null, function (e, res) {
if (e) { return void cb(e); } if (e) { return void cb(e); }

@ -17,7 +17,7 @@ define([
'/bower_components/file-saver/FileSaver.min.js', '/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less', 'less!/customize/src/less2/main.less',
], function ( ], function (
$, $,
@ -279,6 +279,8 @@ define([
var newContentStr = cpNfInner.chainpad.getUserDoc(); var newContentStr = cpNfInner.chainpad.getUserDoc();
if (state === STATE.DELETED) { return; } if (state === STATE.DELETED) { return; }
//UI.updateLoadingProgress({ state: -1 }, false);
var newPad = false; var newPad = false;
if (newContentStr === '') { newPad = true; } if (newContentStr === '') { newPad = true; }
@ -315,8 +317,7 @@ define([
privateDat.availableHashes.viewHash; privateDat.availableHashes.viewHash;
var href = privateDat.pathname + '#' + hash; var href = privateDat.pathname + '#' + hash;
if (AppConfig.textAnalyzer && textContentGetter) { if (AppConfig.textAnalyzer && textContentGetter) {
var channelId = Hash.hrefToHexChannelId(href); AppConfig.textAnalyzer(textContentGetter, privateDat.channel);
AppConfig.textAnalyzer(textContentGetter, channelId);
} }
if (options.thumbnail && privateDat.thumbnails) { if (options.thumbnail && privateDat.thumbnails) {
@ -432,6 +433,9 @@ define([
nThen(function (waitFor) { nThen(function (waitFor) {
UI.addLoadingScreen(); UI.addLoadingScreen();
SFCommon.create(waitFor(function (c) { common = c; })); SFCommon.create(waitFor(function (c) { common = c; }));
/*UI.updateLoadingProgress({
state: 1
}, false);*/
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
common.getSframeChannel().onReady(waitFor()); common.getSframeChannel().onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
@ -443,7 +447,7 @@ define([
patchTransformer: options.patchTransformer || ChainPad.SmartJSONTransformer, patchTransformer: options.patchTransformer || ChainPad.SmartJSONTransformer,
// cryptpad debug logging (default is 1) // cryptpad debug logging (default is 1)
// logLevel: 2, logLevel: 1,
validateContent: options.validateContent || function (content) { validateContent: options.validateContent || function (content) {
try { try {
JSON.parse(content); JSON.parse(content);
@ -457,7 +461,12 @@ define([
}, },
onRemote: onRemote, onRemote: onRemote,
onLocal: onLocal, onLocal: onLocal,
onInit: function () { stateChange(STATE.INITIALIZING); }, onInit: function () {
/*UI.updateLoadingProgress({
state: 2
}, false);*/
stateChange(STATE.INITIALIZING);
},
onReady: function () { evStart.reg(onReady); }, onReady: function () { evStart.reg(onReady); },
onConnectionChange: onConnectionChange, onConnectionChange: onConnectionChange,
onError: onError onError: onError
@ -572,6 +581,9 @@ define([
toolbar.$rightside.append($templateButton); toolbar.$rightside.append($templateButton);
} }
var $importTemplateButton = common.createButton('importtemplate', true);
toolbar.$drawer.append($importTemplateButton);
/* add a forget button */ /* add a forget button */
toolbar.$rightside.append(common.createButton('forget', true, {}, function (err) { toolbar.$rightside.append(common.createButton('forget', true, {}, function (err) {
if (err) { return; } if (err) { return; }

@ -41,7 +41,7 @@ define([
var patchTransformer = config.patchTransformer; var patchTransformer = config.patchTransformer;
var validateContent = config.validateContent; var validateContent = config.validateContent;
var avgSyncMilliseconds = config.avgSyncMilliseconds; var avgSyncMilliseconds = config.avgSyncMilliseconds;
var logLevel = typeof(config.logLevel) !== 'undefined'? config.logLevel : 2; var logLevel = typeof(config.logLevel) !== 'undefined'? config.logLevel : 1;
var readOnly = config.readOnly || false; var readOnly = config.readOnly || false;
var sframeChan = config.sframeChan; var sframeChan = config.sframeChan;
var metadataMgr = config.metadataMgr; var metadataMgr = config.metadataMgr;

@ -39,9 +39,11 @@ define([], function () {
}); });
// shim between chainpad and netflux // shim between chainpad and netflux
var msgIn = function (msg) { var msgIn = function (peer, msg) {
try { try {
var decryptedMsg = Crypto.decrypt(msg, isNewHash); var isHk = peer.length !== 32;
var key = isNewHash ? validateKey : false;
var decryptedMsg = Crypto.decrypt(msg, key, isHk);
return decryptedMsg; return decryptedMsg;
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@ -53,7 +55,16 @@ define([], function () {
if (readOnly) { return; } if (readOnly) { return; }
try { try {
var cmsg = Crypto.encrypt(msg); var cmsg = Crypto.encrypt(msg);
if (msg.indexOf('[4') === 0) { cmsg = 'cp|' + cmsg; } if (msg.indexOf('[4') === 0) {
var id = '';
if (window.nacl) {
var hash = window.nacl.hash(window.nacl.util.decodeUTF8(msg));
id = window.nacl.util.encodeBase64(hash.slice(0, 8)) + '|';
} else {
console.log("Checkpoint sent without an ID. Nacl is missing.");
}
cmsg = 'cp|' + id + cmsg;
}
return cmsg; return cmsg;
} catch (err) { } catch (err) {
console.log(msg); console.log(msg);
@ -67,8 +78,11 @@ define([], function () {
padRpc.sendPadMsg(msg, cb); padRpc.sendPadMsg(msg, cb);
}); });
var onMessage = function(msg) { var onMessage = function(msgObj) {
var message = msgIn(msg); if (msgObj.validateKey && !validateKey) {
validateKey = msgObj.validateKey;
}
var message = msgIn(msgObj.user, msgObj.msg);
verbose(message); verbose(message);

@ -332,6 +332,7 @@ define([
//var cursor = editor.getCursor(); //var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, ''); //var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')'; //var text = '!['+cleanName+']('+data.url+')';
// PASSWORD_FILES
var parsed = Hash.parsePadUrl(data.url); var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;

@ -333,6 +333,8 @@ define([
var editor = config.ckeditor; var editor = config.ckeditor;
editor.document.on('drop', function (ev) { editor.document.on('drop', function (ev) {
var dropped = ev.data.$.dataTransfer.files; var dropped = ev.data.$.dataTransfer.files;
editor.document.focus();
if (!dropped || !dropped.length) { return; }
onFileDrop(dropped, ev); onFileDrop(dropped, ev);
ev.data.preventDefault(true); ev.data.preventDefault(true);
}); });

@ -24,6 +24,8 @@ define([
var Utils = {}; var Utils = {};
var AppConfig; var AppConfig;
var Test; var Test;
var password;
var initialPathInDrive;
nThen(function (waitFor) { nThen(function (waitFor) {
// Load #2, the loading screen is up so grab whatever you need... // Load #2, the loading screen is up so grab whatever you need...
@ -88,7 +90,16 @@ define([
SFrameChannel.create($('#sbox-iframe')[0].contentWindow, waitFor(function (sfc) { SFrameChannel.create($('#sbox-iframe')[0].contentWindow, waitFor(function (sfc) {
sframeChan = sfc; sframeChan = sfc;
}), false, { cache: cache, localStore: localStore, language: Cryptpad.getLanguage() }); }), false, { cache: cache, localStore: localStore, language: Cryptpad.getLanguage() });
Cryptpad.ready(waitFor(), { Cryptpad.loading.onDriveEvent.reg(function (data) {
if (sframeChan) { sframeChan.event('EV_LOADING_INFO', data); }
});
Cryptpad.ready(waitFor(function () {
if (sframeChan) {
sframeChan.event('EV_LOADING_INFO', {
state: -1
});
}
}), {
messenger: cfg.messaging, messenger: cfg.messaging,
driveEvents: cfg.driveEvents driveEvents: cfg.driveEvents
}); });
@ -113,6 +124,7 @@ define([
if (cfg.getSecrets) { if (cfg.getSecrets) {
var w = waitFor(); var w = waitFor();
// No password for drive, profile and todo
cfg.getSecrets(Cryptpad, Utils, waitFor(function (err, s) { cfg.getSecrets(Cryptpad, Utils, waitFor(function (err, s) {
secret = s; secret = s;
Cryptpad.getShareHashes(secret, function (err, h) { Cryptpad.getShareHashes(secret, function (err, h) {
@ -121,19 +133,54 @@ define([
}); });
})); }));
} else { } else {
secret = Utils.Hash.getSecrets(); var parsed = Utils.Hash.parsePadUrl(window.location.href);
if (!secret.channel) { var todo = function () {
// New pad: create a new random channel id secret = Utils.Hash.getSecrets(parsed.type, void 0, password);
secret.channel = Utils.Hash.createChannelId(); Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; }));
};
// Prompt the password here if we have a hash containing /p/
// or get it from the pad attributes
var needPassword = parsed.hashData && parsed.hashData.password;
if (needPassword) {
Cryptpad.getPadAttribute('password', waitFor(function (err, val) {
if (val) {
// We already know the password, use it!
password = val;
todo();
} else {
// Ask for the password and check if the pad exists
// If the pad doesn't exist, it means the password is oncorrect
// or the pad has been deleted
var correctPassword = waitFor();
sframeChan.on('Q_PAD_PASSWORD_VALUE', function (data, cb) {
password = data;
Cryptpad.isNewChannel(window.location.href, password, function (e, isNew) {
if (Boolean(isNew)) {
// Ask again in the inner iframe
// We should receive a new Q_PAD_PASSWORD_VALUE
cb(false);
} else {
todo();
correctPassword();
cb(true);
}
});
});
sframeChan.event("EV_PAD_PASSWORD");
}
}), parsed.getUrl());
return;
} }
Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; })); // If no password, continue...
todo();
} }
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
// Check if the pad exists on server // Check if the pad exists on server
if (!window.location.hash) { isNewFile = true; return; } if (!window.location.hash) { isNewFile = true; return; }
if (realtime) { if (realtime) {
Cryptpad.isNewChannel(window.location.href, waitFor(function (e, isNew) { Cryptpad.isNewChannel(window.location.href, password, waitFor(function (e, isNew) {
if (e) { return console.error(e); } if (e) { return console.error(e); }
isNewFile = Boolean(isNew); isNewFile = Boolean(isNew);
})); }));
@ -188,7 +235,9 @@ define([
}, },
isNewFile: isNewFile, isNewFile: isNewFile,
isDeleted: isNewFile && window.location.hash.length > 0, isDeleted: isNewFile && window.location.hash.length > 0,
forceCreationScreen: forceCreationScreen forceCreationScreen: forceCreationScreen,
password: password,
channel: secret.channel
}; };
for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; } for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; }
@ -256,7 +305,13 @@ define([
sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newTitle, cb) { sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newTitle, cb) {
currentTitle = newTitle; currentTitle = newTitle;
setDocumentTitle(); setDocumentTitle();
Cryptpad.setPadTitle(newTitle, undefined, undefined, function (err) { var data = {
password: password,
title: newTitle,
channel: secret.channel,
path: initialPathInDrive // Where to store the pad if we don't have it in our drive
};
Cryptpad.setPadTitle(data, function (err) {
cb(err); cb(err);
}); });
}); });
@ -325,7 +380,9 @@ define([
validateKey: secret.keys.validateKey validateKey: secret.keys.validateKey
}, function (encryptedMsgs) { }, function (encryptedMsgs) {
cb(encryptedMsgs.map(function (msg) { cb(encryptedMsgs.map(function (msg) {
return crypto.decrypt(msg, true); // 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);
})); }));
}); });
}); });
@ -336,6 +393,7 @@ define([
// If we have a stronger hash, use it for pad attributes // If we have a stronger hash, use it for pad attributes
href = window.location.pathname + '#' + hashes.editHash; href = window.location.pathname + '#' + hashes.editHash;
} }
if (data.href) { href = data.href; }
Cryptpad.getPadAttribute(data.key, function (e, data) { Cryptpad.getPadAttribute(data.key, function (e, data) {
cb({ cb({
error: e, error: e,
@ -349,6 +407,7 @@ define([
// If we have a stronger hash, use it for pad attributes // If we have a stronger hash, use it for pad attributes
href = window.location.pathname + '#' + hashes.editHash; href = window.location.pathname + '#' + hashes.editHash;
} }
if (data.href) { href = data.href; }
Cryptpad.setPadAttribute(data.key, data.value, function (e) { Cryptpad.setPadAttribute(data.key, data.value, function (e) {
cb({error:e}); cb({error:e});
}, href); }, href);
@ -429,6 +488,8 @@ define([
// File picker // File picker
var FP = {}; var FP = {};
var initFilePicker = function (cfg) { var initFilePicker = function (cfg) {
// cfg.hidden means pre-loading the filepicker while keeping it hidden.
// if cfg.hidden is true and the iframe already exists, do nothing
if (!FP.$iframe) { if (!FP.$iframe) {
var config = {}; var config = {};
config.onFilePicked = function (data) { config.onFilePicked = function (data) {
@ -447,7 +508,7 @@ define([
}; };
FP.$iframe = $('<iframe>', {id: 'sbox-filePicker-iframe'}).appendTo($('body')); FP.$iframe = $('<iframe>', {id: 'sbox-filePicker-iframe'}).appendTo($('body'));
FP.picker = FilePicker.create(config); FP.picker = FilePicker.create(config);
} else { } else if (!cfg.hidden) {
FP.$iframe.show(); FP.$iframe.show();
FP.picker.refresh(cfg); FP.picker.refresh(cfg);
} }
@ -469,9 +530,9 @@ define([
cb(templates.length > 0); cb(templates.length > 0);
}); });
}); });
var getKey = function (href) { var getKey = function (href, channel) {
var parsed = Utils.Hash.parsePadUrl(href); var parsed = Utils.Hash.parsePadUrl(href);
return 'thumbnail-' + parsed.type + '-' + parsed.hashData.channel; return 'thumbnail-' + parsed.type + '-' + channel;
}; };
sframeChan.on('Q_CREATE_TEMPLATES', function (type, cb) { sframeChan.on('Q_CREATE_TEMPLATES', function (type, cb) {
Cryptpad.getSecureFilesList({ Cryptpad.getSecureFilesList({
@ -484,12 +545,13 @@ define([
var res = []; var res = [];
nThen(function (waitFor) { nThen(function (waitFor) {
Object.keys(data).map(function (el) { Object.keys(data).map(function (el) {
var k = getKey(data[el].href); var k = getKey(data[el].href, data[el].channel);
Utils.LocalStore.getThumbnail(k, waitFor(function (e, thumb) { Utils.LocalStore.getThumbnail(k, waitFor(function (e, thumb) {
res.push({ res.push({
id: el, id: el,
name: data[el].filename || data[el].title || '?', name: data[el].filename || data[el].title || '?',
thumbnail: thumb thumbnail: thumb,
used: data[el].used || 0
}); });
})); }));
}); });
@ -513,19 +575,6 @@ define([
} }
}); });
sframeChan.on('Q_TAGS_GET', function (data, cb) {
Cryptpad.getPadTags(data, function (err, data) {
cb({
error: err,
data: data
});
});
});
sframeChan.on('EV_TAGS_SET', function (data) {
Cryptpad.resetTags(data.href, data.tags);
});
sframeChan.on('Q_PIN_GET_USAGE', function (data, cb) { sframeChan.on('Q_PIN_GET_USAGE', function (data, cb) {
Cryptpad.isOverPinLimit(function (err, overLimit, data) { Cryptpad.isOverPinLimit(function (err, overLimit, data) {
cb({ cb({
@ -546,6 +595,15 @@ define([
Cryptpad.removeOwnedChannel(channel, cb); Cryptpad.removeOwnedChannel(channel, cb);
}); });
sframeChan.on('Q_GET_ALL_TAGS', function (data, cb) {
Cryptpad.listAllTags(function (err, tags) {
cb({
error: err,
tags: tags
});
});
});
if (cfg.addRpc) { if (cfg.addRpc) {
cfg.addRpc(sframeChan, Cryptpad, Utils); cfg.addRpc(sframeChan, Cryptpad, Utils);
} }
@ -630,7 +688,7 @@ define([
isNewHash: isNewHash, isNewHash: isNewHash,
readOnly: readOnly, readOnly: readOnly,
crypto: Crypto.createEncryptor(secret.keys), crypto: Crypto.createEncryptor(secret.keys),
onConnect: function (wc) { onConnect: function () {
if (window.location.hash && window.location.hash !== '#') { if (window.location.hash && window.location.hash !== '#') {
window.location = parsed.getUrl({ window.location = parsed.getUrl({
present: parsed.hashData.present, present: parsed.hashData.present,
@ -639,20 +697,36 @@ define([
return; return;
} }
if (readOnly || cfg.noHash) { return; } if (readOnly || cfg.noHash) { return; }
replaceHash(Utils.Hash.getEditHashFromKeys(wc, secret.keys)); replaceHash(Utils.Hash.getEditHashFromKeys(secret));
} }
}; };
Object.keys(rtConfig).forEach(function (k) {
cpNfCfg[k] = rtConfig[k]; nThen(function (waitFor) {
if (isNewFile && cfg.owned && !window.location.hash) {
Cryptpad.getMetadata(waitFor(function (err, m) {
cpNfCfg.owners = [m.priv.edPublic];
}));
} else if (isNewFile && !cfg.useCreationScreen && window.location.hash) {
console.log("new file with hash in the address bar in an app without pcs and which requires owners");
sframeChan.onReady(function () {
sframeChan.query("EV_LOADING_ERROR", "DELETED");
});
waitFor.abort();
}
}).nThen(function () {
Object.keys(rtConfig).forEach(function (k) {
cpNfCfg[k] = rtConfig[k];
});
CpNfOuter.start(cpNfCfg);
}); });
CpNfOuter.start(cpNfCfg);
}; };
sframeChan.on('Q_CREATE_PAD', function (data, cb) { sframeChan.on('Q_CREATE_PAD', function (data, cb) {
if (!isNewFile || rtStarted) { return; } if (!isNewFile || rtStarted) { return; }
// Create a new hash // Create a new hash
var newHash = Utils.Hash.createRandomHash(); password = data.password;
secret = Utils.Hash.getSecrets(parsed.type, newHash); var newHash = Utils.Hash.createRandomHash(parsed.type, password);
secret = Utils.Hash.getSecrets(parsed.type, newHash, password);
// Update the hash in the address bar // Update the hash in the address bar
var ohc = window.onhashchange; var ohc = window.onhashchange;
@ -664,7 +738,7 @@ define([
// Update metadata values and send new metadata inside // Update metadata values and send new metadata inside
parsed = Utils.Hash.parsePadUrl(window.location.href); parsed = Utils.Hash.parsePadUrl(window.location.href);
defaultTitle = Utils.Hash.getDefaultName(parsed); defaultTitle = Utils.Hash.getDefaultName(parsed);
hashes = Utils.Hash.getHashes(secret.channel, secret); hashes = Utils.Hash.getHashes(secret);
readOnly = false; readOnly = false;
updateMeta(); updateMeta();
@ -678,7 +752,7 @@ define([
nThen(function(waitFor) { nThen(function(waitFor) {
if (data.templateId) { if (data.templateId) {
if (data.templateId === -1) { if (data.templateId === -1) {
Cryptpad.setInitialPath(['template']); initialPathInDrive = ['template'];
return; return;
} }
Cryptpad.getPadData(data.templateId, waitFor(function (err, d) { Cryptpad.getPadData(data.templateId, waitFor(function (err, d) {
@ -706,8 +780,8 @@ define([
Utils.Feedback.reportAppUsage(); Utils.Feedback.reportAppUsage();
if (!realtime) { return; } if (!realtime && !Test.testing) { return; }
if (isNewFile && cfg.useCreationScreen) { return; } if (isNewFile && cfg.useCreationScreen && !Test.testing) { return; }
//if (isNewFile && Utils.LocalStore.isLoggedIn() //if (isNewFile && Utils.LocalStore.isLoggedIn()
// && AppConfig.displayCreationScreen && cfg.useCreationScreen) { return; } // && AppConfig.displayCreationScreen && cfg.useCreationScreen) { return; }

@ -113,6 +113,7 @@ define([
return '<script src="' + origin + '/common/media-tag-nacl.min.js"></script>'; return '<script src="' + origin + '/common/media-tag-nacl.min.js"></script>';
}; };
funcs.getMediatagFromHref = function (href) { funcs.getMediatagFromHref = function (href) {
// PASSWORD_FILES
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets('file', parsed.hash); var secret = Hash.getSecrets('file', parsed.hash);
var data = ctx.metadataMgr.getPrivateData(); var data = ctx.metadataMgr.getPrivateData();
@ -126,8 +127,7 @@ define([
} }
return; return;
}; };
funcs.getFileSize = function (href, cb) { funcs.getFileSize = function (channelId, cb) {
var channelId = Hash.hrefToHexChannelId(href);
funcs.sendAnonRpcMsg("GET_FILE_SIZE", channelId, function (data) { funcs.sendAnonRpcMsg("GET_FILE_SIZE", channelId, function (data) {
if (!data) { return void cb("No response"); } if (!data) { return void cb("No response"); }
if (data.error) { return void cb(data.error); } if (data.error) { return void cb(data.error); }
@ -170,6 +170,7 @@ define([
// Store // Store
funcs.handleNewFile = function (waitFor) { funcs.handleNewFile = function (waitFor) {
if (window.__CRYPTPAD_TEST__) { return; }
var priv = ctx.metadataMgr.getPrivateData(); var priv = ctx.metadataMgr.getPrivateData();
if (priv.isNewFile) { if (priv.isNewFile) {
var c = (priv.settings.general && priv.settings.general.creation) || {}; var c = (priv.settings.general && priv.settings.general.creation) || {};
@ -196,6 +197,7 @@ define([
ctx.sframeChan.query("Q_CREATE_PAD", { ctx.sframeChan.query("Q_CREATE_PAD", {
owned: cfg.owned, owned: cfg.owned,
expire: cfg.expire, expire: cfg.expire,
password: cfg.password,
template: cfg.template, template: cfg.template,
templateId: cfg.templateId templateId: cfg.templateId
}, cb); }, cb);
@ -233,17 +235,20 @@ define([
}); });
}; };
funcs.getPadAttribute = function (key, cb) { // href is optional here: if not provided, we use the href of the current tab
funcs.getPadAttribute = function (key, cb, href) {
ctx.sframeChan.query('Q_GET_PAD_ATTRIBUTE', { ctx.sframeChan.query('Q_GET_PAD_ATTRIBUTE', {
key: key key: key,
href: href
}, function (err, res) { }, function (err, res) {
cb (err || res.error, res.data); cb (err || res.error, res.data);
}); });
}; };
funcs.setPadAttribute = function (key, value, cb) { funcs.setPadAttribute = function (key, value, cb, href) {
cb = cb || $.noop; cb = cb || $.noop;
ctx.sframeChan.query('Q_SET_PAD_ATTRIBUTE', { ctx.sframeChan.query('Q_SET_PAD_ATTRIBUTE', {
key: key, key: key,
href: href,
value: value value: value
}, cb); }, cb);
}; };
@ -343,6 +348,27 @@ define([
window.open(bounceHref); window.open(bounceHref);
}; };
funcs.fixLinks = function (domElement) {
var origin = ctx.metadataMgr.getPrivateData().origin;
$(domElement).find('a[target="_blank"]').click(function (e) {
e.preventDefault();
e.stopPropagation();
var href = $(this).attr('href');
var absolute = /^https?:\/\//i;
if (!absolute.test(href)) {
if (href.slice(0,1) !== '/') { href = '/' + href; }
href = origin + href;
}
funcs.openUnsafeURL(href);
});
$(domElement).find('a[target!="_blank"]').click(function (e) {
e.preventDefault();
e.stopPropagation();
funcs.gotoURL($(this).attr('href'));
});
return $(domElement)[0];
};
funcs.whenRealtimeSyncs = evRealtimeSynced.reg; funcs.whenRealtimeSyncs = evRealtimeSynced.reg;
var logoutHandlers = []; var logoutHandlers = [];
@ -397,19 +423,6 @@ define([
UI.addTooltips(); UI.addTooltips();
ctx.sframeChan.on('EV_LOGOUT', function () {
$(window).on('keyup', function (e) {
if (e.keyCode === 27) {
UI.removeLoadingScreen();
}
});
UI.addLoadingScreen({hideTips: true});
UI.errorLoadingScreen(Messages.onLogout, true);
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
});
ctx.sframeChan.on('Q_INCOMING_FRIEND_REQUEST', function (confirmMsg, cb) { ctx.sframeChan.on('Q_INCOMING_FRIEND_REQUEST', function (confirmMsg, cb) {
UI.confirm(confirmMsg, cb, null, true); UI.confirm(confirmMsg, cb, null, true);
}); });
@ -419,12 +432,46 @@ define([
UI.log(data.logText); UI.log(data.logText);
}); });
ctx.sframeChan.on("EV_PAD_PASSWORD", function () {
UIElements.displayPasswordPrompt(funcs);
});
ctx.sframeChan.on('EV_LOADING_INFO', function (data) {
UI.updateLoadingProgress(data, true);
});
ctx.metadataMgr.onReady(waitFor()); ctx.metadataMgr.onReady(waitFor());
}).nThen(function () { }).nThen(function () {
try { try {
var feedback = ctx.metadataMgr.getPrivateData().feedbackAllowed; var feedback = ctx.metadataMgr.getPrivateData().feedbackAllowed;
Feedback.init(feedback); Feedback.init(feedback);
} catch (e) { Feedback.init(false); } } catch (e) { Feedback.init(false); }
ctx.sframeChan.on('EV_LOADING_ERROR', function (err) {
if (err === 'DELETED') {
var msg = Messages.deletedError + '<br>' + Messages.errorRedirectToHome;
UI.errorLoadingScreen(msg, false, function () {
funcs.gotoURL('/drive/');
});
}
});
ctx.sframeChan.on('EV_LOGOUT', function () {
$(window).on('keyup', function (e) {
if (e.keyCode === 27) {
UI.removeLoadingScreen();
}
});
UI.addLoadingScreen({hideTips: true});
var origin = ctx.metadataMgr.getPrivateData().origin;
var href = origin + "/login/";
var onLogoutMsg = Messages._getKey('onLogout', ['<a href="' + href + '" target="_blank">', '</a>']);
UI.errorLoadingScreen(onLogoutMsg, true);
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
});
ctx.sframeChan.ready(); ctx.sframeChan.ready();
cb(funcs); cb(funcs);
}); });

@ -165,10 +165,6 @@ define({
// Put one entry in the parent sessionStorage // Put one entry in the parent sessionStorage
'Q_SESSIONSTORAGE_PUT': true, 'Q_SESSIONSTORAGE_PUT': true,
// Set and get the tags using the tag prompt button
'Q_TAGS_GET': true,
'EV_TAGS_SET': true,
// Merge the anonymous drive (FS_hash) into the current logged in user's drive, to keep the pads // Merge the anonymous drive (FS_hash) into the current logged in user's drive, to keep the pads
// in the drive at registration. // in the drive at registration.
'Q_MERGE_ANON_DRIVE': true, 'Q_MERGE_ANON_DRIVE': true,
@ -227,4 +223,17 @@ define({
// This is for sending data out of the iframe when we are in testing mode // This is for sending data out of the iframe when we are in testing mode
// The exact protocol is defined in common/test.js // The exact protocol is defined in common/test.js
'EV_TESTDATA': true, '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,
// Loading events to display in the loading screen
'EV_LOADING_INFO': true,
// Get all existing tags
'Q_GET_ALL_TAGS': true,
}); });

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -576,9 +576,7 @@ define([
if (Common.isLoggedIn()) { return; } if (Common.isLoggedIn()) { return; }
var pd = config.metadataMgr.getPrivateData(); var pd = config.metadataMgr.getPrivateData();
var o = pd.origin; var o = pd.origin;
var hashes = pd.availableHashes; var cid = pd.channel;
var url = pd.origin + pd.pathname + '#' + (hashes.editHash || hashes.viewHash);
var cid = Hash.hrefToHexChannelId(url);
Common.sendAnonRpcMsg('IS_CHANNEL_PINNED', cid, function (x) { Common.sendAnonRpcMsg('IS_CHANNEL_PINNED', cid, function (x) {
if (x.error || !Array.isArray(x.response)) { return void console.log(x); } if (x.error || !Array.isArray(x.response)) { return void console.log(x); }
if (x.response[0] === true) { if (x.response[0] === true) {

@ -384,11 +384,9 @@ define([
// Get drive ids of files from their channel ids // Get drive ids of files from their channel ids
exp.findChannels = function (channels) { exp.findChannels = function (channels) {
var allFilesList = files[FILES_DATA]; var allFilesList = files[FILES_DATA];
var channels64 = channels.slice().map(Util.hexToBase64);
return getFiles([FILES_DATA]).filter(function (k) { return getFiles([FILES_DATA]).filter(function (k) {
var data = allFilesList[k]; var data = allFilesList[k];
var parsed = Hash.parsePadUrl(data.href); return channels.indexOf(data.channel) !== -1;
return parsed.hashData && channels64.indexOf(parsed.hashData.channel) !== -1;
}); });
}; };
@ -629,6 +627,21 @@ define([
if (typeof cb === "function") { cb(); } if (typeof cb === "function") { cb(); }
}; };
// Tags
exp.getTagsList = function () {
var tags = {};
var data;
var pushTag = function (tag) {
tags[tag] = tags[tag] ? ++tags[tag] : 1;
};
for (var id in files[FILES_DATA]) {
data = files[FILES_DATA][id];
if (!data.tags || !Array.isArray(data.tags)) { continue; }
data.tags.forEach(pushTag);
}
return tags;
};
return exp; return exp;
}; };
return module; return module;

@ -1,17 +1,13 @@
@import (once) "../../customize/src/less2/include/browser.less"; @import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) "../../customize/src/less2/include/markdown.less"; @import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/avatar.less'; @import (once) '../../customize/src/less2/include/avatar.less';
@import (once) '../../customize/src/less2/include/framework.less';
.toolbar_main( .framework_min_main(
@bg-color: @colortheme_friends-bg, @bg-color: @colortheme_friends-bg,
@warn-color: @colortheme_friends-warn, @warn-color: @colortheme_friends-warn,
@color: @colortheme_friends-color @color: @colortheme_friends-color
); );
.fileupload_main();
.alertify_main();
// body // body
&.cp-app-contacts { &.cp-app-contacts {

@ -11,7 +11,7 @@ define([
'/common/common-interface.js', '/common/common-interface.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less', 'less!/customize/src/less2/main.less',
], function ( ], function (
$, $,

@ -1,15 +1,11 @@
@import (once) "../../customize/src/less2/include/browser.less"; @import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) "../../customize/src/less2/include/markdown.less"; @import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tools.less'; @import (once) '../../customize/src/less2/include/tools.less';
@import (once) '../../customize/src/less2/include/tokenfield.less'; @import (once) '../../customize/src/less2/include/tokenfield.less';
@import (once) '../../customize/src/less2/include/framework.less';
.toolbar_main();
.fileupload_main();
.alertify_main();
.tokenfield_main(); .tokenfield_main();
.framework_min_main();
// body // body
&.cp-app-debug { &.cp-app-debug {

@ -15,7 +15,7 @@ define([
'/bower_components/secure-fabric.js/dist/fabric.min.js', '/bower_components/secure-fabric.js/dist/fabric.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less', 'less!/customize/src/less2/main.less',
], function ( ], function (
$, $,

@ -1,20 +1,17 @@
@import (once) "../../customize/src/less2/include/browser.less"; @import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) "../../customize/src/less2/include/markdown.less"; @import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/leftside-menu.less'; @import (once) '../../customize/src/less2/include/leftside-menu.less';
@import (once) "../../customize/src/less2/include/tools.less"; @import (once) "../../customize/src/less2/include/tools.less";
@import (once) "../../customize/src/less2/include/limit-bar.less"; @import (once) "../../customize/src/less2/include/limit-bar.less";
@import (once) "../../customize/src/less2/include/tokenfield.less"; @import (once) "../../customize/src/less2/include/tokenfield.less";
@import (once) '../../customize/src/less2/include/framework.less';
.toolbar_main( .framework_min_main(
@bg-color: @colortheme_drive-bg, @bg-color: @colortheme_drive-bg,
@warn-color: @colortheme_drive-warn, @warn-color: @colortheme_drive-warn,
@color: @colortheme_drive-color @color: @colortheme_drive-color
); );
.fileupload_main();
.alertify_main();
.limit-bar_main(); .limit-bar_main();
.tokenfield_main(); .tokenfield_main();
@ -465,6 +462,8 @@ span {
padding-right: 15px; padding-right: 15px;
} }
.cp-app-drive-search-opendir { .cp-app-drive-search-opendir {
display: flex;
justify-content: space-between;
a { a {
cursor: pointer; cursor: pointer;
color: #41b7d8; color: #41b7d8;
@ -498,6 +497,19 @@ span {
} }
} }
} }
&.cp-app-drive-tags-list {
width: 100%;
table {
margin: 10px 50px;
width: ~"calc(100% - 100px)";
table-layout: fixed;
td, th {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
} }
} }
.cp-app-drive-element { .cp-app-drive-element {
@ -547,6 +559,10 @@ span {
cursor: pointer; cursor: pointer;
opacity: 0.5; opacity: 0.5;
padding: 0; padding: 0;
flex-flow: column;
align-items: center;
justify-content: center;
display: inline-flex;
&:hover { &:hover {
opacity: 0.7; opacity: 0.7;
} }

@ -18,7 +18,7 @@ define([
'/customize/messages.js', '/customize/messages.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less', 'less!/customize/src/less2/main.less',
], function ( ], function (
$, $,
@ -51,20 +51,65 @@ define([
var E_OVER_LIMIT = 'E_OVER_LIMIT'; var E_OVER_LIMIT = 'E_OVER_LIMIT';
var SEARCH = "search";
var SEARCH_NAME = Messages.fm_searchName;
var ROOT = "root"; var ROOT = "root";
var ROOT_NAME = Messages.fm_rootName; var ROOT_NAME = Messages.fm_rootName;
var SEARCH = "search";
var SEARCH_NAME = Messages.fm_searchName;
var TRASH = "trash";
var TRASH_NAME = Messages.fm_trashName;
var FILES_DATA = Constants.storageKey; var FILES_DATA = Constants.storageKey;
var FILES_DATA_NAME = Messages.fm_filesDataName; var FILES_DATA_NAME = Messages.fm_filesDataName;
var TEMPLATE = "template"; var TEMPLATE = "template";
var TEMPLATE_NAME = Messages.fm_templateName; var TEMPLATE_NAME = Messages.fm_templateName;
var TRASH = "trash";
var TRASH_NAME = Messages.fm_trashName;
var RECENT = "recent"; var RECENT = "recent";
var RECENT_NAME = Messages.fm_recentPadsName; var RECENT_NAME = Messages.fm_recentPadsName;
var OWNED = "owned"; var OWNED = "owned";
var OWNED_NAME = Messages.fm_ownedPadsName; var OWNED_NAME = Messages.fm_ownedPadsName;
var TAGS = "tags";
var TAGS_NAME = Messages.fm_tagsName;
// Icons
var faFolder = 'fa-folder';
var faFolderOpen = 'fa-folder-open';
var faReadOnly = 'fa-eye';
var faRename = 'fa-pencil';
var faTrash = 'fa-trash';
var faDelete = 'fa-eraser';
var faProperties = 'fa-database';
var faTags = 'fa-hashtag';
var faEmpty = 'fa-trash-o';
var faRestore = 'fa-repeat';
var faShowParent = 'fa-location-arrow';
var $folderIcon = $('<span>', {
"class": faFolder + " fa cp-app-drive-icon-folder cp-app-drive-content-icon"
});
//var $folderIcon = $('<img>', {src: "/customize/images/icons/folder.svg", "class": "folder icon"});
var $folderEmptyIcon = $folderIcon.clone();
var $folderOpenedIcon = $('<span>', {"class": faFolderOpen + " fa cp-app-drive-icon-folder"});
//var $folderOpenedIcon = $('<img>', {src: "/customize/images/icons/folderOpen.svg", "class": "folder icon"});
var $folderOpenedEmptyIcon = $folderOpenedIcon.clone();
//var $upIcon = $('<span>', {"class": "fa fa-arrow-circle-up"});
var $unsortedIcon = $('<span>', {"class": "fa fa-files-o"});
var $templateIcon = $('<span>', {"class": "fa fa-cubes"});
var $recentIcon = $('<span>', {"class": "fa fa-clock-o"});
var $trashIcon = $('<span>', {"class": "fa " + faTrash});
var $trashEmptyIcon = $('<span>', {"class": "fa fa-trash-o"});
//var $collapseIcon = $('<span>', {"class": "fa fa-minus-square-o cp-app-drive-icon-expcol"});
var $expandIcon = $('<span>', {"class": "fa fa-plus-square-o cp-app-drive-icon-expcol"});
var $emptyTrashIcon = $('<button>', {"class": "fa fa-ban"});
var $listIcon = $('<button>', {"class": "fa fa-list"});
var $gridIcon = $('<button>', {"class": "fa fa-th-large"});
var $sortAscIcon = $('<span>', {"class": "fa fa-angle-up sortasc"});
var $sortDescIcon = $('<span>', {"class": "fa fa-angle-down sortdesc"});
var $closeIcon = $('<span>', {"class": "fa fa-window-close"});
//var $backupIcon = $('<span>', {"class": "fa fa-life-ring"});
var $searchIcon = $('<span>', {"class": "fa fa-search cp-app-drive-tree-search-con"});
var $addIcon = $('<span>', {"class": "fa fa-plus"});
var $renamedIcon = $('<span>', {"class": "fa fa-flag"});
var $readonlyIcon = $('<span>', {"class": "fa " + faReadOnly});
var $ownedIcon = $('<span>', {"class": "fa fa-id-card-o"});
var $ownerIcon = $('<span>', {"class": "fa fa-id-card"});
var $tagsIcon = $('<span>', {"class": "fa " + faTags});
var LS_LAST = "app-drive-lastOpened"; var LS_LAST = "app-drive-lastOpened";
var LS_OPENED = "app-drive-openedFolders"; var LS_OPENED = "app-drive-openedFolders";
@ -157,48 +202,6 @@ define([
} }
}; };
// Icons
var faFolder = 'fa-folder';
var faFolderOpen = 'fa-folder-open';
var faReadOnly = 'fa-eye';
var faRename = 'fa-pencil';
var faTrash = 'fa-trash';
var faDelete = 'fa-eraser';
var faProperties = 'fa-database';
var faTags = 'fa-hashtag';
var faEmpty = 'fa-trash-o';
var faRestore = 'fa-repeat';
var faShowParent = 'fa-location-arrow';
var $folderIcon = $('<span>', {
"class": faFolder + " fa cp-app-drive-icon-folder cp-app-drive-content-icon"
});
//var $folderIcon = $('<img>', {src: "/customize/images/icons/folder.svg", "class": "folder icon"});
var $folderEmptyIcon = $folderIcon.clone();
var $folderOpenedIcon = $('<span>', {"class": faFolderOpen + " fa cp-app-drive-icon-folder"});
//var $folderOpenedIcon = $('<img>', {src: "/customize/images/icons/folderOpen.svg", "class": "folder icon"});
var $folderOpenedEmptyIcon = $folderOpenedIcon.clone();
//var $upIcon = $('<span>', {"class": "fa fa-arrow-circle-up"});
var $unsortedIcon = $('<span>', {"class": "fa fa-files-o"});
var $templateIcon = $('<span>', {"class": "fa fa-cubes"});
var $recentIcon = $('<span>', {"class": "fa fa-clock-o"});
var $trashIcon = $('<span>', {"class": "fa " + faTrash});
var $trashEmptyIcon = $('<span>', {"class": "fa fa-trash-o"});
//var $collapseIcon = $('<span>', {"class": "fa fa-minus-square-o cp-app-drive-icon-expcol"});
var $expandIcon = $('<span>', {"class": "fa fa-plus-square-o cp-app-drive-icon-expcol"});
var $emptyTrashIcon = $('<button>', {"class": "fa fa-ban"});
var $listIcon = $('<button>', {"class": "fa fa-list"});
var $gridIcon = $('<button>', {"class": "fa fa-th-large"});
var $sortAscIcon = $('<span>', {"class": "fa fa-angle-up sortasc"});
var $sortDescIcon = $('<span>', {"class": "fa fa-angle-down sortdesc"});
var $closeIcon = $('<span>', {"class": "fa fa-window-close"});
//var $backupIcon = $('<span>', {"class": "fa fa-life-ring"});
var $searchIcon = $('<span>', {"class": "fa fa-search cp-app-drive-tree-search-con"});
var $addIcon = $('<span>', {"class": "fa fa-plus"});
var $renamedIcon = $('<span>', {"class": "fa fa-flag"});
var $readonlyIcon = $('<span>', {"class": "fa " + faReadOnly});
var $ownedIcon = $('<span>', {"class": "fa fa-id-card-o"});
var $ownerIcon = $('<span>', {"class": "fa fa-id-card"});
var history = { var history = {
isHistoryMode: false, isHistoryMode: false,
}; };
@ -360,17 +363,24 @@ define([
// Categories dislayed in the menu // Categories dislayed in the menu
// _WORKGROUP_ : do not display unsorted // _WORKGROUP_ : do not display unsorted
var displayedCategories = [ROOT, TRASH, SEARCH, RECENT]; var displayedCategories = [ROOT, TRASH, SEARCH, RECENT];
// PCS enabled: display owned pads
if (AppConfig.displayCreationScreen) { displayedCategories.push(OWNED); } if (AppConfig.displayCreationScreen) { displayedCategories.push(OWNED); }
// Templates enabled: display template category
if (AppConfig.enableTemplates) { displayedCategories.push(TEMPLATE); } if (AppConfig.enableTemplates) { displayedCategories.push(TEMPLATE); }
// Tags used: display Tags category
if (Object.keys(filesOp.getTagsList()).length) { displayedCategories.push(TAGS); }
if (isWorkgroup()) { displayedCategories = [ROOT, TRASH, SEARCH]; } if (isWorkgroup()) { displayedCategories = [ROOT, TRASH, SEARCH]; }
var virtualCategories = [SEARCH, RECENT, OWNED]; var virtualCategories = [SEARCH, RECENT, OWNED, TAGS];
if (!APP.loggedIn) { if (!APP.loggedIn) {
displayedCategories = [FILES_DATA]; displayedCategories = [FILES_DATA];
currentPath = [FILES_DATA]; currentPath = [FILES_DATA];
$tree.hide(); $tree.hide();
if (Object.keys(files.root).length && !proxy.anonymousAlert) { if (Object.keys(files.root).length && !proxy.anonymousAlert) {
UI.alert(Messages.fm_alert_anonymous, null, true); var msg = common.fixLinks($('<div>').html(Messages.fm_alert_anonymous));
UI.alert(msg);
proxy.anonymousAlert = true; proxy.anonymousAlert = true;
} }
} }
@ -1295,7 +1305,7 @@ define([
$span.attr('title', name); $span.attr('title', name);
var type = Messages.type[hrefData.type] || hrefData.type; var type = Messages.type[hrefData.type] || hrefData.type;
common.displayThumbnail(data.href, $span, function ($thumb) { common.displayThumbnail(data.href, data.channel, $span, function ($thumb) {
// Called only if the thumbnail exists // Called only if the thumbnail exists
// Remove the .hide() added by displayThumnail() because it hides the icon in // Remove the .hide() added by displayThumnail() because it hides the icon in
// list mode too // list mode too
@ -1443,6 +1453,7 @@ define([
case SEARCH: pName = SEARCH_NAME; break; case SEARCH: pName = SEARCH_NAME; break;
case RECENT: pName = RECENT_NAME; break; case RECENT: pName = RECENT_NAME; break;
case OWNED: pName = OWNED_NAME; break; case OWNED: pName = OWNED_NAME; break;
case TAGS: pName = TAGS_NAME; break;
default: pName = name; default: pName = name;
} }
return pName; return pName;
@ -1511,18 +1522,14 @@ define([
case OWNED: case OWNED:
msg = Messages.fm_info_owned; msg = Messages.fm_info_owned;
break; break;
case TAGS:
break;
default: default:
msg = undefined; msg = undefined;
} }
if (!APP.loggedIn) { if (!APP.loggedIn) {
msg = Messages.fm_info_anonymous; msg = Messages.fm_info_anonymous;
$box.html(msg); return $(common.fixLinks($box.html(msg)));
$box.find('a[target!="_blank"]').click(function (e) {
e.preventDefault();
var href = $(this).attr('href');
common.gotoURL(href);
});
return $box;
} }
if (!msg || APP.store['hide-info-' + path[0]] === '1') { if (!msg || APP.store['hide-info-' + path[0]] === '1') {
$box.hide(); $box.hide();
@ -2057,6 +2064,7 @@ define([
$element.data('context', 'default'); $element.data('context', 'default');
$container.append($element); $container.append($element);
}); });
createGhostIcon($container);
}; };
var displayTrashRoot = function ($list, $folderHeader, $fileHeader) { var displayTrashRoot = function ($list, $folderHeader, $fileHeader) {
@ -2100,7 +2108,7 @@ define([
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
var $table = $('<table>'); var $table = $('<table>');
var $icon = $('<td>', {'rowspan': '3', 'class': 'cp-app-drive-search-icon'}) var $icon = $('<td>', {'rowspan': '3', 'class': 'cp-app-drive-search-icon'})
.append(getFileIcon(href)); .append(getFileIcon(r.id));
var $title = $('<td>', { var $title = $('<td>', {
'class': 'cp-app-drive-search-col1 cp-app-drive-search-title' 'class': 'cp-app-drive-search-col1 cp-app-drive-search-title'
}).text(r.data.title) }).text(r.data.title)
@ -2140,6 +2148,13 @@ define([
} }
var $openDir = $('<td>', {'class': 'cp-app-drive-search-opendir'}).append($a); var $openDir = $('<td>', {'class': 'cp-app-drive-search-opendir'}).append($a);
$('<a>').text(Messages.fc_prop).click(function () {
APP.getProperties(r.id, function (e, $prop) {
if (e) { return void logError(e); }
UI.alert($prop[0], undefined, true);
});
}).appendTo($openDir);
// rows 1-3 // rows 1-3
$('<tr>').append($icon).append($title).append($typeName).append($type).appendTo($table); $('<tr>').append($icon).append($title).append($typeName).append($type).appendTo($table);
$('<tr>').append($path).append($atimeName).append($atime).appendTo($table); $('<tr>').append($path).append($atimeName).append($atime).appendTo($table);
@ -2230,6 +2245,35 @@ define([
}); });
}; };
// Tags category
var displayTags = function ($container) {
var list = filesOp.getTagsList();
if (Object.keys(list).length === 0) { return; }
var sortedTags = Object.keys(list);
sortedTags.sort(function (a, b) {
return list[b] - list[a];
});
var lines = [
h('tr', [
h('th', Messages.fm_tags_name),
h('th', Messages.fm_tags_used)
])
];
sortedTags.forEach(function (tag) {
var tagLink = h('a', { href: '#' }, '#' + tag);
$(tagLink).click(function () {
if (displayedCategories.indexOf(SEARCH) !== -1) {
APP.Search.$input.val('#' + tag).keyup();
}
});
lines.push(h('tr', [
h('td', tagLink),
h('td.cp-app-drive-tags-used', list[tag])
]));
});
$(h('li.cp-app-drive-tags-list', h('table', lines))).appendTo($container);
};
// Display the selected directory into the content part (rightside) // Display the selected directory into the content part (rightside)
// NOTE: Elements in the trash are not using the same storage structure as the others // NOTE: Elements in the trash are not using the same storage structure as the others
// _WORKGROUP_ : do not change the lastOpenedFolder value in localStorage // _WORKGROUP_ : do not change the lastOpenedFolder value in localStorage
@ -2259,10 +2303,9 @@ define([
var isTrashRoot = filesOp.comparePath(path, [TRASH]); var isTrashRoot = filesOp.comparePath(path, [TRASH]);
var isTemplate = filesOp.comparePath(path, [TEMPLATE]); var isTemplate = filesOp.comparePath(path, [TEMPLATE]);
var isAllFiles = filesOp.comparePath(path, [FILES_DATA]); var isAllFiles = filesOp.comparePath(path, [FILES_DATA]);
var isSearch = path[0] === SEARCH;
var isRecent = path[0] === RECENT;
var isOwned = path[0] === OWNED;
var isVirtual = virtualCategories.indexOf(path[0]) !== -1; var isVirtual = virtualCategories.indexOf(path[0]) !== -1;
var isSearch = path[0] === SEARCH;
var isTags = path[0] === TAGS;
var root = isVirtual ? undefined : filesOp.find(path); var root = isVirtual ? undefined : filesOp.find(path);
if (!isVirtual && typeof(root) === "undefined") { if (!isVirtual && typeof(root) === "undefined") {
@ -2296,7 +2339,7 @@ define([
var $dirContent = $('<div>', {id: FOLDER_CONTENT_ID}); var $dirContent = $('<div>', {id: FOLDER_CONTENT_ID});
$dirContent.data('path', path); $dirContent.data('path', path);
if (!isSearch) { if (!isSearch && !isTags) {
var mode = getViewMode(); var mode = getViewMode();
if (mode) { if (mode) {
$dirContent.addClass(getViewModeClass()); $dirContent.addClass(getViewModeClass());
@ -2358,10 +2401,12 @@ define([
displayTrashRoot($list, $folderHeader, $fileHeader); displayTrashRoot($list, $folderHeader, $fileHeader);
} else if (isSearch) { } else if (isSearch) {
displaySearch($list, path[1]); displaySearch($list, path[1]);
} else if (isRecent) { } else if (path[0] === RECENT) {
displayRecent($list); displayRecent($list);
} else if (isOwned) { } else if (path[0] === OWNED) {
displayOwned($list); displayOwned($list);
} else if (isTags) {
displayTags($list);
} else { } else {
$dirContent.contextmenu(openContextMenu('content')); $dirContent.contextmenu(openContextMenu('content'));
if (filesOp.hasSubfolder(root)) { $list.append($folderHeader); } if (filesOp.hasSubfolder(root)) { $list.append($folderHeader); }
@ -2503,25 +2548,6 @@ define([
}); });
}; };
var createTemplate = function ($container, path) {
var $icon = $templateIcon.clone();
var isOpened = filesOp.comparePath(path, currentPath);
var $element = createTreeElement(TEMPLATE_NAME, $icon, [TEMPLATE], false, true, false, isOpened);
$element.addClass('cp-app-drive-tree-root');
var $list = $('<ul>', { 'class': 'cp-app-drive-tree-category' }).append($element);
$container.append($list);
};
var createAllFiles = function ($container, path) {
var $icon = $unsortedIcon.clone();
var isOpened = filesOp.comparePath(path, currentPath);
var $allfilesElement = createTreeElement(FILES_DATA_NAME, $icon, [FILES_DATA], false, false, false, isOpened);
$allfilesElement.addClass('root');
var $allfilesList = $('<ul>', { 'class': 'cp-app-drive-tree-category' })
.append($allfilesElement);
$container.append($allfilesList);
};
var createTrash = function ($container, path) { var createTrash = function ($container, path) {
var $icon = filesOp.isFolderEmpty(files[TRASH]) ? $trashEmptyIcon.clone() : $trashIcon.clone(); var $icon = filesOp.isFolderEmpty(files[TRASH]) ? $trashEmptyIcon.clone() : $trashIcon.clone();
var isOpened = filesOp.comparePath(path, currentPath); var isOpened = filesOp.comparePath(path, currentPath);
@ -2534,29 +2560,11 @@ define([
$container.append($trashList); $container.append($trashList);
}; };
var createRecent = function ($container, path) {
var $icon = $recentIcon.clone();
var isOpened = filesOp.comparePath(path, currentPath);
var $element = createTreeElement(RECENT_NAME, $icon, [RECENT], false, false, false, isOpened);
$element.addClass('root');
var $list = $('<ul>', { 'class': 'cp-app-drive-tree-category' }).append($element);
$container.append($list);
};
var createOwned = function ($container, path) {
var $icon = $ownedIcon.clone(); // TODO
var isOpened = filesOp.comparePath(path, currentPath);
var $element = createTreeElement(OWNED_NAME, $icon, [OWNED], false, false, false, isOpened);
$element.addClass('root');
var $list = $('<ul>', { 'class': 'cp-app-drive-tree-category' }).append($element);
$container.append($list);
};
var search = APP.Search = {}; var search = APP.Search = {};
var createSearch = function ($container) { var createSearch = function ($container) {
var isInSearch = currentPath[0] === SEARCH; var isInSearch = currentPath[0] === SEARCH;
var $div = $('<div>', {'id': 'cp-app-drive-tree-search', 'class': 'cp-unselectable'}); var $div = $('<div>', {'id': 'cp-app-drive-tree-search', 'class': 'cp-unselectable'});
var $input = $('<input>', { var $input = APP.Search.$input = $('<input>', {
id: 'cp-app-drive-tree-search-input', id: 'cp-app-drive-tree-search-input',
type: 'text', type: 'text',
draggable: false, draggable: false,
@ -2606,6 +2614,38 @@ define([
$container.append($div); $container.append($div);
}; };
var categories = {};
categories[FILES_DATA] = {
name: FILES_DATA_NAME,
$icon: $unsortedIcon
};
categories[TEMPLATE] = {
name: TEMPLATE_NAME,
droppable: true,
$icon: $templateIcon
};
categories[RECENT] = {
name: RECENT_NAME,
$icon: $recentIcon
};
categories[OWNED] = {
name: OWNED_NAME,
$icon: $ownedIcon
};
categories[TAGS] = {
name: TAGS_NAME,
$icon: $tagsIcon
};
var createCategory = function ($container, cat) {
var options = categories[cat];
var $icon = options.$icon.clone();
var isOpened = filesOp.comparePath([cat], currentPath);
var $element = createTreeElement(options.name, $icon, [cat], options.draggable, options.droppable, false, isOpened);
$element.addClass('cp-app-drive-tree-root');
var $list = $('<ul>', { 'class': 'cp-app-drive-tree-category' }).append($element);
$container.append($list);
};
APP.resetTree = function () { APP.resetTree = function () {
var $categories = $tree.find('.cp-app-drive-tree-categories-container'); var $categories = $tree.find('.cp-app-drive-tree-categories-container');
var s = $categories.scrollTop() || 0; var s = $categories.scrollTop() || 0;
@ -2614,11 +2654,12 @@ define([
if (displayedCategories.indexOf(SEARCH) !== -1) { createSearch($tree); } if (displayedCategories.indexOf(SEARCH) !== -1) { createSearch($tree); }
var $div = $('<div>', {'class': 'cp-app-drive-tree-categories-container'}) var $div = $('<div>', {'class': 'cp-app-drive-tree-categories-container'})
.appendTo($tree); .appendTo($tree);
if (displayedCategories.indexOf(RECENT) !== -1) { createRecent($div, [RECENT]); } if (displayedCategories.indexOf(TAGS) !== -1) { createCategory($div, TAGS); }
if (displayedCategories.indexOf(OWNED) !== -1) { createOwned($div, [OWNED]); } if (displayedCategories.indexOf(RECENT) !== -1) { createCategory($div, RECENT); }
if (displayedCategories.indexOf(OWNED) !== -1) { createCategory($div, OWNED); }
if (displayedCategories.indexOf(ROOT) !== -1) { createTree($div, [ROOT]); } if (displayedCategories.indexOf(ROOT) !== -1) { createTree($div, [ROOT]); }
if (displayedCategories.indexOf(TEMPLATE) !== -1) { createTemplate($div, [TEMPLATE]); } if (displayedCategories.indexOf(TEMPLATE) !== -1) { createCategory($div, TEMPLATE); }
if (displayedCategories.indexOf(FILES_DATA) !== -1) { createAllFiles($div, [FILES_DATA]); } if (displayedCategories.indexOf(FILES_DATA) !== -1) { createCategory($div, FILES_DATA); }
if (displayedCategories.indexOf(TRASH) !== -1) { createTrash($div, [TRASH]); } if (displayedCategories.indexOf(TRASH) !== -1) { createTrash($div, [TRASH]); }
$tree.append(APP.$limit); $tree.append(APP.$limit);
@ -2657,9 +2698,9 @@ define([
if (parsed.hashData.type !== "pad") { return; } if (parsed.hashData.type !== "pad") { return; }
var i = data.href.indexOf('#') + 1; var i = data.href.indexOf('#') + 1;
var base = data.href.slice(0, i); var base = data.href.slice(0, i);
var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash); var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash, data.password);
if (!hrefsecret.keys) { return; } if (!hrefsecret.keys) { return; }
var viewHash = Hash.getViewHashFromKeys(hrefsecret.channel, hrefsecret.keys); var viewHash = Hash.getViewHashFromKeys(hrefsecret);
return base + viewHash; return base + viewHash;
}; };
@ -2672,7 +2713,7 @@ define([
} }
}); });
var getProperties = function (el, cb) { var getProperties = APP.getProperties = function (el, cb) {
if (!filesOp.isFile(el)) { if (!filesOp.isFile(el)) {
return void cb('NOT_FILE'); return void cb('NOT_FILE');
} }
@ -2724,24 +2765,6 @@ define([
$(window).focus(); $(window).focus();
if (!res) { return; } if (!res) { return; }
filesOp.delete(pathsList, refresh); filesOp.delete(pathsList, refresh);
/*
// Try to delete each selected pad from server, and delete from drive if no error
var n = nThen(function () {});
pathsList.forEach(function (p) {
var el = filesOp.find(p);
var data = filesOp.getFileData(el);
var parsed = Hash.parsePadUrl(data.href);
var channel = Util.base64ToHex(parsed.hashData.channel);
n = n.nThen(function (waitFor) {
sframeChan.query('Q_REMOVE_OWNED_CHANNEL', channel,
waitFor(function (e) {
if (e) { return void console.error(e); }
filesOp.delete([p], function () {}, false, true);
}));
});
});
n.nThen(function () { refresh(); });
*/
}); });
}; };
$contextMenu.on("click", "a", function(e) { $contextMenu.on("click", "a", function(e) {

@ -39,6 +39,7 @@ define([
var getSecrets = function (Cryptpad, Utils, cb) { var getSecrets = function (Cryptpad, Utils, cb) {
var hash = window.location.hash.slice(1) || Utils.LocalStore.getUserHash() || var hash = window.location.hash.slice(1) || Utils.LocalStore.getUserHash() ||
Utils.LocalStore.getFSHash(); Utils.LocalStore.getFSHash();
// No password for drive
cb(null, Utils.Hash.getSecrets('drive', hash)); cb(null, Utils.Hash.getSecrets('drive', hash));
}; };
var addRpc = function (sframeChan, Cryptpad, Utils) { var addRpc = function (sframeChan, Cryptpad, Utils) {

@ -1,26 +1,17 @@
@import (once) "../../customize/src/less2/include/browser.less"; @import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) "../../customize/src/less2/include/markdown.less"; @import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tokenfield.less'; @import (once) '../../customize/src/less2/include/tokenfield.less';
@import (once) '../../customize/src/less2/include/framework.less';
.toolbar_main( .framework_min_main(
@bg-color: @colortheme_file-bg, @bg-color: @colortheme_file-bg,
@warn-color: @colortheme_file-warn, @warn-color: @colortheme_file-warn,
@color: @colortheme_file-color @color: @colortheme_file-color
); );
.fileupload_main();
.alertify_main();
.tokenfield_main(); .tokenfield_main();
@button-border: 2px; @button-border: 2px;
/*html, body {
margin: 0px;
height: 100%;
}*/
// body // body
display: flex; display: flex;
flex-flow: column; flex-flow: column;

@ -2,7 +2,7 @@ define([
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
], function () { ], function () {
var Nacl = window.nacl; var Nacl = window.nacl;
var PARANOIA = true; //var PARANOIA = true;
var plainChunkLength = 128 * 1024; var plainChunkLength = 128 * 1024;
var cypherChunkLength = 131088; var cypherChunkLength = 131088;
@ -36,24 +36,11 @@ define([
var increment = function (N) { var increment = function (N) {
var l = N.length; var l = N.length;
while (l-- > 1) { 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 /* jshint probably suspects this is unsafe because we lack types
but as long as this is only used on nonces, it should be safe */ 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 if (N[l] !== 255) { return void N[l]++; } // jshint ignore:line
if (l === 0) { throw new Error('E_NONCE_TOO_LARGE'); }
N[l] = 0; 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');
}
} }
}; };
@ -160,19 +147,21 @@ define([
} }
var takeChunk = function (cb) { var takeChunk = function (cb) {
var start = i * cypherChunkLength + 2 + metadataLength; setTimeout(function () {
var end = start + cypherChunkLength; var start = i * cypherChunkLength + 2 + metadataLength;
i++; var end = start + cypherChunkLength;
var box = new Uint8Array(u8.subarray(start, end)); i++;
var box = new Uint8Array(u8.subarray(start, end));
// decrypt the chunk
var plaintext = Nacl.secretbox.open(box, nonce, key); // decrypt the chunk
increment(nonce); var plaintext = Nacl.secretbox.open(box, nonce, key);
increment(nonce);
if (!plaintext) { return cb('DECRYPTION_ERROR'); } if (!plaintext) { return cb('DECRYPTION_ERROR'); }
_progress(end); _progress(end);
cb(void 0, plaintext); cb(void 0, plaintext);
});
}; };
var chunks = []; var chunks = [];
@ -219,7 +208,7 @@ define([
var state = 0; var state = 0;
var next = function (cb) { var next = function (cb) {
if (state === 2) { return void cb(); } if (state === 2) { return void setTimeout(cb); }
var start; var start;
var end; var end;
@ -238,7 +227,9 @@ define([
.concat(slice(box))); .concat(slice(box)));
state++; state++;
return void cb(void 0, prefixed); return void setTimeout(function () {
cb(void 0, prefixed);
});
} }
// encrypt the rest of the file... // encrypt the rest of the file...
@ -253,7 +244,9 @@ define([
// regular data is done // regular data is done
if (i * plainChunkLength >= u8.length) { state = 2; } if (i * plainChunkLength >= u8.length) { state = 2; }
return void cb(void 0, box); setTimeout(function () {
cb(void 0, box);
});
}; };
return next; return next;

@ -16,7 +16,7 @@ define([
'/bower_components/file-saver/FileSaver.min.js', '/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less', 'less!/customize/src/less2/main.less',
], function ( ], function (
@ -61,6 +61,7 @@ define([
if (!priv.filehash) { if (!priv.filehash) {
uploadMode = true; uploadMode = true;
} else { } else {
// PASSWORD_FILES
secret = Hash.getSecrets('file', priv.filehash); secret = Hash.getSecrets('file', priv.filehash);
if (!secret.keys) { throw new Error("You need a hash"); } if (!secret.keys) { throw new Error("You need a hash"); }
hexFileName = Util.base64ToHex(secret.channel); hexFileName = Util.base64ToHex(secret.channel);
@ -124,6 +125,12 @@ define([
var rightsideDisplayed = false; var rightsideDisplayed = false;
$(window.document).on('decryption', function (e) { $(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; var decrypted = e.originalEvent;
if (decrypted.callback) { if (decrypted.callback) {
decrypted.callback(); decrypted.callback();
@ -133,9 +140,6 @@ define([
$dlform.hide(); $dlform.hide();
var $dlButton = $dlview.find('media-tag button'); var $dlButton = $dlview.find('media-tag button');
if (ev) { $dlButton.click(); } if (ev) { $dlButton.click(); }
if (!$dlButton.length) {
$appContainer.css('background', 'white');
}
$dlButton.addClass('btn btn-success'); $dlButton.addClass('btn btn-success');
var text = Messages.download_mt_button + '<br>'; var text = Messages.download_mt_button + '<br>';
text += '<b>' + Util.fixHTML(title) + '</b><br>'; text += '<b>' + Util.fixHTML(title) + '</b><br>';
@ -230,8 +234,7 @@ define([
if (typeof(sizeMb) === 'number' && sizeMb < 5) { return void onClick(); } if (typeof(sizeMb) === 'number' && sizeMb < 5) { return void onClick(); }
$dlform.find('#cp-app-file-dlfile, #cp-app-file-dlprogress').click(onClick); $dlform.find('#cp-app-file-dlfile, #cp-app-file-dlprogress').click(onClick);
}; };
var href = priv.origin + priv.pathname + priv.filehash; common.getFileSize(hexFileName, function (e, data) {
common.getFileSize(href, function (e, data) {
if (e) { if (e) {
return void UI.errorLoadingScreen(e); return void UI.errorLoadingScreen(e);
} }

@ -3,10 +3,12 @@
@import (once) '../../customize/src/less2/include/icon-colors.less'; @import (once) '../../customize/src/less2/include/icon-colors.less';
@import (once) '../../customize/src/less2/include/fileupload.less'; @import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less'; @import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tippy.less';
.iconColors_main(); .iconColors_main();
.fileupload_main(); .fileupload_main();
.alertify_main(); .alertify_main();
.tippy_main();
#cp-filepicker-dialog { #cp-filepicker-dialog {
display: none; display: none;
@ -36,7 +38,8 @@
line-height: 1em; line-height: 1em;
cursor: pointer; cursor: pointer;
background-color: #111; background-color: @colortheme_modal-bg;
box-shadow: 2px 2px 5px #000;
color: @darker; color: @darker;
transition: all 0.1s; transition: all 0.1s;

@ -11,7 +11,7 @@ define([
'/customize/messages.js', '/customize/messages.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less', 'less!/customize/src/less2/main.less',
], function ( ], function (
$, $,
@ -40,6 +40,7 @@ define([
var parsed = Hash.parsePadUrl(data.url); var parsed = Hash.parsePadUrl(data.url);
hideFileDialog(); hideFileDialog();
if (parsed.type === 'file') { if (parsed.type === 'file') {
// PASSWORD_FILES
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
sframeChan.event("EV_FILE_PICKED", { sframeChan.event("EV_FILE_PICKED", {
@ -138,7 +139,7 @@ define([
}); });
// Add thumbnail if it exists // Add thumbnail if it exists
common.displayThumbnail(data.href, $span); common.displayThumbnail(data.href, data.channel, $span);
}); });
$input.focus(); $input.focus();
}; };

@ -8,7 +8,7 @@ define([
'/common/outer/local-store.js', '/common/outer/local-store.js',
'/common/test.js', '/common/test.js',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
], function ($, Cryptpad, Login, UI, Realtime, Feedback, LocalStore, Test) { ], function ($, Cryptpad, Login, UI, Realtime, Feedback, LocalStore, Test) {
$(function () { $(function () {
var $main = $('#mainBlock'); var $main = $('#mainBlock');

@ -3,6 +3,43 @@
<head> <head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/> <meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/pad/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script> <script async data-bootload="/pad/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
}
#cke_1_top {
overflow: visible;
padding: 0px;
display: flex;
}
#cke_1_toolbox {
display: inline-block;
width: 100%;
background-color: #c1e7ff;
}
#cke_1_toolbox .cke_toolbar {
height: 28px;
padding: 2px 0;
}
#cke_1_top .cryptpad-toolbar {
padding: 0;
display: block;
}
.cke_wysiwyg_frame {
min-width: 60%;
}
@media print {
#cke_1_top {
display:none;
}
body.app-pad .userlist-drawer {
display:none;
}
}
</style>
</head> </head>
<body class="cp-app-pad"> <body class="cp-app-pad">
<textarea style="display:none" id="editor1" name="editor1"></textarea> <textarea style="display:none" id="editor1" name="editor1"></textarea>

@ -36,7 +36,7 @@ define([
'/bower_components/diff-dom/diffDOM.js', '/bower_components/diff-dom/diffDOM.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less', 'less!/customize/src/less2/main.less',
], function ( ], function (
$, $,
@ -82,12 +82,54 @@ define([
Cursor: Cursor, Cursor: Cursor,
}; };
// MEDIATAG: Filter elements to serialize
// * Remove the drag&drop and resizers from the hyperjson
var isWidget = function (el) {
return typeof (el.getAttribute) === "function" &&
(el.getAttribute('data-cke-hidden-sel') ||
(el.getAttribute('class') &&
(/cke_widget_drag/.test(el.getAttribute('class')) ||
/cke_image_resizer/.test(el.getAttribute('class')))
)
);
};
var isNotMagicLine = function (el) { var isNotMagicLine = function (el) {
return !(el && typeof(el.getAttribute) === 'function' && return !(el && typeof(el.getAttribute) === 'function' &&
el.getAttribute('class') && el.getAttribute('class') &&
el.getAttribute('class').split(' ').indexOf('non-realtime') !== -1); el.getAttribute('class').split(' ').indexOf('non-realtime') !== -1);
}; };
var shouldSerialize = function (el) {
return isNotMagicLine(el) && !isWidget(el);
};
// MEDIATAG: Filter attributes in the serialized elements
var widgetFilter = function (hj) {
// Send a widget ID == 0 to avoid a fight between browsers and
// prevent the container from having the "selected" class (blue border)
if (hj[1].class) {
var split = hj[1].class.split(' ');
if (split.indexOf('cke_widget_wrapper') !== -1 &&
split.indexOf('cke_widget_block') !== -1) {
hj[1].class = "cke_widget_wrapper cke_widget_block";
hj[1]['data-cke-widget-id'] = "0";
}
if (split.indexOf('cke_widget_wrapper') !== -1 &&
split.indexOf('cke_widget_inline') !== -1) {
hj[1].class = "cke_widget_wrapper cke_widget_inline";
delete hj[1]['data-cke-widget-id'];
//hj[1]['data-cke-widget-id'] = "0";
}
// Remove the title attribute of the drag&drop icons (translation conflicts)
if (split.indexOf('cke_widget_drag_handler') !== -1 ||
split.indexOf('cke_image_resizer') !== -1) {
hj[1].title = undefined;
}
}
return hj;
};
var hjsonFilters = function (hj) { var hjsonFilters = function (hj) {
/* catch `type="_moz"` before it goes over the wire */ /* catch `type="_moz"` before it goes over the wire */
var brFilter = function (hj) { var brFilter = function (hj) {
@ -100,6 +142,7 @@ define([
}; };
brFilter(hj); brFilter(hj);
mediatagContentFilter(hj); mediatagContentFilter(hj);
widgetFilter(hj);
return hj; return hj;
}; };
@ -168,6 +211,36 @@ define([
return true; return true;
} }
} }
// MEDIATAG
// Never modify widget ids
if (info.node && info.node.tagName === 'SPAN' && info.diff.name === 'data-cke-widget-id') {
return true;
}
if (info.node && info.node.tagName === 'SPAN' &&
info.node.getAttribute('class') &&
/cke_widget_wrapper/.test(info.node.getAttribute('class'))) {
if (info.diff.action === 'modifyAttribute' && info.diff.name === 'class') {
return true;
}
//console.log(info);
}
// CkEditor drag&drop icon container
if (info.node && info.node.tagName === 'SPAN' &&
info.node.getAttribute('class') &&
info.node.getAttribute('class').split(' ').indexOf('cke_widget_drag_handler_container') !== -1) {
return true;
}
// CkEditor drag&drop title (language fight)
if (info.node && info.node.getAttribute &&
info.node.getAttribute('class') &&
(info.node.getAttribute('class').split(' ').indexOf('cke_widget_drag_handler') !== -1 ||
info.node.getAttribute('class').split(' ').indexOf('cke_image_resizer') !== -1 ) ) {
return true;
}
/* /*
Also reject any elements which would insert any one of Also reject any elements which would insert any one of
our forbidden tag types: script, iframe, object, our forbidden tag types: script, iframe, object,
@ -199,20 +272,26 @@ define([
if (info.node && info.node.tagName === 'SPAN' && if (info.node && info.node.tagName === 'SPAN' &&
info.node.getAttribute('contentEditable') === "false") { info.node.getAttribute('contentEditable') === "false") {
// it seems to be a magicline plugin element... // it seems to be a magicline plugin element...
// but it can also be a widget (MEDIATAG), in which case the removal was
// probably intentional
if (info.diff.action === 'removeElement') { if (info.diff.action === 'removeElement') {
// and you're about to remove it... // and you're about to remove it...
// this probably isn't what you want if (!info.node.getAttribute('class') ||
!/cke_widget_wrapper/.test(info.node.getAttribute('class'))) {
/* // This element is not a widget!
I have never seen this in the console, but the // this probably isn't what you want
magic line is still getting removed on remote /*
edits. This suggests that it's getting removed I have never seen this in the console, but the
by something other than diffDom. magic line is still getting removed on remote
*/ edits. This suggests that it's getting removed
console.log("preventing removal of the magic line!"); by something other than diffDom.
*/
// return true to prevent diff application console.log("preventing removal of the magic line!");
return true;
// return true to prevent diff application
return true;
}
} }
} }
@ -322,7 +401,7 @@ define([
var src = tag.getAttribute('src'); var src = tag.getAttribute('src');
if (mediaTagMap[src]) { if (mediaTagMap[src]) {
mediaTagMap[src].forEach(function (n) { mediaTagMap[src].forEach(function (n) {
tag.appendChild(n); tag.appendChild(n.cloneNode());
}); });
} }
}); });
@ -370,8 +449,11 @@ define([
framework.setMediaTagEmbedder(function ($mt) { framework.setMediaTagEmbedder(function ($mt) {
$mt.attr('contenteditable', 'false'); $mt.attr('contenteditable', 'false');
$mt.attr('tabindex', '1'); //$mt.attr('tabindex', '1');
editor.insertElement(new window.CKEDITOR.dom.element($mt[0])); //MEDIATAG
var element = new window.CKEDITOR.dom.element($mt[0]);
editor.insertElement(element);
editor.widgets.initOn( element, 'mediatag' );
}); });
framework.setTitleRecommender(function () { framework.setTitleRecommender(function () {
@ -403,7 +485,18 @@ define([
var patch = (DD).diff(inner, userDocStateDom); var patch = (DD).diff(inner, userDocStateDom);
(DD).apply(inner, patch); (DD).apply(inner, patch);
// MEDIATAG: Migrate old mediatags to the widget system
$(inner).find('media-tag:not(.cke_widget_element)').each(function (i, el) {
var element = new window.CKEDITOR.dom.element(el);
editor.widgets.initOn( element, 'mediatag' );
});
displayMediaTags(framework, inner, mediaTagMap); displayMediaTags(framework, inner, mediaTagMap);
// MEDIATAG: Initialize mediatag widgets inserted in the document by other users
editor.widgets.checkWidgets();
if (framework.isReadOnly()) { if (framework.isReadOnly()) {
var $links = $(inner).find('a'); var $links = $(inner).find('a');
// off so that we don't end up with multiple identical handlers // off so that we don't end up with multiple identical handlers
@ -425,7 +518,7 @@ define([
framework.setContentGetter(function () { framework.setContentGetter(function () {
displayMediaTags(framework, inner, mediaTagMap); displayMediaTags(framework, inner, mediaTagMap);
inner.normalize(); inner.normalize();
return Hyperjson.fromDOM(inner, isNotMagicLine, hjsonFilters); return Hyperjson.fromDOM(inner, shouldSerialize, hjsonFilters);
}); });
$bar.find('#cke_1_toolbar_collapser').hide(); $bar.find('#cke_1_toolbar_collapser').hide();
@ -459,11 +552,15 @@ define([
ckeditor: editor, ckeditor: editor,
body: $('body'), body: $('body'),
onUploaded: function (ev, data) { onUploaded: function (ev, data) {
// PASSWORD_FILES
var parsed = Hash.parsePadUrl(data.url); var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel); var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag contenteditable="false" src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '" tabindex="1"></media-tag>'; var mt = '<media-tag contenteditable="false" src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '" tabindex="1"></media-tag>';
editor.insertElement(window.CKEDITOR.dom.element.createFromHtml(mt)); // MEDIATAG
var element = window.CKEDITOR.dom.element.createFromHtml(mt);
editor.insertElement(element);
editor.widgets.initOn( element, 'mediatag' );
} }
}; };
window.APP.FM = framework._.sfCommon.createFileManager(fmConfig); window.APP.FM = framework._.sfCommon.createFileManager(fmConfig);
@ -508,6 +605,8 @@ define([
Util.blobToImage($(el).data('blob'), waitFor(function (imgSrc) { Util.blobToImage($(el).data('blob'), waitFor(function (imgSrc) {
$clone.find('media-tag[src="' + $(el).attr('src') + '"] img') $clone.find('media-tag[src="' + $(el).attr('src') + '"] img')
.attr('src', imgSrc); .attr('src', imgSrc);
$clone.find('media-tag').parent()
.find('.cke_widget_drag_handler_container').remove();
})); }));
}); });
}).nThen(function () { }).nThen(function () {

@ -23,7 +23,13 @@ CKEDITOR.dialog.add('mediatag', function (editor) {
}, },
], ],
onShow: function () { onShow: function () {
var el = editor.plugins.mediatag.clicked; var sel = editor.getSelection();
element = sel.getSelectedElement();
if (!element) { return; }
var el = element.findOne('media-tag');
if (!el) { return; }
var rect = el.getClientRect(); var rect = el.getClientRect();
var dialog = this.parts.contents.$; var dialog = this.parts.contents.$;
var inputs = dialog.querySelectorAll('input'); var inputs = dialog.querySelectorAll('input');
@ -34,7 +40,14 @@ CKEDITOR.dialog.add('mediatag', function (editor) {
}, },
onOk: function() { onOk: function() {
var dialog = this; var dialog = this;
var el = editor.plugins.mediatag.clicked;
var sel = editor.getSelection();
element = sel.getSelectedElement();
if (!element) { return; }
var el = element.findOne('media-tag');
if (!el) { return; }
var dialog = this.parts.contents.$; var dialog = this.parts.contents.$;
var inputs = dialog.querySelectorAll('input'); var inputs = dialog.querySelectorAll('input');
var wInput = inputs[0]; var wInput = inputs[0];

@ -10,7 +10,7 @@
( function() { ( function() {
CKEDITOR.plugins.add( 'mediatag', { CKEDITOR.plugins.add( 'mediatag', {
requires: 'dialog', requires: 'dialog,widget',
//icons: 'image', //icons: 'image',
//hidpi: true, //hidpi: true,
onLoad: function () { onLoad: function () {
@ -38,144 +38,17 @@
// Register the dialog. // Register the dialog.
CKEDITOR.dialog.add( pluginName, this.path + 'mediatag-plugin-dialog.js' ); CKEDITOR.dialog.add( pluginName, this.path + 'mediatag-plugin-dialog.js' );
var allowed = 'media-tag[!data-crypto-key,!src,contenteditable,width,height]{border-style,border-width,float,height,margin,margin-bottom,margin-left,margin-right,margin-top,width}', editor.widgets.add( 'mediatag', {
required = 'media-tag[data-crypto-key,src]';
// Register the command. getLabel: function () { return " "; },
editor.addCommand( pluginName, new CKEDITOR.dialogCommand( pluginName, { dialog: pluginName,
allowedContent: allowed, inline: true,
requiredContent: required, upcast: function( element ) {
contentTransformations: [ return element.name === 'media-tag';
[ 'media-tag{width}: sizeToStyle', 'media-tag[width]: sizeToAttribute' ],
[ 'media-tag{float}: alignmentToStyle', 'media-tag[align]: alignmentToAttribute' ]
]
} ) );
var isMediaTag = function (el) {
if (el.is('media-tag')) { return el; }
var mt = el.getParents().slice().filter(function (p) {
return p.is('media-tag');
});
if (mt.length !== 1) { return; }
return mt[0];
};
editor.on('doubleclick', function (evt) {
var element = evt.data.element;
var mt = isMediaTag(element);
if (mt && !element.data('cke-realelement')) {
editor.plugins.mediatag.clicked = mt;
evt.data.dialog = 'mediatag';
} }
});
// If the "contextmenu" plugin is loaded, register the listeners. });
if (editor.contextMenu) {
editor.contextMenu.addListener(function (element) {
if (getSelectedMediatag(editor, element)) {
return { mediatag: CKEDITOR.TRISTATE_OFF };
}
});
}
}, },
afterInit: function( editor ) {
// Customize the behavior of the alignment commands. (http://dev.ckeditor.com/ticket/7430)
setupAlignCommand('left');
setupAlignCommand('right');
setupAlignCommand('center');
setupAlignCommand('block');
function setupAlignCommand (value) {
var command = editor.getCommand('justify' + value);
if (command) {
if (value === 'left' || value === 'right') {
command.on('exec', function (evt) {
var img = getSelectedMediatag(editor), align;
if (img) {
align = getMediatagAlignment(img);
if (align === value) {
img.removeStyle('float');
// Remove "align" attribute when necessary.
if (value === getMediatagAlignment(img))
img.removeAttribute( 'align' );
} else {
img.setStyle( 'float', value );
}
evt.cancel();
}
} );
}
command.on('refresh', function (evt) {
var img = getSelectedMediatag(editor), align;
if (img) {
align = getMediatagAlignment(img);
this.setState(
(align === value) ? CKEDITOR.TRISTATE_ON : ( value === 'right' || value === 'left' ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
evt.cancel();
}
} );
}
}
}
} ); } );
function getSelectedMediatag (editor, element) {
if (!element) {
var sel = editor.getSelection();
element = sel.getSelectedElement();
}
if (element && element.is('media-tag') && !element.data('cke-realelement')
&& !element.isReadOnly()) {
return element;
}
}
function getMediatagAlignment (element) {
var align = element.getStyle('float');
if (align === 'inherit' || align === 'none') {
align = 0;
}
if (!align) {
align = element.getAttribute('align');
}
return align;
}
} )(); } )();
/**
* Determines whether dimension inputs should be automatically filled when the image URL changes in the Image plugin dialog window.
*
* config.image_prefillDimensions = false;
*
* @since 4.5
* @cfg {Boolean} [image_prefillDimensions=true]
* @member CKEDITOR.config
*/
/**
* Whether to remove links when emptying the link URL field in the Image dialog window.
*
* config.image_removeLinkByEmptyURL = false;
*
* @cfg {Boolean} [image_removeLinkByEmptyURL=true]
* @member CKEDITOR.config
*/
CKEDITOR.config.mediatag_removeLinkByEmptyURL = true;
/**
* Padding text to set off the image in the preview area.
*
* config.image_previewText = CKEDITOR.tools.repeat( '___ ', 100 );
*
* @cfg {String} [image_previewText='Lorem ipsum dolor...' (placeholder text)]
* @member CKEDITOR.config
*/

@ -1,22 +1,15 @@
@import (once) "../../customize/src/less2/include/browser.less"; @import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) "../../customize/src/less2/include/markdown.less"; @import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tokenfield.less';
@import (once) '../../customize/src/less2/include/tools.less'; @import (once) '../../customize/src/less2/include/tools.less';
@import (once) '../../customize/src/less2/include/avatar.less'; @import (once) '../../customize/src/less2/include/avatar.less';
@import (once) '../../customize/src/less2/include/creation.less'; @import (once) "../../customize/src/less2/include/framework.less";
.toolbar_main(
.framework_main(
@bg-color: @colortheme_poll-bg, @bg-color: @colortheme_poll-bg,
@warn-color: @colortheme_poll-warn, @warn-color: @colortheme_poll-warn,
@color: @colortheme_poll-color @color: @colortheme_poll-color
); );
.fileupload_main();
.alertify_main();
.tokenfield_main();
.creation_main();
@poll-fore: #555; @poll-fore: #555;

@ -2,7 +2,6 @@ define([
'jquery', 'jquery',
'/common/toolbar3.js', '/common/toolbar3.js',
'/common/common-util.js', '/common/common-util.js',
'/common/cryptget.js',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/common/sframe-common.js', '/common/sframe-common.js',
'/common/common-realtime.js', '/common/common-realtime.js',
@ -26,13 +25,12 @@ define([
'/bower_components/file-saver/FileSaver.min.js', '/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less', 'less!/customize/src/less2/main.less',
], function ( ], function (
$, $,
Toolbar, Toolbar,
Util, Util,
Cryptget,
nThen, nThen,
SFCommon, SFCommon,
CommonRealtime, CommonRealtime,

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save