Merge branch 'coati' into kanban

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

@ -1,6 +1,8 @@
node_modules/
www/bower_components/
www/common/pdfjs/
www/common/tippy/
www/common/jquery-ui/
server.js
www/common/media-tag.js
@ -8,7 +10,6 @@ www/scratch
www/common/toolbar.js
www/common/hyperscript.js
www/common/tippy.min.js
www/pad/wysiwygarea-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",
"secure-fabric.js": "secure-v1.7.9",
"hyperjson": "~1.4.0",
"chainpad-crypto": "^0.1.3",
"chainpad-listmap": "^0.4.2",
"chainpad": "^5.0.0",
"chainpad-netflux": "^0.6.1",
"chainpad-crypto": "^0.2.0",
"chainpad-listmap": "^0.5.0",
"chainpad": "^5.1.0",
"chainpad-netflux": "^0.7.0",
"file-saver": "1.3.1",
"alertifyjs": "1.0.11",
"scrypt-async": "1.2.0",
@ -46,7 +46,8 @@
"localforage": "^1.5.2",
"html2canvas": "^0.4.1",
"croppie": "^2.5.0",
"sortablejs": "#^1.6.0"
"sortablejs": "#^1.6.0",
"saferphore": "^0.0.1"
},
"resolutions": {
"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
*/
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.
// 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:
// require('heapdump');
// we prepend a space because every usage expects it
// requiring admins to preserve it is unnecessarily confusing
domain = ' ' + domain;
module.exports = {
// 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[Constants.displayNameKey] = uname;
sessionStorage.createReadme = 1;
if (!shouldImport) { proxy.version = 6; }
Feedback.send('REGISTRATION', true);
} else {
Feedback.send('LOGIN', true);
@ -212,6 +213,7 @@ define([
loadingText: Messages.login_hashing,
hideTips: true,
});
// We need a setTimeout(cb, 0) otherwise the loading screen is only displayed
// after hashing the password
window.setTimeout(function () {
@ -256,7 +258,10 @@ define([
// logMeIn should reset registering = false
UI.removeLoadingScreen(function () {
UI.confirm(Messages.register_alreadyRegistered, function (yes) {
if (!yes) { return; }
if (!yes) {
hashing = false;
return;
}
proxy.login_name = uname;
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',
h('a.navbar-brand', { href: '/index.html'}),
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,
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: '/faq.html'}, Msg.faq_link),
@ -619,23 +628,87 @@ define([
];
};
var loadingScreen = Pages.loadingScreen = function () {
return h('div#cp-loading',
h('div.cp-loading-container', [
h('img.cp-loading-cryptofist', {
src: '/customize/cryptpad-new-logo-colors-logoonly.png?' + urlArgs
}),
h('div.cp-loading-spinner-container',
h('span.fa.fa-circle-o-notch.fa-spin.fa-4x.fa-fw')),
h('p'),
])
);
Pages.createCheckbox = function (id, labelTxt, checked, opts) {
opts = opts|| {};
// Input properties
var inputOpts = {
type: 'checkbox',
id: id
};
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-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 () {
var loader = loadingScreen();
loader.style.display = 'none';
return loader;
Pages.createRadio = function (name, id, labelTxt, checked, opts) {
opts = opts|| {};
// Input properties
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 () {
@ -681,27 +754,10 @@ define([
placeholder: Msg.login_confirm,
}),
h('div.checkbox-container', [
h('input#import-recent', {
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),*/
Pages.createCheckbox('import-recent', Msg.register_importRecent, true)
]),
h('div.checkbox-container', [
h('input#accept-terms', {
name: 'accept-terms',
type: 'checkbox'
}),
setHTML($('<label for="accept-terms"></label>')[0], Msg.register_acceptTerms)
/*setHTML(h('label', {
'for': 'accept-terms',
}), Msg.register_acceptTerms),*/
$(Pages.createCheckbox('accept-terms')).find('.cp-checkmark-label').append(Msg.register_acceptTerms).parent()[0]
]),
h('button#register.btn.cp-login-register', Msg.login_register)
])
@ -716,7 +772,6 @@ define([
]),
infopageFooter(),
hiddenLoader(),
])];
};
@ -743,17 +798,7 @@ define([
placeholder: Msg.login_password,
}),
h('div.checkbox-container', [
h('input#import-recent', {
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),*/
Pages.createCheckbox('import-recent', Msg.register_importRecent, true),
]),
h('div.extra', [
h('button.login.first.btn', Msg.login_login)
@ -762,7 +807,6 @@ define([
]),
]),
infopageFooter(),
hiddenLoader(),
])];
};

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

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

@ -5,7 +5,7 @@
@width: round(@size / 8);
@dim1: round(@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>
.cp-checkmark {
margin: 0;
@ -17,6 +17,10 @@
-ms-user-select: none;
user-select: none;
& > a {
margin-left: 0.25em;
}
&.cp-checkmark-secondary {
.cp-checkmark-mark {
&:after {
@ -26,6 +30,7 @@
input {
&:checked ~ .cp-checkmark-mark {
background-color: @colortheme_checkmark-back2;
border-color: @colortheme_checkmark-back2;
}
}
}
@ -37,12 +42,19 @@
display: none;
&:checked ~ .cp-checkmark-mark {
background-color: @colortheme_checkmark-back1;
border-color: @colortheme_checkmark-back1;
&:after {
display: block;
}
}
}
.cp-checkmark-label {
cursor: default;
&:empty {
display: none;
}
}
.cp-checkmark-mark {
margin-right: 10px;
position: relative;
@ -51,6 +63,8 @@
background-color: @colortheme_checkmark-back0;
display: flex;
justify-content: center;
border: 1px solid @colortheme_form-border;
flex-shrink: 0;
&:after {
content: "";
display: none;
@ -60,6 +74,90 @@
transform: rotate(45deg);
border: solid @colortheme_checkmark-col1;
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: @colortheme_app-font-size @colortheme_font;
@colortheme_logo-1: #326599;
@colortheme_logo-2: #4591c4;
@colortheme_link-color: #0275D8;
@colortheme_link-color-visited: #005999;
@colortheme_info-background: #fafafa;
@ -15,23 +18,42 @@
@colortheme_cp-red: #FA5858; // remove red
@colortheme_cp-green: #46E981;
@colortheme_modal-bg: #222;
@colortheme_modal-fg: #fff;
@colortheme_modal-link: #eee;
@colortheme_form-border: #bbbbbb;
@colortheme_form-bg: @colortheme_logo-2;
@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-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_modal-input: #111;
// TODO modals buttons
@colortheme_alertify-red: #E55236;
@colortheme_alertify-red-color: #FFF;
@colortheme_alertify-red-border: transparent;
@colortheme_alertify-green: #77C825;
@colortheme_alertify-primary: #fff;
@colortheme_alertify-primary-text: #000;
@colortheme_notification-log: rgba(0, 0, 0, 0.8);
@colortheme_alertify-green-color: #FFF;
@colortheme_alertify-green-border: transparent;
@colortheme_alertify-primary: @colortheme_form-bg;
@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_dropdown-bg: #f9f9f9;
@ -117,9 +139,9 @@
@cryptpad_header_col: #1E1F1F;
@cryptpad_text_col: #3F4141;
@colortheme_checkmark-back0: #ffffff;
@colortheme_checkmark-back0-active: #bbbbbb;
@colortheme_checkmark-back1: #FF0073;
@colortheme_checkmark-col1: #ffffff;
@colortheme_checkmark-back2: #FFFFFF;
@colortheme_checkmark-col2: #000000;
@colortheme_checkmark-back0: @colortheme_form-bg-alt;
@colortheme_checkmark-back0-active: @colortheme_form-border;
@colortheme_checkmark-back1: @colortheme_form-bg;
@colortheme_checkmark-col1: @colortheme_form-color;
@colortheme_checkmark-back2: @colortheme_form-bg-alt;
@colortheme_checkmark-col2: @colortheme_form-color-alt;

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

@ -1,8 +1,12 @@
@import (once) "./colortheme-all.less";
@import (once) "./toolbar.less";
@import (once) './fileupload.less';
@import (once) './alertify.less';
@import (once) './tokenfield.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) {
.toolbar_main(
@ -13,6 +17,33 @@
.fileupload_main();
.alertify_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;
padding-top: 5px;
padding-bottom: 5px;
border: 1px solid white;
.cp-icons-name {
width: 100%;
@ -26,7 +25,7 @@
word-wrap: break-word;
}
&.cp-icons-element-selected {
background-color: white;
background-color: rgba(0,0,0,0.2);
color: #666;
}
.fa {

@ -161,6 +161,7 @@
background-size: contain;
height: 50px;
width: 250px;
margin-right: 0;
}
a {
border: 2px solid transparent;
@ -169,7 +170,8 @@
.nav-link {
padding: 0.5em 0.7em;
&:hover {
transform: scale(1.05);
font-size: 1.05em;
//transform: scale(1.05);
};
}
.cp-register-btn {
@ -184,9 +186,18 @@
color: #4591C4;
}
}
@media (max-width: 991px) {
@media (max-width: 1000px) {
#menuCollapse {
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 {
text-align: right !important;
@ -194,7 +205,7 @@
.cp-register-btn {
margin-right: 13px;
text-align: center;
}
}
}
//footer general styles

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

@ -183,8 +183,13 @@
#cp-app-toolbar-creation-dialog.cp-modal-container {
.icons_main();
li:hover {
border: 1px solid white;
li {
border: 1px solid @colortheme_modal-fg;
&:hover {
//border: 1px solid @colortheme_modal-fg;
background: @colortheme_modal-fg;
color: @colortheme_modal-bg;
}
}
.cp-modal {
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/colortheme-all.less";
@import (once) "../include/alertify.less";
@import (once) "../loading.less";
@import (once) "../include/checkmark.less";
.infopages_main();
.infopages_topbar();
.alertify_main();
.checkmark_main(20px);
.form-group {
.extra {

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

@ -3,7 +3,7 @@ define([
'/common/hyperscript.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 () {
var $body = $('body');
@ -27,8 +27,7 @@ $(function () {
window.Tether = function () {};
require([
'less!/customize/src/less2/main.less',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'/bower_components/bootstrap/dist/js/bootstrap.bundle.min.js'
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css'
], function () {
$body.append($main);

File diff suppressed because it is too large Load Diff

@ -31,7 +31,7 @@ define(function () {
out.websocketError = 'Αδυναμία σύνδεσης στον διακομιστή...';
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.loading = "Φόρτωση...";

@ -152,7 +152,7 @@ define(function () {
out.websocketError = "Error al conectarse al servidor WebSocket";
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.error = "Error";
out.language = "Idioma";

@ -27,7 +27,7 @@ define(function () {
out.websocketError = 'Impossible de se connecter au serveur WebSocket...';
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.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.";
@ -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>' +
'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.errorRedirectToHome = 'Appuyez sur <em>Échap</em> pour retourner vers votre CryptDrive.';
out.loading = "Chargement...";
out.error = "Erreur";
@ -145,6 +146,8 @@ define(function () {
out.useTemplate = "Commencer avec un modèle?";
out.useTemplateOK = 'Choisir un modèle (Entrée)';
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";
@ -364,6 +367,7 @@ define(function () {
out.fm_searchName = "Recherche";
out.fm_recentPadsName = "Pads récents";
out.fm_ownedPadsName = "Pads en votre possession";
out.fm_tagsName = "Mots-clés";
out.fm_searchPlaceholder = "Rechercher...";
out.fm_newButton = "Nouveau";
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_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_tags_name = "Mot-clé";
out.fm_tags_used = "Nombre d'utilisations";
// File - Context menu
out.fc_newfolder = "Nouveau dossier";
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_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_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_publicSigningKey = "Clé publique de signature";
@ -609,9 +617,6 @@ define(function () {
out.pad_showToolbar = "Afficher 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
out.mdToolbar_button = "Afficher ou cacher la barre d'outils Markdown";
out.mdToolbar_defaultText = "Votre texte ici";
@ -950,8 +955,6 @@ define(function () {
// 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.header_logoTitle = out.updated_0_header_logoTitle;
out.header_homeTitle = "Aller sur la page d'accueil";
@ -1051,7 +1054,7 @@ define(function () {
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.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.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.";
@ -1081,6 +1084,7 @@ define(function () {
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_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_newTemplate = "Nouveau modèle";
out.creation_create = "Créer";
@ -1092,12 +1096,20 @@ define(function () {
out.creation_ownedByOther = "Appartient à un autre utilisateur";
out.creation_noOwner = "Pas de propriétaire";
out.creation_expiration = "Date d'expiration";
out.creation_passwordValue = "Mot de passe";
out.creation_propertiesTitle = "Disponibilité";
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_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";
// 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
out.share_linkCategory = "Partage";
out.share_linkAccess = "Droits d'accès";
@ -1111,5 +1123,12 @@ define(function () {
out.share_embedCategory = "Intégration";
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;
});

@ -30,7 +30,7 @@ define(function () {
out.websocketError = 'Unable to connect to the websocket server...';
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.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.";
@ -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>' +
'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.errorRedirectToHome = 'Press <em>Esc</em> to be redirected to your CryptDrive.';
out.loading = "Loading...";
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.useTemplateOK = 'Pick a template (Enter)';
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";
@ -370,6 +373,7 @@ define(function () {
out.fm_searchName = "Search";
out.fm_recentPadsName = "Recent pads";
out.fm_ownedPadsName = "Owned";
out.fm_tagsName = "Tags";
out.fm_searchPlaceholder = "Search...";
out.fm_newButton = "New";
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_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_tags_name = "Tag name";
out.fm_tags_used = "Number of uses";
// File - Context menu
out.fc_newfolder = "New folder";
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_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_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_publicSigningKey = "Public Signing Key";
@ -618,9 +626,6 @@ define(function () {
out.pad_showToolbar = "Show 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
out.mdToolbar_button = "Show or hide the Markdown toolbar";
out.mdToolbar_defaultText = "Your text here";
@ -999,9 +1004,6 @@ define(function () {
// 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.header_logoTitle = out.updated_0_header_logoTitle;
out.header_homeTitle = 'Go to CryptPad homepage';
@ -1133,6 +1135,7 @@ define(function () {
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_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_newTemplate = "New template";
out.creation_create = "Create";
@ -1144,12 +1147,20 @@ define(function () {
out.creation_ownedByOther = "Owned by another user";
out.creation_noOwner = "No owner";
out.creation_expiration = "Expiration time";
out.creation_passwordValue = "Password";
out.creation_propertiesTitle = "Availability";
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_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";
// 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
out.share_linkCategory = "Share link";
out.share_linkAccess = "Access rights";
@ -1163,6 +1174,12 @@ define(function () {
out.share_embedCategory = "Embed";
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;
});

@ -38,7 +38,7 @@ define(function () {
out.websocketError = 'Incapaz de se conectar com o servidor websocket...';
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.loading = "Carregando...";

@ -13,7 +13,7 @@ define(function () {
out.common_connectionLost = out.updated_0_common_connectionLost;
out.websocketError = "Conexiune inexistentă către serverul websocket...";
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.loading = "Încarcă...";
out.error = "Eroare";

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

@ -33,7 +33,7 @@ nThen(function (waitFor) {
sem.take(function (give) {
Fs.unlink(f.filename, give(function (err) {
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/ {
add_header Cache-Control max-age=31536000;
try_files $uri =404;
}
location ^~ /datastore/ {
add_header Cache-Control max-age=0;
try_files $uri =404;
}

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

@ -15,6 +15,7 @@ const hashesFromPinFile = (pinFile, fileName) => {
switch (l[0]) {
case 'RESET': {
pins = {};
if (l[1] && l[1].length) { l[1].forEach((x) => { pins[x] = 1; }); }
//jshint -W086
// fallthrough
}
@ -32,7 +33,7 @@ const hashesFromPinFile = (pinFile, fileName) => {
return Object.keys(pins);
};
module.exports.load = function (cb) {
module.exports.load = function (cb, config) {
nThen((waitFor) => {
Fs.readdir('./pins', waitFor((err, list) => {
if (err) {
@ -49,7 +50,10 @@ module.exports.load = function (cb) {
sema.take((returnAfter) => {
Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => {
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]));
});
});
}
}

@ -3,14 +3,21 @@ const Fs = require('fs');
const Semaphore = require('saferphore');
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) => {
var pins = {};
pinFile.split('\n').filter((x)=>(x)).map((l) => JSON.parse(l)).forEach((l) => {
switch (l[0]) {
case 'RESET': {
pins = {};
//jshint -W086
// fallthrough
if (l[1] && l[1].length) { l[1].forEach((x) => { pins[x] = 1; }); }
break;
}
case 'PIN': {
l[1].forEach((x) => { pins[x] = 1; });
@ -26,6 +33,11 @@ const hashesFromPinFile = (pinFile, fileName) => {
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) => {
let sum = 0;
hashes.forEach((h) => {
@ -39,23 +51,31 @@ const sizeForHashes = (hashes, dsFileStats) => {
return sum;
};
// do twenty things at a time
const sema = Semaphore.create(20);
let dirList;
const fileList = [];
const dsFileStats = {};
const out = [];
const pinned = {};
const fileList = []; // array which we reuse for a lot of things
const dsFileStats = {}; // map of stats
const out = []; // what we return at the end
const pinned = {}; // map of pinned files
// define a function: 'load' which takes a config
// and a callback
module.exports.load = function (config, cb) {
nThen((waitFor) => {
// read the subdirectories in the datastore
Fs.readdir('./datastore', waitFor((err, list) => {
if (err) { throw err; }
dirList = list;
}));
}).nThen((waitFor) => {
// iterate over all subdirectories
dirList.forEach((f) => {
// process twenty subdirectories simultaneously
sema.take((returnAfter) => {
// get the list of files in every subdirectory
// and push them to 'fileList'
Fs.readdir('./datastore/' + f, waitFor(returnAfter((err, list2) => {
if (err) { throw err; }
list2.forEach((ff) => { fileList.push('./datastore/' + f + '/' + ff); });
@ -63,14 +83,19 @@ module.exports.load = function (config, cb) {
});
});
}).nThen((waitFor) => {
// read the subdirectories in 'blob'
Fs.readdir('./blob', waitFor((err, list) => {
if (err) { throw err; }
// overwrite dirList
dirList = list;
}));
}).nThen((waitFor) => {
// iterate over all subdirectories
dirList.forEach((f) => {
// process twenty subdirectories simultaneously
sema.take((returnAfter) => {
// get the list of files in every subdirectory
// and push them to 'fileList'
Fs.readdir('./blob/' + f, waitFor(returnAfter((err, list2) => {
if (err) { throw err; }
list2.forEach((ff) => { fileList.push('./blob/' + f + '/' + ff); });
@ -78,24 +103,34 @@ module.exports.load = function (config, cb) {
});
});
}).nThen((waitFor) => {
// iterate over the fileList
fileList.forEach((f) => {
// process twenty files simultaneously
sema.take((returnAfter) => {
// get the stats of each files
Fs.stat(f, waitFor(returnAfter((err, st) => {
if (err) { throw err; }
st.filename = f;
// push them to a big map of stats
dsFileStats[f.replace(/^.*\/([^\/\.]*)(\.ndjson)?$/, (all, a) => (a))] = st;
})));
});
});
}).nThen((waitFor) => {
// read the subdirectories in the pinstore
Fs.readdir('./pins', waitFor((err, list) => {
if (err) { throw err; }
dirList = list;
}));
}).nThen((waitFor) => {
// set file list to an empty array
// fileList = [] ??
fileList.splice(0, fileList.length);
dirList.forEach((f) => {
// process twenty directories at a time
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) => {
if (err) { throw err; }
list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); });
@ -103,70 +138,147 @@ module.exports.load = function (config, cb) {
});
});
}).nThen((waitFor) => {
// iterate over the list of pin logs
fileList.forEach((f) => {
// twenty at a time
sema.take((returnAfter) => {
// read the full content
Fs.readFile(f, waitFor(returnAfter((err, content) => {
if (err) { throw err; }
// get the list of channels pinned by this log
const hashes = hashesFromPinFile(content.toString('utf8'), f);
const size = sizeForHashes(hashes, dsFileStats);
if (config.unpinned) {
hashes.forEach((x) => { pinned[x] = 1; });
} 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))]);
}
})));
});
});
}).nThen(() => {
// handle all the information you've processed so far
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;
// but you can override this with config
if (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');
}
}
// you can specify a different time for blobs...
let blobsbefore = before;
if (config.blobsolderthan) {
// use the supplied date if it exists
blobsbefore = config.blobsolderthan;
if (isNaN(blobsbefore)) {
return void cb('--blobsolderthan error [' + config.blobsolderthan + '] not a valid date');
}
}
let files = [];
// iterate over all the stats that you've saved
Object.keys(dsFileStats).forEach((f) => {
// we only care about files which are not in the pin map
if (!(f in pinned)) {
// check if it's a blob or a 'pad'
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({
filename: dsFileStats[f].filename,
size: dsFileStats[f].size,
atime: dsFileStats[f].atime
mtime: dsFileStats[f].mtime
});
}
});
// return the list of files
cb(null, files);
} else {
// if you're not in 'unpinned' mode, sort by size (ascending)
out.sort((a,b) => (a[1] - b[1]));
// and return the sorted data
cb(null, out.slice());
}
});
};
// This script can be called directly on its own
// or required as part of another script
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; }
// '--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');
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');
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) {
if (err) { throw new Error(err); }
if (!Array.isArray(data)) { return; }
if (err) { throw new Error(err); } // throw errors
if (!Array.isArray(data)) { return; } // if the returned value is not an array, you're done
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 {
// 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'); });
}
});
}
/* 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) {
if (!Array.isArray(channels)) { return cb('INVALID_PIN_LIST'); }
if (typeof(Env.msgStore.getChannelSize) !== 'function') {
@ -1139,6 +1157,7 @@ var isNewChannel = function (Env, channel, cb) {
var isUnauthenticatedCall = function (call) {
return [
'GET_FILE_SIZE',
'GET_METADATA',
'GET_MULTIPLE_FILE_SIZE',
'IS_CHANNEL_PINNED',
'IS_NEW_CHANNEL',
@ -1260,12 +1279,14 @@ RPC.create = function (
}
case 'GET_FILE_SIZE':
return void getFileSize(Env, msg[1], function (e, size) {
if (e) {
console.error(e);
}
WARN(e, msg[1]);
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':
return void getMultipleFileSize(Env, msg[1], function (e, dict) {
if (e) {
@ -1458,9 +1479,9 @@ RPC.create = function (
Respond(void 0, "OK");
});
case 'REMOVE_PINS':
return void removePins(Env, safeKey, function (e, response) {
return void removePins(Env, safeKey, function (e) {
if (e) { return void Respond(e); }
Respond(void 0, response);
Respond(void 0, "OK");
});
// restricted to privileged users...
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')), {
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.dist'));
@ -188,6 +191,7 @@ var custom_four04_path = Path.resolve(__dirname + '/customize/404.html');
var send404 = function (res, path) {
if (!path && path !== four04_path) { path = four04_path; }
Fs.exists(path, function (exists) {
res.setHeader('Content-Type', 'text/html; charset=utf-8');
if (exists) { return Fs.createReadStream(path).pipe(res); }
send404(res);
});
@ -250,4 +254,4 @@ var nt = nThen(function (w) {
if (config.debugReplName) {
require('replify')({ name: config.debugReplName, app: debuggableStore });
}
}

@ -223,6 +223,33 @@ define([
hd.type === 'invite');
}, "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) {
var url = '/pad/?utm_campaign=new_comment&utm_medium=email&utm_source=thread_mailer#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/';
var secret = Hash.parsePadUrl(url);

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

@ -34,7 +34,7 @@ define([
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) {

@ -19,7 +19,50 @@ define([
.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') {
return chanKey + keys;
}
@ -34,7 +77,7 @@ define([
};
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) {
return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/';
};
};*/
Hash.getUserHrefFromKeys = function (origin, username, pubkey) {
return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
};
@ -43,6 +86,24 @@ define([
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
/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy
@ -56,25 +117,56 @@ Version 1
var hashArr = fixDuplicateSlashes(hash).split('/');
if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) {
parsed.type = 'pad';
if (hash.slice(0,1) !== '/' && hash.length >= 56) {
if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0
// Old hash
parsed.channel = hash.slice(0, 32);
parsed.key = hash.slice(32, 56);
parsed.version = 0;
parsed.getHash = function () { return hash; };
return parsed;
}
if (hashArr[1] && hashArr[1] === '1') {
var options;
if (hashArr[1] && hashArr[1] === '1') { // Version 1
parsed.version = 1;
parsed.mode = hashArr[2];
parsed.channel = hashArr[3];
parsed.key = hashArr[4].replace(/-/g, '/');
var options = hashArr.slice(5);
parsed.key = Crypto.b64AddSlashes(hashArr[4]);
options = hashArr.slice(5);
parsed.present = options.indexOf('present') !== -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;
}
parsed.getHash = function () { return hashArr.join('/'); };
if (['media', 'file'].indexOf(type) !== -1) {
parsed.type = 'file';
if (hashArr[1] && hashArr[1] === '1') {
@ -125,17 +217,9 @@ Version 1
url += ret.type + '/';
if (!ret.hashData) { return url; }
if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; }
if (ret.hashData.version !== 1) { return url + '#' + ret.hash; }
url += '#/' + ret.hashData.version +
'/' + ret.hashData.mode +
'/' + ret.hashData.channel.replace(/\//g, '-') +
'/' + ret.hashData.key.replace(/\//g, '-') +'/';
if (options.embed) {
url += 'embed/';
}
if (options.present) {
url += 'present/';
}
if (ret.hashData.version === 0) { return url + '#' + ret.hash; }
var hash = ret.hashData.getHash(options);
url += '#' + hash;
return url;
};
@ -153,12 +237,13 @@ Version 1
return '';
});
idx = href.indexOf('/#');
if (idx === -1) { return ret; }
ret.hash = href.slice(idx + 2);
ret.hashData = parseTypeHash(ret.type, ret.hash);
return ret;
};
var getRelativeHref = Hash.getRelativeHref = function (href) {
Hash.getRelativeHref = function (href) {
if (!href) { return; }
if (href.indexOf('#') === -1) { return; }
var parsed = parsePadUrl(href);
@ -170,11 +255,13 @@ Version 1
* - no argument: use the URL hash or create one if it doesn't exist
* - secretHash provided: use secretHash to find the keys
*/
Hash.getSecrets = function (type, secretHash) {
Hash.getSecrets = function (type, secretHash, password) {
var secret = {};
var generate = function () {
secret.keys = Crypto.createEditCryptor();
secret.key = Crypto.createEditCryptor().editKeyStr;
secret.keys = Crypto.createEditCryptor2(void 0, void 0, password);
secret.channel = base64ToHex(secret.keys.chanId);
secret.version = 2;
secret.type = type;
};
if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) {
generate();
@ -191,7 +278,6 @@ Version 1
parsed = pHref.hashData;
hash = pHref.hash;
}
//var parsed = parsePadUrl(window.location.href);
//var hash = secretHash || window.location.hash.slice(1);
if (hash.length === 0) {
generate();
@ -203,9 +289,10 @@ Version 1
// Old hash
secret.channel = parsed.channel;
secret.key = parsed.key;
}
else if (parsed.version === 1) {
secret.version = 0;
} else if (parsed.version === 1) {
// New hash
secret.version = 1;
if (parsed.type === "pad") {
secret.channel = base64ToHex(parsed.channel);
if (parsed.mode === 'edit') {
@ -229,49 +316,63 @@ Version 1
// version 2 hashes are to be used for encrypted blobs
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;
};
Hash.getHashes = function (channel, secret) {
Hash.getHashes = function (secret) {
var hashes = {};
if (!secret.keys) {
secret = JSON.parse(JSON.stringify(secret));
if (!secret.keys && !secret.key) {
console.error('e');
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) {
hashes.viewHash = getViewHashFromKeys(channel, secret.keys);
hashes.viewHash = getViewHashFromKeys(secret);
}
if (secret.keys.fileKeyStr) {
hashes.fileHash = getFileHashFromKeys(channel, secret.keys.fileKeyStr);
hashes.fileHash = getFileHashFromKeys(secret);
}
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
Hash.findWeaker = function (href, recents) {
var rHref = href || getRelativeHref(window.location.href);
var parsed = parsePadUrl(rHref);
Hash.findWeaker = function (href, channel, recents) {
var parsed = parsePadUrl(href);
if (!parsed.hash) { return false; }
var weaker;
Object.keys(recents).some(function (id) {
@ -279,6 +380,8 @@ Version 1
var p = parsePadUrl(pad.href);
if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
if (channel !== pad.channel) { return; } // Not the same channel
var pHash = p.hashData;
var parsedHash = parsed.hashData;
if (!parsedHash || !pHash) { return; }
@ -287,18 +390,16 @@ Version 1
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
if (pHash.version !== parsedHash.version) { return; }
if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'view' && parsedHash.mode === 'edit') {
weaker = pad.href;
weaker = pad;
return true;
}
return;
});
return weaker;
};
var findStronger = Hash.findStronger = function (href, recents) {
var rHref = href || getRelativeHref(window.location.href);
var parsed = parsePadUrl(rHref);
Hash.findStronger = function (href, channel, recents) {
var parsed = parsePadUrl(href);
if (!parsed.hash) { return false; }
// We can't have a stronger hash if we're already in edit mode
if (parsed.hashData && parsed.hashData.mode === 'edit') { return; }
@ -308,6 +409,8 @@ Version 1
var p = parsePadUrl(pad.href);
if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
if (channel !== pad.channel) { return; } // Not the same channel
var pHash = p.hashData;
var parsedHash = parsed.hashData;
if (!parsedHash || !pHash) { return; }
@ -316,37 +419,20 @@ Version 1
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
if (pHash.version !== parsedHash.version) { return; }
if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'edit' && parsedHash.mode === 'view') {
stronger = pad.href;
stronger = pad;
return true;
}
return;
});
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);
if (!parsed || !parsed.hash) { return; }
parsed = parsed.hashData;
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;
var secret = Hash.getSecrets(parsed.type, parsed.hash, password);
return secret.channel;
};
Hash.getBlobPathFromHex = function (id) {

@ -6,15 +6,18 @@ define([
'/common/common-notifier.js',
'/customize/application_config.js',
'/bower_components/alertifyjs/dist/js/alertify.js',
'/common/tippy.min.js',
'/common/tippy/tippy.min.js',
'/customize/pages.js',
'/common/hyperscript.js',
'/customize/loading.js',
'/common/test.js',
'/common/jquery-ui/jquery-ui.min.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,
Alertify, Tippy, Pages, h, Test) {
Alertify, Tippy, Pages, h, Loading, Test) {
var UI = {};
/*
@ -182,11 +185,17 @@ define([
]);
};
UI.tokenField = function (target) {
UI.tokenField = function (target, autocomplete) {
var t = {
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) {
var tokens = $t.tokenfield('getTokens').map(function (token) {
@ -209,10 +218,17 @@ define([
t.preventDuplicates = function (cb) {
$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;
ev.attrs.value = ev.attrs.value.toLowerCase();
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();
if (typeof(cb) === 'function') { cb(val); }
@ -240,7 +256,7 @@ define([
return t;
};
dialog.tagPrompt = function (tags, cb) {
dialog.tagPrompt = function (tags, existing, cb) {
var input = dialog.textInput();
var tagger = dialog.frame([
@ -254,7 +270,7 @@ define([
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]));
});
@ -395,7 +411,7 @@ define([
stopListening(listener);
cb();
});
listener = listenForKeys(close, close, ok);
listener = listenForKeys(close, close);
var $ok = $(ok).click(close);
document.body.appendChild(frame);
@ -512,6 +528,50 @@ define([
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
*/
@ -539,48 +599,99 @@ define([
var LOADING = 'cp-loading';
var getRandomTip = function () {
/*var getRandomTip = function () {
if (!Messages.tips || !Object.keys(Messages.tips).length) { return ''; }
var keys = Object.keys(Messages.tips);
var rdm = Math.floor(Math.random() * keys.length);
return Messages.tips[keys[rdm]];
};*/
var loading = {
error: false,
driveState: 0,
padState: 0
};
UI.addLoadingScreen = function (config) {
config = config || {};
var loadingText = config.loadingText;
var hideTips = config.hideTips || AppConfig.hideLoadingScreenTips;
var hideLogo = config.hideLogo;
var $loading, $container;
if ($('#' + LOADING).length) {
$loading = $('#' + LOADING); //.show();
var todo = function () {
var $loading = $('#' + LOADING); //.show();
$loading.css('display', '');
$loading.removeClass('cp-loading-hidden');
$('.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) {
$('#' + LOADING).find('p').text(loadingText);
$('#' + LOADING).find('#cp-loading-message').show().text(loadingText);
} 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 {
$loading = $(Pages.loadingScreen());
$container = $loading.find('.cp-loading-container');
if (hideLogo) {
$loading.find('img').hide();
Loading();
todo();
}
};
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 {
$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) {
@ -591,7 +702,7 @@ define([
$('#' + LOADING).addClass("cp-loading-hidden");
setTimeout(cb, 750);
//$('#' + LOADING).fadeOut(750, cb);
loading.error = false;
var $tip = $('#cp-loading-tip').css('top', '')
// loading.less sets transition-delay: $wait-time
// and transition: opacity $fadeout-time
@ -605,18 +716,27 @@ define([
// jquery.fadeout can get stuck
};
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});
}
loading.error = true;
$loading.find('.cp-loading-progress').remove();
$('.cp-loading-spinner-container').hide();
$('#cp-loading-tip').remove();
if (transparent) { $('#' + LOADING).css('opacity', 0.8); }
$('#' + LOADING).find('p').html(error || Messages.error);
if (transparent) { $loading.css('opacity', 0.9); }
var $error = $loading.find('#cp-loading-message').show();
if (error instanceof Element) {
$error.html('').append(error);
} else {
$error.html(error || Messages.error);
}
if (exitable) {
$(window).focus();
$(window).keydown(function (e) {
if (e.which === 27) {
$('#' + LOADING).hide();
$loading.hide();
loading.error = false;
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 () {
var MutationObserver = window.MutationObserver;
var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500;
var addTippy = function (i, el) {
if (el._tippy) { return; }
if (el.nodeName === 'IFRAME') { return; }
Tippy(el, {
position: 'bottom',
distance: 0,
performance: true,
delay: [delay, 0],
sticky: true
var opts = {
distance: 15
};
Array.prototype.slice.apply(el.attributes).filter(function (obj) {
return /^data-tippy-/.test(obj.name);
}).forEach(function (obj) {
opts[obj.name.slice(11)] = obj.value;
});
Tippy(el, opts);
};
// This is the robust solution to remove dangling tooltips
// The mutation observer does not always find removed nodes.
@ -720,5 +862,9 @@ define([
});
};
UI.createCheckbox = Pages.createCheckbox;
UI.createRadio = Pages.createRadio;
return UI;
});

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

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

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

@ -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
var secret = Hash.getSecrets('pad', hash);
var secret = Hash.getSecrets('pad', hash, password);
if (!secret.keys) { secret.keys = secret.key; } // support old hashses
var config = {
websocketURL: NetConfig.getWebsocketURL(),
@ -47,8 +47,10 @@ define([
if (typeof(cb) !== 'function') {
throw new Error('Cryptget expects a callback');
}
opt = opt || {};
var config = makeConfig(hash, opt.password);
var Session = { cb: cb, };
var config = makeConfig(hash);
config.onReady = function (info) {
var rt = Session.session = info.realtime;
@ -64,9 +66,11 @@ define([
if (typeof(cb) !== 'function') {
throw new Error('Cryptput expects a callback');
}
opt = opt || {};
var config = makeConfig(hash);
var config = makeConfig(hash, opt.password);
var Session = { cb: cb, };
config.onReady = function (info) {
var realtime = Session.session = info.realtime;
Session.network = info.network;

@ -246,8 +246,8 @@ define([
});
};
common.getFileSize = function (href, cb) {
postMessage("GET_FILE_SIZE", {href: href}, function (obj) {
common.getFileSize = function (href, password, cb) {
postMessage("GET_FILE_SIZE", {href: href, password: password}, function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
cb(undefined, obj.size);
});
@ -260,8 +260,8 @@ define([
});
};
common.isNewChannel = function (href, cb) {
postMessage('IS_NEW_CHANNEL', {href: href}, function (obj) {
common.isNewChannel = function (href, password, cb) {
postMessage('IS_NEW_CHANNEL', {href: href, password: password}, function (obj) {
if (obj.error) { return void cb(obj.error); }
if (!obj) { return void cb('INVALID_RESPONSE'); }
cb(undefined, obj.isNew);
@ -395,8 +395,10 @@ define([
common.saveAsTemplate = function (Cryptput, data, cb) {
var p = Hash.parsePadUrl(window.location.href);
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;
// PPP: add password as cryptput option
Cryptput(hash, data.toSave, function (e) {
if (e) { throw new Error(e); }
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
// it allows us to add owners and expiration time if it is a new file
var parsed = Hash.parsePadUrl(href);
var parsed2 = Hash.parsePadUrl(window.location.href);
if(!parsed) { throw new Error("Cannot get template hash"); }
Crypt.get(parsed.hash, function (err, val) {
if (err) { throw new Error(err); }
var p = Hash.parsePadUrl(window.location.href);
Crypt.put(p.hash, val, cb, opts);
postMessage("INCREMENT_TEMPLATE_USE", href);
optsPut = optsPut || {};
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
common.setPadTitle = function (title, padHref, path, cb) {
var href = padHref || window.location.href;
common.setPadTitle = function (data, cb) {
if (!data || typeof (data) !== "object") { return cb ('Data is not an object'); }
var href = data.href || window.location.href;
var parsed = Hash.parsePadUrl(href);
if (!parsed.hash) { return; }
href = parsed.getUrl({present: parsed.present});
if (!parsed.hash) { return cb ('Invalid hash'); }
data.href = parsed.getUrl({present: parsed.present});
if (title === null) { return; }
if (title.trim() === "") { title = Hash.getDefaultName(parsed); }
if (typeof (data.title) !== "string") { return cb('Missing title'); }
if (data.title.trim() === "") { data.title = Hash.getDefaultName(parsed); }
postMessage("SET_PAD_TITLE", {
href: href,
title: title,
path: path
}, function (obj) {
postMessage("SET_PAD_TITLE", data, function (obj) {
if (obj && obj.error) {
console.log("unable to set pad title");
return void cb(obj.error);
@ -472,10 +490,6 @@ define([
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)
common.inviteFromUserlist = function (netfluxId, cb) {
@ -548,6 +562,10 @@ define([
pad.onDisconnectEvent = Util.mkEvent();
pad.onErrorEvent = Util.mkEvent();
// Loading events
common.loading = {};
common.loading.onDriveEvent = Util.mkEvent();
common.getFullHistory = function (data, cb) {
postMessage("GET_FULL_HISTORY", data, cb);
};
@ -555,15 +573,15 @@ define([
common.getShareHashes = function (secret, cb) {
var hashes;
if (!window.location.hash) {
hashes = Hash.getHashes(secret.channel, secret);
hashes = Hash.getHashes(secret);
return void cb(null, hashes);
}
var parsed = Hash.parsePadUrl(window.location.href);
if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); }
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
hashes.editHash = window.location.hash.slice(1);
return void cb(null, hashes);
@ -575,7 +593,8 @@ define([
}
postMessage("GET_STRONGER_HASH", {
href: window.location.href
href: window.location.href,
password: secret.password
}, function (hash) {
if (hash) { hashes.editHash = hash; }
cb(null, hashes);
@ -622,6 +641,12 @@ define([
window.location.href = '/login/';
};
common.startAccountDeletion = function (cb) {
// Logout other tabs
LocalStore.logout(null, true);
cb();
};
var onMessage = function (cmd, data, cb) {
cb = cb || function () {};
switch (cmd) {
@ -701,6 +726,14 @@ define([
case 'DRIVE_REMOVE': {
common.drive.onRemove.fire(data); break;
}
// Account deletion
case 'DELETE_ACCOUNT': {
common.startAccountDeletion(cb); break;
}
// Loading
case 'LOADING_DRIVE': {
common.loading.onDriveEvent.fire(data); break;
}
}
};
@ -779,11 +812,11 @@ define([
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
// on safari replaces localStorage with sessionStorage or something
sessionStorage.setItem(Constants.userHashKey, cfg.userHash);
}
}*/
if (cfg.userHash) {
var localToken = tryParsing(localStorage.getItem(Constants.tokenKey));
@ -802,18 +835,18 @@ define([
window.onhashchange = function (ev) {
if (ev && ev.reset) { oldHref = document.location.href; return; }
var newHref = document.location.href;
var parsedOld = Hash.parsePadUrl(oldHref).hashData;
var parsedNew = Hash.parsePadUrl(newHref).hashData;
if (parsedOld && parsedNew && (
parsedOld.type !== parsedNew.type
|| parsedOld.channel !== parsedNew.channel
|| parsedOld.mode !== parsedNew.mode
|| parsedOld.key !== parsedNew.key)) {
if (!parsedOld.channel) { oldHref = newHref; return; }
// Compare the URLs without /embed and /present
var parsedOld = Hash.parsePadUrl(oldHref);
var parsedNew = Hash.parsePadUrl(newHref);
if (parsedOld.hashData && parsedNew.hashData &&
parsedOld.getUrl() !== parsedNew.getUrl()) {
if (!parsedOld.hashData.key) { oldHref = newHref; return; }
// If different, reload
document.location.reload();
return;
}
if (parsedNew) { oldHref = newHref; }
if (parsedNew.hashData) { oldHref = newHref; }
};
// Listen for login/logout in other tabs
window.addEventListener('storage', function (e) {

@ -41,6 +41,7 @@ define([
};
renderer.image = function (href, title, text) {
if (href.slice(0,6) === '/file/') {
// PASSWORD_FILES
var parsed = Hash.parsePadUrl(href);
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
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 () {
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: #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();
require(['/customize/loading.js'], function (Loading) {
Loading();
});

@ -122,21 +122,15 @@ define([
// 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 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
// 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) {
// Update RECENTPADS
newRecentPads.some(function (pad) {
if (pad.href === weaker) {
pad.href = href;
return true;
}
return;
});
weaker.href = href;
// Update the file in the drive
newFo.replace(weaker, href);
newFo.replace(weaker.href, href);
return;
}
// 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 (!metadataLazyObj.users) { metadataLazyObj.users = {}; }
if (!metadataObj.type) { metadataObj.type = meta.doc.type; }
if (!metadataLazyObj.type) { metadataLazyObj.type = meta.doc.type; }
var mdo = {};
// We don't want to add our user data to the object multiple times.
//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
// Versions:
// 1: migrate pad attributes
// 2: migrate indent settings (codemirror)
return function (userObject) {
return function (userObject, cb, progress) {
var version = userObject.version || 0;
// DEPRECATED
// Migration 1: pad attributes moved to filesData
var migratePadAttributesToData = function () {
return true;
};
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];
nThen(function () {
// DEPRECATED
// Migration 1: pad attributes moved to filesData
var migratePadAttributesToData = function () {
return true;
};
if (version < 1) {
migratePadAttributesToData();
}
if (typeof(userObject[drawer]) !== "undefined") {
settings.toolbar = settings.toolbar || {};
settings.toolbar['userlist-drawer'] = userObject[drawer];
delete userObject[drawer];
}).nThen(function () {
// 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") {
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") {
settings.poll = settings.poll || {};
settings.poll['hide-text'] = userObject[polls];
delete userObject[polls];
}).nThen(function () {
// Migration 3: language from localStorage to settings
var migrateLanguage = function () {
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;
}
};
if (version < 2) {
migrateAttributes();
Feedback.send('Migrate-2', true);
userObject.version = version = 2;
}
// Migration 3: language from localStorage to settings
var migrateLanguage = function () {
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;
}
// 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 () {
// 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'];
}
};
if (version < 4) {
migrateFeedback();
Feedback.send('Migrate-4', true);
userObject.version = version = 4;
}
};
if (version < 4) {
migrateFeedback();
Feedback.send('Migrate-4', true);
userObject.version = version = 4;
}
// Migration 5: dates to Number
var migrateDates = function () {
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);
}).nThen(function () {
// Migration 5: dates to Number
var migrateDates = function () {
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;
}
};
if (version < 5) {
migrateDates();
Feedback.send('Migrate-5', true);
userObject.version = version = 5;
}
/*}).nThen(function (waitFor) {
// Test progress bar in the loading screen
var i = 0;
var w = waitFor();
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/chainpad.dist.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,
CpNfWorker, NetConfig, AppConfig,
Crypto, ChainPad, Listmap) {
Crypto, ChainPad, Listmap, nThen, Saferphore) {
var Store = {};
var postMessage = function () {};
@ -66,8 +68,9 @@ define([
var userHash = storeHash;
if (!userHash) { return null; }
var userParsedHash = Hash.parseTypeHash('drive', userHash);
var userChannel = userParsedHash && userParsedHash.channel;
// No password for drive
var secret = Hash.getSecrets('drive', userHash);
var userChannel = secret.channel;
if (!userChannel) { return null; }
// Get the list of pads' channel ID in your drive
@ -79,16 +82,16 @@ define([
var d = store.userObject.getFileData(id);
if (d.owners && d.owners.length && edPublic &&
d.owners.indexOf(edPublic) === -1) { return; }
return Hash.hrefToHexChannelId(d.href);
return d.channel;
})
.filter(function (x) { return x; });
// Get the avatar
var profile = store.proxy.profile;
if (profile) {
var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit) : null;
var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null;
if (profileChan) { list.push(profileChan); }
var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar) : null;
var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar, null) : null;
if (avatarChan) { list.push(avatarChan); }
}
@ -97,7 +100,7 @@ define([
list = list.concat(fList);
}
list.push(Util.base64ToHex(userChannel));
list.push(userChannel);
list.sort();
return list;
@ -113,7 +116,7 @@ define([
// because of the expiration time
if ((data.owners && data.owners.length && data.owners.indexOf(edPublic) === -1) ||
(data.expire && data.expire < (+new Date()))) {
list.push(Hash.hrefToHexChannelId(data.href));
list.push(data.channel);
}
});
return list;
@ -301,7 +304,7 @@ define([
Store.getFileSize = function (data, cb) {
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) {
if (e) { return void cb({error: e}); }
if (response && response.length && typeof(response[0]) === 'number') {
@ -314,7 +317,7 @@ define([
Store.isNewChannel = function (data, cb) {
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) {
if (e) { return void cb({error: e}); }
if (response && response.length && typeof(response[0]) === 'boolean') {
@ -378,6 +381,7 @@ define([
// Get the metadata for sframe-common-outer
Store.getMetadata = function (data, cb) {
var disableThumbnails = Util.find(store.proxy, ['settings', 'general', 'disableThumbnails']);
var metadata = {
// "user" is shared with everybody via the userlist
user: {
@ -392,7 +396,7 @@ define([
edPublic: store.proxy.edPublic,
friends: store.proxy.friends || {},
settings: store.proxy.settings,
thumbnails: !Util.find(store.proxy, ['settings', 'general', 'disableThumbnails'])
thumbnails: disableThumbnails === false
}
};
cb(JSON.parse(JSON.stringify(metadata)));
@ -413,6 +417,8 @@ define([
var pad = makePad(data.href, data.title);
if (data.owners) { pad.owners = data.owners; }
if (data.expire) { pad.expire = data.expire; }
if (data.password) { pad.password = data.password; }
if (data.channel) { pad.channel = data.channel; }
store.userObject.pushData(pad, function (e, id) {
if (e) { return void cb({error: "Error while adding a template:"+ e}); }
var path = data.path || ['root'];
@ -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) {
var toSign = {
intent: 'Please delete my account.'
};
var edPublic = store.proxy.edPublic;
// No password for drive
var secret = Hash.getSecrets('drive', storeHash);
toSign.drive = secret.channel;
toSign.edPublic = store.proxy.edPublic;
var signKey = Crypto.Nacl.util.decodeBase64(secret.keys.signKey);
console.log(Sortify(toSign));
var proof = Crypto.Nacl.sign.detached(Crypto.Nacl.util.decodeUTF8(Sortify(toSign)), signKey);
var proofTxt = Crypto.Nacl.util.encodeBase64(proof);
cb({
proof: proofTxt,
toSign: JSON.parse(Sortify(toSign))
Store.anonRpcMsg({
msg: 'GET_METADATA',
data: secret.channel
}, function (data) {
var metadata = data[0];
// Owned drive
if (metadata && metadata.owners && metadata.owners.length === 1 &&
metadata.owners.indexOf(edPublic) !== -1) {
nThen(function (waitFor) {
var token = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
store.proxy[Constants.tokenKey] = token;
postMessage("DELETE_ACCOUNT", token, waitFor());
}).nThen(function (waitFor) {
removeOwnedPads(waitFor);
}).nThen(function (waitFor) {
// Delete Pin Store
store.rpc.removePins(waitFor(function (err) {
if (err) { console.error(err); }
}));
}).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) {
require(['/common/cryptget.js'], function (Crypt) {
var hash = Hash.createRandomHash();
var hash = Hash.createRandomHash('pad');
Crypt.put(hash, data.driveReadme, function (e) {
if (e) {
return void cb({ error: "Error while creating the default pad:"+ e});
}
var href = '/pad/#' + hash;
var channel = Hash.hrefToHexChannelId(href, null);
var fileData = {
href: href,
channel: channel,
title: data.driveReadmeTitle,
atime: +new Date(),
ctime: +new Date()
};
store.userObject.pushData(fileData, function (e, id) {
if (e) {
return void cb({ error: "Error while creating the default pad:"+ e});
}
store.userObject.add(id);
onSync(cb);
});
Store.addPad(fileData, cb);
});
});
};
@ -515,8 +593,12 @@ define([
// Reset the drive part of the userObject (from settings)
Store.resetDrive = function (data, cb) {
store.proxy.drive = store.fo.getStructure();
onSync(cb);
nThen(function (waitFor) {
removeOwnedPads(waitFor);
}).nThen(function () {
store.proxy.drive = store.fo.getStructure();
onSync(cb);
});
};
/**
@ -554,18 +636,7 @@ define([
// Tags
Store.listAllTags = function (data, cb) {
var all = [];
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);
cb(store.userObject.getTagsList());
};
// Templates
@ -578,6 +649,15 @@ define([
});
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
Store.moveToTrash = function (data, cb) {
@ -588,17 +668,18 @@ define([
Store.setPadTitle = function (data, cb) {
var title = data.title;
var href = data.href;
var channel = data.channel;
var p = Hash.parsePadUrl(href);
var h = p.hashData;
if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); }
var owners;
if (Store.channel && Store.channel.wc && 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;
}
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;
}
@ -621,13 +702,13 @@ define([
// Different types, proceed to the next one
// No hash data: corrupted pad?
if (p.type !== p2.type || !h2) { continue; }
// Different channel: continue
if (pad.channel !== channel) { continue; }
var shouldUpdate = p.hash.replace(/\/$/, '') === p2.hash.replace(/\/$/, '');
// If the hash is different but represents the same channel, check if weaker or stronger
if (!shouldUpdate &&
h.version === 1 && h2.version === 1 &&
h.channel === h2.channel) {
if (!shouldUpdate && h.version !== 0) {
// We had view & now we have edit, update
if (h2.mode === 'view' && h.mode === 'edit') { shouldUpdate = true; }
// Same mode and we had present URL, update
@ -664,10 +745,12 @@ define([
if (!contains) {
Store.addPad({
href: href,
channel: channel,
title: title,
owners: owners,
expire: expire,
path: data.path || (store.data && store.data.initialPath)
password: data.password,
path: data.path
}, cb);
return;
}
@ -707,10 +790,7 @@ define([
Store.getPadData = function (id, cb) {
cb(store.userObject.getFileData(id));
};
Store.setInitialPath = function (path) {
if (!store.data) { return; }
store.data.initialPath = path;
};
// Messaging (manage friends from the userlist)
var getMessagingCfg = function () {
@ -742,9 +822,9 @@ define([
var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {};
// If we have a stronger version in drive, add it and add a redirect button
var stronger = Hash.findStronger(data.href, allPads);
var stronger = Hash.findStronger(data.href, data.channel, allPads);
if (stronger) {
var parsed2 = Hash.parsePadUrl(stronger);
var parsed2 = Hash.parsePadUrl(stronger.href);
return void cb(parsed2.hash);
}
cb();
@ -836,8 +916,12 @@ define([
channel.data = padData || {};
postMessage("PAD_READY");
}, // post EV_PAD_READY
onMessage: function (m) {
postMessage("PAD_MESSAGE", m);
onMessage: function (user, m, validateKey) {
postMessage("PAD_MESSAGE", {
user: user,
msg: m,
validateKey: validateKey
});
}, // post EV_PAD_MESSAGE
onJoin: function (m) {
postMessage("PAD_JOIN", m);
@ -906,7 +990,7 @@ define([
if (parsed[1][3] !== data.channel) { return; }
msg = parsed[1][4];
if (msg) {
msg = msg.replace(/^cp\|/, '');
msg = msg.replace(/cp\|(([A-Za-z0-9+\/=]+)\|)?/, '');
//var decryptedMsg = crypto.decrypt(msg, true);
msgs.push(msg);
}
@ -956,11 +1040,24 @@ define([
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();
Migrate(proxy);
var requestLogin = function () {
postMessage("REQUEST_LOGIN");
};
@ -1023,16 +1120,16 @@ define([
proxy.on('change', [Constants.tokenKey], function () {
postMessage("UPDATE_TOKEN", { token: proxy[Constants.tokenKey] });
});
};
userObject.migrate(todo);
});
};
var connect = function (data, cb) {
var hash = data.userHash || data.anonHash || Hash.createRandomHash();
var hash = data.userHash || data.anonHash || Hash.createRandomHash('drive');
storeHash = hash;
if (!hash) {
throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...');
}
// No password for drive
var secret = Hash.getSecrets('drive', hash);
var listmapConfig = {
data: {},
@ -1055,7 +1152,7 @@ define([
store.realtime = info.realtime;
store.network = info.network;
if (!data.userHash) {
returned.anonHash = Hash.getEditHashFromKeys(info.channel, secret.keys);
returned.anonHash = Hash.getEditHashFromKeys(secret);
}
}).on('ready', function () {
if (store.userObject) { return; } // the store is already ready, it is a reconnection
@ -1066,6 +1163,7 @@ define([
&& !drive['filesData']) {
drive[Constants.oldStorageKey] = [];
}
postMessage('LOADING_DRIVE', { state: 1 });
// Drive already exist: return the existing drive, don't load data from legacy store
onReady(returned, cb);
})

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

@ -92,7 +92,7 @@ define([
});
};
var logoutHandlers = [];
LocalStore.logout = function (cb) {
LocalStore.logout = function (cb, isDeletion) {
[
Constants.userNameKey,
Constants.userHashKey,
@ -108,13 +108,15 @@ define([
// 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
if (!LocalStore.getFSHash()) {
LocalStore.setFSHash(Hash.createRandomHash());
LocalStore.setFSHash(Hash.createRandomHash('drive'));
}
eraseTempSessionValues();
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
if (!isDeletion) {
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
}
if (typeof(AppConfig.customizeLogout) === 'function') {
return void AppConfig.customizeLogout(cb);

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

@ -51,14 +51,28 @@ define([
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 title = metadata.name;
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); }
onComplete(href);
common.setPadAttribute('fileType', metadata.type, null, href);

@ -75,7 +75,7 @@ define([
return void todo();
}
if (!pinPads) { return; }
pinPads([Hash.hrefToHexChannelId(data.href)], function (obj) {
pinPads([data.channel], function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
todo();
});
@ -98,7 +98,7 @@ define([
exp.getFiles([FILES_DATA]).forEach(function (id) {
if (filesList.indexOf(id) === -1) {
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 (!isOwnPadRemoved &&
fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) {
@ -552,32 +552,52 @@ define([
for (var id in fd) {
id = Number(id);
var el = fd[id];
// Clean corrupted data
if (!el || typeof(el) !== "object") {
debug("An element in filesData was not an object.", el);
toClean.push(id);
continue;
}
// Clean missing href
if (!el.href) {
debug("Removing an element in filesData with a missing href.", el);
toClean.push(id);
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);
if (!el.title) { el.title = Hash.getDefaultName(parsed); }
// Clean invalid hash
if (!parsed.hash) {
debug("Removing an element in filesData with a invalid href.", el);
toClean.push(id);
continue;
}
// Clean invalid type
if (!parsed.type) {
debug("Removing an element in filesData with a invalid type.", el);
toClean.push(id);
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) {
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el);
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) {
rpc.send('UPLOAD_COMPLETE', null, function (e, res) {
if (e) { return void cb(e); }

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

@ -41,7 +41,7 @@ define([
var patchTransformer = config.patchTransformer;
var validateContent = config.validateContent;
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 sframeChan = config.sframeChan;
var metadataMgr = config.metadataMgr;

@ -39,9 +39,11 @@ define([], function () {
});
// shim between chainpad and netflux
var msgIn = function (msg) {
var msgIn = function (peer, msg) {
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;
} catch (err) {
console.error(err);
@ -53,7 +55,16 @@ define([], function () {
if (readOnly) { return; }
try {
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;
} catch (err) {
console.log(msg);
@ -67,8 +78,11 @@ define([], function () {
padRpc.sendPadMsg(msg, cb);
});
var onMessage = function(msg) {
var message = msgIn(msg);
var onMessage = function(msgObj) {
if (msgObj.validateKey && !validateKey) {
validateKey = msgObj.validateKey;
}
var message = msgIn(msgObj.user, msgObj.msg);
verbose(message);

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

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

@ -24,6 +24,8 @@ define([
var Utils = {};
var AppConfig;
var Test;
var password;
var initialPathInDrive;
nThen(function (waitFor) {
// 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) {
sframeChan = sfc;
}), 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,
driveEvents: cfg.driveEvents
});
@ -113,6 +124,7 @@ define([
if (cfg.getSecrets) {
var w = waitFor();
// No password for drive, profile and todo
cfg.getSecrets(Cryptpad, Utils, waitFor(function (err, s) {
secret = s;
Cryptpad.getShareHashes(secret, function (err, h) {
@ -121,19 +133,54 @@ define([
});
}));
} else {
secret = Utils.Hash.getSecrets();
if (!secret.channel) {
// New pad: create a new random channel id
secret.channel = Utils.Hash.createChannelId();
var parsed = Utils.Hash.parsePadUrl(window.location.href);
var todo = function () {
secret = Utils.Hash.getSecrets(parsed.type, void 0, password);
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) {
// Check if the pad exists on server
if (!window.location.hash) { isNewFile = true; return; }
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); }
isNewFile = Boolean(isNew);
}));
@ -188,7 +235,9 @@ define([
},
isNewFile: isNewFile,
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]; }
@ -256,7 +305,13 @@ define([
sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newTitle, cb) {
currentTitle = newTitle;
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);
});
});
@ -325,7 +380,9 @@ define([
validateKey: secret.keys.validateKey
}, function (encryptedMsgs) {
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
href = window.location.pathname + '#' + hashes.editHash;
}
if (data.href) { href = data.href; }
Cryptpad.getPadAttribute(data.key, function (e, data) {
cb({
error: e,
@ -349,6 +407,7 @@ define([
// If we have a stronger hash, use it for pad attributes
href = window.location.pathname + '#' + hashes.editHash;
}
if (data.href) { href = data.href; }
Cryptpad.setPadAttribute(data.key, data.value, function (e) {
cb({error:e});
}, href);
@ -429,6 +488,8 @@ define([
// File picker
var FP = {};
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) {
var config = {};
config.onFilePicked = function (data) {
@ -447,7 +508,7 @@ define([
};
FP.$iframe = $('<iframe>', {id: 'sbox-filePicker-iframe'}).appendTo($('body'));
FP.picker = FilePicker.create(config);
} else {
} else if (!cfg.hidden) {
FP.$iframe.show();
FP.picker.refresh(cfg);
}
@ -469,9 +530,9 @@ define([
cb(templates.length > 0);
});
});
var getKey = function (href) {
var getKey = function (href, channel) {
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) {
Cryptpad.getSecureFilesList({
@ -484,12 +545,13 @@ define([
var res = [];
nThen(function (waitFor) {
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) {
res.push({
id: el,
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) {
Cryptpad.isOverPinLimit(function (err, overLimit, data) {
cb({
@ -546,6 +595,15 @@ define([
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) {
cfg.addRpc(sframeChan, Cryptpad, Utils);
}
@ -630,7 +688,7 @@ define([
isNewHash: isNewHash,
readOnly: readOnly,
crypto: Crypto.createEncryptor(secret.keys),
onConnect: function (wc) {
onConnect: function () {
if (window.location.hash && window.location.hash !== '#') {
window.location = parsed.getUrl({
present: parsed.hashData.present,
@ -639,20 +697,36 @@ define([
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) {
if (!isNewFile || rtStarted) { return; }
// Create a new hash
var newHash = Utils.Hash.createRandomHash();
secret = Utils.Hash.getSecrets(parsed.type, newHash);
password = data.password;
var newHash = Utils.Hash.createRandomHash(parsed.type, password);
secret = Utils.Hash.getSecrets(parsed.type, newHash, password);
// Update the hash in the address bar
var ohc = window.onhashchange;
@ -664,7 +738,7 @@ define([
// Update metadata values and send new metadata inside
parsed = Utils.Hash.parsePadUrl(window.location.href);
defaultTitle = Utils.Hash.getDefaultName(parsed);
hashes = Utils.Hash.getHashes(secret.channel, secret);
hashes = Utils.Hash.getHashes(secret);
readOnly = false;
updateMeta();
@ -678,7 +752,7 @@ define([
nThen(function(waitFor) {
if (data.templateId) {
if (data.templateId === -1) {
Cryptpad.setInitialPath(['template']);
initialPathInDrive = ['template'];
return;
}
Cryptpad.getPadData(data.templateId, waitFor(function (err, d) {
@ -706,8 +780,8 @@ define([
Utils.Feedback.reportAppUsage();
if (!realtime) { return; }
if (isNewFile && cfg.useCreationScreen) { return; }
if (!realtime && !Test.testing) { return; }
if (isNewFile && cfg.useCreationScreen && !Test.testing) { return; }
//if (isNewFile && Utils.LocalStore.isLoggedIn()
// && AppConfig.displayCreationScreen && cfg.useCreationScreen) { return; }

@ -113,6 +113,7 @@ define([
return '<script src="' + origin + '/common/media-tag-nacl.min.js"></script>';
};
funcs.getMediatagFromHref = function (href) {
// PASSWORD_FILES
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets('file', parsed.hash);
var data = ctx.metadataMgr.getPrivateData();
@ -126,8 +127,7 @@ define([
}
return;
};
funcs.getFileSize = function (href, cb) {
var channelId = Hash.hrefToHexChannelId(href);
funcs.getFileSize = function (channelId, cb) {
funcs.sendAnonRpcMsg("GET_FILE_SIZE", channelId, function (data) {
if (!data) { return void cb("No response"); }
if (data.error) { return void cb(data.error); }
@ -170,6 +170,7 @@ define([
// Store
funcs.handleNewFile = function (waitFor) {
if (window.__CRYPTPAD_TEST__) { return; }
var priv = ctx.metadataMgr.getPrivateData();
if (priv.isNewFile) {
var c = (priv.settings.general && priv.settings.general.creation) || {};
@ -196,6 +197,7 @@ define([
ctx.sframeChan.query("Q_CREATE_PAD", {
owned: cfg.owned,
expire: cfg.expire,
password: cfg.password,
template: cfg.template,
templateId: cfg.templateId
}, 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', {
key: key
key: key,
href: href
}, function (err, res) {
cb (err || res.error, res.data);
});
};
funcs.setPadAttribute = function (key, value, cb) {
funcs.setPadAttribute = function (key, value, cb, href) {
cb = cb || $.noop;
ctx.sframeChan.query('Q_SET_PAD_ATTRIBUTE', {
key: key,
href: href,
value: value
}, cb);
};
@ -343,6 +348,27 @@ define([
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;
var logoutHandlers = [];
@ -397,19 +423,6 @@ define([
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) {
UI.confirm(confirmMsg, cb, null, true);
});
@ -419,12 +432,46 @@ define([
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());
}).nThen(function () {
try {
var feedback = ctx.metadataMgr.getPrivateData().feedbackAllowed;
Feedback.init(feedback);
} 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();
cb(funcs);
});

@ -165,10 +165,6 @@ define({
// Put one entry in the parent sessionStorage
'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
// in the drive at registration.
'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
// The exact protocol is defined in common/test.js
'EV_TESTDATA': true,
// Critical error outside the iframe during loading screen
'EV_LOADING_ERROR': true,
// Ask for the pad password when a pad is protected
'EV_PAD_PASSWORD': true,
'Q_PAD_PASSWORD_VALUE': true,
// 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; }
var pd = config.metadataMgr.getPrivateData();
var o = pd.origin;
var hashes = pd.availableHashes;
var url = pd.origin + pd.pathname + '#' + (hashes.editHash || hashes.viewHash);
var cid = Hash.hrefToHexChannelId(url);
var cid = pd.channel;
Common.sendAnonRpcMsg('IS_CHANNEL_PINNED', cid, function (x) {
if (x.error || !Array.isArray(x.response)) { return void console.log(x); }
if (x.response[0] === true) {

@ -384,11 +384,9 @@ define([
// Get drive ids of files from their channel ids
exp.findChannels = function (channels) {
var allFilesList = files[FILES_DATA];
var channels64 = channels.slice().map(Util.hexToBase64);
return getFiles([FILES_DATA]).filter(function (k) {
var data = allFilesList[k];
var parsed = Hash.parsePadUrl(data.href);
return parsed.hashData && channels64.indexOf(parsed.hashData.channel) !== -1;
return channels.indexOf(data.channel) !== -1;
});
};
@ -629,6 +627,21 @@ define([
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 module;

@ -1,17 +1,13 @@
@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/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.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,
@warn-color: @colortheme_friends-warn,
@color: @colortheme_friends-color
);
.fileupload_main();
.alertify_main();
// body
&.cp-app-contacts {

@ -11,7 +11,7 @@ define([
'/common/common-interface.js',
'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',
], function (
$,

@ -1,15 +1,11 @@
@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/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tools.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();
.framework_min_main();
// body
&.cp-app-debug {

@ -15,7 +15,7 @@ define([
'/bower_components/secure-fabric.js/dist/fabric.min.js',
'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',
], function (
$,

@ -1,20 +1,17 @@
@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/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/tools.less";
@import (once) "../../customize/src/less2/include/limit-bar.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,
@warn-color: @colortheme_drive-warn,
@color: @colortheme_drive-color
);
.fileupload_main();
.alertify_main();
.limit-bar_main();
.tokenfield_main();
@ -465,6 +462,8 @@ span {
padding-right: 15px;
}
.cp-app-drive-search-opendir {
display: flex;
justify-content: space-between;
a {
cursor: pointer;
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 {
@ -547,6 +559,10 @@ span {
cursor: pointer;
opacity: 0.5;
padding: 0;
flex-flow: column;
align-items: center;
justify-content: center;
display: inline-flex;
&:hover {
opacity: 0.7;
}

@ -18,7 +18,7 @@ define([
'/customize/messages.js',
'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',
], function (
$,
@ -51,20 +51,65 @@ define([
var E_OVER_LIMIT = 'E_OVER_LIMIT';
var SEARCH = "search";
var SEARCH_NAME = Messages.fm_searchName;
var ROOT = "root";
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_NAME = Messages.fm_filesDataName;
var TEMPLATE = "template";
var TEMPLATE_NAME = Messages.fm_templateName;
var TRASH = "trash";
var TRASH_NAME = Messages.fm_trashName;
var RECENT = "recent";
var RECENT_NAME = Messages.fm_recentPadsName;
var OWNED = "owned";
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_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 = {
isHistoryMode: false,
};
@ -360,17 +363,24 @@ define([
// Categories dislayed in the menu
// _WORKGROUP_ : do not display unsorted
var displayedCategories = [ROOT, TRASH, SEARCH, RECENT];
// PCS enabled: display owned pads
if (AppConfig.displayCreationScreen) { displayedCategories.push(OWNED); }
// Templates enabled: display template category
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]; }
var virtualCategories = [SEARCH, RECENT, OWNED];
var virtualCategories = [SEARCH, RECENT, OWNED, TAGS];
if (!APP.loggedIn) {
displayedCategories = [FILES_DATA];
currentPath = [FILES_DATA];
$tree.hide();
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;
}
}
@ -1295,7 +1305,7 @@ define([
$span.attr('title', name);
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
// Remove the .hide() added by displayThumnail() because it hides the icon in
// list mode too
@ -1443,6 +1453,7 @@ define([
case SEARCH: pName = SEARCH_NAME; break;
case RECENT: pName = RECENT_NAME; break;
case OWNED: pName = OWNED_NAME; break;
case TAGS: pName = TAGS_NAME; break;
default: pName = name;
}
return pName;
@ -1511,18 +1522,14 @@ define([
case OWNED:
msg = Messages.fm_info_owned;
break;
case TAGS:
break;
default:
msg = undefined;
}
if (!APP.loggedIn) {
msg = Messages.fm_info_anonymous;
$box.html(msg);
$box.find('a[target!="_blank"]').click(function (e) {
e.preventDefault();
var href = $(this).attr('href');
common.gotoURL(href);
});
return $box;
return $(common.fixLinks($box.html(msg)));
}
if (!msg || APP.store['hide-info-' + path[0]] === '1') {
$box.hide();
@ -2057,6 +2064,7 @@ define([
$element.data('context', 'default');
$container.append($element);
});
createGhostIcon($container);
};
var displayTrashRoot = function ($list, $folderHeader, $fileHeader) {
@ -2100,7 +2108,7 @@ define([
var parsed = Hash.parsePadUrl(href);
var $table = $('<table>');
var $icon = $('<td>', {'rowspan': '3', 'class': 'cp-app-drive-search-icon'})
.append(getFileIcon(href));
.append(getFileIcon(r.id));
var $title = $('<td>', {
'class': 'cp-app-drive-search-col1 cp-app-drive-search-title'
}).text(r.data.title)
@ -2140,6 +2148,13 @@ define([
}
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
$('<tr>').append($icon).append($title).append($typeName).append($type).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)
// NOTE: Elements in the trash are not using the same storage structure as the others
// _WORKGROUP_ : do not change the lastOpenedFolder value in localStorage
@ -2259,10 +2303,9 @@ define([
var isTrashRoot = filesOp.comparePath(path, [TRASH]);
var isTemplate = filesOp.comparePath(path, [TEMPLATE]);
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 isSearch = path[0] === SEARCH;
var isTags = path[0] === TAGS;
var root = isVirtual ? undefined : filesOp.find(path);
if (!isVirtual && typeof(root) === "undefined") {
@ -2296,7 +2339,7 @@ define([
var $dirContent = $('<div>', {id: FOLDER_CONTENT_ID});
$dirContent.data('path', path);
if (!isSearch) {
if (!isSearch && !isTags) {
var mode = getViewMode();
if (mode) {
$dirContent.addClass(getViewModeClass());
@ -2358,10 +2401,12 @@ define([
displayTrashRoot($list, $folderHeader, $fileHeader);
} else if (isSearch) {
displaySearch($list, path[1]);
} else if (isRecent) {
} else if (path[0] === RECENT) {
displayRecent($list);
} else if (isOwned) {
} else if (path[0] === OWNED) {
displayOwned($list);
} else if (isTags) {
displayTags($list);
} else {
$dirContent.contextmenu(openContextMenu('content'));
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 $icon = filesOp.isFolderEmpty(files[TRASH]) ? $trashEmptyIcon.clone() : $trashIcon.clone();
var isOpened = filesOp.comparePath(path, currentPath);
@ -2534,29 +2560,11 @@ define([
$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 createSearch = function ($container) {
var isInSearch = currentPath[0] === SEARCH;
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',
type: 'text',
draggable: false,
@ -2606,6 +2614,38 @@ define([
$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 () {
var $categories = $tree.find('.cp-app-drive-tree-categories-container');
var s = $categories.scrollTop() || 0;
@ -2614,11 +2654,12 @@ define([
if (displayedCategories.indexOf(SEARCH) !== -1) { createSearch($tree); }
var $div = $('<div>', {'class': 'cp-app-drive-tree-categories-container'})
.appendTo($tree);
if (displayedCategories.indexOf(RECENT) !== -1) { createRecent($div, [RECENT]); }
if (displayedCategories.indexOf(OWNED) !== -1) { createOwned($div, [OWNED]); }
if (displayedCategories.indexOf(TAGS) !== -1) { createCategory($div, TAGS); }
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(TEMPLATE) !== -1) { createTemplate($div, [TEMPLATE]); }
if (displayedCategories.indexOf(FILES_DATA) !== -1) { createAllFiles($div, [FILES_DATA]); }
if (displayedCategories.indexOf(TEMPLATE) !== -1) { createCategory($div, TEMPLATE); }
if (displayedCategories.indexOf(FILES_DATA) !== -1) { createCategory($div, FILES_DATA); }
if (displayedCategories.indexOf(TRASH) !== -1) { createTrash($div, [TRASH]); }
$tree.append(APP.$limit);
@ -2657,9 +2698,9 @@ define([
if (parsed.hashData.type !== "pad") { return; }
var i = data.href.indexOf('#') + 1;
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; }
var viewHash = Hash.getViewHashFromKeys(hrefsecret.channel, hrefsecret.keys);
var viewHash = Hash.getViewHashFromKeys(hrefsecret);
return base + viewHash;
};
@ -2672,7 +2713,7 @@ define([
}
});
var getProperties = function (el, cb) {
var getProperties = APP.getProperties = function (el, cb) {
if (!filesOp.isFile(el)) {
return void cb('NOT_FILE');
}
@ -2724,24 +2765,6 @@ define([
$(window).focus();
if (!res) { return; }
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) {

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

@ -1,26 +1,17 @@
@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/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.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,
@warn-color: @colortheme_file-warn,
@color: @colortheme_file-color
);
.fileupload_main();
.alertify_main();
.tokenfield_main();
@button-border: 2px;
/*html, body {
margin: 0px;
height: 100%;
}*/
// body
display: flex;
flex-flow: column;

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

@ -16,7 +16,7 @@ define([
'/bower_components/file-saver/FileSaver.min.js',
'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',
], function (
@ -61,6 +61,7 @@ define([
if (!priv.filehash) {
uploadMode = true;
} else {
// PASSWORD_FILES
secret = Hash.getSecrets('file', priv.filehash);
if (!secret.keys) { throw new Error("You need a hash"); }
hexFileName = Util.base64ToHex(secret.channel);
@ -124,6 +125,12 @@ define([
var rightsideDisplayed = false;
$(window.document).on('decryption', function (e) {
/* FIXME
we're listening for decryption events and assuming that only
the main media-tag exists. In practice there is also your avatar
and there could be other things in the future, so we should
figure out a generic way target media-tag decryption events.
*/
var decrypted = e.originalEvent;
if (decrypted.callback) {
decrypted.callback();
@ -133,9 +140,6 @@ define([
$dlform.hide();
var $dlButton = $dlview.find('media-tag button');
if (ev) { $dlButton.click(); }
if (!$dlButton.length) {
$appContainer.css('background', 'white');
}
$dlButton.addClass('btn btn-success');
var text = Messages.download_mt_button + '<br>';
text += '<b>' + Util.fixHTML(title) + '</b><br>';
@ -230,8 +234,7 @@ define([
if (typeof(sizeMb) === 'number' && sizeMb < 5) { return void onClick(); }
$dlform.find('#cp-app-file-dlfile, #cp-app-file-dlprogress').click(onClick);
};
var href = priv.origin + priv.pathname + priv.filehash;
common.getFileSize(href, function (e, data) {
common.getFileSize(hexFileName, function (e, data) {
if (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/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tippy.less';
.iconColors_main();
.fileupload_main();
.alertify_main();
.tippy_main();
#cp-filepicker-dialog {
display: none;
@ -36,7 +38,8 @@
line-height: 1em;
cursor: pointer;
background-color: #111;
background-color: @colortheme_modal-bg;
box-shadow: 2px 2px 5px #000;
color: @darker;
transition: all 0.1s;

@ -11,7 +11,7 @@ define([
'/customize/messages.js',
'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',
], function (
$,
@ -40,6 +40,7 @@ define([
var parsed = Hash.parsePadUrl(data.url);
hideFileDialog();
if (parsed.type === 'file') {
// PASSWORD_FILES
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
sframeChan.event("EV_FILE_PICKED", {
@ -138,7 +139,7 @@ define([
});
// Add thumbnail if it exists
common.displayThumbnail(data.href, $span);
common.displayThumbnail(data.href, data.channel, $span);
});
$input.focus();
};

@ -8,7 +8,7 @@ define([
'/common/outer/local-store.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 () {
var $main = $('#mainBlock');

@ -3,6 +3,43 @@
<head>
<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>
<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>
<body class="cp-app-pad">
<textarea style="display:none" id="editor1" name="editor1"></textarea>

@ -36,7 +36,7 @@ define([
'/bower_components/diff-dom/diffDOM.js',
'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',
], function (
$,
@ -82,12 +82,54 @@ define([
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) {
return !(el && typeof(el.getAttribute) === 'function' &&
el.getAttribute('class') &&
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) {
/* catch `type="_moz"` before it goes over the wire */
var brFilter = function (hj) {
@ -100,6 +142,7 @@ define([
};
brFilter(hj);
mediatagContentFilter(hj);
widgetFilter(hj);
return hj;
};
@ -168,6 +211,36 @@ define([
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
our forbidden tag types: script, iframe, object,
@ -199,20 +272,26 @@ define([
if (info.node && info.node.tagName === 'SPAN' &&
info.node.getAttribute('contentEditable') === "false") {
// 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') {
// and you're about to remove it...
// this probably isn't what you want
/*
I have never seen this in the console, but the
magic line is still getting removed on remote
edits. This suggests that it's getting removed
by something other than diffDom.
*/
console.log("preventing removal of the magic line!");
// return true to prevent diff application
return true;
if (!info.node.getAttribute('class') ||
!/cke_widget_wrapper/.test(info.node.getAttribute('class'))) {
// This element is not a widget!
// this probably isn't what you want
/*
I have never seen this in the console, but the
magic line is still getting removed on remote
edits. This suggests that it's getting removed
by something other than diffDom.
*/
console.log("preventing removal of the magic line!");
// return true to prevent diff application
return true;
}
}
}
@ -322,7 +401,7 @@ define([
var src = tag.getAttribute('src');
if (mediaTagMap[src]) {
mediaTagMap[src].forEach(function (n) {
tag.appendChild(n);
tag.appendChild(n.cloneNode());
});
}
});
@ -370,8 +449,11 @@ define([
framework.setMediaTagEmbedder(function ($mt) {
$mt.attr('contenteditable', 'false');
$mt.attr('tabindex', '1');
editor.insertElement(new window.CKEDITOR.dom.element($mt[0]));
//$mt.attr('tabindex', '1');
//MEDIATAG
var element = new window.CKEDITOR.dom.element($mt[0]);
editor.insertElement(element);
editor.widgets.initOn( element, 'mediatag' );
});
framework.setTitleRecommender(function () {
@ -403,7 +485,18 @@ define([
var patch = (DD).diff(inner, userDocStateDom);
(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);
// MEDIATAG: Initialize mediatag widgets inserted in the document by other users
editor.widgets.checkWidgets();
if (framework.isReadOnly()) {
var $links = $(inner).find('a');
// off so that we don't end up with multiple identical handlers
@ -425,7 +518,7 @@ define([
framework.setContentGetter(function () {
displayMediaTags(framework, inner, mediaTagMap);
inner.normalize();
return Hyperjson.fromDOM(inner, isNotMagicLine, hjsonFilters);
return Hyperjson.fromDOM(inner, shouldSerialize, hjsonFilters);
});
$bar.find('#cke_1_toolbar_collapser').hide();
@ -459,11 +552,15 @@ define([
ckeditor: editor,
body: $('body'),
onUploaded: function (ev, data) {
// PASSWORD_FILES
var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
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>';
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);
@ -508,6 +605,8 @@ define([
Util.blobToImage($(el).data('blob'), waitFor(function (imgSrc) {
$clone.find('media-tag[src="' + $(el).attr('src') + '"] img')
.attr('src', imgSrc);
$clone.find('media-tag').parent()
.find('.cke_widget_drag_handler_container').remove();
}));
});
}).nThen(function () {

@ -23,7 +23,13 @@ CKEDITOR.dialog.add('mediatag', function (editor) {
},
],
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 dialog = this.parts.contents.$;
var inputs = dialog.querySelectorAll('input');
@ -34,7 +40,14 @@ CKEDITOR.dialog.add('mediatag', function (editor) {
},
onOk: function() {
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 inputs = dialog.querySelectorAll('input');
var wInput = inputs[0];

@ -10,7 +10,7 @@
( function() {
CKEDITOR.plugins.add( 'mediatag', {
requires: 'dialog',
requires: 'dialog,widget',
//icons: 'image',
//hidpi: true,
onLoad: function () {
@ -38,144 +38,17 @@
// Register the dialog.
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}',
required = 'media-tag[data-crypto-key,src]';
editor.widgets.add( 'mediatag', {
// Register the command.
editor.addCommand( pluginName, new CKEDITOR.dialogCommand( pluginName, {
allowedContent: allowed,
requiredContent: required,
contentTransformations: [
[ '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';
getLabel: function () { return " "; },
dialog: pluginName,
inline: true,
upcast: function( element ) {
return element.name === 'media-tag';
}
});
// 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/toolbar.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/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,
@warn-color: @colortheme_poll-warn,
@color: @colortheme_poll-color
);
.fileupload_main();
.alertify_main();
.tokenfield_main();
.creation_main();
@poll-fore: #555;

@ -2,7 +2,6 @@ define([
'jquery',
'/common/toolbar3.js',
'/common/common-util.js',
'/common/cryptget.js',
'/bower_components/nthen/index.js',
'/common/sframe-common.js',
'/common/common-realtime.js',
@ -26,13 +25,12 @@ define([
'/bower_components/file-saver/FileSaver.min.js',
'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',
], function (
$,
Toolbar,
Util,
Cryptget,
nThen,
SFCommon,
CommonRealtime,

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

Loading…
Cancel
Save