Big manual merge

pull/1/head
Caleb James DeLisle 7 years ago
commit 0d1f19f5d4

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 48 KiB

@ -18,7 +18,7 @@ module.exports = {
httpHeaders: {
"X-XSS-Protection": "1; mode=block",
"X-Content-Type-Options": "nosniff",
// 'X-Frame-Options': 'SAMEORIGIN',
'X-Frame-Options': 'SAMEORIGIN',
},
contentSecurity: [
@ -249,7 +249,7 @@ module.exports = {
/* Setting this value to anything other than true will cause file upload
* attempts to be rejected outright.
*/
enableUploads: false,
enableUploads: true,
/* If you have enabled file upload, you have the option of restricting it
* to a list of users identified by their public keys. If this value is set

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 KiB

After

Width:  |  Height:  |  Size: 207 KiB

@ -4,7 +4,7 @@ define(function() {
/* Select the buttons displayed on the main page to create new collaborative sessions
* Existing types : pad, code, poll, slide
*/
config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'whiteboard', 'file', 'contacts'];
config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'whiteboard', 'file', 'todo', 'contacts'];
config.registeredOnlyTypes = ['file', 'contacts'];
/* Cryptpad apps use a common API to display notifications to users
@ -49,6 +49,7 @@ define(function() {
users.
*/
config.loginSalt = '';
config.badStateTimeout = 30000;
return config;
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

@ -151,10 +151,12 @@ define(req, function($, Default, Language) {
messages._applyTranslation = function () {
$('[data-localization]').each(translateText);
$('[data-localization-append]').each(translateAppend);
$('#pad-iframe').contents().find('[data-localization]').each(translateText);
$('[data-localization-title]').each(translateTitle);
$('[data-localization-placeholder]').each(translatePlaceholder);
$('#pad-iframe').contents().find('[data-localization]').each(translateText);
$('#pad-iframe').contents().find('[data-localization-append]').each(translateAppend);
$('#pad-iframe').contents().find('[data-localization-title]').each(translateTitle);
$('#pad-iframe').contents().find('[data-localization-placeholder]').each(translatePlaceholder);
};
messages.driveReadme = '["BODY",{"class":"cke_editable cke_editable_themed cke_contents_ltr cke_show_borders","contenteditable":"true","spellcheck":"false","style":"color: rgb(51, 51, 51);"},' +

@ -572,5 +572,13 @@ define([
];
};
Pages['/todo/'] = Pages['/todo/index.html'] = function () {
return [
h('div#toolbar'),
h('div#container'),
loadingScreen()
];
};
return Pages;
});

@ -1,7 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<script src="respond.js"></script>
</head>
<body>

@ -1,173 +0,0 @@
(function () {
var Frame = {};
var uid = function () {
return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
.toString(32).replace(/\./g, '');
};
// create an invisible iframe with a given source
// append it to a parent element
// execute a callback when it has loaded
Frame.create = function (parent, src, onload, timeout) {
var iframe = document.createElement('iframe');
timeout = timeout || 10000;
var to = window.setTimeout(function () {
onload('[timeoutError] could not load iframe at ' + src);
}, timeout);
iframe.setAttribute('id', 'cors-store');
iframe.onload = function (e) {
onload(void 0, iframe, e);
window.clearTimeout(to);
};
// We must pass a unique parameter here to avoid cache problems in Firefox with
// the NoScript plugin: if the iframe's content is taken from the cache, the JS
// is not executed with NoScript....
iframe.setAttribute('src', src + '?t=' + new Date().getTime());
parent.appendChild(iframe);
};
/* given an iframe with an rpc script loaded, create a frame object
with an asynchronous 'send' method */
Frame.open = function (e, A, timeout) {
var win = e.contentWindow;
var frame = {};
var listeners = {};
var timeouts = {};
timeout = timeout || 5000;
frame.accepts = function (o) {
return A.some(function (e) {
switch (typeof(e)) {
case 'string': return e === o;
case 'object': return e.test(o);
}
});
};
var changeHandlers = frame.changeHandlers = [];
frame.change = function (f) {
if (typeof(f) !== 'function') {
throw new Error('[Frame.change] expected callback');
}
changeHandlers.push(f);
};
var _listener = function (e) {
if (!frame.accepts(e.origin)) {
console.log("message from %s rejected!", e.origin);
return;
}
var message = JSON.parse(e.data);
var uid = message._uid;
var error = message.error;
var data = message.data;
if (!uid) {
console.log("No uid!");
return;
}
if (uid === 'change' && changeHandlers.length) {
changeHandlers.forEach(function (f) {
f(data);
});
return;
}
if (timeouts[uid]) {
window.clearTimeout(timeouts[uid]);
}
if (listeners[uid]) {
listeners[uid](error, data, e);
delete listeners[uid];
}
};
window.addEventListener('message', _listener);
frame.close = function () {
window.removeEventListener('message', _listener);
};
/* method (string): (set|get|remove)
key (string)
data (string)
cb (function) */
var send = frame.send = function (method, key, data, cb) {
var req = {
method: method,
key: key,
data: data,
};
var id = req._uid = uid();
// uid must not equal 'change'
while(id === 'change') {
id = req._uid = uid();
}
if (typeof(cb) === 'function') {
//console.log("setting callback!");
listeners[id] = cb;
//console.log("setting timeout of %sms", timeout);
timeouts[id] = window.setTimeout(function () {
// when the callback is executed it will clear this timeout
cb('[TimeoutError] request timed out after ' + timeout + 'ms');
}, timeout);
} else {
console.log(typeof(cb));
}
win.postMessage(JSON.stringify(req), '*');
};
frame.set = function (key, val, cb) {
send('set', key, val, cb);
};
frame.setBatch = function (map, cb) {
send('batchset', void 0, map, cb);
};
frame.get = function (key, cb) {
send('get', key, void 0, cb);
};
frame.getBatch = function (keys, cb) {
send('batchget', void 0, keys, cb);
};
frame.remove = function (key, cb) {
send('remove', key, void 0, cb);
};
frame.removeBatch = function (keys, cb) {
send('batchremove', void 0, keys, cb);
};
frame.keys = function (cb) {
send('keys', void 0, void 0, cb);
};
return frame;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = Frame;
} else if (typeof(define) === 'function' && define.amd) {
define(['jquery'], function () {
return Frame;
});
} else {
window.Frame = Frame;
}
}());

@ -1,93 +0,0 @@
var validDomains = [
/cryptpad.fr$/i,
];
var isValidDomain = function (o) {
return validDomains.some(function (e) {
switch (typeof(e)) {
case 'string': return e === o;
case 'object': return e.test(o);
}
});
};
var isArray = function (o) { return Object.prototype.toString.call(o) === '[object Array]'; };
window.addEventListener('message', function(e) {
if (!isValidDomain(e.origin)) { return; }
var payload = JSON.parse(e.data);
var parent = window.parent;
var respond = function (error, data) {
var res = {
_uid: payload._uid,
error: error,
data: data,
};
parent.postMessage(JSON.stringify(res), '*');
};
//console.log(payload);
switch(payload.method) {
case 'set':
localStorage.setItem(payload.key, JSON.stringify(payload.data));
respond();
break;
case 'batchset':
if (isArray(payload.data) || typeof(payload.data) !== 'object') {
respond('[batchset.TypeError] expected key-value pairs to set');
return;
}
Object.keys(payload.data).forEach(function (k) {
localStorage.setItem(k, JSON.stringify(payload.data[k]));
});
respond();
break;
case 'get':
respond(void 0, JSON.parse(localStorage.getItem(payload.key)));
break;
case 'batchget':
if (!isArray(payload.data)) {
respond('[batchget.TypeError] expected array of keys to return');
return;
}
var map = {};
payload.data.forEach(function (k) {
map[k] = JSON.parse(localStorage.getItem(k));
});
respond(void 0, map);
break;
case 'remove':
//console.log("Removing %s from localStorage", payload.key);
localStorage.removeItem(payload.key);
respond();
break;
case 'batchremove':
if (!isArray(payload.data)) {
respond('[batchremove.TypeError] expected array of keys to remove');
return;
}
payload.data.forEach(function (k) {
localStorage.removeItem(k);
});
respond();
break;
case 'keys':
respond(void 0, Object.keys(localStorage));
break;
case undefined:
respond('No method supplied');
break;
}
});
window.addEventListener('storage', function (ev) {
parent.postMessage(JSON.stringify({
_uid: 'change',
data: {
key: ev.key,
oldValue: ev.oldValue,
newValue: ev.newValue,
}
}), '*');
});

@ -1,28 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<script data-main="test" src="/bower_components/requirejs/require.js"></script>
<style>
#status { border: 5px solid red; }
#status.working { border: 5px solid green; }
</style>
</head>
<body>
<p>This page opens up an iframe which connects to beta.cryptpad.fr</p>
<p>the idea is that some other subdomain of cryptpad.fr should be able to use this technique to open up a connection
to beta.cryptpad.fr and read from its local storage, so that we can make a seamless transition from beta
to www. or *.cryptpad.fr.</p>
<div id="status">
<p>If this box turns green, you've configured your scripts correctly.</p>
<p>If your scripts are not working correctly, check that:
<ol>
<li>/customize/share/respond.js is configured to respond to requests from your domain (see the validDomains variable)</li>
<li>the script attempting to connect to this endpoint is configured to listen for responses from it (in this case main.js)</li>
</ol>
</div>

@ -1,124 +0,0 @@
define([
'jquery',
'/customize/share/frame.js'
], function ($, Frame) {
var domain = 'https://beta.cryptpad.fr';
var path = '/customize/share/frame.html';
var acceptResponseFrom = [
/cryptpad.fr$/
];
var lock = 0;
var unlock = function (i) {
lock--;
console.log("Test #%s passed", i + 1);
if (!lock) { $('#status').addClass('working'); }
};
var runTest = function (test, i) {
lock++;
test(i);
};
var randInt = function () {
return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
};
var handleErr = function (err) {
if (err) {
console.error(err);
return true;
}
};
var areNull = function (keys, data) {
return !keys.some(function (k) { return data[k] !== null; });
};
Frame.create(document.body, domain + path, function (err, iframe) {
if (handleErr(err)) { return; }
console.log("Created iframe");
// open a channel into the frame, accept messages from (sub)domain(s)
var frame = window.Beta = Frame.open(iframe, acceptResponseFrom);
/* Run your actual tests */
[function (i) { // test #1
var pew = randInt();
frame.set('pew', pew, function (err) {
if (handleErr(err)) { return; }
frame.get('pew', function (err, num) {
if (handleErr(err)) { return; }
if (pew === num) {
frame.remove('pew', function (err) {
if (handleErr(err)) { return; }
frame.get('pew', function (err, data) {
if (handleErr(err)) { return; }
if (data !== null) { return; }
unlock(i);
});
});
}
});
});
}, function (i) { // test #2
var map = {
bang: randInt(),
pow: randInt(),
lol: randInt(),
};
var keys = Object.keys(map);
frame.setBatch(map, function (err) {
if (handleErr(err)) { return; }
frame.getBatch(keys, function (err) {
if (handleErr(err)) { return; }
frame.removeBatch(Object.keys(map), function (err) {
if (handleErr(err)) { return; }
frame.getBatch(keys, function (err, data) {
if (areNull(keys, data)) { unlock(i); }
});
});
});
});
}, function (i) { // test #3
var map = {
bang2: true,
pow2: true,
lol2: true,
};
var keys = Object.keys(map);
// set some keys to arbitrary values
frame.setBatch(map, function (err) {
if (handleErr(err)) { return; }
// remove those values
frame.removeBatch(keys, function (err) {
if (handleErr(err)) { return; }
// check that they were actually removed
frame.getBatch(keys, function (err, data) {
if (handleErr(err)) { return; }
// all keys should be null after you've removed them
if (areNull(keys, data)) {
unlock(i);
return;
}
console.log("Expected all keys to return null");
});
});
});
}].forEach(runTest);
});
});

@ -582,7 +582,7 @@ noscript {
margin-top: 20px;
.cryptpad-limit-bar {
display: inline-block;
max-width: 40vw;
max-width: 100%;
margin: 3px;
box-sizing: border-box;
border: 1px solid #999;

@ -138,3 +138,75 @@
background: white;
}
}
.fileIcon {
li {
display: inline-block;
margin: 10px 10px;
width: 140px;
height: 140px;
text-align: center;
vertical-align: top;
overflow: hidden;
text-overflow: ellipsis;
padding-top: 5px;
padding-bottom: 5px;
&:not(.selected):not(.selectedTmp) {
border: 1px solid #CCC;
}
.name {
width: 100%;
height: 48px;
margin: 8px 0;
display: inline-block;
//align-items: center;
//justify-content: center;
overflow: hidden;
//text-overflow: ellipsis;
word-wrap: break-word;
}
.truncated {
display: block;
position: absolute;
bottom: 0px;
left: 0; right: 0;
text-align: center;
}
img.icon {
height: 48px;
max-height: none;
max-width: none;
margin: 8px 0;
}
.fa {
display: block;
margin: auto;
font-size: 48px;
margin: 8px 0;
text-align: center;
&.listonly {
display: none;
}
}
}
}
.sidebarButton {
button.btn {
background-color: @button-bg;
border-color: darken(@button-bg, 10%);
color: white;
&:hover {
background-color: darken(@button-bg, 10%);
}
&.btn-danger {
background-color: @button-red-bg;
border-color: darken(@button-red-bg, 10%);
color: white;
&:hover {
background-color: darken(@button-red-bg, 10%);
}
}
}
}

@ -7,9 +7,6 @@
@description-color: #777;
@button-width: 400px;
@button-bg: #3066e5;
@button-alt-bg: #fff;
@button-red-bg: #e54e4e;
.cp {
@ -72,25 +69,10 @@
border-left: 0px;
}
}
button.btn {
background-color: @button-bg;
border-color: darken(@button-bg, 10%);
color: white;
&:hover {
background-color: darken(@button-bg, 10%);
}
&.btn-danger {
background-color: @button-red-bg;
border-color: darken(@button-red-bg, 10%);
color: white;
&:hover {
background-color: darken(@button-red-bg, 10%);
}
}
}
&>div {
margin: 10px 0;
}
.sidebarButton;
}
}
}

@ -29,6 +29,7 @@
.settingsColor { color: @toolbar-settings-bg; }
.profileColor { color: @toolbar-settings-bg; }
.defaultColor { color: @toolbar-default-bg; }
.todoColor { color:@toolbar-todo-bg; }
.toolbar-container {
display: flex;
@ -284,6 +285,11 @@ body {
@color: @toolbar-profile-color;
.addToolbarColors(@color, @bgcolor);
}
&.app-todo {
@bgcolor: @toolbar-todo-bg;
@color: @toolbar-todo-color;
.addToolbarColors(@color, @bgcolor);
}
}

@ -109,7 +109,8 @@
@toolbar-settings-color: @colortheme_settings-color;
@toolbar-profile-bg: @colortheme_profile-bg;
@toolbar-profile-color: @colortheme_profile-color;
@toolbar-todo-bg: #7bccd1;
@toolbar-todo-color: #000;
@topbar-back: #fff;
@topbar-color: #000;
@ -129,6 +130,10 @@
@category-bg: #f4f4f4;
@button-bg: #3066e5;
@button-alt-bg: #fff;
@button-red-bg: #e54e4e;
.unselectable () {
-webkit-touch-callout: none;
-webkit-user-select: none;

@ -12,7 +12,7 @@ $(function () {
var Messages = Cryptpad.Messages;
var $body = $('body');
var isMainApp = function () {
return /^\/(pad|code|slide|poll|whiteboard|file|media|contacts|drive|settings|profile)\/$/.test(location.pathname);
return /^\/(pad|code|slide|poll|whiteboard|file|media|contacts|drive|settings|profile|todo)\/$/.test(location.pathname);
};
var rightLink = function (ref, loc, txt) {
@ -112,7 +112,7 @@ $(function () {
])
])
]),
h('div.version-footer', "CryptPad v1.11.0 (Lutin)")
h('div.version-footer', "CryptPad v1.12.0 (Minotaur)")
]));
var pathname = location.pathname;
@ -166,6 +166,9 @@ $(function () {
} else if (/^\/profile\//.test(pathname)) {
$('body').append(h('body', Pages[pathname]()).innerHTML);
require([ '/profile/main.js', ], ready);
} else if (/^\/todo\//.test(pathname)) {
$('body').append(h('body', Pages[pathname]()).innerHTML);
require([ '/todo/main.js', ], ready);
}
});

@ -5,8 +5,8 @@
// the language dropdowns that are shown throughout Cryptpad's interface
out._languageName = 'German';
out.main_title = "Cryptpad: Echtzeitzusammenarbeit, ohne Vorwissen";
out.main_slogan = "Einigkeit macht stark - Zusammenarbeit ist der Schlüssel"; // Der Slogan sollte evtl. besser englisch bleiben.
out.main_title = "Cryptpad: Echtzeitzusammenarbeit, ohne Preisgabe von Informationen";
out.main_slogan = "Einigkeit ist Stärke - Zusammenarbeit der Schlüssel"; // Der Slogan sollte evtl. besser englisch bleiben.
out.type = {};
out.type.pad = 'Pad';
@ -44,16 +44,16 @@
out.changeNamePrompt = 'Ändere deinen Namen (oder lasse dieses Feld leer um anonym mitzuarbeiten): ';
out.clickToEdit = "Zum bearbeiten klicken";
out.clickToEdit = "Zum Bearbeiten klicken";
out.forgetButtonTitle = 'Entferne dieses Dokumnt von deiner Startseitenliste';
out.forgetButtonTitle = 'Entferne dieses Dokument von deiner Startseitenliste';
out.forgetPrompt = 'Mit dem Klick auf OK wird das Dokument aus deinem lokalen Speicher gelöscht. Fortfahren?';
out.shareButton = 'Teilen';
out.shareSuccess = 'URL wurde in die Zwischenablage kopiert';
out.shareSuccess = 'Die URL wurde in die Zwischenablage kopiert';
out.presentButtonTitle = "Präsentationsmodus starten";
out.presentSuccess = 'Hit ESC to exit presentation mode';
out.presentSuccess = 'Drücke ESC um den Präsentationsmodus zu verlassen!';
out.backgroundButtonTitle = 'Die Hintergrundfarbe der Präsentation ändern';
out.colorButtonTitle = 'Die Textfarbe im Präsentationsmodus ändern';
@ -73,36 +73,43 @@
out.tryIt = 'Probier\'s aus!';
out.okButton = 'OK (enter)';
out.cancelButton = 'Abbrechen (esc)';
out.okButton = 'OK (Enter)';
out.cancelButton = 'Abbrechen (ESC)';
// Polls
out.poll_title = "Kenntnisfreier Datumsplaner";
out.poll_subtitle = "Kenntnisfreies, <em>echtzeit</em> planen";
out.poll_title = "Datumsplaner ohne Preisgabe von Infos";
out.poll_subtitle = "<em>Echtzeit</em>-planen ohne Preisgabe von Infos";
out.poll_p_save = "Deine Einstellungen werden sofort automatisch gesichtert.";
out.poll_p_save = "Deine Einstellungen werden sofort automatisch gesichert.";
out.poll_p_encryption = "Alle Eingaben sind verschlüsselt, deshalb haben nur Leute im Besitz des Links Zugriff. Selbst der Server sieht nicht was du änderst.";
out.wizardLog = "Klicke auf den Button links oben um zur Umfrage zurückzukehren.";
out.wizardTitle = "Nutze den Assistenten um deine Umfrage zu erstellen.";
out.wizardConfirm = "Bist du wirklich bereit die angegebenen Optionen bereits zu deiner Umfrage hinzuzufügen?";
out.poll_closeWizardButton = "Assistenten schließen";
out.poll_closeWizardButtonTitle = "Assistenten schließen";
out.poll_publish_button = "Veröffentlichen";
out.poll_admin_button = "Admin";
out.poll_create_user = "Neuen Benutzer hinzufügen";
out.poll_create_option = "Neue Option hinzufügen";
out.poll_commit = "Einchecken";
out.poll_closeWizardButton = "Assistent schließen";
out.poll_closeWizardButtonTitle = "Assistent schließen";
out.poll_wizardComputeButton = "Optionen übernehmen";
out.poll_wizardClearButton = "Tabelle leeren";
out.poll_wizardDescription = "Erstellt automatisch die Optionen indem eine beliebige Anzahl von Daten und Zeiten eingegeben wird.";
out.poll_wizardDescription = "Erstelle die Optionen automatisch, indem du eine beliebige Anzahl von Daten und Zeiten eingibst.";
out.poll_wizardAddDateButton = "+ Daten";
out.poll_wizardAddTimeButton = "+ Zeiten";
out.poll_optionPlaceholder = "Option";
out.poll_userPlaceholder = "Dein Name";
out.poll_removeOption = "Bist du sicher, dass du diese Option entfernen möchtest?";
out.poll_removeUser = "Bist du sicher, dass du diese Nutzer*in entfernen möchtest?";
out.poll_removeUser = "Bist du sicher, dass du diese(n) Nutzer*in entfernen möchtest?";
out.poll_titleHint = "Titel";
out.poll_descriptionHint = "Beschreibung";
out.poll_descriptionHint = "Beschreibe deine Abstimmung und publiziere sie mit dem 'Veröffentlichen'-Knopf wenn du fertig bis. Jeder mit dem Link kann die Beschreibung ändern.";
// index.html
@ -112,10 +119,10 @@
out.main_about_p2 = 'Für Fragen und Kommentare kannst du uns <a href="https://twitter.com/cryptpad">tweeten</a>, ein Ticket <a href="https://github.com/xwiki-labs/cryptpad/issues/" title="our issue tracker">auf Github öffnen</a>, hi auf irc sagen (<a href="http://webchat.freenode.net?channels=%23cryptpad&uio=MT1mYWxzZSY5PXRydWUmMTE9Mjg3JjE1PXRydWUe7" title="freenode webchat">irc.freenode.net</a>), oder <a href="mailto:research@xwiki.com">eine Mail zukommen lassen</a>.';
out.button_newpad = 'NEUES WYSIWYG-PAD ERSTELLEN';
out.button_newcode = 'NEUES CODE-PAD ERSTELLEN';
out.button_newpoll = 'NEUE ABSTIMMUNG ERSTELLEN';
out.button_newslide = 'NEUE PRÄSENTATION ERSTELLEN';
out.button_newpad = 'Neues WYSIWYG-Pad erstellen';
out.button_newcode = 'Neues Code-Pad erstellen';
out.button_newpoll = 'Neue Abstimmung erstellen';
out.button_newslide = 'Neue Präsentation erstellen';
// privacy.html

@ -18,7 +18,7 @@ define(function () {
out.synchronizing = "Sincronización";
out.reconnecting = "Reconectando...";
out.lag = "Retraso";
out.readonly = 'Solo lectura';
out.readonly = 'Sólo lectura';
out.anonymous = 'Anónimo';
out.yourself = "Tú mismo";
out.anonymousUsers = "usuarios anónimos";
@ -30,8 +30,8 @@ define(function () {
out.editor = "editor";
out.editors = "editores";
out.greenLight = "Todo funciona bién";
out.orangeLight = "La conexión es lenta y podria impactar la experiencia";
out.greenLight = "Todo funciona bien";
out.orangeLight = "La conexión es lenta y podría afectar la experiencia";
out.redLight = "Has sido desconectado de la sesión";
out.importButtonTitle = 'Importar un documento de tus archivos locales';
@ -44,7 +44,7 @@ define(function () {
out.clickToEdit = "Haz clic para cambiar";
out.forgetButtonTitle = 'Eliminar este documento de la lista en la pagina de inicio';
out.forgetPrompt = 'Pulser OK eliminará este documento del almacenamiento local (localStorage), ¿estás seguro?';
out.forgetPrompt = 'Pulsar OK eliminará este documento del almacenamiento local (localStorage), ¿estás seguro?';
out.shareButton = 'Compartir';
out.shareSuccess = 'URL copiada al portapapeles';
@ -57,10 +57,10 @@ define(function () {
out.editShare = "URL de edición compartida";
out.editShareTitle = "Copiar la URL de edición al portapapeles";
out.viewShare = "Compartir URL de solo lectura";
out.viewShareTitle = "Copiar la URL de solo lectura al portapapeles";
out.viewShare = "Compartir URL de sólo lectura";
out.viewShareTitle = "Copiar la URL de sólo lectura al portapapeles";
out.viewOpen = "Ver en pestaña nueva";
out.viewOpenTitle = "Abrir el documento en solo lectura en una pestaña nueva";
out.viewOpenTitle = "Abrir el documento en sólo lectura en una pestaña nueva";
out.notifyJoined = "{0} se ha unido a la sesión de colaboración";
out.notifyRenamed = "{0} ahora se conoce como {1}";
@ -76,10 +76,10 @@ define(function () {
out.poll_title = "Selector de fecha Zero Knowledge";
out.poll_subtitle = "Agenda en <em>tiempo real</em> Zero Knowledge";
out.poll_p_save = "Tus configuraciones se actualizan instantaneamente, no es necesario guardar cambios.";
out.poll_p_encryption = "Todos los datos entrados son cifrados, solo las personas que poseen el enlace tienen acceso. Incluso el servidor no puede ver el contenido.";
out.poll_p_save = "Tus configuraciones se actualizan instantáneamente, no es necesario guardar cambios.";
out.poll_p_encryption = "Todos los datos entrados son cifrados, sólo las personas que poseen el enlace tienen acceso. Incluso el servidor no puede ver el contenido.";
out.wizardLog = "Presiona el boton en la parte superior izquierda para volver a la encuesta";
out.wizardLog = "Presiona el botón en la parte superior izquierda para volver a la encuesta";
out.wizardTitle = "Utiliza el asistente para crear tu encuesta";
out.wizardConfirm = "¿Estás realmente seguro de agregar estas opciones a tu encuesta?";
@ -87,14 +87,14 @@ define(function () {
out.poll_closeWizardButtonTitle = "Cerrar el asistente";
out.poll_wizardComputeButton = "Generar opciones";
out.poll_wizardClearButton = "Limpiar tabla";
out.poll_wizardDescription = "Automaticamente crear opciones ingresando cualquier cantidad de fechas y horas";
out.poll_wizardDescription = "Crear opciones automáticamente ingresando cualquier cantidad de fechas y horas";
out.poll_wizardAddDateButton = "+ Fechas";
out.poll_wizardAddTimeButton = "+ Horas";
out.poll_optionPlaceholder = "Opción";
out.poll_userPlaceholder = "Tu nombre";
out.poll_removeOption = "¿Estás seguro que quieres eliminar esta opción?";
out.poll_removeUser = "¿Estás seguro que quieres eliminar este usuario?";
out.poll_removeOption = "¿Estás seguro de que quieres eliminar esta opción?";
out.poll_removeUser = "¿Estás seguro de que quieres eliminar este usuario?";
out.poll_titleHint = "Título";
out.poll_descriptionHint = "Descripción";
@ -104,9 +104,13 @@ define(function () {
out.main_p2 = 'Este proyecto utiliza el editor de texto visual <a href="http://ckeditor.com/">CKEditor</a>, <a href="https://codemirror.net/">CodeMirror</a>, y el motor en tiempo real <a href="https://github.com/xwiki-contrib/chainpad">ChainPad</a>.';
out.main_howitworks = '¿Cómo funciona?';
out.main_howitworks_p1 = "CryptPad utiliza una variante del algoritmo de <a href='https://en.wikipedia.org/wiki/Operational_transformation'>transformación operacional</a> (página en inglés) que es capaz de encontrar un consenso distribuido usando un <a href='https://bitcoin.org/bitcoin.pdf'>Blockchain Nakamoto</a> (página en inglés), popularizado por <a href='https://es.wikipedia.org/wiki/Bitcoin'>Bitcoin</a>. De esta manera el algoritmo puede evitar la necesidad de un servidor central para resolver conflictos de edición de la transformación operacional y sin necesidad de resolver conflictos, el servidor puede mantenerse inconsciente del contenido que se está editando en el pad.";
<<<<<<< HEAD
out.main_about_p2 = 'Si tienes preguntas o comentarios, puedes <a href="https://twitter.com/cryptpad"><i class="fa fa-twitter"></i>enviarnos un tweet</a>, abrir un issue <a href="https://github.com/xwiki-labs/cryptpad/issues/" title="nuestro issue tracker">en <i class="fa fa-github"></i>GitHub</a>. saludarnos en <a href="https://riot.im/app/#/room/#cryptpad:matrix.org" title="Matrix">nuestro <i class="fa fa-comment"></i>canal Matrix</a> o en IRC (#cryptpad on irc.freenode.net), o <a href="mailto:research@xwiki.com"><i class="fa fa-envelope"></i>envianos un email</a>.';
=======
out.main_about_p2 = 'Si tienes preguntas o comentarios, puedes <a href="https://twitter.com/cryptpad">enviarnos un tweet</a>, abrir un issue <a href="https://github.com/xwiki-labs/cryptpad/issues/" title="nuestro issue tracker">en GitHub</a>, saludarnos en nuestro canal IRC (<a href="http://webchat.freenode.net?channels=%23cryptpad&uio=MT1mYWxzZSY5PXRydWUmMTE9Mjg3JjE1PXRydWUe7" title="freenode webchat">irc.freenode.net</a>), o <a href="mailto:research@xwiki.com">envíanos un email</a>.';
>>>>>>> fb13e656b7b3ee611bf195a1f1ccf7475f2f1ee4
out.button_newpad = 'Crear nuevo pad de texto enriquezido';
out.button_newpad = 'Crear nuevo pad de texto enriquecido';
out.button_newcode = 'Crear nuevo pad de código';
out.button_newpoll = 'Crear nueva encuesta';
out.button_newslide = 'Crear nueva presentación';
@ -115,15 +119,15 @@ define(function () {
out.policy_title = 'Política de privacidad Cryptpad';
out.policy_whatweknow = 'Qué sabemos sobre tí';
out.policy_whatweknow_p1 = 'Como cualquier aplicación que está en la red, Cryptpad tiene acceso a los metadatos expuestos por el protócolo HTTP. Esto incluye tu dirección IP, y otros headers HTTP que pueden ser utilizados para identificar a tu navegador propio. Puedes ver la información que comparte tu navegador visitando <a target="_blank" rel="noopener noreferrer" href="https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending" title="Que headers HTTP esta compartiendo mi navegador">WhatIsMyBrowser.com</a> (página en inglés).';
out.policy_whatweknow_p2 = 'Nosotros usamos <a href="https://piwik.org/" target="_blank" rel="noopener noreferrer" title="open source analytics platform">Piwik</a>, una plataforma de analítica de datos abierta, para mejor conocer a nuestros usuarios. Piwik nos dice como encontráste Cryptpad, en entrada manual, por un motor de busquéda, or por referal de otra página como Reddit o Twitter. También aprendemos cuándo visitas, que páginas vees en nuestra web, y cuánto tiempo te quedas en cada una.';
out.policy_whatweknow_p1 = 'Como cualquier aplicación que está en la red, Cryptpad tiene acceso a los metadatos expuestos por el protocolo HTTP. Esto incluye tu dirección IP, y otros headers HTTP que pueden ser utilizados para identificar a tu navegador propio. Puedes ver la información que comparte tu navegador visitando <a target="_blank" rel="noopener noreferrer" href="https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending" title="Qué headers HTTP esta compartiendo mi navegador">WhatIsMyBrowser.com</a> (página en inglés).';
out.policy_whatweknow_p2 = 'Nosotros usamos <a href="https://piwik.org/" target="_blank" rel="noopener noreferrer" title="open source analytics platform">Piwik</a>, una plataforma de analítica de datos abierta, para conocer mejor a nuestros usuarios. Piwik nos dice como encontraste Cryptpad, en entrada manual, por un motor de búsqueda, or por referal de otra página como Reddit o Twitter. También aprendemos cuándo visitas, qué páginas ves en nuestra web, y cuánto tiempo te quedas en cada una.';
out.policy_howweuse = 'Cómo usamos lo que aprendemos';
out.policy_howweuse_p1 = 'Usamos esta información para tomar mejores decisiones para promocionar Cryptpad, para evaluar cuáles de nuestros esfuerzos han sido exitosos. La información sobre tu ubicación nos permite saber si deberíamos considerar mejor soporte para idiomas diferentes al inglés.';
out.policy_howweuse_p2 = "La información sobre tu navegador (en escritorio u movil) nos ayuda a saber qué caracteristicas que debemos mejorar. Nuestro equipo de desarrollo es pequeño, e intentamos tomar decisiones que beneficien a la experiencia de la mayoria de nuestros usuarios.";
out.policy_howweuse_p2 = "La información sobre tu navegador (en escritorio o móvil) nos ayuda a saber qué características que debemos mejorar. Nuestro equipo de desarrollo es pequeño, e intentamos tomar decisiones que beneficien a la experiencia de la mayoría de nuestros usuarios.";
out.policy_whatwetell = 'Lo que decimos a otros sobre tí';
out.policy_whatwetell_p1 = 'No suministramos la información que colectamos a terceros a menos de ser legalmente obligados a hacerlo.';
out.policy_whatwetell_p1 = 'No suministramos la información que recolectamos a terceros a menos de estar legalmente obligados a hacerlo.';
out.policy_links = 'Enlaces a otras páginas';
out.policy_links_p1 = 'Esta web contiene enlaces a otros sitios, incluyendo algunos producidos por otras organizaciones. No somos responsables por el tratamiento de la privacidad de los datos y el contenido de páginas externas. Como regla general, los enlaces externos se abren en una nueva pestaña del navegador, para clarificar que estás abandonando a Cryptpad.fr.';
out.policy_links_p1 = 'Esta web contiene enlaces a otros sitios, incluyendo algunos producidos por otras organizaciones. No somos responsables del tratamiento de la privacidad de los datos ni el contenido de páginas externas. Como regla general, los enlaces externos se abren en una nueva pestaña del navegador, para clarificar que estás abandonando a Cryptpad.fr.';
out.policy_ads = 'Anuncios';
out.policy_ads_p1 = 'Nosotros no mostramos anuncios, pero podemos poner enlaces a las organizaciones que financian nuestro trabajo de investigación.';
out.policy_choices = 'Lo que puedes hacer';
@ -135,8 +139,8 @@ define(function () {
out.tos_title = "Condiciones de servicio Cryptpad";
out.tos_legal = "Por favor, no seas malicioso, abusivo o hagas algo ilegal.";
out.tos_availability = "Esperamos que este servicio te parezca util, pero nuestra disponibilidad o rendimiento no pueden ser garantizados. Por favor, exporta tus datos regularmente.";
out.tos_e2ee = "Los documentos Cryptpad pueden ser leidos o modificados por cualquiera que pueda adivinar o que pueda tener el enlace. Recomendamos que utilizes mensajes cifrados de punto a punto (e2ee) para compartir URLs, no asumimos ninguna responsabilidad en el evento de alguna fuga.";
out.tos_availability = "Esperamos que este servicio te parezca útil, pero nuestra disponibilidad o rendimiento no pueden ser garantizados. Por favor, exporta tus datos regularmente.";
out.tos_e2ee = "Los documentos Cryptpad pueden ser leídos o modificados por cualquiera que pueda adivinar o que pueda tener el enlace. Recomendamos que utilices mensajes cifrados de punto a punto (e2ee) para compartir URLs, no asumimos ninguna responsabilidad en el evento de alguna fuga.";
out.tos_logs = "Los metadatos entregados por el navegador al servidor pueden ser almacenados para la mantenencia del servicio.";
out.tos_3rdparties = "No proveemos datos individualizados a terceros a menos de ser obligados por la ley.";
@ -152,8 +156,8 @@ define(function () {
out.header_logoTitle = 'Ir a la página principal';
out.websocketError = "Error al conectarse al servidor WebSocket";
out.typeError = "Este documento no es compatible con la applicación seleccionada";
out.onLogout = "Tu sesión está cerrada, <a href=\"/\" target=\"_blank\">haz clic aquí</a> para iniciar sesión<br>o apreta sobre <em>Escape</em> para acceder al documento en modo solo lectura.";
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.loading = "Cargando...";
out.error = "Error";
out.language = "Idioma";
@ -185,37 +189,37 @@ define(function () {
out.fm_creation = "Creación";
out.fm_forbidden = "Acción prohibida";
out.fm_originalPath = "Enlace original";
out.fm_noname = "Documento sín título";
out.fm_emptyTrashDialog = "¿Seguro qué quieres vaciar la papelera?";
out.fm_removeSeveralPermanentlyDialog = "¿Seguro qué quieres eliminar estos {0} elementos de la papelera para siempre?";
out.fm_removePermanentlyDialog = "¿Seguro qué quieres eliminar este elemento para siempre?";
out.fm_removeSeveralDialog = "¿Seguro qué quieres mover estos {0} elementos a la papelera?";
out.fm_removeDialog = "¿Seguro qué quieres mover {0} a la papelera?";
out.fm_noname = "Documento sin título";
out.fm_emptyTrashDialog = "¿Seguro que quieres vaciar la papelera?";
out.fm_removeSeveralPermanentlyDialog = "¿Seguro que quieres eliminar estos {0} elementos de la papelera para siempre?";
out.fm_removePermanentlyDialog = "¿Seguro que quieres eliminar este elemento para siempre?";
out.fm_removeSeveralDialog = "¿Seguro que quieres mover estos {0} elementos a la papelera?";
out.fm_removeDialog = "¿Seguro que quieres mover {0} a la papelera?";
out.fm_restoreDialog = "¿Seguro que quieres recuperar {0}?";
out.fm_unknownFolderError = "La carpeta seleccionada ya no existe. Abriendo la carpeta anterior...";
out.fm_contextMenuError = "No se puedo abrir el menú para este elemento. Si persiste el problema, recarga la página.";
out.fm_selectError = "No se puedo abrir el elemento. Si persiste el problema, recarga la página.";
out.fm_contextMenuError = "No se pudo abrir el menú para este elemento. Si persiste el problema, recarga la página.";
out.fm_selectError = "No se pudo abrir el elemento. Si persiste el problema, recarga la página.";
out.fm_info_root = "Crea carpetas aquí para organizar tus documentos.";
out.fm_info_unsorted = "Contiene todos los documentos que has visitado que no estan organizados en \"Documentos\" o movidos a la \"Papelera\".";
out.fm_info_unsorted = "Contiene todos los documentos que has visitado que no están organizados en \"Documentos\" o movidos a la \"Papelera\".";
out.fm_info_template = "Contiene todas las plantillas que puedes volver a usar para crear nuevos documentos.";
out.fm_info_allFiles = "Contiene todos los archivos de \"Documentos\", \"Sin organizar\" y \"Papelera\". No puedes mover o eliminar archivos aquí.";
out.fm_alert_backupUrl = "Enlace de copia de seguridad para este drive. Te recomendamos <strong>muy fuertemente</strong> que lo guardes secreto.<br>Lo puedes usar para recuparar todos tus archivos en el caso que la memoria de tu navegador se borre.<br>Cualquiera con este enlace puede editar o eliminar todos los archivos en el explorador.<br>";
out.fm_alert_backupUrl = "Enlace de copia de seguridad para este drive. Te recomendamos <strong>encarecidamente</strong> que lo guardes secreto.<br>Lo puedes usar para recuperar todos tus archivos en el caso que la memoria de tu navegador se borre.<br>Cualquiera con este enlace puede editar o eliminar todos los archivos en el explorador.<br>";
out.fm_backup_title = "Enlace de copia de seguridad";
out.fm_nameFile = "¿Cómo quieres nombrar este archivo?";
out.fc_newfolder = "Nueva carpeta";
out.fc_rename = "Cambiar nombre";
out.fc_open = "Abrir";
out.fc_open_ro = "Abrir (solo lectura)";
out.fc_open_ro = "Abrir (sólo lectura)";
out.fc_delete = "Eliminar";
out.fc_restore = "Recuperar";
out.fc_remove = "Eliminar para siempre";
out.fc_empty = "Vaciar la papelera";
out.fc_prop = "Propriedades";
out.fc_prop = "Propiedades";
out.fo_moveUnsortedError = "No puedes mover una carpeta en la lista de documentos no organizados";
out.fo_existingNameError = "Nombre ya utilizado en esta carpeta. Por favor elige otro.";
out.fo_moveFolderToChildError = "No puedes mover una carpeta en una de sus subcarpetas";
out.fo_unableToRestore = "No se pudo restaurar este archivo a la localización de orígen. Puedes intentar moverlo a otra localización.";
out.fo_unavailableName = "Un archivo o carpeta ya tiene este nombre. Cambiálo y vuelve a intentarlo.";
out.fo_unableToRestore = "No se pudo restaurar este archivo a la localización de origen. Puedes intentar moverlo a otra localización.";
out.fo_unavailableName = "Un archivo o carpeta ya tiene este nombre. Cámbialo y vuelve a intentarlo.";
out.login_login = "Iniciar sesión";
out.login_makeAPad = "Crear documento anónimo";
out.login_nologin = "Ver documentos locales";
@ -226,52 +230,52 @@ define(function () {
out.login_password = "Contraseña";
out.login_confirm = "Confirmar contraseña";
out.login_remember = "Recuérdame";
out.login_hashing = "Tratamiento de datos, esto puede tardar un poco.";
out.login_hashing = "Generando hash de tu contraseña, esto puede tardar un poco.";
out.login_hello = "Hola {0},";
out.login_helloNoName = "Hola,";
out.login_accessDrive = "Acceder a tu drive";
out.login_orNoLogin = "o";
out.login_noSuchUser = "Credenciales invalidos. Inténtalo de nuevo, o registrate";
out.login_invalUser = "Nombre de usuario requirido";
out.login_invalPass = "Contraseña requirida";
out.login_unhandledError = "Un error inesperado se produjo :(";
out.login_noSuchUser = "Credenciales inválidos. Inténtalo de nuevo, o regístrate";
out.login_invalUser = "Nombre de usuario requerido";
out.login_invalPass = "Contraseña requerida";
out.login_unhandledError = "Ha ocurrido un error inesperado :(";
out.register_importRecent = "Importar historial (recomendado)";
out.register_acceptTerms = "Accepto los <a href='/terms.html' tabindex='-1'>términos de servicio</a>";
out.register_acceptTerms = "Acepto los <a href='/terms.html' tabindex='-1'>términos de servicio</a>";
out.register_passwordsDontMatch = "Las contraseñas no corresponden";
out.register_mustAcceptTerms = "Tienes que acceptar los términos de servicio";
out.register_mustAcceptTerms = "Tienes que aceptar los términos de servicio";
out.register_mustRememberPass = "No podemos reiniciar tu contraseña si la olvidas. ¡Es muy importante que la recuerdes! Marca la casilla para confirmarlo.";
out.register_header = "Bienvenido a CryptPad";
out.register_explanation = ["<p>Vamos a ver algunas cosas antes</p>", "<ul>", "<li>Tu contraseña es tu clave secreta que cifra todos tus documentos. Si la pierdes no podremos recuparar tus datos.</li>", "<li>Puedes importar documentos que has visto recientemente en tu navegador para tenerlos en tu cuenta.</li>", "<li>Si estás usando un ordenador compartido, tienes que cerrar sesión cuando terminas, cerrar la pestaña no es suficiente.</li>", "</ul>"].join('');
out.register_explanation = ["<p>Vamos a ver algunas cosas antes</p>", "<ul>", "<li>Tu contraseña es tu clave secreta que cifra todos tus documentos. Si la pierdes no podremos recuperar tus datos.</li>", "<li>Puedes importar documentos que has visto recientemente en tu navegador para tenerlos en tu cuenta.</li>", "<li>Si estás usando un ordenador compartido, tienes que cerrar sesión cuando terminas, cerrar la pestaña no es suficiente.</li>", "</ul>"].join('');
out.settings_title = "Preferencias";
out.settings_save = "Guardar";
out.settings_backupTitle = "Copia de seguridad";
out.settings_backup = "Copia de seguridad";
out.settings_restore = "Recuparar datos";
out.settings_restore = "Recuperar datos";
out.settings_reset = "Quita todos los documentos de tu CryptDrive";
out.settings_resetPrompt = "Esta acción eliminará todos tus documentos.<br>¿Seguro que quieres continuar?<br>Introduce “<em>I love CryptPad</em>” para confirmar.";
out.settings_resetDone = "¡Tu drive ahora está vacio!";
out.settings_resetTips = "Consejos en CryptDrive";
out.settings_resetTipsButton = "Restaurar consejos";
out.settings_resetTipsDone = "Todos los consejos ahora están visibles";
out.main_info = "<h1>Collabora en Confidencia</h1><br>Cultiva ideas juntos con documentos compartidos con tecnología <strong>Zero Knowledge</strong> que protege tu privacidad.";
out.main_info = "<h1>Colabora con Confianza</h1><br>Cultiva ideas juntos con documentos compartidos con tecnología <strong>Zero Knowledge</strong> que protege tu privacidad.";
out.main_zeroKnowledge = "Zero Knowledge";
out.main_zeroKnowledge_p = "No tienes que confiar que <em>no</em> veremos tus documentos, con la tecnología Zero Knowledge de CryptPad <em>no podemos</em>. Aprende más sobre como protegemos tu <a href=\"/privacy.html\" title='Privacidad'>Privacidad y Seguridad</a>.";
out.main_zeroKnowledge_p = "No tienes que confiar en que <em>no</em> veremos tus documentos, con la tecnología Zero Knowledge de CryptPad <em>no podemos</em>. Aprende más sobre cómo protegemos tu <a href=\"/privacy.html\" title='Privacidad'>Privacidad y Seguridad</a>.";
out.main_writeItDown = "Escríbelo";
out.main_writeItDown_p = "Los mejores proyectos vienen de las más pequeñas ideas. Escribe tus momentos de inspiración y ideas inesperadas porque nunca sabrás cual será tu próximo descubrimiento.";
out.main_writeItDown_p = "Los mejores proyectos vienen de las más pequeñas ideas. Escribe tus momentos de inspiración e ideas inesperadas porque nunca sabrás cuál será tu próximo descubrimiento.";
out.main_share = "Comparte el enlace, comparte el pad";
out.main_share_p = "Cultiva ideas juntos: ten reuniones eficaces, collabora en listas y haz presentaciones rápidas en todos tus dispositivos.";
out.main_organize = "Organizate";
out.main_organize_p = "Con CryptPad Drive, porta tu atención en lo más importante. Carpetas te permiten organizar tus proyectos y tener una visión global de donde van las cosas.";
out.main_richText = "Editor de Texto Enriquezido";
out.main_richText_p = "Collabora en texto enriquezido con nuestro editor Zero Knowledge en tiempo real <a href=\"http://ckeditor.com\" target=\"_blank\">CkEditor</a>.";
out.main_share_p = "Cultiva ideas juntos: ten reuniones eficaces, colabora en listas y haz presentaciones rápidas en todos tus dispositivos.";
out.main_organize = "Organízate";
out.main_organize_p = "Con CryptPad Drive, mantén tu atención en lo más importante. Las carpetas te permiten organizar tus proyectos y tener una visión global de dónde van las cosas.";
out.main_richText = "Editor de Texto Enriquecido";
out.main_richText_p = "Colabora en texto enriquecido con nuestro editor Zero Knowledge en tiempo real <a href=\"http://ckeditor.com\" target=\"_blank\">CkEditor</a>.";
out.main_code = "Editor de código";
out.main_code_p = "Edita código fuente para tus programas con nuestro editor Zero Knowledge en tiempo real <a href=\"https://www.codemirror.net\" target=\"_blank\">CodeMirror</a>.";
out.main_slide = "Editor de presentación";
out.main_slide_p = "Crea presentaciones utilizando Markdown, y visualizalos en tu navegador";
out.main_slide_p = "Crea presentaciones utilizando Markdown, y visualízalos en tu navegador";
out.main_poll = "Encuestas";
out.main_poll_p = "Planifica tus reuniones y eventos, o vota para la mejor solución a un problema.";
out.main_drive = "CryptDrive";
out.footer_applications = "Applicaciones";
out.footer_applications = "Aplicaciones";
out.footer_contact = "Contacto";
out.footer_aboutUs = "Acerca de nosotros";
out.about = "Acerca de nosotros";
@ -282,49 +286,49 @@ define(function () {
// 1.1.0 - Bunyip
out.movedToTrash = "Este pad fue movido a la papelera.<br><a href\"/drive/\">Acceder a mi Drive</a>";
out.fm_newFile = "Nuevo pad";
out.fm_type = "Típo";
out.fm_type = "Tipo";
out.fm_categoryError = "No se pudo abrir la categoría seleccionada, mostrando la raíz.";
out.settings_userFeedbackHint1 = "CryptPad suministra informaciones muy básicas al servidor, para ayudarnos a mejorar vuestra experiencia.";
out.settings_userFeedbackHint2 = "El contenido de tu pad nunca será compartido con el servidor.";
out.settings_userFeedback = "Activar feedback";
out.settings_anonymous = "No has iniciado sesión. Tus ajustes se aplicarán solo a este navegador.";
out.settings_anonymous = "No has iniciado sesión. Tus ajustes se aplicarán sólo a este navegador.";
out.blog = "Blog";
out.initialState = [
'<span style="font-size:18px;"><p>',
'Esto es&nbsp;<strong>CryptPad</strong>, el editor collaborativo en tiempo real Zero Knowledge. Todo está guardado cuando escribes.',
'Esto es&nbsp;<strong>CryptPad</strong>, el editor colaborativo en tiempo real Zero Knowledge. Todo está guardado cuando escribes.',
'<br>',
'Comparte el enlace a este pad para editar con amigos o utiliza el botón <span class="fa fa-share-alt" style="border: 1px solid black;color:#000;">&nbsp;Compartir&nbsp;</span> para obtener un <em>enlace solo lectura</em>&nbsp;que permite leer pero no escribir.',
'Comparte el enlace a este pad para editar con amigos o utiliza el botón <span class="fa fa-share-alt" style="border: 1px solid black;color:#000;">&nbsp;Compartir&nbsp;</span> para obtener un <em>enlace sólo lectura</em>&nbsp;que permite leer pero no escribir.',
'</p>',
'<p><em>',
'Vamos, solo empezia a escribir...',
'Vamos, empieza a escribir...',
'</em></p></span>',
'<p>&nbsp;<br></p>'
].join('');
out.codeInitialState = "/*\n Esto es CryptPad, el editor collaborativo en tiempo real zero knowledge.\n Lo que escribes aquí es cifrado, con lo cual solo las personas con el enlace pueden accederlo.\n Incluso el servidor no puede ver lo que escribes.\n Lo que ves aquí, lo que escuchas aquí, cuando sales, se queda aquí\n*/";
out.slideInitialState = "# CryptSlide\n* Esto es CryptPad, el editor collaborativo en tiempo real zero knowledge.\n* Lo que escribes aquí es cifrado, con lo cual solo las personas con el enlace pueden accederlo.\n* Incluso el servidor no puede ver lo que escribes.\n* Lo que ves aquí, lo que escuchas aquí, cuando sales, se queda aquí\n\n---\n# Como utilizarlo\n1. Escribe tu contenido en Markdown\n - Puedes aprender más sobre Markdown [aquí](http://www.markdowntutorial.com/)\n2. Separa tus slides con ---\n3. Haz clic en \"Presentar\" para ver el resultado - Tus slides se actualizan en tiempo real";
out.codeInitialState = "/*\n Esto es CryptPad, el editor colaborativo en tiempo real zero knowledge.\n Lo que escribes aquí está cifrado de manera que sólo las personas con el enlace pueden acceder a ello.\n Incluso el servidor no puede ver lo que escribes.\n Lo que ves aquí, lo que escuchas aquí, cuando sales, se queda aquí\n*/";
out.slideInitialState = "# CryptSlide\n* Esto es CryptPad, el editor colaborativo en tiempo real zero knowledge.\n* Lo que escribes aquí está cifrado de manera que sólo las personas con el enlace pueden acceder a ello.\n* Incluso el servidor no puede ver lo que escribes.\n* Lo que ves aquí, lo que escuchas aquí, cuando sales, se queda aquí\n\n---\n# Cómo utilizarlo\n1. Escribe tu contenido en Markdown\n - Puedes aprender más sobre Markdown [aquí](http://www.markdowntutorial.com/)\n2. Separa tus diapositivas con ---\n3. Haz clic en \"Presentar\" para ver el resultado - Tus diapositivas se actualizan en tiempo real";
out.driveReadmeTitle = "¿Qué es CryptDrive?";
out.readme_welcome = "¡Bienvenido a CryptPad!";
out.readme_p1 = "Bienvenido a CryptPad, aquí podrás tomar nota de cosas sólo u con otra gente.";
out.readme_p2 = "Este pad es un guía rapida para aprender a usar a CryptPad para tomar notas, organizarlas y trabajar con más personas.";
out.readme_cat1 = "Aprende a conocer a tu CryptDrive";
out.readme_p1 = "Bienvenido a CryptPad, aquí podrás anotar cosas solo o con otra gente.";
out.readme_p2 = "Este pad es una guía rápida para aprender a usar a CryptPad para tomar notas, organizarlas y trabajar con más personas.";
out.readme_cat1 = "Conoce tu CryptDrive";
out.readme_cat1_l1 = "Crea un pad: En CryptDrive, haz clic en {0} y luego en {1} para crear un pad.";
out.readme_cat1_l2 = "Abrir pads desde CryptDrive: haz doble clic en un icono para abrirlo.";
out.readme_cat1_l3 = "Organiza tus pads: Cuando has iniciado sesión, cada pad que accedes se quedaran en tu drive en {0}.";
out.readme_cat1_l3 = "Organiza tus pads: Cuando has iniciado sesión, cada pad al que accedes se quedará en tu drive en {0}.";
out.readme_cat1_l3_l1 = "Puedes hacer clic y arrastrar archivos en carpetas desde {0}, y crear nuevas carpetas.";
out.readme_cat1_l3_l2 = "Recuerda hacer clic derecho en varios iconos, ya que hay menús addicionales.";
out.readme_cat1_l3_l2 = "Recuerda hacer clic derecho en los iconos, ya que suele haber menús adicionales.";
out.readme_cat1_l4 = "Elimina tus viejos pads: Haz clic y arrastra tus pads en la {0} de la misma manera que lo harías con carpetas.";
out.readme_cat2 = "Haz pads como un pro";
out.edit = "editar";
out.view = "ver";
out.readme_cat2_l1 = "El botón {0} en tu pad te permite dar acceso a collaboradores para {1} o {2} el pad.";
out.readme_cat2_l1 = "El botón {0} en tu pad te permite dar acceso a colaboradores para {1} o {2} el pad.";
out.readme_cat2_l2 = "Cambia el título del pad haciendo clic en el lápiz";
out.readme_cat3 = "Descubre las apps CryptPad";
out.readme_cat3_l1 = "Con el editor de código CryptPad, puedes collaborar en código fuente, como por ejemplo JavaScript y Markdown";
out.readme_cat3_l1 = "Con el editor de código CryptPad, puedes colaborar en código fuente, como por ejemplo JavaScript y Markdown";
out.readme_cat3_l2 = "Con los slides CryptPad, puedes hacer presentaciones rápidas con Markdown";
out.readme_cat3_l3 = "Con CryptPoll puedes tomar votos rápidos, especialmente utíl para programar un horario que conviene a todo el mundo";
out.readme_cat3_l3 = "Con CryptPoll puedes hacer una encuesta rápida, especialmente útil para programar un horario que conviene a todo el mundo";
// 1.2.0 - Chupacabra
@ -337,25 +341,25 @@ define(function () {
out.printDate = "Mostrar la fecha";
out.printTitle = "Mostrar el título";
out.printCSS = "CSS personalizado:";
out.editOpen = "Abrir enlances de edición en pestaña nueva";
out.editOpen = "Abrir enlaces de edición en pestaña nueva";
out.editOpenTitle = "Abrir en modo edición en pestaña nueva";
out.settings_importTitle = "Importar pads recientes locales en CryptDrive";
out.settings_import = "Importar";
out.settings_importConfirm = "¿Seguro qué quieres importar tus pads recientes a tu cuenta CryptDrive?";
out.settings_importConfirm = "¿Seguro que quieres importar tus pads recientes a tu cuenta CryptDrive?";
out.settings_importDone = "Importación terminada";
out.tips = {};
out.tips.lag = "El icono verde en la parte superior derecha muestra la calidad de tu connexión a CryptPad.";
out.tips.shortcuts = "`ctrl+b`, `ctrl+i`, y `ctrl+u` son accesos rápidos para negrita, itálica y subrayado.";
out.tips.indent = "Cuando editas listas, puedes usar tab o shift+tab para icrementar o decrementar indentación.";
out.tips.indent = "Cuando editas listas, puedes usar tab o shift+tab para incrementar o decrementar la sangría.";
out.tips.title = "Puedes cambiar el título de tus pads en la parte superior de la pantalla.";
out.tips.store = "Cada vez que visitas un pad con una sesión iniciada se guardará a tu CryptDrive.";
out.tips.store = "Cada vez que visitas un pad con una sesión iniciada se guardará en tu CryptDrive.";
out.tips.marker = "Puedes resaltar texto en un pad utilizando el \"marcador\" en el menú de estílo.";
out.tips.driveUpload = "Usuarios registrados pueden subir archivos cifrados arrastrandolos hacia CryptDrive.";
out.tips.driveUpload = "Los usuarios registrados pueden subir archivos cifrados arrastrándolos hacia CryptDrive.";
out.feedback_about = "Si estas leyendo esto, quizas estés curioso de saber porqué CryptPad solicita esta página cuando haces algunas acciones";
out.feedback_privacy = "Nos importa tu privacidad, y al mismo tiempo queremos que CryptPad sea muy fácil de usar. Utilizamos esta página para conocer las funcionalidades que importan a nuestros usuarios, pidiendolo con un parametro que nos dice que accion fue realizada.";
out.feedback_optout = "Si quieres darte de baja, visita <a href='/settings/'>tus preferencias</a>, donde podrás activar o desactivar feedback";
out.feedback_about = "Si estas leyendo esto, quizás sientas curiosidad por saber por qué CryptPad solicita páginas cuando realizas algunas acciones";
out.feedback_privacy = "Nos importa tu privacidad, y al mismo tiempo queremos que CryptPad sea muy fácil de usar. Utilizamos este archivo para conocer las funcionalidades que importan a nuestros usuarios, pidiéndolo con un parametro que nos dice qué acción fue realizada.";
out.feedback_optout = "Si quieres darte de baja, visita <a href='/settings/'>tus preferencias</a>, donde podrás activar o desactivar el feedback";
out.fm_searchName = "Buscar";
out.fm_searchPlaceholder = "Buscar...";
@ -369,12 +373,12 @@ define(function () {
// 1.4.0 - Easter Bunny
out.button_newwhiteboard = "Nueva Pizarra";
out.wrongApp = "No se pudo mostrar el contenido de la sessión en tiempo real en tu navigador. Por favor, actualiza la página.";
out.wrongApp = "No se pudo mostrar el contenido de la sesión en tiempo real en tu navegador. Por favor, actualiza la página.";
out.synced = "Todo está guardado.";
out.saveTemplateButton = "Guardar como plantilla";
out.saveTemplatePrompt = "Élige un título para la plantilla";
out.saveTemplatePrompt = "Elige un título para la plantilla";
out.templateSaved = "¡Plantilla guardada!";
out.selectTemplate = "Élige una plantilla o pulsa ESC";
out.selectTemplate = "Elige una plantilla o pulsa ESC";
out.slideOptionsTitle = "Personaliza tus diapositivas";
out.slideOptionsButton = "Guardar (enter)";
out.canvas_clear = "Limpiar";
@ -385,8 +389,8 @@ define(function () {
out.canvas_opacity = "Opacidad";
out.settings_publicSigningKey = "Clave de Firma Pública";
out.settings_usage = "Utilización";
out.settings_usageTitle = "Vee el uso total de tus pads en MB";
out.settings_pinningNotAvailable = "Los pads pegados solo están disponibles para usuarios registrados.";
out.settings_usageTitle = "Ve el uso total de tus pads en MB";
out.settings_pinningNotAvailable = "Los pads pegados sólo están disponibles para usuarios registrados.";
out.settings_pinningError = "Algo salió mal";
out.settings_usageAmount = "Tus pads pegados utilizan {0}MB";
out.historyButton = "Mostrar el historial del documento";
@ -397,9 +401,9 @@ define(function () {
out.history_closeTitle = "Cerrar el historial";
out.history_restore = "Restaurar";
out.history_restoreTitle = "Restaurar la versión seleccionada del documento";
out.history_restorePrompt = "¿Estás seguro que quieres cambiar la versión actual del documento por esta?";
out.history_restorePrompt = "¿Estás seguro de que quieres cambiar la versión actual del documento por ésta?";
out.history_restoreDone = "Documento restaurado";
out.fc_sizeInKilobytes = "Talla en Kilobytes";
out.fc_sizeInKilobytes = "Tamaño en Kilobytes";
// 1.5.0/1.6.0 - Fenrir/Grootslang
@ -415,15 +419,15 @@ define(function () {
out.formattedGB = "{0} GB";
out.formattedKB = "{0} KB";
out.pinLimitReached = "Has llegado al limite de espacio";
out.pinLimitNotPinned = "Has llegado al limite de espacio.<br>Este pad no estará presente en tu CryptDrive.";
out.pinLimitDrive = "Has llegado al limite de espacio.<br>No puedes crear nuevos pads.";
out.pinLimitReached = "Has llegado al límite de espacio";
out.pinLimitNotPinned = "Has llegado al límite de espacio.<br>Este pad no estará presente en tu CryptDrive.";
out.pinLimitDrive = "Has llegado al límite de espacio.<br>No puedes crear nuevos pads.";
out.printTransition = "Activar transiciones";
out.history_version = "Versión: ";
out.settings_logoutEverywhereTitle = "Cerrar sessión en todas partes";
out.settings_logoutEverywhere = "Cerrar todas las otras sessiones";
out.settings_logoutEverywhereConfirm = "¿Estás seguro? Tendrás que volver a iniciar sessión con todos tus dispositivos.";
out.upload_serverError = "Error: no pudimos subir tu archivo.";
out.settings_logoutEverywhereTitle = "Cerrar sesión en todas partes";
out.settings_logoutEverywhere = "Cerrar todas las otras sesiones";
out.settings_logoutEverywhereConfirm = "¿Estás seguro? Tendrás que volver a iniciar sesión con todos tus dispositivos.";
out.upload_serverError = "Error: no se pudo subir tu archivo en este momento.";
out.upload_uploadPending = "Ya tienes una subida en progreso. ¿Cancelar y subir el nuevo archivo?";
out.upload_success = "Tu archivo ({0}) ha sido subido con éxito y fue añadido a tu drive.";
@ -432,11 +436,11 @@ define(function () {
out.newVersion = ["<b>CryptPad ha sido actualizado!</b>",
"Puedes ver lo que ha cambiado aquí (en inglés):",
"<a href=\"https://github.com/xwiki-labs/cryptpad/releases/tag/{0}\" target=\"_blank\">Notas de versión para CryptPad {0}</a>"].join("<br>");
out.pinLimitReachedAlertNoAccounts = "Has llegado a tu limite de espacio";
out.pinLimitReachedAlertNoAccounts = "Has llegado a tu límite de espacio";
out.previewButtonTitle = "Mostrar/esconder la vista previa Markdown";
out.fm_info_anonymous = "No estás conectado, así que estos pads pueden ser borrados (<a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">¿por qué?</a>). <a href=\"/register/\">Registrate</a> o <a href=\"/login/\">Inicia sesión</a> para asegurarlos.";
out.fm_alert_anonymous = "Hola, estás usando CryptPad anónimamente. Está bien, pero tus pads pueden ser borrados después de un périodo de inactividad. Hemos desactivado funciones avanzadas de CryptDrive para usuarios anónimos porque queremos ser claros que no es un lugar seguro para almacenar cosas. Puedes <a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">leer este articulo</a> (en inglés) sobre por qué hacemos esto y por qué deberías <a href=\"/register/\">Registrarte</a> e <a href=\"/login/\">Iniciar sesión</a>.";
out.fm_error_cantPin = "Error del servidor. Por favor, recarga la página e intentalo de nuevo.";
out.fm_info_anonymous = "No estás conectado, así que estos pads pueden ser borrados (<a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">¿por qué?</a>). <a href=\"/register/\">Regístrate</a> o <a href=\"/login/\">Inicia sesión</a> para asegurarlos.";
out.fm_alert_anonymous = "Hola, estás usando CryptPad anónimamente. Está bien, pero tus pads pueden ser borrados después de un périodo de inactividad. Hemos desactivado funciones avanzadas de CryptDrive para usuarios anónimos porque queremos dejar claro que no es un lugar seguro para almacenar cosas. Puedes <a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">leer este articulo</a> (en inglés) acerca de por qué hacemos esto y por qué deberías <a href=\"/register/\">Registrarte</a> e <a href=\"/login/\">Iniciar sesión</a>.";
out.fm_error_cantPin = "Error del servidor. Por favor, recarga la página e inténtalo de nuevo.";
out.upload_notEnoughSpace = "No tienes suficiente espacio para este archivo en tu CryptDrive";
out.upload_tooLarge = "Este archivo supera el límite de carga.";
out.upload_choose = "Escoge un archivo";
@ -446,7 +450,7 @@ define(function () {
out.upload_size = "Tamaño";
out.upload_progress = "Progreso";
out.download_button = "Descifrar y descargar";
out.warn_notPinned = "Este pad no está en ningun CryptDrive. Expirará después de 3 meses. <a href='/about.html#pinning'>Acerca de...</a>";
out.warn_notPinned = "Este pad no está en ningún CryptDrive. Expirará después de 3 meses. <a href='/about.html#pinning'>Acerca de...</a>";
out.poll_remove = "Quitar";
out.poll_edit = "Editar";
@ -458,13 +462,13 @@ define(function () {
// 1.8.0 - Idopogo
out.common_connectionLost = "<b>Connexión perdida</b><br>El documento está ahora en modo solo lectura hasta que la conexión vuelva.";
out.common_connectionLost = "<b>Conexión perdida</b><br>El documento está ahora en modo sólo lectura hasta que la conexión vuelva.";
out.updated_0_common_connectionLost = out.common_connectionLost;
out.supportCryptpad = "Ayudar a CryptPad";
out.pinLimitReachedAlert = ["Has llegado a tu limite de espacio. Nuevos pads no serán guardados en tu CryptDrive.",
out.pinLimitReachedAlert = ["Has llegado a tu límite de espacio. Los nuevos pads no serán guardados en tu CryptDrive.",
"Puedes eliminar pads de tu CryptDrive o <a href=\"https://accounts.cryptpad.fr/#!on={0}\" target=\"_blank\">suscribirte a una oferta premium</a> para obtener más espacio."].join("<br>");
out.updated_0_pinLimitReachedAlert = out.pinLimitReachedAlert;
out.fm_info_trash = "Vacía tu papelera para liberar espaci en tu CryptDrive.";
out.fm_info_trash = "Vacía tu papelera para liberar espacio en tu CryptDrive.";
out.updated_0_fm_info_trash = out.fm_info_trash;
out.fs_migration = "Tu CryptDrive fue actualizado a una nueva versión.<br><strong>Por favor, recarga la página.</strong>";
@ -496,14 +500,14 @@ define(function () {
out.slideOptionsText = "Opciones";
out.historyText = "Historial";
out.openLinkInNewTab = "Abrir enlace en pestaña nueva";
out.profileButton = "Perfíl";
out.profileButton = "Perfil";
out.profile_urlPlaceholder = "URL";
out.profile_namePlaceholder = "Nombre mostrado en su perfíl";
out.profile_avatar = "Imágen";
out.profile_upload = "Subir una imágen";
out.profile_error = "Error al crear tu perfíl: {0}";
out.profile_register = "Tienes que registrarte para crear perfíl";
out.profile_create = "Crear perfíl";
out.profile_namePlaceholder = "Nombre mostrado en su perfil";
out.profile_avatar = "Imagen";
out.profile_upload = "Subir una imagen";
out.profile_error = "Error al crear tu perfil: {0}";
out.profile_register = "Tienes que registrarte para crear un perfil";
out.profile_create = "Crear perfil";
out.profile_description = "Descripción";
out.profile_fieldSaved = "Guardado: {0}";
out.download_mt_button = "Descargar";
@ -512,23 +516,23 @@ define(function () {
// 1.11.0 - Lutin
out.realtime_unrecoverableError = "El motor de tiempo real a encontrado un error. Haga clic en OK para recargar la página.";
out.realtime_unrecoverableError = "El motor de tiempo real ha encontrado un error. Haga clic en OK para recargar la página.";
out.typing = "Escribiendo";
out.profile_inviteButton = "Connectar";
out.profile_inviteButton = "Conectar";
out.profile_inviteButtonTitle = "Crear un enlace de invitación para este usuario.";
out.profile_inviteExplanation = "Hacer clic en <strong>OK</strong> creará un enlace de mensaje seguro que <em>sólo {0} podrá ver.</em><br><br>El enlace será copiado a tu portapapeles y puede ser compartido publicamente.";
out.profile_viewMyProfile = "Ver mi perfíl";
out.profile_inviteExplanation = "Hacer clic en <strong>OK</strong> creará un enlace de mensaje seguro que <em>sólo {0} podrá ver.</em><br><br>El enlace será copiado a tu portapapeles y puede ser compartido públicamente.";
out.profile_viewMyProfile = "Ver mi perfil";
out.userlist_addAsFriendTitle = 'Agregar "{0}" como contacto';
out.userlist_thisIsYou = 'Tú mismo ("{0}")';
out.contacts_title = "Contactos";
out.contacts_addError = "Error al agregar este contacto a la lista";
out.contacts_added = "Invitación acceptada";
out.contacts_added = "Invitación aceptada";
out.contacts_rejected = "Invitación denegada";
out.contacts_request = "<em>{0}</em> quiere agregarte como contacto. <b>Acceptar</b>?";
out.contacts_request = "<em>{0}</em> quiere agregarte como contacto. ¿<b>Aceptar</b>?";
out.contacts_send = "Enviar";
out.contacts_remove = "Eliminar este contacto";
out.contacts_confirmRemove = "Estás seguro que quieres eliminar <em>{0}</em> de tus contactos?";
out.contacts_info1 = "Estos son tus contactos. De aquí, puedes:";
out.contacts_confirmRemove = "¿Estás seguro de que quieres eliminar <em>{0}</em> de tus contactos?";
out.contacts_info1 = "Estos son tus contactos. Desde aquí, puedes:";
out.contacts_info2 = "Hacer clic en el icono de tu contacto para chatear";
out.contacts_info3 = "Hacer doble-clic para ver su perfil";
out.contacts_info4 = "Cualquier participante puede eliminar definitivamente el historial de chat";
@ -539,11 +543,26 @@ define(function () {
out.settings_resetButton = "Eliminar";
out.settings_resetTipsAction = "Reiniciar";
out.settings_userFeedbackTitle = "Feedback";
out.settings_logoutEverywhereButton = "Cerar sesión";
out.settings_logoutEverywhereButton = "Cerrar sesión";
out.upload_title = "Subir archivo";
<<<<<<< HEAD
// 1.13.0 - Naiad
out.topbar_whatIsCryptpad = "Qué es CryptPad";
=======
// 1.12.0 - Minotaur
out.userlist_pending = "Pendiente...";
out.contacts_typeHere = "Escribe un mensaje aquí...";
out.contacts_removeHistoryTitle = "Borrar el historial de chat";
out.contacts_confirmRemoveHistory = "¿Estás seguro de que quieres borrar el historial de forma permanente? No se podrán recuparar los datos.";
out.contacts_removeHistoryServerError = "Hubo un error al borrar el historial. Inténtalo de nuevo más tarde.";
out.todo_title = "CryptTodo";
out.todo_newTodoNamePlaceholder = "Describe tu tarea...";
out.todo_newTodoNameTitle = "Añadir tarea a la lista";
out.todo_markAsCompleteTitle = "Marcar esta tarea como completa";
out.todo_markAsIncompleteTitle = "Marcar esta tarea como incompleta";
out.todo_removeTaskTitle = "Borrar esta tarea de la lista";
>>>>>>> fb13e656b7b3ee611bf195a1f1ccf7475f2f1ee4
return out;
});

@ -13,6 +13,7 @@ define(function () {
out.type.whiteboard = "Tableau Blanc";
out.type.file = "Fichier";
out.type.media = "Média";
out.type.todo = "Todo";
out.type.contacts = "Contacts";
out.button_newpad = 'Nouveau document texte';
@ -278,6 +279,10 @@ define(function () {
out.contacts_info3 = "Double-cliquer sur son nom pour voir son profil";
out.contacts_info4 = "Chaque participant peut nettoyer définitivement l'historique d'une discussion";
out.contacts_removeHistoryTitle = "Supprimer l'historique du chat";
out.contacts_confirmRemoveHistory = 'Êtes-vous sûr de vouloir supprimer définitivement l\'historique de votre chat ? Les messages ne pourront pas être restaurés.';
out.contacts_removeHistoryServerError = 'Une erreur est survenue lors de la supprimer de l\'historique du chat. Veuillez réessayer plus tard.';
// File manager
out.fm_rootName = "Documents";
@ -465,6 +470,13 @@ define(function () {
out.download_button = "Déchiffrer et télécharger";
out.download_mt_button = "Télécharger";
out.todo_title = "CryptTodo";
out.todo_newTodoNamePlaceholder = "Décrivez votre tâche...";
out.todo_newTodoNameTitle = "Ajouter cette tâche à votre liste";
out.todo_markAsCompleteTitle = "Marquer tâche comme terminée";
out.todo_markAsIncompleteTitle = "Marquer tâche comme non incomplète";
out.todo_removeTaskTitle = "Enlever cette tâche de votre liste";
// 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>";

@ -13,6 +13,7 @@ define(function () {
out.type.whiteboard = 'Whiteboard';
out.type.file = 'File';
out.type.media = 'Media';
out.type.todo = "Todo";
out.type.contacts = 'Contacts';
out.button_newpad = 'New Rich Text pad';
@ -245,6 +246,7 @@ define(function () {
out.canvas_opacity = "Opacity";
out.canvas_opacityLabel = "opacity: {0}";
out.canvas_widthLabel = "Width: {0}";
out.canvas_saveToDrive = "Save this image as a file in your CryptDrive";
// Profile
out.profileButton = "Profile"; // dropdown menu
@ -282,6 +284,10 @@ define(function () {
out.contacts_info3 = "Double-click their icon to view their profile";
out.contacts_info4 = "Either participant can clear permanently a chat history";
out.contacts_removeHistoryTitle = 'Clean the chat history';
out.contacts_confirmRemoveHistory = 'Are you sure you want to permanently remove your chat history? Data cannot be restored';
out.contacts_removeHistoryServerError = 'There was an error while removing your chat history. Try again later';
// File manager
out.fm_rootName = "Documents";
@ -470,6 +476,13 @@ define(function () {
out.download_button = "Decrypt & Download";
out.download_mt_button = "Download";
out.todo_title = "CryptTodo";
out.todo_newTodoNamePlaceholder = "Describe your task...";
out.todo_newTodoNameTitle = "Add this task to your todo list";
out.todo_markAsCompleteTitle = "Mark this task as complete";
out.todo_markAsIncompleteTitle = "Mark this task as incomplete";
out.todo_removeTaskTitle = "Remove this task from your todo list";
// 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>";

@ -1,5 +1,5 @@
// Tradução para protuguês brasileiro efetuada por Gustavo Henrique Machado da Silva (www.linkedin.com/in/gustavohmsilva)
// Embora o software original possa não possuir as mesmas licenças, a tradução produzida por mim is protected under
// Embora o software original possa não possuir as mesmas licenças, a tradução produzida por mim é protegida sob termos
// Creative Commons, Attribution-ShareAlike 4.0 International
// Contate-me via email no endereço gustavohmsilva@member.fsf.org
// Translation to brazilian portuguese done by Gustavo Henrique Machado da Silva (www.linkedin.com/in/gustavohmsilva)
@ -9,8 +9,6 @@
define(function () {
var out = {};
// translations must set this key for their language to be available in
// the language dropdowns that are shown throughout Cryptpad's interface
out._languageName = 'Brazilian Portuguese';
out.main_title = "Cryptpad: Zero Knowledge, Edição Colaborativa em Tempo Real";
@ -22,7 +20,33 @@ define(function () {
out.type.poll = 'votação';
out.type.slide = 'Apresentação';
out.common_connectionLost = 'Conexão Perdida com o servidor';
out.type.drive = 'Drive';
out.type.whiteboard = 'Whiteboard';
out.type.file = 'File';
out.type.media = 'Media';
out.button_newpad = 'Novo bloco RTF';
out.button_newcode = 'Novo bloco de código';
out.button_newpoll = 'Novo questionário';
out.button_newslide = 'Nova apresentação';
out.button_newwhiteboard = 'Novo quadro branco';
// NOTE: We want to update the 'common_connectionLost' key.
// Please do not add a new 'updated_common_connectionLostAndInfo' but change directly the value of 'common_connectionLost'
out.updated_0_common_connectionLost = "<b>Conexão com o Servidor Perdida</b><br>Você agora está em modo somente leitura até a conexão ser restaurada.";
out.common_connectionLost = out.updated_0_common_connectionLost;
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.wrongApp = "Incapaz de mostrar o conteúdo em tempo real no seu navegador. Por favor tente recarregar a página.";
out.loading = "Carregando...";
out.error = "Erro";
out.saved = "Salvo";
out.synced = "Tudo foi salvo";
out.deleted = "Bloco deletado do seu CryptDrive";
out.disconnected = 'Desconectado';
out.synchronizing = 'Sincronizando';
@ -40,45 +64,117 @@ define(function () {
out.editor = "editor";
out.editors = "editores";
out.language = "Lingua";
out.comingSoon = "Em breve...";
out.newVersion = '<b>O CryptPad foi atualizado!</b><br>' +
'Cheque as novidades na última versão:<br>'+
'<a href="https://github.com/xwiki-labs/cryptpad/releases/tag/{0}" target="_blank">Notas da atualização do CryptPad {0}</a>';
out.upgrade = "Upgrade";
out.upgradeTitle = "Faça um upgrade na sua conta para aumentar o limite de armazenamento";
out.MB = "MB";
out.GB = "GB";
out.KB = "KB";
out.formattedMB = "{0} MB";
out.formattedGB = "{0} GB";
out.formattedKB = "{0} KB";
out.greenLight = "Tudo está funcionando bem";
out.orangeLight = "Sua conexão longa pode impactar sua experiência";
out.orangeLight = "Sua conexão lenta pode impactar sua experiência";
out.redLight = "Você está desconectado da sua sessão";
out.pinLimitReached = "Você alcançou o limite de armazenamento";
out.updated_0_pinLimitReachedAlert = "Você alcançou o limite de armazenamento. Novos blocos não serão mais salvos no seu CryptDrive.<br>" +
'Você pode deletar blocos do seu CryptDrive ou <a href="https://accounts.cryptpad.fr/#!on={0}" target="_blank">se inscrever como premium</a> para aumentar o limite de espaço.';
out.pinLimitReachedAlert = out.updated_0_pinLimitReachedAlert;
out.pinAboveLimitAlert = 'A partir desta atualização, nós estamos impondo um limite de 50MB no armazenamento gratuito. Você está atualmente usando {0}. Você irá precisar deletar alguns blocos ou se inscrever no <a href="https://accounts.cryptpad.fr/#!on={1}" target="_blank">accounts.cryptpad.fr</a>. Sua contribuição irá nos ajudar a melhorar o CryptPad e expandir a metodologia Zero Knowledge. Por favor contacte o <a href="https://accounts.cryptpad.fr/#/support" target="_blank">suporte</a> se você possui outras dúvidas.';
out.pinLimitNotPinned = "Você alcançou o limite de armazenamento.<br>"+
"Este bloco não está armazenado no seu CryptDrive.";
out.pinLimitDrive = "Você alcançou o limite de armazenamento.<br>" +
"Você não pode criar novos blocos.";
out.importButtonTitle = 'Importar um documento de um arquivo local';
out.exportButtonTitle = 'Exportar esta sesão para um arquivo local';
out.exportPrompt = 'Como deseja nomeear seu arquivo?';
out.exportPrompt = 'Como deseja nomear seu arquivo?';
out.changeNamePrompt = 'Mude seu nome (deixe em branco para se manter anônimo): ';
out.user_rename = "Mudar nome de exibição";
out.user_displayName = "Nome visível";
out.user_accountName = "Nome da Conta";
out.clickToEdit = "Clique para Editar";
out.forgetButtonTitle = 'Remova este documento da listagem da sua página';
out.forgetPrompt = 'Clicando OK você irá remover o endereço deste bloco de notas do armazenamento local, você tem certeza?';
out.movedToTrash = 'That pad has been moved to the trash.<br><a href="/drive/">Access my Drive</a>';
out.shareButton = 'Compartilhar';
out.shareSuccess = 'Endereço copiado para o clipboard';
out.newButton = 'Novo';
out.newButtonTitle = 'Criar um novo bloco';
out.saveTemplateButton = "Salvar como modelo";
out.saveTemplatePrompt = "Escolha o nome do modelo";
out.templateSaved = "Modelo salvo!";
out.selectTemplate = "Selecione um modelo ou pressione ESC";
out.previewButtonTitle = "Mostrar ou esconder o modo de visualização markdown";
out.presentButtonTitle = "Entrar no modo apresentação";
out.presentSuccess = 'Pressione ESC para sair do modo de apresentação';
out.backgroundButtonTitle = 'Mudar cor do fundo da apresentação';
out.colorButtonTitle = 'Mudar a cor do texto no modo apresentação';
out.printButton = "Imprimir (Enter)";
out.printButtonTitle = "Imprimir seus slides ou exportá-los como PDF";
out.printOptions = "Opções de leiaute";
out.printSlideNumber = "Mostrar o número do slide";
out.printDate = "Mostrar a data";
out.printTitle = "Mostrar título do bloco";
out.printCSS = "Custom style rules (CSS):";
out.printTransition = "Ativar animações de transição";
out.slideOptionsTitle = "Personalizar seus slides";
out.slideOptionsButton = "Salvar (Enter)";
out.editShare = "Compartilhar endereço editável";
out.editShareTitle = "Copiar endereço editável";
out.viewShare = "Compartilhar endereó de visualização";
out.editOpen = "Abrir endereço editável em nova aba";
out.editOpenTitle = "Abrir este bloco em modo editável em nova aba";
out.viewShare = "Compartilhar endereço de visualização";
out.viewShareTitle = "Copiar o endereço somente leitura";
out.viewOpen = "Ver em nova aba";
out.viewOpenTitle = "Abrir o documento em modo somente leitura em nova aba";
out.notifyJoined = "{0} entraram na sessão colaborativa";
out.notifyRenamed = "{0} agora é conhecido como {1}";
out.notifyLeft = "{0} deixou essa sessão colaborativa";
out.tryIt = 'Experimente!';
out.okButton = 'OK (enter)';
out.cancelButton = 'Cancelar (esc)';
out.okButton = 'OK (Enter)';
out.cancel = "Cancelar";
out.cancelButton = 'Cancelar (ESC)';
out.historyButton = "Exibir histórico do documento";
out.history_next = "Ir para próxima versão";
out.history_prev = "Ir para versão anterior";
out.history_goTo = "Ir para versão selecionada";
out.history_close = "Voltar";
out.history_closeTitle = "Fechar o histórico";
out.history_restore = "Restaurar";
out.history_restoreTitle = "Restaurar a versão selecionada do documento";
out.history_restorePrompt = "Você tem certeza que deseja substituir a versão atual do documento pela que está sendo exibida agora?";
out.history_restoreDone = "Documento restaurado";
out.history_version = "Versão:";
out.tryIt = 'Experimente!';
// Polls
@ -92,6 +188,12 @@ define(function () {
out.wizardTitle = "Use o assistente para criar sua enquete";
out.wizardConfirm = "Você está realmente pronto para adicionar estas opções em sua enquete?";
out.poll_publish_button = "Publicar";
out.poll_admin_button = "Admin";
out.poll_create_user = "Adicionar novo usuário";
out.poll_create_option = "Adicionar nova opção";
out.poll_commit = "Submeter";
out.poll_closeWizardButton = "Fechar assistente";
out.poll_closeWizardButtonTitle = "Fechar assistente";
out.poll_wizardComputeButton = "Computar opções";
@ -102,23 +204,241 @@ define(function () {
out.poll_optionPlaceholder = "Alternativa";
out.poll_userPlaceholder = "Seu nome";
out.poll_removeOption = "Você tem certeza que deseja remover esta opção?";
out.poll_removeUser = "Você tem certeza que quer remover este usuário?";
out.poll_titleHint = "Título";
out.poll_descriptionHint = "Descrição";
// index.html
out.main_p2 = 'Este projeto utiliza os Editores visuais <a href="http://ckeditor.com/">CKEditor</a> e <a href="https://codemirror.net/">CodeMirror</a>, e a engine de tempo real <a href="https://github.com/xwiki-contrib/chainpad">ChainPad</a>.';
out.main_howitworks = 'Como funciona';
   out.main_howitworks_p1 = 'CryptPad usa uma variante do algorítmo de <a href="https://en.wikipedia.org/wiki/Operational_transformation">Transformação Operacional</a>, que é capaz de encontrar consenso distribuido usando o <a href="https://bitcoin.org/bitcoin.pdf">Blockchain de Nakamoto</a>, um constructo popularizado pela <a href="https://en.wikipedia.org/wiki/Bitcoin">Criptomoeda Bitcoin</a>. Desta forma o algorítmo pode evitar a necessidade de um servidor central para resolver conflitos de edição operacional sem a necessidade do servidor armazenar o conteúdo que está sendo editado pelos colaboradores.';
// Canvas
out.canvas_clear = "Limpar";
out.canvas_delete = "Deletar seleção";
out.canvas_disable = "Desabilitar desenho";
out.canvas_enable = "Habilitar desenho";
out.canvas_width = "Largura";
out.canvas_opacity = "Opacidade";
// File manager
out.fm_rootName = "Documentos";
out.fm_trashName = "Lixeira";
out.fm_unsortedName = "Arquivos não organizados";
out.fm_filesDataName = "Todos os Arquivos";
out.fm_templateName = "Temas";
out.fm_searchName = "Busca";
out.fm_searchPlaceholder = "Buscar...";
out.fm_newButton = "Novo";
out.fm_newButtonTitle = "Criar um novo bloco ou diretório";
out.fm_newFolder = "Novo diretório";
out.fm_newFile = "Novo bloco";
out.fm_folder = "Diretório";
out.fm_folderName = "Nome do diretório";
out.fm_numberOfFolders = "# de diretórios";
out.fm_numberOfFiles = "# de arquivos";
out.fm_fileName = "Nome do arquivo";
out.fm_title = "Título";
out.fm_type = "Tipo";
out.fm_lastAccess = "Último acesso";
out.fm_creation = "Criação";
out.fm_forbidden = "Ação não permitida";
out.fm_originalPath = "Caminho original";
out.fm_openParent = "Exibir no diretório";
out.fm_noname = "Documento sem título";
out.fm_emptyTrashDialog = "Você tem certeza que deseja limpar a lixeira??";
out.fm_removeSeveralPermanentlyDialog = "Você tem certeza que deseja deletar estes {0} elementos da lixeira permanentemente?";
out.fm_removePermanentlyDialog = "Você tem certeza que deseja deletar este elemento da lixeira permanentemente?";
out.fm_removeSeveralDialog = "Você tem certeza que deseja mover estes {0} elementos para a lixeira?";
out.fm_removeDialog = "Você tem certeza que deseja mover {0} para a lixeira?";
out.fm_restoreDialog = "Você tem certeza que deseja restaurar {0} de volta para seu diretório original?";
out.fm_unknownFolderError = "O diretório selecionado ou visitado por último não existe mais. Abrindo diretório superior...";
out.fm_contextMenuError = "Incapaz de abrir o menu de contextualização para este elementos. Se o problema persistir, tente recarregar a página.";
out.fm_selectError = "Incapaz de selecionar o elemento marcado. Se o problema persistir, tente recarregar a página.";
out.fm_categoryError = "Incapaz de abrir a categoria selecionada, Exibindo diretório raiz";
out.fm_info_root = "Crie quantos diretórios aninhados aqui desejar para organizar seus arquivos..";
out.fm_info_unsorted = "Contém todos os arquivos que você visitou e não estão ainda organizados na pasta Documentos ou foram movidos para a pasta lixeira"; // "My Documents" should match with the "out.fm_rootName" key, and "Trash" with "out.fm_trashName" out.fm_info_template = 'Contains all the pads stored as templates and that you can re-use when you create a new pad.';
out.updated_0_fm_info_trash = 'Empty your trash to free space in your CryptDrive.';
out.fm_info_trash = out.updated_0_fm_info_trash;
out.fm_info_allFiles = 'Contém todos os arquivos de "Documentos", "Não organizados" e "Lixeira". Não é possível mover ou remover arquivos daqui.'; // Same here
out.fm_info_anonymous = 'Você não está logado, então estes blocos podem ser deletados! (<a href="https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/" target="_blank">Descubra o porque</a>). ' +
'<a href="/register/">Cadastre-se</a> or <a href="/login/">Entre</a> Para deixá-los salvos.';
out.fm_alert_backupUrl = "Link de backup desta conta.<br>" +
"É <strong>fortemente recomendado</strong> que você deixe para você e somente você.<br>" +
"Você pode usá-lo para resgatar os seus dados caso a memória do seu navegador se perca.<br>" +
"Qualquer um com este link pode editar ou apagar todos os arquivos no gerenciador da conta.<br>";
out.fm_alert_anonymous = "Ola! Você está utilizando o CryptPad anonimamente, isto é ok, mas seus blocos podem ser apagados " +
"se ficarem muito tempo inativo. Nós desativamos as funções avançadas nas contas anônimas para que isto fique claro para você " +
'Este não é um bom lugar apra salvar senhas! Entenda: <a href="https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/" target="_blank">Clicando aqui!</a> ' +
'Porque estamos fazendo isso e porque você deveria criar uma onta? <a href="/register/">Sign up</a> and <a href="/login/">Clique e entenda!</a>.';
out.fm_backup_title = 'Link de restauração';
out.fm_nameFile = 'Como deseja nomear este arquivo?';
out.fm_error_cantPin = "Erro interno do servidor. Por favor recarregue a página e tente novamente.";
// File - Context menu
out.fc_newfolder = "Nova pasta";
out.fc_rename = "Renomear";
out.fc_open = "Abrir";
out.fc_open_ro = "Abrir (somente leitura)";
out.fc_delete = "Deletar";
out.fc_restore = "Restaurar";
out.fc_remove = "Deletar permanentemente";
out.fc_empty = "Esvaziar lixeira";
out.fc_prop = "Propriedades";
out.fc_sizeInKilobytes = "tamanho em Kilobytes";
// fileObject.js (logs)
out.fo_moveUnsortedError = "Você não pode mover uma pasta na lista de notas não organizadas";
out.fo_existingNameError = "Nome já em uso neste diretório. Por favor escolha outro.";
out.fo_moveFolderToChildError = "Você não pode mover uma sub-diretório para dentro de um de seus sub-diretórios";
out.fo_unableToRestore = "Fomos incapazes de restaurar este arquivo para sua posição original. Você pode tentar move-lo para o local de destino porém.";
out.fo_unavailableName = "Um arquivo ou diretório com o mesmo nome já existe no novo locao. Renomeie-o e tente novamente.";
// login
out.login_login = "Entrar";
out.login_makeAPad = 'Criar bloco anonimamente';
out.login_nologin = "Navegar nos blocos locais";
out.login_register = "Cadastro";
out.logoutButton = "Sair";
out.settingsButton = "Configurações";
out.login_username = "Usuário";
out.login_password = "Senha";
out.login_confirm = "Confirme sua senha";
out.login_remember = "Memorize-me";
out.login_hashing = "Encriptando sua senha, isto pode tomar algum tempo.";
out.login_hello = 'Ola {0},'; // {0} is the username
out.login_helloNoName = 'Ola,';
out.login_accessDrive = 'Acesse seu diretório';
out.login_orNoLogin = 'ou';
out.login_noSuchUser = 'Usuário ou senha inválido. Tente nocamente ou cadastre-se';
out.login_invalUser = 'É necessário um usuário';
out.login_invalPass = 'É necessário uma senha';
out.login_unhandledError = 'Um erro não esperado ocorreu :(';
out.register_importRecent = "Importar histórico de blocos (Recomendado)";
out.register_acceptTerms = "Eu aceito <a href='/terms.html'>os termos de serviço</a>";
out.register_passwordsDontMatch = "Senhas não coincidem!";
out.register_mustAcceptTerms = "Você precisa aceitar os termos de serviço.";
out.register_mustRememberPass = "Nós não podemos restaurar sua senha caso você a esqueça. É muito importante que você lembre-se dela! Clique nesta caixa de seleção para confirmar que você compreendeu isto.";
out.register_header = "Bem vindo ao CryptPad";
out.register_explanation = [
"<p>Lets go over a couple things first</p>",
"<ul>",
"<li>Your password is your secret key which encrypts all of your pads. If you lose it there is no way we can recover your data.</li>",
"<li>You can import pads which were recently viewed in your browser so you have them in your account.</li>",
"<li>If you are using a shared computer, you need to log out when you are done, closing the tab is not enough.</li>",
"</ul>"
].join('');
out.register_writtenPassword = "I have written down my username and password, proceed";
out.register_cancel = "Go back";
out.register_warning = "Zero Knowledge means that we can't recover your data if you lose your password.";
out.register_alreadyRegistered = "This user already exists, do you want to log in?";
// Settings
out.settings_title = "Settings";
out.settings_save = "Save";
out.settings_backupTitle = "Backup or restore all your data";
out.settings_backup = "Backup";
out.settings_restore = "Restore";
out.settings_resetTitle = "Clean your drive";
out.settings_reset = "Remove all the files and folders from your CryptDrive";
out.settings_resetPrompt = "This action will remove all the pads from your drive.<br>"+
"Are you sure you want to continue?<br>" +
"Type “<em>I love CryptPad</em>” to confirm.";
out.settings_resetDone = "Your drive is now empty!";
out.settings_resetError = "Incorrect verification text. Your CryptDrive has not been changed.";
out.settings_resetTips = "Tips in CryptDrive";
out.settings_resetTipsButton = "Reset the available tips in CryptDrive";
out.settings_resetTipsDone = "All the tips are now visible again.";
out.settings_importTitle = "Import this browser's recent pads in my CryptDrive";
out.settings_import = "Import";
out.settings_importConfirm = "Are you sure you want to import recent pads from this browser to your user account's CryptDrive?";
out.settings_importDone = "Import completed";
out.settings_userFeedbackHint1 = "CryptPad provides some very basic feedback to the server, to let us know how to improve your experience.";
out.settings_userFeedbackHint2 = "Your pad's content will never be shared with the server.";
out.settings_userFeedback = "Enable user feedback";
out.settings_anonymous = "You are not logged in. Settings here are specific to this browser.";
out.settings_publicSigningKey = "Public Signing Key";
out.settings_usage = "Usage";
out.settings_usageTitle = "See the total size of your pinned pads in MB";
out.settings_pinningNotAvailable = "Pinned pads are only available to registered users.";
out.settings_pinningError = "Something went wrong";
out.settings_usageAmount = "Your pinned pads occupy {0}MB";
out.settings_logoutEverywhereTitle = "Log out everywhere";
out.settings_logoutEverywhere = "Log out of all other web sessions";
out.settings_logoutEverywhereConfirm = "Are you sure? You will need to log in with all your devices.";
out.upload_serverError = "Server Error: unable to upload your file at this time.";
out.upload_uploadPending = "You already have an upload in progress. Cancel it and upload your new file?";
out.upload_success = "Your file ({0}) has been successfully uploaded and added to your drive.";
out.upload_notEnoughSpace = "There is not enough space for this file in your CryptDrive.";
out.upload_tooLarge = "This file exceeds the maximum upload size.";
out.upload_choose = "Choose a file";
out.upload_pending = "Pending";
out.upload_cancelled = "Cancelled";
out.upload_name = "File name";
out.upload_size = "Size";
out.upload_progress = "Progress";
out.download_button = "Decrypt & Download";
// 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>";
   out.main_about_p2 = 'Se você tem alguma questão ou comentário, você pode <a href="https://twitter.com/cryptpad">nos mandar um tweet</a> ou abrir uma requisição <a href="https://github.com/xwiki-labs/cryptpad/issues/" title="no nosso tracker">no github</a>. Venha também nos dar um Oi no IRC (<a href="http://webchat.freenode.net?channels=%23cryptpad&uio=MT1mYWxzZSY5PXRydWUmMTE9Mjg3JjE1PXRydWUe7" title="freenode webchat">irc.freenode.net</a>), ou até mesmo via <a href="mailto:research@xwiki.com">e-mail</a>.';
out.button_newpad = 'NOVO BLOCO WYSIWYG';
out.button_newcode = 'NOVO BLOCO DE NOTAS';
out.button_newpoll = 'NOVA ENQUETE';
out.button_newslide = 'NOVA APRESENTAÇÃO';
// index.html
//about.html
out.main_p2 = 'This project uses the <a href="http://ckeditor.com/">CKEditor</a> Visual Editor, <a href="https://codemirror.net/">CodeMirror</a>, and the <a href="https://github.com/xwiki-contrib/chainpad">ChainPad</a> realtime engine.';
out.main_howitworks_p1 = 'CryptPad uses a variant of the <a href="https://en.wikipedia.org/wiki/Operational_transformation">Operational transformation</a> algorithm which is able to find distributed consensus using a <a href="https://bitcoin.org/bitcoin.pdf">Nakamoto Blockchain</a>, a construct popularized by <a href="https://en.wikipedia.org/wiki/Bitcoin">Bitcoin</a>. This way the algorithm can avoid the need for a central server to resolve Operational Transform Edit Conflicts and without the need for resolving conflicts, the server can be kept unaware of the content which is being edited on the pad.';
// contact.html
out.main_about_p2 = 'If you have any questions or comments, you can <a href="https://twitter.com/cryptpad">tweet us</a>, open an issue <a href="https://github.com/xwiki-labs/cryptpad/issues/" title="our issue tracker">on github</a>, come say hi on irc (<a href="http://webchat.freenode.net?channels=%23cryptpad&uio=MT1mYWxzZSY5PXRydWUmMTE9Mjg3JjE1PXRydWUe7" title="freenode webchat">irc.freenode.net</a>), or <a href="mailto:research@xwiki.com">send us an email</a>.';
out.main_info = "<h1>Collaborate in Confidence</h1><br> Grow your ideas together with shared documents while <strong>Zero Knowledge</strong> technology secures your privacy; even from us.";
out.main_howitworks = 'How It Works';
out.main_zeroKnowledge = 'Zero Knowledge';
out.main_zeroKnowledge_p = "You don't have to trust that we <em>won't</em> look at your pads, with CryptPad's revolutionary Zero Knowledge Technology we <em>can't</em>. Learn more about how we protect your <a href=\"/privacy.html\" title='Privacy'>Privacy and Security</a>.";
out.main_writeItDown = 'Write it down';
out.main_writeItDown_p = "The greatest projects come from the smallest ideas. Take down the moments of inspiration and unexpected ideas because you never know which one might be a breakthrough.";
out.main_share = 'Share the link, share the pad';
out.main_share_p = "Grow your ideas together: conduct efficient meetings, collaborate on TODO lists and make quick presentations with all your friends and all your devices.";
out.main_organize = 'Get organized';
out.main_organize_p = "With CryptPad Drive, you can keep your sights on what's important. Folders allow you to keep track of your projects and have a global vision of where things are going.";
out.tryIt = 'Try it out!';
out.main_richText = 'Rich Text editor';
out.main_richText_p = 'Edit rich text pads collaboratively with our realtime Zero Knowledge <a href="http://ckeditor.com" target="_blank">CkEditor</a> application.';
out.main_code = 'Code editor';
out.main_code_p = 'Edit code from your software collaboratively with our realtime Zero Knowledge <a href="https://www.codemirror.net" target="_blank">CodeMirror</a> application.';
out.main_slide = 'Slide editor';
out.main_slide_p = 'Create your presentations using the Markdown syntax, and display them in your browser.';
out.main_poll = 'Polls';
out.main_poll_p = 'Plan your meeting or your event, or vote for the best solution regarding your problem.';
out.main_drive = 'CryptDrive';
out.footer_applications = "Applications";
out.footer_contact = "Contact";
out.footer_aboutUs = "About us";
out.about = "About";
out.privacy = "Privacy";
out.contact = "Contact";
out.terms = "ToS";
out.blog = "Blog";
// privacy.html
@ -152,17 +472,89 @@ define(function () {
// BottomBar.html
out.bottom_france = '<a href="http://www.xwiki.com/" target="_blank" rel="noopener noreferrer">Feito com <img class="bottom-bar-heart" src="/customize/heart.png" /> na <img class="bottom-bar-fr" src="/customize/fr.png" /></a>';
   out.bottom_support = '<a href="http://labs.xwiki.com/" title="XWiki Labs" target="_blank" rel="noopener noreferrer">Um projeto do laboratório <img src="/customize/logo-xwiki2.png" alt="XWiki SAS" class="bottom-bar-xwiki"/></a> com o suporte da <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.bottom_support = '<a href="http://labs.xwiki.com/" title="XWiki Labs" target="_blank" rel="noopener noreferrer">Um projeto do laboratório <img src="/customize/logo-xwiki2.png" alt="XWiki SAS" class="bottom-bar-xwiki"/></a> com o suporte da <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>';
// Header.html
out.header_france = '<a href="http://www.xwiki.com/" target="_blank" rel="noopener noreferrer">Com <img class="bottom-bar-heart" src="/customize/heart.png" /> da <img class="bottom-bar-fr" src="/customize/fr.png" title="France" alt="France"/> por <img src="/customize/logo-xwiki.png" alt="XWiki SAS" class="bottom-bar-xwiki"/></a>';
// TODO Hardcode cause YOLO
//out.header_xwiki = '<a href="http://www.xwiki.com/" target="_blank" rel="noopener noreferrer"><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.header_logoTitle = 'Ir para página principal';
out.header_logoTitle = 'Go to the main page';
// Initial states
out.initialState = [
'<span style="font-size:16px;"><p>',
'This is&nbsp;<strong>CryptPad</strong>, the Zero Knowledge realtime collaborative editor. Everything is saved as you type.',
'<br>',
'Share the link to this pad to edit with friends or use the <span style="background-color:#5cb85c;color:#ffffff;">&nbsp;Share&nbsp;</span> button to share a <em>read-only link</em>&nbsp;which allows viewing but not editing.',
'</p>',
'<p><span style="color:#808080;"><em>',
'Go ahead, just start typing...',
'</em></span></p></span>',
'<p>&nbsp;<br></p>'
].join('');
out.codeInitialState = [
'# CryptPad\'s Zero Knowledge collaborative code editor\n',
'\n',
'* What you type here is encrypted so only people who have the link can access it.\n',
'* You can choose the programming language to highlight and the UI color scheme in the upper right.'
].join('');
out.slideInitialState = [
'# CryptSlide\n',
'* This is a zero knowledge realtime collaborative editor.\n',
'* What you type here is encrypted so only people who have the link can access it.\n',
'* Even the server cannot see what you type.\n',
'* What you see here, what you hear here, when you leave here, let it stay here.\n',
'\n',
'---',
'\n',
'# How to use\n',
'1. Write your slides content using markdown syntax\n',
' - Learn more about markdown syntax [here](http://www.markdowntutorial.com/)\n',
'2. Separate your slides with ---\n',
'3. Click on the "Play" button to see the result',
' - Your slides are updated in realtime'
].join('');
// Readme
out.driveReadmeTitle = "What is CryptDrive?";
out.readme_welcome = "Welcome to CryptPad !";
out.readme_p1 = "Welcome to CryptPad, this is where you can take note of things alone and with friends.";
out.readme_p2 = "This pad will give you a quick walk through of how you can use CryptPad to take notes, keep them organized and work together on them.";
out.readme_cat1 = "Get to know your CryptDrive";
out.readme_cat1_l1 = "Make a pad: In your CryptDrive, click {0} then {1} and you can make a pad."; // 0: New, 1: Rich Text
out.readme_cat1_l2 = "Open Pads from your CryptDrive: double-click on a pad icon to open it.";
out.readme_cat1_l3 = "Organize your pads: When you are logged in, every pad you access will be shown as in the {0} section of your drive."; // 0: Unsorted files
out.readme_cat1_l3_l1 = "You can click and drag files into folders in the {0} section of your drive and make new folders."; // 0: Documents
out.readme_cat1_l3_l2 = "Remember to try right clicking on icons because there are often additional menus.";
out.readme_cat1_l4 = "Put old pads in the trash: You can click and drag your pads into the {0} the same way you drag them into folders."; // 0: Trash
out.readme_cat2 = "Make pads like a pro";
out.edit = "edit";
out.view = "view";
out.readme_cat2_l1 = "The {0} button in your pad allows you to give access to collaborators to either {1} or to {2} the pad."; // 0: Share, 1: edit, 2: view
out.readme_cat2_l2 = "Change the title of the pad by clicking on the pencil";
out.readme_cat3 = "Discover CryptPad apps";
out.readme_cat3_l1 = "With CryptPad code editor, you can collaborate on code like Javascript and markdown like HTML and Markdown";
out.readme_cat3_l2 = "With CryptPad slide editor, you can make quick presentations using Markdown";
out.readme_cat3_l3 = "With CryptPoll you can take quick votes, especially for scheduling meetings which fit with everybody's calendar";
// Tips
out.tips = {};
out.tips.lag = "The green icon in the upper right shows the quality of your internet connection to the CryptPad server.";
out.tips.shortcuts = "`ctrl+b`, `ctrl+i` and `ctrl+u` are quick shortcuts for bold, italic and underline.";
out.tips.indent = "In numbered and bulleted lists, you can use tab or shift+tab to quickly increase or decrease indentation.";
out.tips.title = "You can set the title of your pad by clicking the top center.";
out.tips.store = "Every time you visit a pad, if you're logged in it will be saved to your CryptDrive.";
out.tips.marker = "You can highlight text in a pad using the \"marker\" item in the styles dropdown menu.";
out.feedback_about = "If you're reading this, you were probably curious why CryptPad is requesting web pages when you perform certain actions";
out.feedback_privacy = "We care about your privacy, and at the same time we want CryptPad to be very easy to use. We use this file to figure out which UI features matter to our users, by requesting it along with a parameter specifying which action was taken.";
out.feedback_optout = "If you would like to opt out, visit <a href='/settings/'>your user settings page</a>, where you'll find a checkbox to enable or disable user feedback";
return out;
});

@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server",
"version": "1.11.0",
"version": "1.12.0",
"dependencies": {
"chainpad-server": "^1.0.1",
"express": "~4.10.1",

@ -1,114 +1,26 @@
[![XWiki labs logo](https://raw.githubusercontent.com/xwiki-labs/xwiki-labs-logo/master/projects/xwikilabs/xlabs-project.png "XWiki labs")](https://labs.xwiki.com/xwiki/bin/view/Main/WebHome)
[![An XWiki Labs Project](https://raw.githubusercontent.com/xwiki-labs/xwiki-labs-logo/master/projects/xwikilabs/xlabs-project.png "XWiki labs")](https://labs.xwiki.com/xwiki/bin/view/Main/WebHome)
<p align="center">
<img src="https://github.com/cjdelisle/cryptpad/raw/master/cryptofist.png" width="60%">
</p>
![CryptPad screenshot](https://github.com/xwiki-labs/cryptpad/raw/master/screenshot.png "Pads are an easy way to collaborate")
Unity is Strength - Collaboration is Key
CryptPad is the **Zero Knowledge** realtime collaborative editor.
![and_so_it_begins.png](https://github.com/cjdelisle/cryptpad/raw/master/and_so_it_begins.png "We are the 99%")
CryptPad is the **zero knowledge** realtime collaborative editor.
Encryption carried out in your web browser protects the data from the server, the cloud
and the NSA. This project uses the [CKEditor] Visual Editor and the [ChainPad] realtime
engine. The secret key is stored in the URL [fragment identifier] which is never sent to
the server but is available to javascript so by sharing the URL, you give authorization
to others who want to participate.
and the NSA. It relies on the [ChainPad] realtime engine.
<!--If you'd like to know more, please read [the Whitepaper]().-->
# Installation
Cryptpad depends on the Nodejs runtime.
We recommend installing it via [NVM](https://github.com/creationix/nvm "Node Version Manager") to ensure that you are running an up to date version.
Once you have a recent runtime (we use v6.6.0):
```
git clone <this repo>
cd cryptpad
npm install
npm install -g bower ## if necessary
bower install
## copy config.example.js to config.js
cp config.example.js config.js
node ./server.js
```
## Configuration
CryptPad _should_ work with an unmodified configuration file, though there are many things which you may want to customize.
Attributes in the config should have comments indicating how they are used.
```
$EDITOR config.js
```
If you are deploying CryptPad in a production environment, we recommend that you take the time to understand and correctly customize your server's [Content Security Policy headers](https://content-security-policy.com/).
Modern browsers use these headers to allow or deny actions from malicious clients which could compromise the confidentiality of your user's data.
These settings can be found in your configuration file in the `contentSecurity` and `padContentSecurity` sections.
## Maintenance
Before upgrading your CryptPad instance to the latest version, we recommend that you check what has changed since your last update.
You can do so by checking which version you have (see package.json), and comparing it against newer [release notes](https://github.com/xwiki-labs/cryptpad/releases).
To get access to the most recent codebase:
```
cd /your/cryptpad/instance/location;
git pull
```
To update dependencies:
```
# clientside dependencies
bower update;
Installing CryptPad is pretty straightforward. You can read all about it in the
[installation guide](https://github.com/xwiki-labs/cryptpad/wiki/Installation-guide).
# serverside dependencies
npm update;
```
## Deleting all data and resetting Cryptpad
It also contains information on keeping your instance of CryptPad up to date.
## Setup using Docker
To reset your instance of Cryptpad and remove all the data that is being stored:
**WARNING: This will reset your Cryptpad instance and remove all data**
```
# change into your cryptpad directory
cd /your/cryptpad/instance/location;
# delete the datastore
rm -rf ./datastore
```
If you are using the mongodb adaptor, [drop the relevant collection](https://docs.mongodb.org/manual/reference/method/db.collection.drop/#db.collection.drop).
If you are using the [leveldb adaptor](https://github.com/xwiki-labs/cryptpad-level-store), delete the datastore directory you have configured.
## Testing
To test CryptPad, go to http://your.server:3000/assert/
You can use WebDriver to run this test automatically by running TestSelenium.js but you will need chromedriver installed.
If you use Mac, you can `brew install chromedriver`.
## Developing CryptPad
CryptPad is built with a lot of small javascript libraries.
To make js files load faster, we apply an aggressive caching policy.
If you want to add new features to CryptPad, you'll want to turn off caching.
You can do so by launching your server in _dev mode_, like so:
`DEV=1 node server.js`
See [Cryptpad-Docker](docs/cryptpad-docker.md)
## Security
# Security
CryptPad is *private*, not *anonymous*. Privacy protects your data, anonymity protects you.
As such, it is possible for a collaborator on the pad to include some silly/ugly/nasty things
@ -127,11 +39,7 @@ the battery out of your computer before it spawns Agent Smith.
Still there are other low-lives in the world so using CryptPad over HTTPS is probably a good idea.
## Setup using Docker
See [Cryptpad-Docker](docs/cryptpad-docker.md)
## Translations
# Translations
We'd like to make it easy for more people to use encryption in their routine activities.
As such, we've tried to make language-specific parts of CryptPad translatable. If you're
@ -139,14 +47,14 @@ able to translate CryptPad's interface, and would like to help, please contact u
You can also see [our translation guide](/customize.dist/translations/README.md).
## Contacting Us
# Contacting Us
You can reach members of the CryptPad development team on [twitter](https://twitter.com/cryptpad),
via our [github issue tracker](https://github.com/xwiki-labs/cryptpad/issues/), on the
[freenode](http://webchat.freenode.net/?channels=%23cryptpad&uio=MT1mYWxzZSY5PXRydWUmMTE9Mjg3JjE1PXRydWUe7)
irc network, or by [email](mailto:research@xwiki.com).
You can reach members of the CryptPad development team on [Twitter](https://twitter.com/cryptpad),
via our [GitHub issue tracker](https://github.com/xwiki-labs/cryptpad/issues/), on our
[Matrix channel](https://riot.im/app/#/room/#cryptpad:matrix.org), or by
[e-mail](mailto:research@xwiki.com).
## Contributing
# Contributing
We love Open Source and we love contribution. It is our intent to keep this project available
under the AGPL license forever but in order to finance more development on this and other FOSS
@ -156,7 +64,9 @@ please read and
If you have any questions or comments, or if you're interested in contributing to Cryptpad, come say hi on IRC, `#cryptpad` on Freenode.
### License
# License
![AGPL logo](https://www.gnu.org/graphics/agplv3-155x51.png "GNU Affero General Public License")
This software is and will always be available under the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the License, or (at your option)
@ -164,7 +74,4 @@ any later version. If you wish to use this technology in a proprietary product,
sales@xwiki.com
[ChainPad]: https://github.com/xwiki-contrib/chainpad
[CKEditor]: http://ckeditor.com/
[fragment identifier]: https://en.wikipedia.org/wiki/Fragment_identifier
[active attack]: https://en.wikipedia.org/wiki/Attack_(computing)#Types_of_attacks
[Creative Commons Attribution 2.5 License]: http://creativecommons.org/licenses/by/2.5/

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

@ -1,5 +1,7 @@
@import "/customize/src/less/variables.less";
@import "/customize/src/less/mixins.less";
@import "/common/markdown.less";
@import "/common/file-dialog.less";
html, body{
height: 100%;
@ -66,15 +68,8 @@ body {
max-width: 40vw;
margin: auto;
table {
border-collapse: collapse;
tr {
th {
border: 3px solid black;
padding: 15px;
}
}
}
.markdown_preformatted-code;
.markdown_gfm-table(black);
}
@media (max-width: @media-medium-screen) {

@ -147,6 +147,17 @@ define([
}
};
var mediaTagModes = [
'markdown',
'html',
'htmlembedded',
'htmlmixed',
'index.html',
'php',
'velocity',
'xml',
];
var onModeChanged = function (mode) {
var $codeMirror = $iframe.find('.CodeMirror');
window.clearTimeout(APP.previewTo);
@ -154,6 +165,10 @@ define([
APP.previewTo = window.setTimeout(function () {
$codeMirror.removeClass('transition');
}, 500);
if (mediaTagModes.indexOf(mode) !== -1) {
APP.$mediaTagButton.show();
} else { APP.$mediaTagButton.hide(); }
if (mode === "markdown") {
APP.$previewButton.show();
Cryptpad.getPadAttribute('previewMode', function (e, data) {
@ -253,6 +268,25 @@ define([
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
$rightside.append($forgetPad);
var fileDialogCfg = {
$body: $iframe.find('body'),
onSelect: function (href) {
var parsed = Cryptpad.parsePadUrl(href);
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
editor.replaceSelection(mt);
},
data: APP
};
APP.$mediaTagButton = $('<button>', {
title: Messages.filePickerButton,
'class': 'rightside-button fa fa-picture-o',
style: 'font-size: 17px'
}).click(function () {
Cryptpad.createFileDialog(fileDialogCfg);
}).appendTo($rightside);
var $previewButton = APP.$previewButton = Cryptpad.createButton(null, true);
$previewButton.removeClass('fa-question').addClass('fa-eye');
$previewButton.attr('title', Messages.previewButtonTitle);

@ -254,6 +254,7 @@ define([
var createAreaHandlers = File.createDropArea = function ($area, $hoverArea) {
var counter = 0;
if (!$hoverArea) { $hoverArea = $area; }
if (!$area) { return; }
$hoverArea
.on('dragenter', function (e) {
e.preventDefault();

@ -276,6 +276,7 @@ define([
var $slideIcon = $('<span>', {"class": "fa fa-file-powerpoint-o file icon slideColor"});
var $pollIcon = $('<span>', {"class": "fa fa-calendar file icon pollColor"});
var $whiteboardIcon = $('<span>', {"class": "fa fa-paint-brush whiteboardColor"});
var $todoIcon = $('<span>', {"class": "fa fa-tasks todoColor"});
var $contactsIcon = $('<span>', {"class": "fa fa-users friendsColor"});
UI.getIcon = function (type) {
var $icon;
@ -287,6 +288,7 @@ define([
case 'slide': $icon = $slideIcon.clone(); break;
case 'poll': $icon = $pollIcon.clone(); break;
case 'whiteboard': $icon = $whiteboardIcon.clone(); break;
case 'todo': $icon = $todoIcon.clone(); break;
case 'contacts': $icon = $contactsIcon.clone(); break;
default: $icon = $fileIcon.clone();
}
@ -295,16 +297,21 @@ define([
};
// Tooltips
UI.clearTooltips = function () {
$('.tippy-popper').remove();
};
UI.addTooltips = function () {
var MutationObserver = window.MutationObserver;
var addTippy = function (el) {
if (el.nodeName === 'IFRAME') { return; }
console.log(el);
var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500;
Tippy(el, {
position: 'bottom',
distance: 0,
performance: true,
delay: [500, 0]
delay: [delay, 0]
});
};
var $body = $('body');

@ -4,7 +4,27 @@ define([
'/common/curve.js',
'/bower_components/marked/marked.min.js',
], function ($, Crypto, Curve, Marked) {
var Msg = {};
var Msg = {
inputs: [],
};
var Messages;
Msg.setEditable = function (bool) {
bool = !bool;
Msg.inputs.forEach(function (input) {
if (bool) {
input.setAttribute('disabled', bool);
} else {
input.removeAttribute('disabled');
}
if (Messages) {
// set placeholder
var placeholder = bool? Messages.disconnected: Messages.contacts_typeHere;
input.setAttribute('placeholder', placeholder);
}
});
};
var Types = {
message: 'MSG',
@ -227,7 +247,6 @@ define([
if (!isId) { return; }
var decryptedMsg = channel.encryptor.decrypt(msg);
console.log(decryptedMsg);
var parsed = JSON.parse(decryptedMsg);
if (parsed[0] !== Types.mapId && parsed[0] !== Types.mapIdAck) { return; }
if (parsed[2] !== sender || !parsed[1]) { return; }
@ -267,6 +286,7 @@ define([
var chan = parsed[3];
if (!chan || !channels[chan]) { return; }
pushMsg(common, channels[chan], parsed[4]);
channels[chan].refresh();
};
var onMessage = function (common, msg, sender, chan) {
if (!channels[chan.id]) { return; }
@ -284,32 +304,50 @@ define([
var data = getFriend(common, curvePublic);
var proxy = common.getProxy();
var $header = $('<div>', {'class': 'header avatar'}).appendTo($container);
/*
var $removeHistory = $('<button>', {
'class': 'remove-history'
}).text('remove chat history').click(function () {
Cryptpad.confirm('are you sure?', function (yes) {
// Input
var channel = channels[data.channel];
var $header = $('<div>', {
'class': 'header',
}).appendTo($container);
var $avatar = $('<div>', {'class': 'avatar'}).appendTo($header);
// more history...
$('<span>', {
'class': 'more-history',
})
.text('get more history')
.click(function () {
console.log("GETTING HISTORY");
channel.getPreviousMessages();
})
.appendTo($header);
var $removeHistory = $('<span>', {
'class': 'remove-history fa fa-eraser',
title: common.Messages.contacts_removeHistoryTitle
})
.click(function () {
common.confirm(common.Messages.contacts_confirmRemoveHistory, function (yes) {
if (!yes) { return; }
Cryptpad.clearOwnedChannel(data.channel, function (e) {
common.clearOwnedChannel(data.channel, function (e) {
if (e) {
console.error(e);
Cryptpad.alert("Something went wrong");
common.alert(common.Messages.contacts_removeHistoryServerError);
return;
}
});
});
});
$removeHistory.appendTo($header); //rightCol);
*/
$removeHistory.appendTo($header);
$('<div>', {'class': 'messages'}).appendTo($container);
var $inputBlock = $('<div>', {'class': 'input'}).appendTo($container);
// Input
var channel = channels[data.channel];
var $input = $('<textarea>').appendTo($inputBlock);
$input.attr('placeholder', common.Messages.contacts_typeHere);
Msg.inputs.push($input[0]);
var sending = false;
var send = function () {
@ -338,7 +376,7 @@ define([
});*/
var onKeyDown = function (e) {
if (e.keyCode === 13) {
if (e.ctrlKey) {
if (e.ctrlKey || e.shiftKey) {
var val = this.value;
if (typeof this.selectionStart === "number" && typeof this.selectionEnd === "number") {
var start = this.selectionStart;
@ -363,24 +401,35 @@ define([
var $rightCol = $('<span>', {'class': 'right-col'});
$('<span>', {'class': 'name'}).text(data.displayName).appendTo($rightCol);
if (data.avatar && avatars[data.avatar]) {
$header.append(avatars[data.avatar]);
$header.append($rightCol);
$avatar.append(avatars[data.avatar]);
$avatar.append($rightCol);
} else {
common.displayAvatar($header, data.avatar, data.displayName, function ($img) {
common.displayAvatar($avatar, data.avatar, data.displayName, function ($img) {
if (data.avatar && $img) {
avatars[data.avatar] = $img[0].outerHTML;
}
$header.append($rightCol);
$avatar.append($rightCol);
});
}
};
Msg.getLatestMessages = function () {
Object.keys(channels).forEach(function (id) {
if (id === 'me') { return; }
var friend = channels[id];
friend.getMessagesSinceDisconnect();
friend.refresh();
});
};
Msg.init = function (common, $listContainer, $msgContainer) {
var network = common.getNetwork();
var proxy = common.getProxy();
Msg.hk = network.historyKeeper;
var friends = getFriendList(common);
Messages = Messages || common.Messages;
network.on('message', function(msg, sender) {
onDirectMessage(common, msg, sender);
});
@ -450,6 +499,7 @@ define([
$chat.show();
Msg.active = curvePublic;
// TODO don't mark messages as read unless you have displayed them
refresh(curvePublic);
};
@ -539,6 +589,37 @@ define([
var statusText = status ? 'online' : 'offline';
$friend.find('.status').attr('class', 'status '+statusText);
};
var getMoreHistory = function (network, chan, hash, count) {
var msg = [
'GET_HISTORY_RANGE',
chan.id,
{
from: hash,
count: count,
}
];
console.log(msg);
network.sendto(network.historyKeeper, JSON.stringify(msg)).then(function (a, b, c) {
console.log(a, b, c);
}, function (err) {
throw new Error(err);
});
};
var getChannelMessagesSince = function (network, chan, data, keys) {
var cfg = {
validateKey: keys.validateKey,
owners: [proxy.edPublic, data.edPublic],
lastKnownHash: data.lastKnownHash
};
var msg = ['GET_HISTORY', chan.id, cfg];
network.sendto(network.historyKeeper, JSON.stringify(msg))
.then($.noop, function (err) {
throw new Error(err);
});
};
// Open the channels
var openFriendChannel = function (f) {
@ -558,9 +639,18 @@ define([
removeUI: function () { removeUI(data.curvePublic); },
updateUI: function (types) { updateUI(data.curvePublic, types); },
updateStatus: function () { updateStatus(data.curvePublic); },
getMessagesSinceDisconnect: function () {
getChannelMessagesSince(network, chan, data, keys);
},
wc: chan,
userList: [],
mapId: {}
mapId: {},
getPreviousMessages: function () {
var oldestMessages = channel.messages[0];
var oldestHash = oldestMessages[0];
getMoreHistory(network, chan, oldestHash, 10);
},
};
chan.on('message', function (msg, sender) {
onMessage(common, msg, sender, chan);
@ -589,20 +679,13 @@ define([
}
channel.updateStatus();
});
var cfg = {
validateKey: keys.validateKey,
owners: [proxy.edPublic, data.edPublic],
lastKnownHash: data.lastKnownHash
};
var msg = ['GET_HISTORY', chan.id, cfg];
network.sendto(network.historyKeeper, JSON.stringify(msg))
.then($.noop, function (err) {
throw new Error(err);
});
getChannelMessagesSince(network, chan, data, keys);
}, function (err) {
console.error(err);
});
};
Object.keys(friends).forEach(openFriendChannel);
var checkNewFriends = function () {

@ -51,7 +51,7 @@ define([
common.displayNameKey = 'cryptpad.username';
var newPadNameKey = common.newPadNameKey = "newPadName";
var newPadPathKey = common.newPadPathKey = "newPadPath";
var oldStorageKey = common.oldStorageKey = 'CryptPad_RECENTPADS';
common.oldStorageKey = 'CryptPad_RECENTPADS';
common.storageKey = 'filesData';
var PINNING_ENABLED = AppConfig.enablePinning;
@ -77,6 +77,7 @@ define([
common.unnotify = UI.unnotify;
common.getIcon = UI.getIcon;
common.addTooltips = UI.addTooltips;
common.clearTooltips = UI.clearTooltips;
// import common utilities for export
common.find = Util.find;
@ -127,6 +128,8 @@ define([
common.getFriendListUI = Messaging.getFriendListUI;
common.createData = Messaging.createData;
common.getPendingInvites = Messaging.getPending;
common.enableMessaging = Messaging.setEditable;
common.getLatestMessages = Messaging.getLatestMessages;
// Userlist
common.createUserList = UserList.create;
@ -244,6 +247,8 @@ define([
};
common.infiniteSpinnerDetected = false;
var BAD_STATE_TIMEOUT = typeof(AppConfig.badStateTimeout) === 'number'?
AppConfig.badStateTimeout: 30000;
var whenRealtimeSyncs = common.whenRealtimeSyncs = function (realtime, cb) {
realtime.sync();
@ -263,7 +268,7 @@ define([
window.location.reload();
});
common.infiniteSpinnerDetected = true;
}, 30000);
}, BAD_STATE_TIMEOUT);
realtime.onSettle(function () {
clearTimeout(to);
cb();
@ -409,7 +414,7 @@ define([
return parsed.hashData;
};
// Migrate from legacy store (localStorage)
var migrateRecentPads = common.migrateRecentPads = function (pads) {
common.migrateRecentPads = function (pads) {
return pads.map(function (pad) {
var parsedHash;
if (Array.isArray(pad)) { // TODO DEPRECATE_F
@ -448,24 +453,6 @@ define([
});
};
// Get the pads from localStorage to migrate them to the object store
common.getLegacyPads = function (cb) {
require(['/customize/store.js'], function(Legacy) { // TODO DEPRECATE_F
Legacy.ready(function (err, legacy) {
if (err) { cb(err, null); return; }
legacy.get(oldStorageKey, function (err2, recentPads) {
if (err2) { cb(err2, null); return; }
if (Array.isArray(recentPads)) {
feedback('MIGRATE_LEGACY_STORE');
cb(void 0, migrateRecentPads(recentPads));
return;
}
cb(void 0, []);
});
});
});
};
// Create untitled documents when no name is given
var getLocaleDate = common.getLocaleDate = function () {
if (window.Intl && window.Intl.DateTimeFormat) {
@ -1098,6 +1085,7 @@ define([
}
break;
case 'upload':
console.log('UPLOAD');
button = $('<button>', {
'class': 'btn btn-primary new',
title: Messages.uploadButtonTitle,
@ -1257,6 +1245,13 @@ define([
style: 'font:'+size+' FontAwesome'
});
break;
case 'savetodrive':
button = $('<button>', {
'class': 'fa fa-cloud-upload',
title: Messages.canvas_saveToDrive,
})
.click(prepareFeedback(type));
break;
default:
button = $('<button>', {
'class': "fa fa-question",
@ -1403,6 +1398,67 @@ define([
}
};
common.createFileDialog = function (cfg) {
var $body = cfg.$body || $('body');
var $block = $body.find('#fileDialog');
if (!$block.length) {
$block = $('<div>', {id: "fileDialog"}).appendTo($body);
}
$block.html('');
$('<span>', {
'class': 'close fa fa-times',
'title': Messages.filePicker_close
}).click(function () {
$block.hide();
}).appendTo($block);
var $description = $('<p>').text(Messages.filePicker_description);
$block.append($description);
var $filter = $('<p>').appendTo($block);
var $container = $('<span>', {'class': 'fileContainer'}).appendTo($block);
var updateContainer = function () {
$container.html('');
var filter = $filter.find('.filter').val().trim();
var list = common.getUserFilesList();
var fo = common.getFO();
list.forEach(function (id) {
var data = fo.getFileData(id);
var name = fo.getTitle(id);
if (filter && name.toLowerCase().indexOf(filter.toLowerCase()) === -1) {
return;
}
var $span = $('<span>', {'class': 'element'}).appendTo($container);
var $inner = $('<span>').text(name);
$span.append($inner).click(function () {
if (typeof cfg.onSelect === "function") { cfg.onSelect(data.href); }
$block.hide();
});
});
};
var to;
$('<input>', {
type: 'text',
'class': 'filter',
'placeholder': Messages.filePicker_filter
}).appendTo($filter).on('keypress', function () {
if (to) { window.clearTimeout(to); }
to = window.setTimeout(updateContainer, 300);
});
//$filter.append(' '+Messages.or+' ');
var data = {FM: cfg.data.FM};
$filter.append(common.createButton('upload', false, data, function () {
$block.hide();
}));
updateContainer();
$body.keydown(function (e) {
if (e.which === 27) { $block.hide(); }
});
$block.show();
};
// Create a button with a dropdown menu
// input is a config object with parameters:
// - container (optional): the dropdown container (span)
@ -1779,6 +1835,7 @@ define([
initialized = true;
updateLocalVersion();
common.addTooltips();
f(void 0, env);
if (typeof(window.onhashchange) === 'function') { window.onhashchange(); }
}
@ -1797,7 +1854,6 @@ define([
store = common.store = env.store = storeObj;
common.addDirectMessageHandler(common);
common.addTooltips();
var proxy = getProxy();
var network = getNetwork();

@ -164,7 +164,6 @@ define([
var $mts = $content.find('media-tag:not(:has(*))');
$mts.each(function (i, el) {
MediaTag(el);
console.log(el.outerHTML);
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'childList') {

@ -0,0 +1,39 @@
#fileDialog {
position: absolute;
background-color: rgba(200, 200, 200, 0.8);
top: 15vh; bottom: 15vh;
left: 10vw; right: 10vw;
border: 1px solid black;
z-index: 100000;
overflow: auto;
display: none;
font-family: -apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
font-size: 16px;
text-align: center;
.close {
position: absolute;
top: 0;
right: 0;
padding: 5px;
cursor: pointer;
}
.element {
cursor: pointer;
display: inline-flex;
width: 100px;
height: 100px;
border: 1px solid #ccc;
margin: 5px;
overflow: hidden;
word-wrap: break-word;
background-color: white;
padding: 5px;
align-items: center;
span {
width: 100px;
text-align: center;
}
}
}

@ -294,10 +294,8 @@ define([
// Creating a new anon drive: import anon pads from localStorage
if ((!drive[Cryptpad.oldStorageKey] || !Cryptpad.isArray(drive[Cryptpad.oldStorageKey]))
&& !drive['filesData']) {
Cryptpad.getLegacyPads(function (err, data) {
drive[Cryptpad.oldStorageKey] = data;
onReady(f, rt.proxy, Cryptpad, exp);
});
drive[Cryptpad.oldStorageKey] = [];
onReady(f, rt.proxy, Cryptpad, exp);
return;
}
// Drive already exist: return the existing drive, don't load data from legacy store

@ -0,0 +1,27 @@
.markdown_preformatted-code (@color: #333) {
pre > code {
display: block;
position: relative;
border: 1px solid @color;
width: 90%;
margin: auto;
padding-left: .25vw;
overflow-x: auto;
overflow-y: hidden;
}
}
.markdown_gfm-table (@color: black) {
table {
border-collapse: collapse;
tr {
th {
border: 3px solid @color;
padding: 15px;
}
}
}
}
// todo ul, ol

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 B

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 B

After

Width:  |  Height:  |  Size: 135 B

@ -142,7 +142,11 @@ define([
}
rpc.send('CLEAR_OWNED_CHANNEL', channel, function (e, response) {
if (e) { return cb(e); }
cb(response);
if (response && response.length) {
cb(void 0, response[0]);
} else {
cb();
}
});
};

@ -63,7 +63,7 @@ types of messages:
// RPC responses are arrays. this message isn't meant for us.
return;
}
if (/FULL_HISTORY/.test(parsed[0])) { return; }
if (/(FULL_HISTORY|HISTORY_RANGE)/.test(parsed[0])) { return; }
var response = parsed.slice(2);

File diff suppressed because one or more lines are too long

@ -199,6 +199,7 @@ define([
// Update the userlist
var $editUsers = $userlistContent.find('.' + USERLIST_CLS).html('');
Cryptpad.clearTooltips();
var $editUsersList = $('<div>', {'class': 'userlist-others'});
@ -689,6 +690,14 @@ define([
config.realtime.onPatch(ks(toolbar, config));
config.realtime.onMessage(ks(toolbar, config, true));
}
// without this, users in read-only mode say 'synchronizing' until they
// receive a patch.
if (Cryptpad) {
typing = 0;
Cryptpad.whenRealtimeSyncs(config.realtime, function () {
kickSpinner(toolbar, config);
});
}
return $spin;
};
@ -990,13 +999,18 @@ define([
var failed = toolbar.failed = function () {
toolbar.connected = false;
toolbar.spinner.text(Messages.disconnected);
if (toolbar.spinner) {
toolbar.spinner.text(Messages.disconnected);
}
//checkLag(toolbar, config);
};
toolbar.reconnecting = function (userId) {
if (config.userList) { config.userList.userNetfluxId = userId; }
toolbar.connected = false;
toolbar.spinner.text(Messages.reconnecting);
if (toolbar.spinner) {
toolbar.spinner.text(Messages.reconnecting);
}
//checkLag(toolbar, config);
};

@ -39,12 +39,13 @@ define([
toolbar.$rightside.html(''); // Remove the drawer if we don't use it to hide the toolbar
Cryptpad.getProxy().on('disconnect', function () {
// TODO readonly
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
Cryptpad.enableMessaging(false);
});
Cryptpad.getProxy().on('reconnect', function () {
// TODO cancel readonly
Cryptpad.findOKButton().click();
Cryptpad.enableMessaging(true);
Cryptpad.getLatestMessages();
});
Cryptpad.initMessaging(Cryptpad, $list, $messages);

@ -66,7 +66,7 @@ body {
}
}
#friendList .friend, #messaging .header {
#friendList .friend, #messaging .avatar {
.avatar(30px);
&.avatar {
display: flex;
@ -112,17 +112,65 @@ body {
}
}
.placeholder (@color: #bbb) {
&::-webkit-input-placeholder { /* WebKit, Blink, Edge */
color: @color;
}
&:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
color: @color;
opacity: 1;
}
&::-moz-placeholder { /* Mozilla Firefox 19+ */
color: @color;
opacity: 1;
}
&:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: @color;
}
&::-ms-input-placeholder { /* Microsoft Edge */
color: @color;
}
}
#messaging {
flex: 1;
height: 100%;
background-color: lighten(@bg-color, 20%);
min-width: 0;
.info {
padding: 20px;
}
.header {
background-color: lighten(@bg-color, 15%);
padding: 10px;
padding: 0;
display: flex;
justify-content: space-between;
align-items: center;
height: 50px;
.hover () {
height: 100%;
line-height: 30px;
padding: 10px;
&:hover {
background-color: rgba(50,50,50,0.3);
}
}
.avatar,
.right-col {
flex:1 1 auto;
}
.remove-history {
.hover;
}
.avatar {
margin: 10px;
}
.more-history {
.hover;
}
}
.chat {
height: 100%;
@ -173,6 +221,12 @@ body {
background-color: darken(@bg-color, 10%);
color: @color;
resize: none;
line-height: 50px;
overflow-y: auto;
.placeholder(#bbb);
&[disabled=true] {
.placeholder(#999);
}
&:placeholder-shown { line-height: 50px; }
}
button {

@ -468,62 +468,15 @@ span {
}
div.grid {
padding: 20px;
.fileIcon;
li {
display: inline-block;
margin: 10px 10px;
width: 140px;
height: 140px;
text-align: center;
vertical-align: top;
overflow: hidden;
text-overflow: ellipsis;
padding-top: 5px;
padding-bottom: 5px;
&:not(.selected):not(.selectedTmp) {
border: 1px solid #CCC;
}
.name {
width: 100%;
height: 48px;
margin: 8px 0;
display: inline-block;
//align-items: center;
//justify-content: center;
overflow: hidden;
//text-overflow: ellipsis;
word-wrap: break-word;
}
&.element {
position: relative;
}
.truncated {
display: block;
position: absolute;
bottom: 0px;
left: 0; right: 0;
text-align: center;
}
input {
width: 100%;
margin-top: 5px;
}
img.icon {
height: 48px;
max-height: none;
max-width: none;
margin: 8px 0;
}
.fa {
display: block;
margin: auto;
font-size: 48px;
margin: 8px 0;
text-align: center;
&.listonly {
display: none;
}
}
}
.listElement {
display: none;

@ -299,6 +299,13 @@ define([
var DD = new DiffDom(diffOptions);
var openLink = function (e) {
var el = e.currentTarget;
if (!el || el.nodeName !== 'A') { return; }
var href = el.getAttribute('href');
if (href) { window.open(href, '_blank'); }
};
// apply patches, and try not to lose the cursor in the process!
var applyHjson = function (shjson) {
var userDocStateDom = hjsonToDom(JSON.parse(shjson));
@ -308,6 +315,11 @@ define([
}
var patch = (DD).diff(inner, userDocStateDom);
(DD).apply(inner, patch);
if (readOnly) {
var $links = $(inner).find('a');
// off so that we don't end up with multiple identical handlers
$links.off('click', openLink).on('click', openLink);
}
};
var stringifyDOM = module.stringifyDOM = function (dom) {
@ -549,7 +561,7 @@ define([
var $templateButton = Cryptpad.createButton('template', true, templateObj);
$rightside.append($templateButton);
}
console.log('init');
/* add an export button */
var $export = Cryptpad.createButton('export', true, {}, exportFile);
$drawer.append($export);

@ -525,10 +525,9 @@ define([
APP.$rightside.append($p);
return;
}
var $create = $('<div>', {id: CREATE_ID});
var $button = $('<button>', {'class': 'btn btn-success'});
$button.text(Messages.profile_create).click(todo).appendTo($create);
APP.$rightside.append($create);
// make an empty profile for the user on their first visit
todo();
};
var onCryptpadReady = function () {

@ -95,7 +95,7 @@ define([
}
});
Slide.setModal(APP, $modal, $content, $pad, ifrw, slideOptions, initialState);
Slide.setModal($modal, $content, $pad, ifrw, slideOptions, initialState);
var enterPresentationMode = function (shouldLog) {
Slide.show(true, editor.getValue());
@ -199,70 +199,6 @@ define([
}
};
var createFileDialog = function () {
var $body = $iframe.find('body');
var $block = $body.find('#fileDialog');
if (!$block.length) {
$block = $('<div>', {id: "fileDialog"}).appendTo($body);
}
$block.html('');
$('<span>', {
'class': 'close fa fa-times',
'title': Messages.filePicker_close
}).click(function () {
$block.hide();
}).appendTo($block);
var $description = $('<p>').text(Messages.filePicker_description);
$block.append($description);
var $filter = $('<p>').appendTo($block);
var $container = $('<span>', {'class': 'fileContainer'}).appendTo($block);
var updateContainer = function () {
$container.html('');
var filter = $filter.find('.filter').val().trim();
var list = Cryptpad.getUserFilesList();
var fo = Cryptpad.getFO();
list.forEach(function (id) {
var data = fo.getFileData(id);
var name = fo.getTitle(id);
if (filter && name.toLowerCase().indexOf(filter.toLowerCase()) === -1) {
return;
}
var $span = $('<span>', {'class': 'element'}).appendTo($container);
var $inner = $('<span>').text(name);
$span.append($inner).click(function () {
var parsed = Cryptpad.parsePadUrl(data.href);
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
editor.replaceSelection(mt);
//var cleanName = name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.href+')';
//editor.replaceSelection(text);
$block.hide();
});
});
};
var to;
$('<input>', {
type: 'text',
'class': 'filter',
'placeholder': Messages.filePicker_filter
}).appendTo($filter).on('keypress', function () {
if (to) { window.clearTimeout(to); }
to = window.setTimeout(updateContainer, 300);
});
$filter.append(' '+Messages.or+' ');
var data = {FM: APP.FM};
$filter.append(Cryptpad.createButton('upload', false, data, function () {
$block.hide();
}));
updateContainer();
$body.keydown(function (e) {
if (e.which === 27) { $block.hide(); }
});
$block.show();
};
var createPrintDialog = function () {
var slideOptionsTmp = {
title: false,
@ -282,7 +218,6 @@ define([
// Slide number
$('<input>', {type: 'checkbox', id: 'checkNumber', checked: slideOptionsTmp.slide}).on('change', function () {
var c = this.checked;
console.log(c);
slideOptionsTmp.slide = c;
}).appendTo($p).css('width', 'auto');
$('<label>', {'for': 'checkNumber'}).text(Messages.printSlideNumber).appendTo($p);
@ -350,6 +285,8 @@ define([
};
Title = Cryptpad.createTitle(titleCfg, config.onLocal, Cryptpad);
Slide.setTitle(Title);
Metadata = Cryptpad.createMetadata(UserList, Title, metadataCfg, Cryptpad);
var configTb = {
@ -426,12 +363,23 @@ define([
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
$rightside.append($forgetPad);
var fileDialogCfg = {
$body: $iframe.find('body'),
onSelect: function (href) {
var parsed = Cryptpad.parsePadUrl(href);
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
editor.replaceSelection(mt);
},
data: APP
};
$('<button>', {
title: Messages.filePickerButton,
'class': 'rightside-button fa fa-picture-o',
style: 'font-size: 17px'
}).click(function () {
$('body').append(createFileDialog());
Cryptpad.createFileDialog(fileDialogCfg);
}).appendTo($rightside);
var $previewButton = APP.$previewButton = Cryptpad.createButton(null, true);
@ -472,9 +420,7 @@ define([
//$('body').append(createPrintDialog());
}).append($('<span>', {'class': 'drawer'}).text(Messages.printText));
// TODO reenable this when it is working again
$printButton = $printButton;
//$drawer.append($printButton);
$drawer.append($printButton);
var $slideOptions = $('<button>', {
title: Messages.slideOptionsTitle,
@ -689,7 +635,9 @@ define([
toolbar.reconnecting(info.myId);
Cryptpad.findOKButton().click();
} else {
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
if (!Slide.isPresentURL()) {
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
}
}
};

@ -9,7 +9,6 @@ define([
content: [],
changeHandlers: [],
};
var APP;
var ifrw;
var $modal;
var $content;
@ -19,6 +18,7 @@ define([
var separator = '<hr data-pewpew="pezpez">';
var separatorReg = /<hr data\-pewpew="pezpez">/g;
var slideClass = 'slide-frame';
var Title;
Slide.onChange = function (f) {
if (typeof(f) === 'function') {
@ -98,7 +98,7 @@ define([
$('<div>', {'class': 'slideDate'}).text(new Date().toLocaleDateString()).appendTo($(el));
}
if (options.title) {
$('<div>', {'class': 'slideTitle'}).text(APP.title).appendTo($(el));
$('<div>', {'class': 'slideTitle'}).text(Title.title).appendTo($(el));
}
});
$content.removeClass('transition');
@ -304,17 +304,20 @@ define([
};
Slide.setModal = function (appObj, $m, $c, $p, iframe, opt, ph) {
Slide.setModal = function ($m, $c, $p, iframe, opt, ph) {
$modal = Slide.$modal = $m;
$content = Slide.$content = $c;
$pad = Slide.$pad = $p;
ifrw = Slide.ifrw = iframe;
placeholder = Slide.placeholder = ph;
options = Slide.options = opt;
APP = appObj;
addEvent();
addSwipeEvents();
};
Slide.setTitle = function (titleObj) {
Title = titleObj;
};
return Slide;
});

@ -1,5 +1,6 @@
@import "/customize/src/less/variables.less";
@import "/customize/src/less/mixins.less";
@import "/common/markdown.less";
// used for slides
.viewportRatio (@x, @y, @p: 100) {
@ -63,6 +64,7 @@ body {
display:block;
}
html, body {
height: auto;
max-height: none;
overflow: visible;
}
@ -74,6 +76,20 @@ body {
visibility: visible;
}
}
.cp #modal {
display: none !important;
}
.cp {
flex: 1 !important;
overflow: visible !important;
}
.userlist-drawer {
display: none !important;
}
#editorContainer {
height: auto;
display: block;
}
}
}
@ -98,30 +114,37 @@ body {
}
}
}
.preview .cp {
flex: 1;
overflow: hidden;
div#modal:not(.shown) {
position: relative;
top: auto;
left: auto;
width: auto;
display: block;
height: 100%;
#content {
.slide-container {
width: 100%;
.preview {
.cp {
width: 50vw;
overflow: hidden;
div#modal:not(.shown) {
position: relative;
top: auto;
left: auto;
width: auto;
display: block;
height: 100%;
#content {
.slide-container {
width: 100%;
}
.slide-frame {
width: 50vw;
height: 28.125vw; // height:width ratio = 9/16 = .5625
max-height: ~"calc(100vh - 96px)";
max-width: ~"calc(16 / 9 * (100vh - 96px))";
//max-height: 100vh;
//max-width: 177.78vh; // 16/9 = 1.778
}
}
.slide-frame {
width: 50vw;
height: 28.125vw; // height:width ratio = 9/16 = .5625
max-height: 100vh;
max-width: 177.78vh; // 16/9 = 1.778
#button_exit {
visibility: hidden;
}
}
#button_exit {
visibility: hidden;
}
}
.CodeMirror {
flex: 1;
}
}
.cp {
@ -156,7 +179,7 @@ body {
.slide-container {
width: 90vw;
height: 100vh;
margin: 0vh 5vw;
margin: 0vh 5vw !important;
display: flex;
&:last-child {
height: ~"calc(100vh - 2px)";
@ -303,16 +326,7 @@ div#modal #content, #print {
margin-bottom: 0.5em;
}
pre > code {
display: block;
position: relative;
border: 1px solid #333;
width: 90%;
margin: auto;
padding-left: .25vw;
overflow-x: auto;
overflow-y: hidden;
}
.markdown_preformatted-code;
ul, ol {
min-width: 50%;
@ -352,43 +366,7 @@ div#modal #content, #print {
}
}
#fileDialog {
position: absolute;
background-color: rgba(200, 200, 200, 0.8);
top: 15vh; bottom: 15vh;
left: 10vw; right: 10vw;
border: 1px solid black;
z-index: 100000;
overflow: auto;
display: none;
font-family: -apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
font-size: 16px;
text-align: center;
.close {
position: absolute;
top: 0;
right: 0;
padding: 5px;
cursor: pointer;
}
.element {
cursor: pointer;
display: inline-flex;
width: 100px;
height: 100px;
border: 1px solid #ccc;
margin: 5px;
overflow: hidden;
word-wrap: break-word;
background-color: white;
padding: 5px;
align-items: center;
span {
width: 100px;
text-align: center;
}
}
}
@import "/common/file-dialog.less";
.slide-frame * {
max-width: 100%;

@ -0,0 +1,378 @@
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
font-smoothing: antialiased;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #f5f5f5;
color: #4d4d4d;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
font-smoothing: antialiased;
font-weight: 300;
}
button,
input[type="checkbox"] {
outline: none;
}
.hidden {
display: none;
}
.todoapp {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.todoapp input::-webkit-input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::-moz-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp h1 {
position: absolute;
top: -155px;
width: 100%;
font-size: 100px;
font-weight: 100;
text-align: center;
color: rgba(175, 47, 47, 0.15);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
border: 0;
outline: none;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
font-smoothing: antialiased;
}
.new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}
.main {
position: relative;
z-index: 2;
border-top: 1px solid #e6e6e6;
}
label[for='toggle-all'] {
display: none;
}
.toggle-all {
position: absolute;
top: -55px;
left: -12px;
width: 60px;
height: 34px;
text-align: center;
border: none; /* Mobile Safari */
}
.toggle-all:before {
content: '';
font-size: 22px;
color: #e6e6e6;
padding: 10px 27px 10px 27px;
}
.toggle-all:checked:before {
color: #737373;
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
.todo-list li.editing .edit {
display: block;
width: 506px;
padding: 13px 17px 12px 17px;
margin: 0 0 0 43px;
}
.todo-list li.editing .view {
display: none;
}
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.todo-list li .toggle:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
}
.todo-list li .toggle:checked:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
}
.todo-list li label {
white-space: pre-line;
word-break: break-all;
padding: 15px 60px 15px 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
transition: color 0.4s;
}
.todo-list li.completed label {
color: #d9d9d9;
text-decoration: line-through;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 30px;
color: #cc9a9a;
margin-bottom: 11px;
transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover {
color: #af5b5e;
}
.todo-list li .destroy:after {
content: '×';
}
.todo-list li:hover .destroy {
display: block;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
.footer {
color: #777;
padding: 10px 15px;
height: 20px;
text-align: center;
border-top: 1px solid #e6e6e6;
}
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
float: left;
text-align: left;
}
.todo-count strong {
font-weight: 300;
}
.filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.filters li {
display: inline;
}
.filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
.filters li a.selected,
.filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
}
.filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
cursor: pointer;
position: relative;
}
.clear-completed:hover {
text-decoration: underline;
}
.info {
margin: 65px auto 0;
color: #bfbfbf;
font-size: 10px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center;
}
.info p {
line-height: 1;
}
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.toggle-all,
.todo-list li .toggle {
background: none;
}
.todo-list li .toggle {
height: 40px;
}
.toggle-all {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
appearance: none;
}
}
@media (max-width: 430px) {
.footer {
height: 50px;
}
.filters {
bottom: 10px;
}
}

@ -0,0 +1,141 @@
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #c5c5c5;
border-bottom: 1px dashed #f7f7f7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
#issue-count {
display: none;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
transition-property: left;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
padding-left: 300px;
}
.learn-bar > .learn {
left: 8px;
}
}

@ -0,0 +1,249 @@
/* global _ */
(function () {
'use strict';
/* jshint ignore:start */
// Underscore's Template Module
// Courtesy of underscorejs.org
var _ = (function (_) {
_.defaults = function (object) {
if (!object) {
return object;
}
for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
var iterable = arguments[argsIndex];
if (iterable) {
for (var key in iterable) {
if (object[key] == null) {
object[key] = iterable[key];
}
}
}
}
return object;
}
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
};
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var noMatch = /(.)^/;
// Certain characters need to be escaped so that they can be put into a
// string literal.
var escapes = {
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\t': 't',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(text, data, settings) {
var render;
settings = _.defaults({}, settings, _.templateSettings);
// Combine delimiters into one regular expression via alternation.
var matcher = new RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
].join('|') + '|$', 'g');
// Compile the template source, escaping string literals appropriately.
var index = 0;
var source = "__p+='";
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset)
.replace(escaper, function(match) { return '\\' + escapes[match]; });
if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
}
if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
}
if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
}
index = offset + match.length;
return match;
});
source += "';\n";
// If a variable is not specified, place data values in local scope.
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
source + "return __p;\n";
try {
render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
}
if (data) return render(data, _);
var template = function(data) {
return render.call(this, data, _);
};
// Provide the compiled function source as a convenience for precompilation.
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
return template;
};
return _;
})({});
if (location.hostname === 'todomvc.com') {
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-31081062-1', 'auto');
ga('send', 'pageview');
}
/* jshint ignore:end */
function redirect() {
if (location.hostname === 'tastejs.github.io') {
location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com');
}
}
function findRoot() {
var base = location.href.indexOf('examples/');
return location.href.substr(0, base);
}
function getFile(file, callback) {
if (!location.host) {
return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.');
}
var xhr = new XMLHttpRequest();
xhr.open('GET', findRoot() + file, true);
xhr.send();
xhr.onload = function () {
if (xhr.status === 200 && callback) {
callback(xhr.responseText);
}
};
}
function Learn(learnJSON, config) {
if (!(this instanceof Learn)) {
return new Learn(learnJSON, config);
}
var template, framework;
if (typeof learnJSON !== 'object') {
try {
learnJSON = JSON.parse(learnJSON);
} catch (e) {
return;
}
}
if (config) {
template = config.template;
framework = config.framework;
}
if (!template && learnJSON.templates) {
template = learnJSON.templates.todomvc;
}
if (!framework && document.querySelector('[data-framework]')) {
framework = document.querySelector('[data-framework]').dataset.framework;
}
this.template = template;
if (learnJSON.backend) {
this.frameworkJSON = learnJSON.backend;
this.frameworkJSON.issueLabel = framework;
this.append({
backend: true
});
} else if (learnJSON[framework]) {
this.frameworkJSON = learnJSON[framework];
this.frameworkJSON.issueLabel = framework;
this.append();
}
this.fetchIssueCount();
}
Learn.prototype.append = function (opts) {
var aside = document.createElement('aside');
aside.innerHTML = _.template(this.template, this.frameworkJSON);
aside.className = 'learn';
if (opts && opts.backend) {
// Remove demo link
var sourceLinks = aside.querySelector('.source-links');
var heading = sourceLinks.firstElementChild;
var sourceLink = sourceLinks.lastElementChild;
// Correct link path
var href = sourceLink.getAttribute('href');
sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http')));
sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML;
} else {
// Localize demo links
var demoLinks = aside.querySelectorAll('.demo-link');
Array.prototype.forEach.call(demoLinks, function (demoLink) {
if (demoLink.getAttribute('href').substr(0, 4) !== 'http') {
demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href'));
}
});
}
document.body.className = (document.body.className + ' learn-bar').trim();
document.body.insertAdjacentHTML('afterBegin', aside.outerHTML);
};
Learn.prototype.fetchIssueCount = function () {
var issueLink = document.getElementById('issue-count-link');
if (issueLink) {
var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos');
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function (e) {
var parsedResponse = JSON.parse(e.target.responseText);
if (parsedResponse instanceof Array) {
var count = parsedResponse.length;
if (count !== 0) {
issueLink.innerHTML = 'This app has ' + count + ' open issues';
document.getElementById('issue-count').style.display = 'inline';
}
}
};
xhr.send();
}
};
redirect();
getFile('learn.json', Learn);
})();

@ -0,0 +1,49 @@
<!doctype html>
<html lang="en" data-framework="javascript">
<head>
<meta charset="utf-8">
<title>Crypt Todo</title>
<link rel="stylesheet" href="assets/todomvc-common/base.css">
<link rel="stylesheet" href="assets/todomvc-app-css/index.css">
</head>
<body>
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<input class="new-todo" placeholder="What needs to be done?" autofocus>
</header>
<section class="main">
<input class="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list"></ul>
</section>
<footer class="footer">
<span class="todo-count"></span>
<ul class="filters">
<li>
<a href="#/" class="selected">All</a>
</li>
<li>
<a href="#/active">Active</a>
</li>
<li>
<a href="#/completed">Completed</a>
</li>
</ul>
<button class="clear-completed">Clear completed</button>
</footer>
</section>
<footer class="info">
</footer>
<script src="assets/todomvc-common/base.js"></script>
<script src="js/helpers.js"></script>
<script src="js/store.js"></script>
<script src="js/model.js"></script>
<script src="js/template.js"></script>
<script src="js/view.js"></script>
<script src="js/controller.js"></script>
<script src="js/app.js"></script>
</body>
</html>

@ -0,0 +1,25 @@
/*global app, $on */
(function () {
'use strict';
/**
* Sets up a brand new Todo list.
*
* @param {string} name The name of your new to do list.
*/
function Todo(name) {
this.storage = new app.Store(name);
this.model = new app.Model(this.storage);
this.template = new app.Template();
this.view = new app.View(this.template);
this.controller = new app.Controller(this.model, this.view);
}
var todo = new Todo('todos-vanillajs');
function setView() {
todo.controller.setView(document.location.hash);
}
$on(window, 'load', setView);
$on(window, 'hashchange', setView);
})();

@ -0,0 +1,270 @@
(function (window) {
'use strict';
/**
* Takes a model and view and acts as the controller between them
*
* @constructor
* @param {object} model The model instance
* @param {object} view The view instance
*/
function Controller(model, view) {
var self = this;
self.model = model;
self.view = view;
self.view.bind('newTodo', function (title) {
self.addItem(title);
});
self.view.bind('itemEdit', function (item) {
self.editItem(item.id);
});
self.view.bind('itemEditDone', function (item) {
self.editItemSave(item.id, item.title);
});
self.view.bind('itemEditCancel', function (item) {
self.editItemCancel(item.id);
});
self.view.bind('itemRemove', function (item) {
self.removeItem(item.id);
});
self.view.bind('itemToggle', function (item) {
self.toggleComplete(item.id, item.completed);
});
self.view.bind('removeCompleted', function () {
self.removeCompletedItems();
});
self.view.bind('toggleAll', function (status) {
self.toggleAll(status.completed);
});
}
/**
* Loads and initialises the view
*
* @param {string} '' | 'active' | 'completed'
*/
Controller.prototype.setView = function (locationHash) {
var route = locationHash.split('/')[1];
var page = route || '';
this._updateFilterState(page);
};
/**
* An event to fire on load. Will get all items and display them in the
* todo-list
*/
Controller.prototype.showAll = function () {
var self = this;
self.model.read(function (data) {
self.view.render('showEntries', data);
});
};
/**
* Renders all active tasks
*/
Controller.prototype.showActive = function () {
var self = this;
self.model.read({ completed: false }, function (data) {
self.view.render('showEntries', data);
});
};
/**
* Renders all completed tasks
*/
Controller.prototype.showCompleted = function () {
var self = this;
self.model.read({ completed: true }, function (data) {
self.view.render('showEntries', data);
});
};
/**
* An event to fire whenever you want to add an item. Simply pass in the event
* object and it'll handle the DOM insertion and saving of the new item.
*/
Controller.prototype.addItem = function (title) {
var self = this;
if (title.trim() === '') {
return;
}
self.model.create(title, function () {
self.view.render('clearNewTodo');
self._filter(true);
});
};
/*
* Triggers the item editing mode.
*/
Controller.prototype.editItem = function (id) {
var self = this;
self.model.read(id, function (data) {
self.view.render('editItem', {id: id, title: data[0].title});
});
};
/*
* Finishes the item editing mode successfully.
*/
Controller.prototype.editItemSave = function (id, title) {
var self = this;
title = title.trim();
if (title.length !== 0) {
self.model.update(id, {title: title}, function () {
self.view.render('editItemDone', {id: id, title: title});
});
} else {
self.removeItem(id);
}
};
/*
* Cancels the item editing mode.
*/
Controller.prototype.editItemCancel = function (id) {
var self = this;
self.model.read(id, function (data) {
self.view.render('editItemDone', {id: id, title: data[0].title});
});
};
/**
* By giving it an ID it'll find the DOM element matching that ID,
* remove it from the DOM and also remove it from storage.
*
* @param {number} id The ID of the item to remove from the DOM and
* storage
*/
Controller.prototype.removeItem = function (id) {
var self = this;
self.model.remove(id, function () {
self.view.render('removeItem', id);
});
self._filter();
};
/**
* Will remove all completed items from the DOM and storage.
*/
Controller.prototype.removeCompletedItems = function () {
var self = this;
self.model.read({ completed: true }, function (data) {
data.forEach(function (item) {
self.removeItem(item.id);
});
});
self._filter();
};
/**
* Give it an ID of a model and a checkbox and it will update the item
* in storage based on the checkbox's state.
*
* @param {number} id The ID of the element to complete or uncomplete
* @param {object} checkbox The checkbox to check the state of complete
* or not
* @param {boolean|undefined} silent Prevent re-filtering the todo items
*/
Controller.prototype.toggleComplete = function (id, completed, silent) {
var self = this;
self.model.update(id, { completed: completed }, function () {
self.view.render('elementComplete', {
id: id,
completed: completed
});
});
if (!silent) {
self._filter();
}
};
/**
* Will toggle ALL checkboxes' on/off state and completeness of models.
* Just pass in the event object.
*/
Controller.prototype.toggleAll = function (completed) {
var self = this;
self.model.read({ completed: !completed }, function (data) {
data.forEach(function (item) {
self.toggleComplete(item.id, completed, true);
});
});
self._filter();
};
/**
* Updates the pieces of the page which change depending on the remaining
* number of todos.
*/
Controller.prototype._updateCount = function () {
var self = this;
self.model.getCount(function (todos) {
self.view.render('updateElementCount', todos.active);
self.view.render('clearCompletedButton', {
completed: todos.completed,
visible: todos.completed > 0
});
self.view.render('toggleAll', {checked: todos.completed === todos.total});
self.view.render('contentBlockVisibility', {visible: todos.total > 0});
});
};
/**
* Re-filters the todo items, based on the active route.
* @param {boolean|undefined} force forces a re-painting of todo items.
*/
Controller.prototype._filter = function (force) {
var activeRoute = this._activeRoute.charAt(0).toUpperCase() + this._activeRoute.substr(1);
// Update the elements on the page, which change with each completed todo
this._updateCount();
// If the last active route isn't "All", or we're switching routes, we
// re-create the todo item elements, calling:
// this.show[All|Active|Completed]();
if (force || this._lastActiveRoute !== 'All' || this._lastActiveRoute !== activeRoute) {
this['show' + activeRoute]();
}
this._lastActiveRoute = activeRoute;
};
/**
* Simply updates the filter nav's selected states
*/
Controller.prototype._updateFilterState = function (currentPage) {
// Store a reference to the active route, allowing us to re-filter todo
// items as they are marked complete or incomplete.
this._activeRoute = currentPage;
if (currentPage === '') {
this._activeRoute = 'All';
}
this._filter();
this.view.render('setFilter', currentPage);
};
// Export to window
window.app = window.app || {};
window.app.Controller = Controller;
})(window);

@ -0,0 +1,52 @@
/*global NodeList */
(function (window) {
'use strict';
// Get element(s) by CSS selector:
window.qs = function (selector, scope) {
return (scope || document).querySelector(selector);
};
window.qsa = function (selector, scope) {
return (scope || document).querySelectorAll(selector);
};
// addEventListener wrapper:
window.$on = function (target, type, callback, useCapture) {
target.addEventListener(type, callback, !!useCapture);
};
// Attach a handler to event for all elements that match the selector,
// now or in the future, based on a root element
window.$delegate = function (target, selector, type, handler) {
function dispatchEvent(event) {
var targetElement = event.target;
var potentialElements = window.qsa(selector, target);
var hasMatch = Array.prototype.indexOf.call(potentialElements, targetElement) >= 0;
if (hasMatch) {
handler.call(targetElement, event);
}
}
// https://developer.mozilla.org/en-US/docs/Web/Events/blur
var useCapture = type === 'blur' || type === 'focus';
window.$on(target, type, dispatchEvent, useCapture);
};
// Find the element's parent with the given tag name:
// $parent(qs('a'), 'div');
window.$parent = function (element, tagName) {
if (!element.parentNode) {
return;
}
if (element.parentNode.tagName.toLowerCase() === tagName.toLowerCase()) {
return element.parentNode;
}
return window.$parent(element.parentNode, tagName);
};
// Allow for looping on nodes by chaining:
// qsa('.foo').forEach(function () {})
NodeList.prototype.forEach = Array.prototype.forEach;
})(window);

@ -0,0 +1,120 @@
(function (window) {
'use strict';
/**
* Creates a new Model instance and hooks up the storage.
*
* @constructor
* @param {object} storage A reference to the client side storage class
*/
function Model(storage) {
this.storage = storage;
}
/**
* Creates a new todo model
*
* @param {string} [title] The title of the task
* @param {function} [callback] The callback to fire after the model is created
*/
Model.prototype.create = function (title, callback) {
title = title || '';
callback = callback || function () {};
var newItem = {
title: title.trim(),
completed: false
};
this.storage.save(newItem, callback);
};
/**
* Finds and returns a model in storage. If no query is given it'll simply
* return everything. If you pass in a string or number it'll look that up as
* the ID of the model to find. Lastly, you can pass it an object to match
* against.
*
* @param {string|number|object} [query] A query to match models against
* @param {function} [callback] The callback to fire after the model is found
*
* @example
* model.read(1, func); // Will find the model with an ID of 1
* model.read('1'); // Same as above
* //Below will find a model with foo equalling bar and hello equalling world.
* model.read({ foo: 'bar', hello: 'world' });
*/
Model.prototype.read = function (query, callback) {
var queryType = typeof query;
callback = callback || function () {};
if (queryType === 'function') {
callback = query;
return this.storage.findAll(callback);
} else if (queryType === 'string' || queryType === 'number') {
query = parseInt(query, 10);
this.storage.find({ id: query }, callback);
} else {
this.storage.find(query, callback);
}
};
/**
* Updates a model by giving it an ID, data to update, and a callback to fire when
* the update is complete.
*
* @param {number} id The id of the model to update
* @param {object} data The properties to update and their new value
* @param {function} callback The callback to fire when the update is complete.
*/
Model.prototype.update = function (id, data, callback) {
this.storage.save(data, callback, id);
};
/**
* Removes a model from storage
*
* @param {number} id The ID of the model to remove
* @param {function} callback The callback to fire when the removal is complete.
*/
Model.prototype.remove = function (id, callback) {
this.storage.remove(id, callback);
};
/**
* WARNING: Will remove ALL data from storage.
*
* @param {function} callback The callback to fire when the storage is wiped.
*/
Model.prototype.removeAll = function (callback) {
this.storage.drop(callback);
};
/**
* Returns a count of all todos
*/
Model.prototype.getCount = function (callback) {
var todos = {
active: 0,
completed: 0,
total: 0
};
this.storage.findAll(function (data) {
data.forEach(function (todo) {
if (todo.completed) {
todos.completed++;
} else {
todos.active++;
}
todos.total++;
});
callback(todos);
});
};
// Export to window
window.app = window.app || {};
window.app.Model = Model;
})(window);

@ -0,0 +1,141 @@
/*jshint eqeqeq:false */
(function (window) {
'use strict';
/**
* Creates a new client side storage object and will create an empty
* collection if no collection already exists.
*
* @param {string} name The name of our DB we want to use
* @param {function} callback Our fake DB uses callbacks because in
* real life you probably would be making AJAX calls
*/
function Store(name, callback) {
callback = callback || function () {};
this._dbName = name;
if (!localStorage[name]) {
var data = {
todos: []
};
localStorage[name] = JSON.stringify(data);
}
callback.call(this, JSON.parse(localStorage[name]));
}
/**
* Finds items based on a query given as a JS object
*
* @param {object} query The query to match against (i.e. {foo: 'bar'})
* @param {function} callback The callback to fire when the query has
* completed running
*
* @example
* db.find({foo: 'bar', hello: 'world'}, function (data) {
* // data will return any items that have foo: bar and
* // hello: world in their properties
* });
*/
Store.prototype.find = function (query, callback) {
if (!callback) {
return;
}
var todos = JSON.parse(localStorage[this._dbName]).todos;
callback.call(this, todos.filter(function (todo) {
for (var q in query) {
if (query[q] !== todo[q]) {
return false;
}
}
return true;
}));
};
/**
* Will retrieve all data from the collection
*
* @param {function} callback The callback to fire upon retrieving data
*/
Store.prototype.findAll = function (callback) {
callback = callback || function () {};
callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
};
/**
* Will save the given data to the DB. If no item exists it will create a new
* item, otherwise it'll simply update an existing item's properties
*
* @param {object} updateData The data to save back into the DB
* @param {function} callback The callback to fire after saving
* @param {number} id An optional param to enter an ID of an item to update
*/
Store.prototype.save = function (updateData, callback, id) {
var data = JSON.parse(localStorage[this._dbName]);
var todos = data.todos;
callback = callback || function () {};
// If an ID was actually given, find the item and update each property
if (id) {
for (var i = 0; i < todos.length; i++) {
if (todos[i].id === id) {
for (var key in updateData) {
todos[i][key] = updateData[key];
}
break;
}
}
localStorage[this._dbName] = JSON.stringify(data);
callback.call(this, todos);
} else {
// Generate an ID
updateData.id = new Date().getTime();
todos.push(updateData);
localStorage[this._dbName] = JSON.stringify(data);
callback.call(this, [updateData]);
}
};
/**
* Will remove an item from the Store based on its ID
*
* @param {number} id The ID of the item you want to remove
* @param {function} callback The callback to fire after saving
*/
Store.prototype.remove = function (id, callback) {
var data = JSON.parse(localStorage[this._dbName]);
var todos = data.todos;
for (var i = 0; i < todos.length; i++) {
if (todos[i].id == id) {
todos.splice(i, 1);
break;
}
}
localStorage[this._dbName] = JSON.stringify(data);
callback.call(this, todos);
};
/**
* Will drop all storage and start fresh
*
* @param {function} callback The callback to fire after dropping the data
*/
Store.prototype.drop = function (callback) {
var data = {todos: []};
localStorage[this._dbName] = JSON.stringify(data);
callback.call(this, data.todos);
};
// Export to window
window.app = window.app || {};
window.app.Store = Store;
})(window);

@ -0,0 +1,114 @@
/*jshint laxbreak:true */
(function (window) {
'use strict';
var htmlEscapes = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
'\'': '&#x27;',
'`': '&#x60;'
};
var escapeHtmlChar = function (chr) {
return htmlEscapes[chr];
};
var reUnescapedHtml = /[&<>"'`]/g;
var reHasUnescapedHtml = new RegExp(reUnescapedHtml.source);
var escape = function (string) {
return (string && reHasUnescapedHtml.test(string))
? string.replace(reUnescapedHtml, escapeHtmlChar)
: string;
};
/**
* Sets up defaults for all the Template methods such as a default template
*
* @constructor
*/
function Template() {
this.defaultTemplate
= '<li data-id="{{id}}" class="{{completed}}">'
+ '<div class="view">'
+ '<input class="toggle" type="checkbox" {{checked}}>'
+ '<label>{{title}}</label>'
+ '<button class="destroy"></button>'
+ '</div>'
+ '</li>';
}
/**
* Creates an <li> HTML string and returns it for placement in your app.
*
* NOTE: In real life you should be using a templating engine such as Mustache
* or Handlebars, however, this is a vanilla JS example.
*
* @param {object} data The object containing keys you want to find in the
* template to replace.
* @returns {string} HTML String of an <li> element
*
* @example
* view.show({
* id: 1,
* title: "Hello World",
* completed: 0,
* });
*/
Template.prototype.show = function (data) {
var i = 0, l = data.length;
var view = '';
for (; i < l; i++) {
var template = this.defaultTemplate;
var completed = '';
var checked = '';
if (data[i].completed) {
completed = 'completed';
checked = 'checked';
}
template = template.replace('{{id}}', data[i].id);
template = template.replace('{{title}}', escape(data[i].title));
template = template.replace('{{completed}}', completed);
template = template.replace('{{checked}}', checked);
view = view + template;
}
return view;
};
/**
* Displays a counter of how many to dos are left to complete
*
* @param {number} activeTodos The number of active todos.
* @returns {string} String containing the count
*/
Template.prototype.itemCounter = function (activeTodos) {
var plural = activeTodos === 1 ? '' : 's';
return '<strong>' + activeTodos + '</strong> item' + plural + ' left';
};
/**
* Updates the text within the "Clear completed" button
*
* @param {[type]} completedTodos The number of completed todos.
* @returns {string} String containing the count
*/
Template.prototype.clearCompletedButton = function (completedTodos) {
if (completedTodos > 0) {
return 'Clear completed';
} else {
return '';
}
};
// Export to window
window.app = window.app || {};
window.app.Template = Template;
})(window);

@ -0,0 +1,219 @@
/*global qs, qsa, $on, $parent, $delegate */
(function (window) {
'use strict';
/**
* View that abstracts away the browser's DOM completely.
* It has two simple entry points:
*
* - bind(eventName, handler)
* Takes a todo application event and registers the handler
* - render(command, parameterObject)
* Renders the given command with the options
*/
function View(template) {
this.template = template;
this.ENTER_KEY = 13;
this.ESCAPE_KEY = 27;
this.$todoList = qs('.todo-list');
this.$todoItemCounter = qs('.todo-count');
this.$clearCompleted = qs('.clear-completed');
this.$main = qs('.main');
this.$footer = qs('.footer');
this.$toggleAll = qs('.toggle-all');
this.$newTodo = qs('.new-todo');
}
View.prototype._removeItem = function (id) {
var elem = qs('[data-id="' + id + '"]');
if (elem) {
this.$todoList.removeChild(elem);
}
};
View.prototype._clearCompletedButton = function (completedCount, visible) {
this.$clearCompleted.innerHTML = this.template.clearCompletedButton(completedCount);
this.$clearCompleted.style.display = visible ? 'block' : 'none';
};
View.prototype._setFilter = function (currentPage) {
qs('.filters .selected').className = '';
qs('.filters [href="#/' + currentPage + '"]').className = 'selected';
};
View.prototype._elementComplete = function (id, completed) {
var listItem = qs('[data-id="' + id + '"]');
if (!listItem) {
return;
}
listItem.className = completed ? 'completed' : '';
// In case it was toggled from an event and not by clicking the checkbox
qs('input', listItem).checked = completed;
};
View.prototype._editItem = function (id, title) {
var listItem = qs('[data-id="' + id + '"]');
if (!listItem) {
return;
}
listItem.className = listItem.className + ' editing';
var input = document.createElement('input');
input.className = 'edit';
listItem.appendChild(input);
input.focus();
input.value = title;
};
View.prototype._editItemDone = function (id, title) {
var listItem = qs('[data-id="' + id + '"]');
if (!listItem) {
return;
}
var input = qs('input.edit', listItem);
listItem.removeChild(input);
listItem.className = listItem.className.replace('editing', '');
qsa('label', listItem).forEach(function (label) {
label.textContent = title;
});
};
View.prototype.render = function (viewCmd, parameter) {
var self = this;
var viewCommands = {
showEntries: function () {
self.$todoList.innerHTML = self.template.show(parameter);
},
removeItem: function () {
self._removeItem(parameter);
},
updateElementCount: function () {
self.$todoItemCounter.innerHTML = self.template.itemCounter(parameter);
},
clearCompletedButton: function () {
self._clearCompletedButton(parameter.completed, parameter.visible);
},
contentBlockVisibility: function () {
self.$main.style.display = self.$footer.style.display = parameter.visible ? 'block' : 'none';
},
toggleAll: function () {
self.$toggleAll.checked = parameter.checked;
},
setFilter: function () {
self._setFilter(parameter);
},
clearNewTodo: function () {
self.$newTodo.value = '';
},
elementComplete: function () {
self._elementComplete(parameter.id, parameter.completed);
},
editItem: function () {
self._editItem(parameter.id, parameter.title);
},
editItemDone: function () {
self._editItemDone(parameter.id, parameter.title);
}
};
viewCommands[viewCmd]();
};
View.prototype._itemId = function (element) {
var li = $parent(element, 'li');
return parseInt(li.dataset.id, 10);
};
View.prototype._bindItemEditDone = function (handler) {
var self = this;
$delegate(self.$todoList, 'li .edit', 'blur', function () {
if (!this.dataset.iscanceled) {
handler({
id: self._itemId(this),
title: this.value
});
}
});
$delegate(self.$todoList, 'li .edit', 'keypress', function (event) {
if (event.keyCode === self.ENTER_KEY) {
// Remove the cursor from the input when you hit enter just like if it
// were a real form
this.blur();
}
});
};
View.prototype._bindItemEditCancel = function (handler) {
var self = this;
$delegate(self.$todoList, 'li .edit', 'keyup', function (event) {
if (event.keyCode === self.ESCAPE_KEY) {
this.dataset.iscanceled = true;
this.blur();
handler({id: self._itemId(this)});
}
});
};
View.prototype.bind = function (event, handler) {
var self = this;
if (event === 'newTodo') {
$on(self.$newTodo, 'change', function () {
handler(self.$newTodo.value);
});
} else if (event === 'removeCompleted') {
$on(self.$clearCompleted, 'click', function () {
handler();
});
} else if (event === 'toggleAll') {
$on(self.$toggleAll, 'click', function () {
handler({completed: this.checked});
});
} else if (event === 'itemEdit') {
$delegate(self.$todoList, 'li label', 'dblclick', function () {
handler({id: self._itemId(this)});
});
} else if (event === 'itemRemove') {
$delegate(self.$todoList, '.destroy', 'click', function () {
handler({id: self._itemId(this)});
});
} else if (event === 'itemToggle') {
$delegate(self.$todoList, '.toggle', 'click', function () {
handler({
id: self._itemId(this),
completed: this.checked
});
});
} else if (event === 'itemEditDone') {
self._bindItemEditDone(handler);
} else if (event === 'itemEditCancel') {
self._bindItemEditCancel(handler);
}
};
// Export to window
window.app = window.app || {};
window.app.View = View;
}(window));

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html class="cp pad">
<head>
<title>CryptPad</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.1.15"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#pad-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
</style>
</head>
<body>
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
<script async data-bootload="/todo/inner.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.1.15"></script>
<style>.loading-hidden, .loading-hidden * {display: none !important;}</style>
</head>
<body class="loading-hidden">
<div id="toolbar" class="toolbar-container"></div>
<div id="container">
<div class="cp-create-form">
<input type="text" id="newTodoName" data-localization-placeholder="todo_newTodoNamePlaceholder" />
<button class="btn btn-success fa fa-plus" data-localization-title="todo_newTodoNameTitle"></button>
</div>
<div id="tasksList"></div>
</div>
</body>
</html>

@ -0,0 +1,15 @@
define([
'jquery',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/todo/todo.less',
//'less!/customize/src/less/cryptpad.less',
'less!/customize/src/less/toolbar.less',
], function ($) {
$('.loading-hidden').removeClass('loading-hidden');
// dirty hack to get rid the flash of the lock background
/*
setTimeout(function () {
$('#app').addClass('ready');
}, 100);*/
});

@ -0,0 +1,229 @@
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js',
'/common/toolbar2.js',
'/common/cryptpad-common.js',
'/todo/todo.js',
//'/common/media-tag.js',
//'/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less/cryptpad.less',
], function ($, Crypto, Listmap, Toolbar, Cryptpad, Todo) {
var Messages = Cryptpad.Messages;
var APP = window.APP = {};
$(function () {
var $iframe = $('#pad-iframe').contents();
var $body = $iframe.find('body');
var ifrw = $('#pad-iframe')[0].contentWindow;
var $list = $iframe.find('#tasksList');
var removeTips = function () {
Cryptpad.clearTooltips();
};
var onReady = function () {
var todo = Todo.init(APP.lm.proxy, Cryptpad);
var deleteTask = function(id) {
todo.remove(id);
var $els = $list.find('.cp-task').filter(function (i, el) {
return $(el).data('id') === id;
});
$els.fadeOut(null, function () {
$els.remove();
removeTips();
});
//APP.display();
};
// TODO make this actually work, and scroll to bottom...
var scrollTo = function (t) {
var $list = $iframe.find('#tasksList');
$list.animate({
scrollTop: t,
});
};
scrollTo = scrollTo;
var makeCheckbox = function (id, cb) {
var entry = APP.lm.proxy.data[id];
var checked = entry.state === 1? 'cp-task-checkbox-checked fa-check-square-o': 'cp-task-checkbox-unchecked fa-square-o';
var title = entry.state === 1?
Messages.todo_markAsIncompleteTitle:
Messages.todo_markAsCompleteTitle;
title = title;
removeTips();
return $('<span>', {
'class': 'cp-task-checkbox fa ' + checked,
//title: title,
}).on('click', function () {
entry.state = (entry.state + 1) % 2;
if (typeof(cb) === 'function') {
cb(entry.state);
}
});
};
var addTaskUI = function (el, animate) {
var $taskDiv = $('<div>', {
'class': 'cp-task'
});
if (animate) {
$taskDiv.prependTo($list);
} else {
$taskDiv.appendTo($list);
}
$taskDiv.data('id', el);
makeCheckbox(el, function (/*state*/) {
APP.display();
})
.appendTo($taskDiv);
var entry = APP.lm.proxy.data[el];
if (entry.state) {
$taskDiv.addClass('cp-task-complete');
}
$('<span>', { 'class': 'cp-task-text' })
.text(entry.task)
.appendTo($taskDiv);
/*$('<span>', { 'class': 'cp-task-date' })
.text(new Date(entry.ctime).toLocaleString())
.appendTo($taskDiv);*/
$('<button>', {
'class': 'fa fa-times cp-task-remove btn btn-danger',
title: Messages.todo_removeTaskTitle,
}).appendTo($taskDiv).on('click', function() {
deleteTask(el);
});
if (animate) {
$taskDiv.hide();
window.setTimeout(function () {
// ???
$taskDiv.fadeIn();
}, 0);
}
removeTips();
};
var display = APP.display = function () {
$list.empty();
removeTips();
APP.lm.proxy.order.forEach(function (el) {
addTaskUI(el);
});
//scrollTo('300px');
};
var addTask = function () {
var $input = $iframe.find('#newTodoName');
// if the input is empty after removing leading and trailing spaces
// don't create a new entry
if (!$input.val().trim()) { return; }
var obj = {
"state": 0,
"task": $input.val(),
"ctime": +new Date(),
"mtime": +new Date()
};
var id = Cryptpad.createChannelId();
todo.add(id, obj);
$input.val("");
addTaskUI(id, true);
//display();
};
var $formSubmit = $iframe.find('.cp-create-form button').on('click', addTask);
$iframe.find('#newTodoName').on('keypress', function (e) {
switch (e.which) {
case 13:
$formSubmit.click();
break;
default:
console.log(e.which);
}
}).focus();
var editTask = function () {
};
editTask = editTask;
display();
Cryptpad.removeLoadingScreen();
};
var onInit = function () {
Cryptpad.addLoadingScreen();
$body.on('dragover', function (e) { e.preventDefault(); });
$body.on('drop', function (e) { e.preventDefault(); });
var Title;
var $bar = $iframe.find('.toolbar-container');
Title = Cryptpad.createTitle({}, function(){}, Cryptpad);
var configTb = {
displayed: ['useradmin', 'newpad', 'limit', 'upgrade', 'pageTitle'],
ifrw: ifrw,
common: Cryptpad,
//hideDisplayName: true,
$container: $bar,
pageTitle: Messages.todo_title
};
APP.toolbar = Toolbar.create(configTb);
APP.toolbar.$rightside.html(''); // Remove the drawer if we don't use it to hide the toolbar
};
var createTodo = function() {
var obj = Cryptpad.getProxy();
var hash = Cryptpad.createRandomHash();
if(obj.todo) {
hash = obj.todo;
} else {
obj.todo = hash;
}
var secret = Cryptpad.getSecrets('todo', hash);
var listmapConfig = {
data: {},
websocketURL: Cryptpad.getWebsocketURL(),
channel: secret.channel,
validateKey: secret.keys.validateKey || undefined,
crypto: Crypto.createEncryptor(secret.keys),
userName: 'todo',
logLevel: 1,
};
var lm = APP.lm = Listmap.create(listmapConfig);
lm.proxy.on('create', onInit)
.on('ready', onReady);
};
Cryptpad.ready(function () {
createTodo();
Cryptpad.reportAppUsage();
});
});
});

@ -0,0 +1,83 @@
define([
], function () {
var Todo = {};
var Cryptpad;
/* data model
{
"order": [
"123456789abcdef0",
"23456789abcdef01",
"0123456789abcedf"
],
"data": {
"0123456789abcedf": {
"state": 0, // used to sort completed elements
"task": "pewpewpew",
"ctime": +new Date(), // used to display chronologically
"mtime": +new Date(), // used to display recent actions
// "deadline": +new Date() + 1000 * 60 * 60 * 24 * 7
},
"123456789abcdef0": {},
"23456789abcdef01": {}
}
}
*/
var val = function (proxy, id, k, v) {
var el = proxy.data[id];
if (!el) {
throw new Error('expected an element');
}
if (typeof(v) === 'function') { el[k] = v(el[k]); }
else { el[k] = v; }
return el[k];
};
var initialize = function (proxy) {
// run migration
if (typeof(proxy.data) !== 'object') { proxy.data = {}; }
if (!Array.isArray(proxy.order)) { proxy.order = []; }
if (typeof(proxy.type) !== 'string') { proxy.type = 'todo'; }
};
/* add (id, obj) push id to order, add object to data */
var add = function (proxy, id, obj) {
if (!Array.isArray(proxy.order)) {
throw new Error('expected an array');
}
proxy.order.unshift(id);
proxy.data[id] = obj;
};
/* delete (id) remove id from order, delete id from data */
var remove = function (proxy, id) {
if (Array.isArray(proxy.order)) {
var i = proxy.order.indexOf(id);
proxy.order.splice(i, 1);
}
if (proxy.data[id]) { delete proxy.data[id]; }
};
Todo.init = function (proxy, common) {
Cryptpad = common;
var api = {};
initialize(proxy);
api.val = function (id, k, v) {
return val(proxy, id, k, v);
};
api.add = function (id, obj) {
return add(proxy, id, obj);
};
api.remove = function (id) {
return remove(proxy, id);
};
return api;
};
return Todo;
});

@ -0,0 +1,121 @@
@import "/customize/src/less/variables.less";
@import "/customize/src/less/mixins.less";
@button-border: 2px;
html, body {
margin: 0px;
height: 100%;
}
#toolbar {
display: flex; // We need this to remove a 3px border at the bottom of the toolbar
}
body {
display: flex;
flex-flow: column;
}
#app {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
.cryptpad-toolbar {
padding: 0px;
display: inline-block;
}
#container {
display: flex;
flex: 1;
flex-flow: column;
padding: 20px;
align-items: center;
background-color: lighten(@toolbar-todo-bg, 15%);
min-height: 0;
}
@spacing: 15px;
#tasksList {
flex: 1;
min-height: 0;
overflow-y: auto;
min-width: 40%;
max-width: 90%;
}
.cp-create-form {
margin: @spacing;
min-width: 40%;
display: flex;
#newTodoName {
flex: 1;
margin-right: 15px;
border-radius: 0;
border: 0;
background-color: darken(@toolbar-todo-bg, 10%);
color: #fff;
padding: 5px 10px;
font-weight: bold;
}
button {
cursor: pointer;
border-radius: 0;
background-color: darken(@toolbar-todo-bg, 20%);
border:0;
&:hover {
background-color: darken(@toolbar-todo-bg, 25%);
}
}
}
.cp-task {
border: 1px solid black;
padding: @spacing;
display: flex;
align-items: center;
background-color: white;
&.cp-task-complete {
background-color: #f0f0f0;
color: #777;
}
.cp-task-text {
margin: @spacing;
flex: 1;
word-wrap: break-word;
min-width: 0;
font-weight: bold;
}
.cp-task-date {
margin: @spacing;
}
.cp-task-remove {
margin: @spacing;
cursor: pointer;
}
.cp-task-checkbox {
font-size: 45px;
width: 45px;
cursor: pointer;
&:hover {
color: #999;
}
}
.cp-task-checkbox-checked {
}
.cp-task-checkbox-unchecked {
}
button {
border-radius: 0;
border:0;
}
}

@ -212,6 +212,18 @@ window.canvas = canvas;
});
};
module.FM = Cryptpad.createFileManager({});
module.upload = function (title) {
$canvas[0].toBlob(function (blob) {
blob.name = title;
var reader = new FileReader();
reader.onloadend = function () {
module.FM.handleFile(blob);
};
reader.readAsArrayBuffer(blob);
});
};
var initializing = true;
var $bar = $('#toolbar');
@ -337,6 +349,15 @@ window.canvas = canvas;
var $export = Cryptpad.createButton('export', true, {}, saveImage);
$rightside.append($export);
Cryptpad.createButton('savetodrive', true, {}, function () {})
.click(function () {
Cryptpad.prompt(Messages.exportPrompt, document.title + '.png',
function (name) {
if (name === null || !name.trim()) { return; }
module.upload(name);
});
}).appendTo($rightside);
var $forget = Cryptpad.createButton('forget', true, {}, function (err) {
if (err) { return; }
setEditable(false);
@ -344,7 +365,6 @@ window.canvas = canvas;
});
$rightside.append($forget);
var editHash;
if (!readOnly) {

Loading…
Cancel
Save