Merge branch 'staging' into newDrive

pull/1/head
yflory 8 years ago
commit b0fe04381d

1
.gitignore vendored

@ -13,4 +13,5 @@ data
npm-debug.log npm-debug.log
pins/ pins/
blob/ blob/
blobstage/
privileged.conf privileged.conf

@ -1,8 +1,12 @@
language: node_js language: node_js
env: env:
matrix: matrix:
- "BROWSER='firefox:19:Windows 2012'" - "BROWSER='firefox::Windows 10'"
- "BROWSER='chrome::Windows 2008'" - "BROWSER='chrome::Windows 10'"
#- "BROWSER='MicrosoftEdge:14.14393:Windows 10'"
#- "BROWSER='internet explorer:11.103:Windows 10'"
#- "BROWSER='safari:10.0:macOS 10.12'"
#- "BROWSER='safari:9.0:OS X 10.11'"
branches: branches:
only: only:
- master - master

@ -1,11 +1,13 @@
/* global process */ /* global process */
var WebDriver = require("selenium-webdriver"); var WebDriver = require("selenium-webdriver");
var nThen = require('nthen');
if (process.env.TRAVIS_PULL_REQUEST && process.env.TRAVIS_PULL_REQUEST !== 'false') { if (process.env.TRAVIS_PULL_REQUEST && process.env.TRAVIS_PULL_REQUEST !== 'false') {
// We can't do saucelabs on pull requests so don't fail. // We can't do saucelabs on pull requests so don't fail.
return; return;
} }
// https://wiki.saucelabs.com/display/DOCS/Platform+Configurator#/
var driver; var driver;
if (process.env.SAUCE_USERNAME !== undefined) { if (process.env.SAUCE_USERNAME !== undefined) {
var browserArray = process.env.BROWSER.split(':'); var browserArray = process.env.BROWSER.split(':');
@ -21,18 +23,59 @@ if (process.env.SAUCE_USERNAME !== undefined) {
driver = new WebDriver.Builder().withCapabilities({ browserName: "chrome" }).build(); driver = new WebDriver.Builder().withCapabilities({ browserName: "chrome" }).build();
} }
driver.get('http://localhost:3000/assert/'); var SC_GET_DATA = "return (window.__CRYPTPAD_TEST__) ? window.__CRYPTPAD_TEST__.getData() : '[]'";
var report = driver.wait(WebDriver.until.elementLocated(WebDriver.By.className("report")), 5000);
report.getAttribute("class").then(function (cls) { var failed = false;
report.getText().then(function (text) { var nt = nThen;
console.log("\n-----\n" + text + "\n-----"); [
driver.quit(); //'/register/#?test=test',
if (!cls) { '/assert/#?test=test',
throw new Error("cls is null"); // '/auth/#?test=test' // TODO(cjd): Not working on automatic tests, understand why.
} else if (cls.indexOf("failure") !== -1) { ].forEach(function (path) {
throw new Error("cls contains the word failure"); if (failed) { return; }
} else if (cls.indexOf("success") === -1) { var url = 'http://localhost:3000' + path;
throw new Error("cls does not contain the word success"); nt = nt(function (waitFor) {
} var done = waitFor();
}); console.log('\n\n-----TEST ' + url + ' -----');
var waitTo = setTimeout(function () {
console.log("no report in 20 seconds, timing out");
failed = true;
done();
done = undefined;
}, 20000);
var logMore = function () {
if (!done) { return; }
driver.executeScript(SC_GET_DATA).then(waitFor(function (dataS) {
if (!done) { return; }
var data = JSON.parse(dataS);
data.forEach(function (d) {
if (d.type !== 'log') { return; }
console.log('>' + d.val);
});
data.forEach(function (d) {
if (d.type !== 'report') { return; }
console.log('RESULT: ' + d.val);
if (d.val !== 'passed') {
if (d.error) {
console.log(d.error.message);
console.log(d.error.stack);
}
failed = true;
}
clearTimeout(waitTo);
console.log('-----END TEST ' + url + ' -----');
done();
done = undefined;
});
if (done) { setTimeout(logMore, 50); }
}));
};
driver.get(url).then(waitFor(logMore));
}).nThen;
});
nt(function (waitFor) {
driver.quit().then(waitFor(function () {
if (failed) { process.exit(100); }
}));
}); });

@ -37,6 +37,7 @@
"diff-dom": "^2.1.1", "diff-dom": "^2.1.1",
"alertifyjs": "^1.0.11", "alertifyjs": "^1.0.11",
"scrypt-async": "^1.2.0", "scrypt-async": "^1.2.0",
"bootstrap": "#v4.0.0-alpha.6" "bootstrap": "#v4.0.0-alpha.6",
"pdfjs-dist": "^1.8.398"
} }
} }

@ -10,7 +10,7 @@ module.exports = {
// the port on which your httpd will listen // the port on which your httpd will listen
/* Cryptpad can be configured to send customized HTTP Headers /* CryptPad can be configured to send customized HTTP Headers
* These settings may vary widely depending on your needs * These settings may vary widely depending on your needs
* Examples are provided below * Examples are provided below
*/ */
@ -31,18 +31,23 @@ module.exports = {
* connect-src is used to restrict what domains can connect to the websocket. * connect-src is used to restrict what domains can connect to the websocket.
* *
* it is recommended that you configure these fields to match the * it is recommended that you configure these fields to match the
* domain which will serve your cryptpad instance. * domain which will serve your CryptPad instance.
*/ */
"child-src 'self' *", "child-src 'self' *",
"media-src *",
/* this allows connections over secure or insecure websockets /* this allows connections over secure or insecure websockets
if you are deploying to production, you'll probably want to remove if you are deploying to production, you'll probably want to remove
the ws://* directive, and change '*' to your domain the ws://* directive, and change '*' to your domain
*/ */
"connect-src 'self' ws: wss:", "connect-src 'self' ws: wss: blob:",
// data: is used by codemirror // data: is used by codemirror
"img-src 'self' data: blob:", "img-src 'self' data: blob:",
// for accounts.cryptpad.fr authentication
"frame-ancestors 'self' accounts.cryptpad.fr",
].join('; '), ].join('; '),
// CKEditor requires significantly more lax content security policy in order to function. // CKEditor requires significantly more lax content security policy in order to function.
@ -82,24 +87,24 @@ module.exports = {
*/ */
//websocketPort: 3000, //websocketPort: 3000,
/* if you want to run a different version of cryptpad but using the same websocket /* if you want to run a different version of CryptPad but using the same websocket
* server, you should use the other server port as websocketPort and disable * server, you should use the other server port as websocketPort and disable
* the websockets on that server * the websockets on that server
*/ */
//useExternalWebsocket: false, //useExternalWebsocket: false,
/* If Cryptpad is proxied without using https, the server needs to know. /* If CryptPad is proxied without using https, the server needs to know.
* Specify 'useSecureWebsockets: true' so that it can send * Specify 'useSecureWebsockets: true' so that it can send
* Content Security Policy Headers that prevent http and https from mixing * Content Security Policy Headers that prevent http and https from mixing
*/ */
useSecureWebsockets: false, useSecureWebsockets: false,
/* Cryptpad can log activity to stdout /* CryptPad can log activity to stdout
* This may be useful for debugging * This may be useful for debugging
*/ */
logToStdout: false, logToStdout: false,
/* Cryptpad supports verbose logging /* CryptPad supports verbose logging
* (false by default) * (false by default)
*/ */
verbose: false, verbose: false,
@ -116,11 +121,57 @@ module.exports = {
'contact', 'contact',
], ],
/* Domain /* Limits, Donations, Subscriptions and Contact
* If you want to have enable payments on your CryptPad instance, it has to be able to tell *
* our account server what is your domain * By default, CryptPad limits every registered user to 50MB of storage. It also shows a
* donate button which allows for making a donation to support CryptPad development.
*
* You can either:
* A: Leave it exactly as it is.
* B: Hide the donate button.
* C: Change the donate button to a subscribe button, people who subscribe will get more
* storage on your instance and you get 50% of the revenue earned.
*
* CryptPad is developed by people who need to live and who deserve an equivilent life to
* what they would get at a company which monitizes user data. However, we intend to have
* a mutually positive relationship with every one of our users, including you. If you are
* getting value from CryptPad, you should be giving equal value back.
*
* If you are using CryptPad in a business context, please consider taking a support contract
* by contacting sales@cryptpad.fr
*
* If you choose A then there's nothing to do.
*
* If you choose B, set this variable to true and it will remove the donate button.
*/
removeDonateButton: false,
/*
* If you choose C, set allowSubscriptions to true, then set myDomain to the domain which people
* use to reach your CryptPad instance. Then contact sales@cryptpad.fr and tell us your domain.
* We will tell you what is needed to get paid.
*/
allowSubscriptions: false,
myDomain: 'i.did.not.read.my.config.myserver.tld',
/*
* If you are using CryptPad internally and you want to increase the per-user storage limit,
* change the following value.
*
* Please note: This limit is what makes people subscribe and what pays for CryptPad
* development. Running a public instance that provides a "better deal" than cryptpad.fr
* is effectively using the project against itself.
*/ */
// domain: 'https://cryptpad.fr', defaultStorageLimit: 50 * 1024 * 1024,
/*
* By default, CryptPad also contacts our accounts server once a day to check for changes in
* the people who have accounts. This check-in will also send the version of your CryptPad
* instance and your email so we can reach you if we are aware of a serious problem. We will
* never sell it or send you marketing mail. If you want to block this check-in and remain
* completely invisible, set this and allowSubscriptions both to false.
*/
adminEmail: 'i.did.not.read.my.config@cryptpad.fr',
/* /*
You have the option of specifying an alternative storage adaptor. You have the option of specifying an alternative storage adaptor.
@ -141,7 +192,7 @@ module.exports = {
storage: './storage/file', storage: './storage/file',
/* /*
Cryptpad stores each document in an individual file on your hard drive. CryptPad stores each document in an individual file on your hard drive.
Specify a directory where files should be stored. Specify a directory where files should be stored.
It will be created automatically if it does not already exist. It will be created automatically if it does not already exist.
*/ */
@ -164,17 +215,17 @@ module.exports = {
*/ */
blobStagingPath: './blobstage', blobStagingPath: './blobstage',
/* Cryptpad's file storage adaptor closes unused files after a configurale /* CryptPad's file storage adaptor closes unused files after a configurale
* number of milliseconds (default 30000 (30 seconds)) * number of milliseconds (default 30000 (30 seconds))
*/ */
channelExpirationMs: 30000, channelExpirationMs: 30000,
/* Cryptpad's file storage adaptor is limited by the number of open files. /* CryptPad's file storage adaptor is limited by the number of open files.
* When the adaptor reaches openFileLimit, it will clean up older files * When the adaptor reaches openFileLimit, it will clean up older files
*/ */
openFileLimit: 2048, openFileLimit: 2048,
/* Cryptpad's socket server can be extended to respond to RPC calls /* CryptPad's socket server can be extended to respond to RPC calls
* you can configure it to respond to custom RPC calls if you like. * you can configure it to respond to custom RPC calls if you like.
* provide the path to your RPC module here, or `false` if you would * provide the path to your RPC module here, or `false` if you would
* like to disable the RPC interface completely * like to disable the RPC interface completely
@ -211,12 +262,6 @@ module.exports = {
*/ */
//restrictUploads: false, //restrictUploads: false,
/* Default user storage limit (bytes)
* if you don't want to limit users,
* you can set this to the size of your hard disk
*/
defaultStorageLimit: 50 * 1024 * 1024,
/* Max Upload Size (bytes) /* Max Upload Size (bytes)
* this sets the maximum size of any one file uploaded to the server. * this sets the maximum size of any one file uploaded to the server.
* anything larger than this size will be rejected * anything larger than this size will be rejected
@ -232,7 +277,7 @@ module.exports = {
*/ */
//logFeedback: true, //logFeedback: true,
/* it is recommended that you serve cryptpad over https /* it is recommended that you serve CryptPad over https
* the filepaths below are used to configure your certificates * the filepaths below are used to configure your certificates
*/ */
//privKeyAndCertFiles: [ //privKeyAndCertFiles: [

@ -1,16 +0,0 @@
<!-- This is an HTML fragment which is included into the bottom toolbar -->
<div>
<div class="bottom-bar">
<div class="bottom-bar-left">
<span class="bottom-bar-language">
<select id="language-selector"></select>
</span>
<p data-localization="bottom_france">
</p>
</div>
<div class="bottom-bar-right">
<p data-localization="bottom_support">
</p>
</div>
</div>
</div>

@ -39,6 +39,9 @@
<span class="link right"> <span class="link right">
<a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a> <a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a>
</span> </span>
<span class="link right">
<button id="upgrade" class="upgrade btn buttonSuccess" style="display: none;"></button>
</span>
</div> </div>
@ -114,7 +117,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="version-footer">CryptPad v1.7.0 (Hodag)</div> <div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
</footer> </footer>
</body> </body>

@ -4,7 +4,8 @@ define(function() {
/* Select the buttons displayed on the main page to create new collaborative sessions /* Select the buttons displayed on the main page to create new collaborative sessions
* Existing types : pad, code, poll, slide * Existing types : pad, code, poll, slide
*/ */
config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'whiteboard']; config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'whiteboard', 'file'];
config.registeredOnlyTypes = ['file'];
/* Cryptpad apps use a common API to display notifications to users /* Cryptpad apps use a common API to display notifications to users
* by default, notifications are hidden after 5 seconds * by default, notifications are hidden after 5 seconds
@ -37,8 +38,6 @@ define(function() {
config.enableHistory = true; config.enableHistory = true;
config.enablePinLimit = true;
/* user passwords are hashed with scrypt, and salted with their username. /* user passwords are hashed with scrypt, and salted with their username.
this value will be appended to the username, causing the resulting hash this value will be appended to the username, causing the resulting hash
to differ from other CryptPad instances if customized. This makes it to differ from other CryptPad instances if customized. This makes it

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

@ -39,6 +39,9 @@
<span class="link right"> <span class="link right">
<a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a> <a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a>
</span> </span>
<span class="link right">
<button id="upgrade" class="upgrade btn buttonSuccess" style="display: none;"></button>
</span>
</div> </div>
@ -111,7 +114,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="version-footer">CryptPad v1.7.0 (Hodag)</div> <div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
</footer> </footer>
</body> </body>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 545 B

@ -0,0 +1,53 @@
define([
'jquery',
'/customize/application_config.js',
'/common/cryptpad-common.js',
'/api/config',
], function ($, Config, Cryptpad, ApiConfig) {
window.APP = {
Cryptpad: Cryptpad,
};
var Messages = Cryptpad.Messages;
$(function () {
// Language selector
var $sel = $('#language-selector');
Cryptpad.createLanguageSelector(undefined, $sel);
$sel.find('button').addClass('btn').addClass('btn-secondary');
$sel.show();
var $upgrade = $('#upgrade');
var showUpgrade = function (text, feedback, url) {
if (ApiConfig.removeDonateButton) { return; }
if (localStorage.plan) { return; }
if (!text) { return; }
$upgrade.text(text).show();
$upgrade.click(function () {
Cryptpad.feedback(feedback);
window.open(url,'_blank');
});
};
// User admin menu
var $userMenu = $('#user-menu');
var userMenuCfg = {
$initBlock: $userMenu
};
var $userAdmin = Cryptpad.createUserAdminMenu(userMenuCfg);
$userAdmin.find('button').addClass('btn').addClass('btn-secondary');
$(window).click(function () {
$('.cryptpad-dropdown').hide();
});
if (Cryptpad.isLoggedIn() && ApiConfig.allowSubscriptions) {
showUpgrade(Messages.upgradeAccount, "HOME_UPGRADE_ACCOUNT", Cryptpad.upgradeURL);
} else {
showUpgrade(Messages.supportCryptpad, "HOME_SUPPORT_CRYPTPAD", Cryptpad.donateURL);
}
});
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 749 B

@ -39,6 +39,9 @@
<span class="link right"> <span class="link right">
<a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a> <a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a>
</span> </span>
<span class="link right">
<button id="upgrade" class="upgrade btn buttonSuccess" style="display: none;"></button>
</span>
</div> </div>
@ -233,7 +236,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="version-footer">CryptPad v1.7.0 (Hodag)</div> <div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
</footer> </footer>
</body> </body>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

@ -387,6 +387,8 @@
left: 0; left: 0;
right: 0; right: 0;
text-align: center; text-align: center;
transition: opacity 750ms;
transition-delay: 3000ms;
} }
@media screen and (max-height: 600px) { @media screen and (max-height: 600px) {
.cp #loadingTip { .cp #loadingTip {
@ -525,6 +527,22 @@
margin: 0px 10px; margin: 0px 10px;
line-height: 40px; line-height: 40px;
} }
#cryptpadTopBar .right .buttonSuccess {
color: #fff;
background: #5cb85c;
border-color: #5cb85c;
}
#cryptpadTopBar .right .buttonSuccess:hover {
color: #fff;
background: #449d44;
border: 1px solid #419641;
}
#cryptpadTopBar .right .buttonSuccess span {
color: #fff;
}
#cryptpadTopBar .right .buttonSuccess .large {
margin-left: 5px;
}
#cryptpadTopBar .right button .buttonTitle .fa-user { #cryptpadTopBar .right button .buttonTitle .fa-user {
margin-right: 5px; margin-right: 5px;
} }
@ -875,6 +893,12 @@ html.cp,
.cp #main_other #main-container { .cp #main_other #main-container {
display: inline-block; display: inline-block;
} }
.cp #main #userForm .extra p,
.cp #main_other #userForm .extra p {
font-size: 28px;
padding: 15px;
text-align: center;
}
.cp #main #data, .cp #main #data,
.cp #main_other #data { .cp #main_other #data {
width: 600px; width: 600px;

@ -1,7 +1,8 @@
define([ define([
'jquery', 'jquery',
'/customize/application_config.js', '/customize/application_config.js',
'/common/cryptpad-common.js' '/common/cryptpad-common.js',
'/customize/header.js',
], function ($, Config, Cryptpad) { ], function ($, Config, Cryptpad) {
window.APP = { window.APP = {
@ -13,25 +14,10 @@ define([
$(function () { $(function () {
var $main = $('#mainBlock'); var $main = $('#mainBlock');
// Language selector
var $sel = $('#language-selector');
Cryptpad.createLanguageSelector(undefined, $sel);
$sel.find('button').addClass('btn').addClass('btn-secondary');
$sel.show();
// User admin menu
var $userMenu = $('#user-menu');
var userMenuCfg = {
$initBlock: $userMenu
};
var $userAdmin = Cryptpad.createUserAdminMenu(userMenuCfg);
$userAdmin.find('button').addClass('btn').addClass('btn-secondary');
$(window).click(function () { $(window).click(function () {
$('.cryptpad-dropdown').hide(); $('.cryptpad-dropdown').hide();
}); });
// main block is hidden in case javascript is disabled // main block is hidden in case javascript is disabled
$main.removeClass('hidden'); $main.removeClass('hidden');
@ -58,8 +44,8 @@ define([
}); });
$loggedInBlock.removeClass('hidden'); $loggedInBlock.removeClass('hidden');
//return; }
} else { else {
$main.find('#userForm').removeClass('hidden'); $main.find('#userForm').removeClass('hidden');
$('#name').focus(); $('#name').focus();
} }
@ -70,6 +56,8 @@ define([
var $container = $('<div>', {'class': 'dropdown-bar'}).appendTo($parent); var $container = $('<div>', {'class': 'dropdown-bar'}).appendTo($parent);
Config.availablePadTypes.forEach(function (el) { Config.availablePadTypes.forEach(function (el) {
if (el === 'drive') { return; } if (el === 'drive') { return; }
if (!Cryptpad.isLoggedIn() && Config.registeredOnlyTypes &&
Config.registeredOnlyTypes.indexOf(el) !== -1) { return; }
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: { attributes: {
@ -90,7 +78,6 @@ define([
$block.appendTo($parent); $block.appendTo($parent);
}; };
/* Log in UI */ /* Log in UI */
var Login; var Login;
// deferred execution to avoid unnecessary asset loading // deferred execution to avoid unnecessary asset loading

@ -9,6 +9,7 @@ var map = {
'de': 'Deutsch', 'de': 'Deutsch',
'pt-br': 'Português do Brasil', 'pt-br': 'Português do Brasil',
'ro': 'Română', 'ro': 'Română',
'zh': '繁體中文',
}; };
var getStoredLanguage = function () { return localStorage.getItem(LS_LANG); }; var getStoredLanguage = function () { return localStorage.getItem(LS_LANG); };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

@ -39,6 +39,9 @@
<span class="link right"> <span class="link right">
<a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a> <a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a>
</span> </span>
<span class="link right">
<button id="upgrade" class="upgrade btn buttonSuccess" style="display: none;"></button>
</span>
</div> </div>
@ -132,7 +135,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="version-footer">CryptPad v1.7.0 (Hodag)</div> <div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
</footer> </footer>
</body> </body>

@ -39,5 +39,5 @@
</div> </div>
</div> </div>
</div> </div>
<div class="version-footer">CryptPad v1.7.0 (Hodag)</div> <div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
</footer> </footer>

@ -24,4 +24,7 @@
<span class="link right"> <span class="link right">
<a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a> <a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a>
</span> </span>
<span class="link right">
<button id="upgrade" class="upgrade btn buttonSuccess" style="display: none;"></button>
</span>
</div> </div>

@ -337,6 +337,14 @@ noscript {
display: inline-block; display: inline-block;
} }
#userForm .extra {
p {
font-size: 28px;
padding: 15px;
text-align: center;
}
}
#data { #data {
p { p {
margin: 0; margin: 0;

@ -36,6 +36,9 @@
left: 0; left: 0;
right: 0; right: 0;
text-align: center; text-align: center;
transition: opacity 750ms;
transition-delay: 3000ms;
@media screen and (max-height: @media-medium-screen) { @media screen and (max-height: @media-medium-screen) {
display: none; display: none;
} }

@ -47,6 +47,24 @@
margin: 0px 10px; margin: 0px 10px;
line-height: 40px; line-height: 40px;
.buttonSuccess {
// Bootstrap 4 colors
color: #fff;
background: @toolbar-green;
border-color: @toolbar-green;
&:hover {
color: #fff;
background: #449d44;
border: 1px solid #419641;
}
span {
color: #fff;
}
.large {
margin-left: 5px;
}
}
button { button {
.buttonTitle { .buttonTitle {
.fa-user { .fa-user {

@ -39,6 +39,9 @@
<span class="link right"> <span class="link right">
<a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a> <a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a>
</span> </span>
<span class="link right">
<button id="upgrade" class="upgrade btn buttonSuccess" style="display: none;"></button>
</span>
</div> </div>
@ -115,7 +118,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="version-footer">CryptPad v1.7.0 (Hodag)</div> <div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
</footer> </footer>
</body> </body>

@ -13,8 +13,7 @@ define(function () {
out.type.slide = 'Presentación'; out.type.slide = 'Presentación';
out.type.whiteboard = 'Pizarra'; out.type.whiteboard = 'Pizarra';
out.updated_0_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>Connexión perdida</b><br>El documento está ahora en modo solo lectura hasta que la conexión vuelva.";
out.common_connectionLost = out.updated_0_common_connectionLost;
out.disconnected = "Desconectado"; out.disconnected = "Desconectado";
out.synchronizing = "Sincronización"; out.synchronizing = "Sincronización";
@ -200,7 +199,6 @@ define(function () {
out.fm_info_root = "Crea carpetas aquí para organizar tus documentos."; 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 estan 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_template = "Contiene todas las plantillas que puedes volver a usar para crear nuevos documentos.";
out.fm_info_trash = "Archivos eliminados de la papelera también se eliminan de \"Todos los archivos\" y es imposible recuparlos desde el explorador.";
out.fm_info_allFiles = "Contiene todos los archivos de \"Documentos\", \"Sin organizar\" y \"Papelera\". No puedes mover o eliminar archivos aquí."; 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>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_backup_title = "Enlace de copia de seguridad"; out.fm_backup_title = "Enlace de copia de seguridad";
@ -409,6 +407,8 @@ define(function () {
out.deleted = "El pad fue borrado de tu CryptDrive"; out.deleted = "El pad fue borrado de tu CryptDrive";
out.upgrade = "Mejorar"; out.upgrade = "Mejorar";
out.upgradeTitle = "Mejora tu cuenta para obtener más espacio"; out.upgradeTitle = "Mejora tu cuenta para obtener más espacio";
out.upgradeAccount = "Mejorar cuenta";
out.MB = "MB"; out.MB = "MB";
out.GB = "GB"; out.GB = "GB";
out.KB = "KB"; out.KB = "KB";
@ -417,7 +417,6 @@ define(function () {
out.formattedKB = "{0} KB"; out.formattedKB = "{0} KB";
out.pinLimitReached = "Has llegado al limite de espacio"; out.pinLimitReached = "Has llegado al limite de espacio";
out.pinLimitReachedAlert = "Has llegado al limite de espacio. Nuevos pads no serán movidos a tu CryptDrive.<br>Para resolver este problema, puedes quitar pads de tu CryptDrive (incluso en la papelera) o mejorar tu cuenta para obtener más espacio.";
out.pinLimitNotPinned = "Has llegado al limite de espacio.<br>Este pad no estará presente en tu CryptDrive."; 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.pinLimitDrive = "Has llegado al limite de espacio.<br>No puedes crear nuevos pads.";
out.printTransition = "Activar transiciones"; out.printTransition = "Activar transiciones";
@ -429,5 +428,34 @@ define(function () {
out.upload_uploadPending = "Ya tienes una subida en progreso. ¿Cancelar y subir el nuevo archivo?"; 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."; out.upload_success = "Tu archivo ({0}) ha sido subido con éxito y fue añadido a tu drive.";
// 1.7.0 - Hodag
out.comingSoon = "Próximamente..."; // "Coming soon..."
out.newVersion = ["<b>CryptPad ha sido actualizado!</b>",
"Puedes ver lo que ha cambiada 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.pinLimitReachedAlert = ["Has llegado a tu limite de espacio. 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.pinLimitReachedAlertNoAccounts = "Has llegado a tu limite de espacio";
out.previewButtonTitle = "Mostrar/esconder la vista previa Markdown";
out.fm_info_trash = "Vacía tu papelera para liberar espaci en tu CryptDrive.";
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.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";
out.upload_pending = "Esperando";
out.upload_cancelled = "Cancelado";
out.upload_name = "Nombre";
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.poll_remove = "Quitar";
out.poll_edit = "Editar";
out.poll_locked = "Cerrado";
out.poll_unlocked = "Abierto";
return out; return out;
}); });

@ -60,10 +60,14 @@ define(function () {
out.upgrade = "Augmenter votre limite"; out.upgrade = "Augmenter votre limite";
out.upgradeTitle = "Améliorer votre compte pour augmenter la limite de stockage"; out.upgradeTitle = "Améliorer votre compte pour augmenter la limite de stockage";
out.upgradeAccount = "Améliorer le compte";
out.MB = "Mo"; out.MB = "Mo";
out.GB = "Go"; out.GB = "Go";
out.KB = "Ko"; out.KB = "Ko";
out.supportCryptpad = "Soutenir CryptPad";
out.formattedMB = "{0} Mo"; out.formattedMB = "{0} Mo";
out.formattedGB = "{0} Go"; out.formattedGB = "{0} Go";
out.formattedKB = "{0} Ko"; out.formattedKB = "{0} Ko";
@ -76,7 +80,7 @@ define(function () {
out.updated_0_pinLimitReachedAlert = "Vous avez atteint votre limite de stockage. Les nouveaux pads ne seront pas enregistrés dans votre CryptDrive.<br>" + out.updated_0_pinLimitReachedAlert = "Vous avez atteint votre limite de stockage. Les nouveaux pads ne seront pas enregistrés dans votre CryptDrive.<br>" +
'Vous pouvez soit supprimer des pads de votre CryptDrive, soit vous <a href="https://accounts.cryptpad.fr/#!on={0}" target="_blank">abonner à une offre premium</a> pour augmenter la limite maximale.'; 'Vous pouvez soit supprimer des pads de votre CryptDrive, soit vous <a href="https://accounts.cryptpad.fr/#!on={0}" target="_blank">abonner à une offre premium</a> pour augmenter la limite maximale.';
out.pinLimitReachedAlert = out.updated_0_pinLimitReachedAlert; out.pinLimitReachedAlert = out.updated_0_pinLimitReachedAlert;
out.pinAboveLimitAlert = 'Depuis la dernière version, nous imposons désormais une limite de 50 Mo de stockage gratuit et vous utilisez actuellement {0}. You devriez soit supprimer certains pads ou soit vous abonner sur <a href="https://accounts.cryptpad.fr/#!on={1}" target="_blank">accounts.cryptpad.fr</a>. Votre contribution nous aidera à améliorer CryptPad et à répandre le Zero Knowledge. Vous pouvez contacter le <a href="https://accounts.cryptpad.fr/#/support" target="_blank">support</a> pour tout problème ou question concernant ces changements.'; out.pinLimitReachedAlertNoAccounts = out.pinLimitReached;
out.pinLimitNotPinned = "Vous avez atteint votre limite de stockage.<br>"+ out.pinLimitNotPinned = "Vous avez atteint votre limite de stockage.<br>"+
"Ce pad n'est pas enregistré dans votre CryptDrive."; "Ce pad n'est pas enregistré dans votre CryptDrive.";
out.pinLimitDrive = out.pinLimitReached+ ".<br>" + out.pinLimitDrive = out.pinLimitReached+ ".<br>" +
@ -193,6 +197,11 @@ define(function () {
out.poll_titleHint = "Titre"; out.poll_titleHint = "Titre";
out.poll_descriptionHint = "Description"; out.poll_descriptionHint = "Description";
out.poll_remove = "Supprimer";
out.poll_edit = "Modifier";
out.poll_locked = "Verrouillé";
out.poll_unlocked = "Déverrouillé";
// Canvas // Canvas
out.canvas_clear = "Nettoyer"; out.canvas_clear = "Nettoyer";
out.canvas_delete = "Supprimer la sélection"; out.canvas_delete = "Supprimer la sélection";
@ -299,6 +308,8 @@ define(function () {
out.login_invalPass = 'Mot de passe requis'; out.login_invalPass = 'Mot de passe requis';
out.login_unhandledError = "Une erreur inattendue s'est produite :("; out.login_unhandledError = "Une erreur inattendue s'est produite :(";
out.login_notRegistered = 'Pas encore inscrit ?';
out.register_importRecent = "Importer l'historique (Recommendé)"; out.register_importRecent = "Importer l'historique (Recommendé)";
out.register_acceptTerms = "J'accepte <a href='/terms.html'>les conditions d'utilisation</a>"; out.register_acceptTerms = "J'accepte <a href='/terms.html'>les conditions d'utilisation</a>";
out.register_passwordsDontMatch = "Les mots de passe doivent être identiques!"; out.register_passwordsDontMatch = "Les mots de passe doivent être identiques!";
@ -369,6 +380,7 @@ define(function () {
out.upload_name = "Nom du fichier"; out.upload_name = "Nom du fichier";
out.upload_size = "Taille"; out.upload_size = "Taille";
out.upload_progress = "État"; out.upload_progress = "État";
out.upload_mustLogin = "Vous devez vous connecter pour uploader un fichier";
out.download_button = "Déchiffrer et télécharger"; out.download_button = "Déchiffrer et télécharger";
// general warnings // general warnings

@ -62,10 +62,14 @@ define(function () {
out.upgrade = "Upgrade"; out.upgrade = "Upgrade";
out.upgradeTitle = "Upgrade your account to increase the storage limit"; out.upgradeTitle = "Upgrade your account to increase the storage limit";
out.upgradeAccount = "Upgrade account";
out.MB = "MB"; out.MB = "MB";
out.GB = "GB"; out.GB = "GB";
out.KB = "KB"; out.KB = "KB";
out.supportCryptpad = "Support CryptPad";
out.formattedMB = "{0} MB"; out.formattedMB = "{0} MB";
out.formattedGB = "{0} GB"; out.formattedGB = "{0} GB";
out.formattedKB = "{0} KB"; out.formattedKB = "{0} KB";
@ -78,7 +82,7 @@ define(function () {
out.updated_0_pinLimitReachedAlert = "You've reached your storage limit. New pads won't be stored in your CryptDrive.<br>" + out.updated_0_pinLimitReachedAlert = "You've reached your storage limit. New pads won't be stored in your CryptDrive.<br>" +
'You can either remove pads from your CryptDrive or <a href="https://accounts.cryptpad.fr/#!on={0}" target="_blank">subscribe to a premium offer</a> to increase your limit.'; 'You can either remove pads from your CryptDrive or <a href="https://accounts.cryptpad.fr/#!on={0}" target="_blank">subscribe to a premium offer</a> to increase your limit.';
out.pinLimitReachedAlert = out.updated_0_pinLimitReachedAlert; out.pinLimitReachedAlert = out.updated_0_pinLimitReachedAlert;
out.pinAboveLimitAlert = 'As of this release, we are imposing a 50MB limit on free data storage and you are currently using {0}. You will need to either delete some pads or subscribe on <a href="https://accounts.cryptpad.fr/#!on={1}" target="_blank">accounts.cryptpad.fr</a>. Your contribution will help us improve CryptPad and spread Zero Knowledge. Please contact <a href="https://accounts.cryptpad.fr/#/support" target="_blank">support</a> if you have any other questions.'; out.pinLimitReachedAlertNoAccounts = out.pinLimitReached;
out.pinLimitNotPinned = "You've reached your storage limit.<br>"+ out.pinLimitNotPinned = "You've reached your storage limit.<br>"+
"This pad is not stored in your CryptDrive."; "This pad is not stored in your CryptDrive.";
out.pinLimitDrive = "You've reached your storage limit.<br>" + out.pinLimitDrive = "You've reached your storage limit.<br>" +
@ -195,6 +199,11 @@ define(function () {
out.poll_titleHint = "Title"; out.poll_titleHint = "Title";
out.poll_descriptionHint = "Describe your poll, and use the 'publish' button when you're done. Anyone with the link can change the description, but this is discouraged."; out.poll_descriptionHint = "Describe your poll, and use the 'publish' button when you're done. Anyone with the link can change the description, but this is discouraged.";
out.poll_remove = "Remove";
out.poll_edit = "Edit";
out.poll_locked = "Locked";
out.poll_unlocked = "Unlocked";
// Canvas // Canvas
out.canvas_clear = "Clear"; out.canvas_clear = "Clear";
out.canvas_delete = "Delete selection"; out.canvas_delete = "Delete selection";
@ -301,6 +310,8 @@ define(function () {
out.login_invalPass = 'Password required'; out.login_invalPass = 'Password required';
out.login_unhandledError = 'An unexpected error occurred :('; out.login_unhandledError = 'An unexpected error occurred :(';
out.login_notRegistered = 'Not registered?';
out.register_importRecent = "Import pad history (Recommended)"; out.register_importRecent = "Import pad history (Recommended)";
out.register_acceptTerms = "I accept <a href='/terms.html'>the terms of service</a>"; out.register_acceptTerms = "I accept <a href='/terms.html'>the terms of service</a>";
out.register_passwordsDontMatch = "Passwords do not match!"; out.register_passwordsDontMatch = "Passwords do not match!";
@ -374,6 +385,7 @@ define(function () {
out.upload_name = "File name"; out.upload_name = "File name";
out.upload_size = "Size"; out.upload_size = "Size";
out.upload_progress = "Progress"; out.upload_progress = "Progress";
out.upload_mustLogin = "You must be logged in to upload files";
out.download_button = "Decrypt & Download"; out.download_button = "Decrypt & Download";
// general warnings // general warnings
@ -395,6 +407,7 @@ define(function () {
out.main_zeroKnowledge = 'Zero Knowledge'; 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_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 = '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_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 = '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_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.";

@ -1,357 +1,371 @@
define(function () { define(function () {
var out = {}; var out = {};
/* out.main_title = "CryptPad: Zero Knowledge, Colaborare în timp real";
* out.main_slogan = "Puterea stă în cooperare - Colaborarea este cheia";
* ro
*
*/
out.main_title = ""; // "CryptPad: Zero Knowledge, Collaborative Real Time Editing" out.type = {};
out.main_slogan = ""; // "Unity is Strength - Collaboration is Key" out.pad = "Rich text";
out.type = ""; // {"pad":"Rich text","code":"Code","poll":"Poll","slide":"Presentation","drive":"Drive","whiteboard":"Whiteboard","file":"File","media":"Media"} out.code = "Code";
out.button_newpad = ""; // "New Rich Text pad" out.poll = "Poll";
out.button_newcode = ""; // "New Code pad" out.slide = "Presentation";
out.button_newpoll = ""; // "New Poll" out.drive = "Drive";
out.button_newslide = ""; // "New Presentation" out.whiteboard = "Whiteboard";
out.button_newwhiteboard = ""; // "New Whiteboard" out.file = "File";
out.updated_0_common_connectionLost = ""; // "<b>Server Connection Lost</b><br>You're now in read-only mode until the connection is back." out.media = "Media";
out.common_connectionLost = out.updated_0_common_connectionLost; // TODO: Key updated --> make sure the updated key "out.updated_0_common_connectionLost" exists and is translated before that one.
out.websocketError = ""; // "Unable to connect to the websocket server..."
out.typeError = ""; // "This pad is not compatible with the selected application"
out.onLogout = ""; // "You are logged out, <a href=\"/\" target=\"_blank\">click here</a> to log in<br>or press <em>Escape</em> to access your pad in read-only mode."
out.wrongApp = ""; // "Unable to display the content of that realtime session in your browser. Please try to reload that page."
out.loading = ""; // "Loading..."
out.error = ""; // "Error"
out.saved = ""; // "Saved"
out.synced = ""; // "Everything is saved"
out.deleted = ""; // "Pad deleted from your CryptDrive"
out.disconnected = ""; // "Disconnected"
out.synchronizing = ""; // "Synchronizing"
out.reconnecting = ""; // "Reconnecting..."
out.lag = ""; // "Lag"
out.readonly = ""; // "Read only"
out.anonymous = ""; // "Anonymous"
out.yourself = ""; // "Yourself"
out.anonymousUsers = ""; // "anonymous editors"
out.anonymousUser = ""; // "anonymous editor"
out.users = ""; // "Users"
out.and = ""; // "And"
out.viewer = ""; // "viewer"
out.viewers = ""; // "viewers"
out.editor = ""; // "editor"
out.editors = ""; // "editors"
out.language = ""; // "Language"
out.upgrade = ""; // "Upgrade"
out.upgradeTitle = ""; // "Upgrade your account to increase the storage limit"
out.MB = ""; // "MB"
out.greenLight = ""; // "Everything is working fine"
out.orangeLight = ""; // "Your slow connection may impact your experience"
out.redLight = ""; // "You are disconnected from the session"
out.pinLimitReached = ""; // "You've reached your storage limit"
out.pinLimitReachedAlert = ""; // "You've reached your storage limit. New pads won't be stored in your CryptDrive.<br>To fix this problem, you can either remove pads from your CryptDrive (including the trash) or subscribe to a premium offer to increase your limit."
out.pinLimitNotPinned = ""; // "You've reached your storage limit.<br>This pad is not stored in your CryptDrive."
out.pinLimitDrive = ""; // "You've reached your storage limit.<br>You can't create new pads."
out.importButtonTitle = ""; // "Import a pad from a local file"
out.exportButtonTitle = ""; // "Export this pad to a local file"
out.exportPrompt = ""; // "What would you like to name your file?"
out.changeNamePrompt = ""; // "Change your name (leave empty to be anonymous): "
out.user_rename = ""; // "Change display name"
out.user_displayName = ""; // "Display name"
out.user_accountName = ""; // "Account name"
out.clickToEdit = ""; // "Click to edit"
out.forgetButtonTitle = ""; // "Move this pad to the trash"
out.forgetPrompt = ""; // "Clicking OK will move this pad to your trash. Are you sure?"
out.movedToTrash = ""; // "That pad has been moved to the trash.<br><a href=\"/drive/\">Access my Drive</a>"
out.shareButton = ""; // "Share"
out.shareSuccess = ""; // "Copied link to clipboard"
out.newButton = ""; // "New"
out.newButtonTitle = ""; // "Create a new pad"
out.saveTemplateButton = ""; // "Save as template"
out.saveTemplatePrompt = ""; // "Choose a title for the template"
out.templateSaved = ""; // "Template saved!"
out.selectTemplate = ""; // "Select a template or press escape"
out.presentButtonTitle = ""; // "Enter presentation mode"
out.presentSuccess = ""; // "Hit ESC to exit presentation mode"
out.backgroundButtonTitle = ""; // "Change the background color in the presentation"
out.colorButtonTitle = ""; // "Change the text color in presentation mode"
out.printButton = ""; // "Print (enter)"
out.printButtonTitle = ""; // "Print your slides or export them as a PDF file"
out.printOptions = ""; // "Layout options"
out.printSlideNumber = ""; // "Display the slide number"
out.printDate = ""; // "Display the date"
out.printTitle = ""; // "Display the pad title"
out.printCSS = ""; // "Custom style rules (CSS):"
out.printTransition = ""; // "Enable transition animations"
out.slideOptionsTitle = ""; // "Customize your slides"
out.slideOptionsButton = ""; // "Save (enter)"
out.editShare = ""; // "Editing link"
out.editShareTitle = ""; // "Copy the editing link to clipboard"
out.editOpen = ""; // "Open editing link in a new tab"
out.editOpenTitle = ""; // "Open this pad in editing mode in a new tab"
out.viewShare = ""; // "Read-only link"
out.viewShareTitle = ""; // "Copy the read-only link to clipboard"
out.viewOpen = ""; // "Open read-only link in a new tab"
out.viewOpenTitle = ""; // "Open this pad in read-only mode in a new tab"
out.notifyJoined = ""; // "{0} has joined the collaborative session"
out.notifyRenamed = ""; // "{0} is now known as {1}"
out.notifyLeft = ""; // "{0} has left the collaborative session"
out.okButton = ""; // "OK (enter)"
out.cancel = ""; // "Cancel"
out.cancelButton = ""; // "Cancel (esc)"
out.historyButton = ""; // "Display the document history"
out.history_next = ""; // "Go to the next version"
out.history_prev = ""; // "Go to the previous version"
out.history_goTo = ""; // "Go to the selected version"
out.history_close = ""; // "Back"
out.history_closeTitle = ""; // "Close the history"
out.history_restore = ""; // "Restore"
out.history_restoreTitle = ""; // "Restore the selected version of the document"
out.history_restorePrompt = ""; // "Are you sure you want to replace the current version of the document by the displayed one?"
out.history_restoreDone = ""; // "Document restored"
out.history_version = ""; // "Version:"
out.poll_title = ""; // "Zero Knowledge Date Picker"
out.poll_subtitle = ""; // "Zero Knowledge, <em>realtime</em> scheduling"
out.poll_p_save = ""; // "Your settings are updated instantly, so you never need to save."
out.poll_p_encryption = ""; // "All your input is encrypted so only people who have the link can access it. Even the server cannot see what you change."
out.wizardLog = ""; // "Click the button in the top left to return to your poll"
out.wizardTitle = ""; // "Use the wizard to create your poll"
out.wizardConfirm = ""; // "Are you really ready to add these options to your poll?"
out.poll_publish_button = ""; // "Publish"
out.poll_admin_button = ""; // "Admin"
out.poll_create_user = ""; // "Add a new user"
out.poll_create_option = ""; // "Add a new option"
out.poll_commit = ""; // "Commit"
out.poll_closeWizardButton = ""; // "Close wizard"
out.poll_closeWizardButtonTitle = ""; // "Close wizard"
out.poll_wizardComputeButton = ""; // "Compute Options"
out.poll_wizardClearButton = ""; // "Clear Table"
out.poll_wizardDescription = ""; // "Automatically create a number of options by entering any number of dates and times segments"
out.poll_wizardAddDateButton = ""; // "+ Dates"
out.poll_wizardAddTimeButton = ""; // "+ Times"
out.poll_optionPlaceholder = ""; // "Option"
out.poll_userPlaceholder = ""; // "Your name"
out.poll_removeOption = ""; // "Are you sure you'd like to remove this option?"
out.poll_removeUser = ""; // "Are you sure you'd like to remove this user?"
out.poll_titleHint = ""; // "Title"
out.poll_descriptionHint = ""; // "Describe your poll, and use the 'publish' button when you're done. Anyone with the link can change the description, but this is discouraged."
out.canvas_clear = ""; // "Clear"
out.canvas_delete = ""; // "Delete selection"
out.canvas_disable = ""; // "Disable draw"
out.canvas_enable = ""; // "Enable draw"
out.canvas_width = ""; // "Width"
out.canvas_opacity = ""; // "Opacity"
out.fm_rootName = ""; // "Documents"
out.fm_trashName = ""; // "Trash"
out.fm_unsortedName = ""; // "Unsorted files"
out.fm_filesDataName = ""; // "All files"
out.fm_templateName = ""; // "Templates"
out.fm_searchName = ""; // "Search"
out.fm_searchPlaceholder = ""; // "Search..."
out.fm_newButton = ""; // "New"
out.fm_newButtonTitle = ""; // "Create a new pad or folder"
out.fm_newFolder = ""; // "New folder"
out.fm_newFile = ""; // "New pad"
out.fm_folder = ""; // "Folder"
out.fm_folderName = ""; // "Folder name"
out.fm_numberOfFolders = ""; // "# of folders"
out.fm_numberOfFiles = ""; // "# of files"
out.fm_fileName = ""; // "File name"
out.fm_title = ""; // "Title"
out.fm_type = ""; // "Type"
out.fm_lastAccess = ""; // "Last access"
out.fm_creation = ""; // "Creation"
out.fm_forbidden = ""; // "Forbidden action"
out.fm_originalPath = ""; // "Original path"
out.fm_openParent = ""; // "Show in folder"
out.fm_noname = ""; // "Untitled Document"
out.fm_emptyTrashDialog = ""; // "Are you sure you want to empty the trash?"
out.fm_removeSeveralPermanentlyDialog = ""; // "Are you sure you want to remove these {0} elements from the trash permanently?"
out.fm_removePermanentlyDialog = ""; // "Are you sure you want to remove that element permanently?"
out.fm_removeSeveralDialog = ""; // "Are you sure you want to move these {0} elements to the trash?"
out.fm_removeDialog = ""; // "Are you sure you want to move {0} to the trash?"
out.fm_restoreDialog = ""; // "Are you sure you want to restore {0} to its previous location?"
out.fm_unknownFolderError = ""; // "The selected or last visited directory no longer exist. Opening the parent folder..."
out.fm_contextMenuError = ""; // "Unable to open the context menu for that element. If the problem persist, try to reload the page."
out.fm_selectError = ""; // "Unable to select the targetted element. If the problem persist, try to reload the page."
out.fm_categoryError = ""; // "Unable to open the selected category, displaying root."
out.fm_info_root = ""; // "Create as many nested folders here as you want to sort your files."
out.fm_info_unsorted = ""; // "Contains all the files you've visited that are not yet sorted in \"Documents\" or moved to the \"Trash\"."
out.fm_info_template = ""; // "Contains all the pads stored as templates and that you can re-use when you create a new pad."
out.fm_info_trash = ""; // "Files deleted from the trash are also removed from \"All files\" and it is impossible to recover them from the file manager."
out.fm_info_allFiles = ""; // "Contains all the files from \"Documents\", \"Unsorted\" and \"Trash\". You can't move or remove files from here."
out.fm_info_login = ""; // "Log in"
out.fm_info_register = ""; // "Sign up"
out.fm_info_anonymous = ""; // "You are not logged in so these pads may be deleted (<a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">find out why</a>). <a href=\"/register/\">Sign up</a> or <a href=\"/login/\">Log in</a> to keep them alive."
out.fm_alert_backupUrl = ""; // "Backup link for this drive.<br>It is <strong>highly recommended</strong> that you keep ip for yourself only.<br>You can use it to retrieve all your files in case your browser memory got erased.<br>Anybody with that link can edit or remove all the files in your file manager.<br>"
out.fm_alert_anonymous = ""; // "Hello there, you are currently using CryptPad anonymously, that's ok but your pads may be deleted after a period of inactivity. We have disabled advanced features of the drive for anonymous users because we want to be clear that it is not a safe place to store things. You can <a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">read more</a> about why we are doing this and why you really should <a href=\"/register/\">Sign up</a> and <a href=\"/login/\">Log in</a>."
out.fm_backup_title = ""; // "Backup link"
out.fm_nameFile = ""; // "How would you like to name that file?"
out.fc_newfolder = ""; // "New folder"
out.fc_rename = ""; // "Rename"
out.fc_open = ""; // "Open"
out.fc_open_ro = ""; // "Open (read-only)"
out.fc_delete = ""; // "Delete"
out.fc_restore = ""; // "Restore"
out.fc_remove = ""; // "Delete permanently"
out.fc_empty = ""; // "Empty the trash"
out.fc_prop = ""; // "Properties"
out.fc_sizeInKilobytes = ""; // "Size in Kilobytes"
out.fo_moveUnsortedError = ""; // "You can't move a folder to the list of unsorted pads"
out.fo_existingNameError = ""; // "Name already used in that directory. Please choose another one."
out.fo_moveFolderToChildError = ""; // "You can't move a folder into one of its descendants"
out.fo_unableToRestore = ""; // "Unable to restore that file to its original location. You can try to move it to a new location."
out.fo_unavailableName = ""; // "A file or a folder with the same name already exist at the new location. Rename the element and try again."
out.login_login = ""; // "Log in"
out.login_makeAPad = ""; // "Create a pad anonymously"
out.login_nologin = ""; // "Browse local pads"
out.login_register = ""; // "Sign up"
out.logoutButton = ""; // "Log out"
out.settingsButton = ""; // "Settings"
out.login_username = ""; // "Username"
out.login_password = ""; // "Password"
out.login_confirm = ""; // "Confirm your password"
out.login_remember = ""; // "Remember me"
out.login_hashing = ""; // "Hashing your password, this might take some time."
out.login_hello = ""; // "Hello {0},"
out.login_helloNoName = ""; // "Hello,"
out.login_accessDrive = ""; // "Access your drive"
out.login_orNoLogin = ""; // "or"
out.login_noSuchUser = ""; // "Invalid username or password. Try again, or sign up"
out.login_invalUser = ""; // "Username required"
out.login_invalPass = ""; // "Password required"
out.login_unhandledError = ""; // "An unexpected error occurred :("
out.register_importRecent = ""; // "Import pad history (Recommended)"
out.register_acceptTerms = ""; // "I accept <a href='/terms.html'>the terms of service</a>"
out.register_passwordsDontMatch = ""; // "Passwords do not match!"
out.register_mustAcceptTerms = ""; // "You must accept the terms of service."
out.register_mustRememberPass = ""; // "We cannot reset your password if you forget it. It's very important that you remember it! Please check the checkbox to confirm."
out.register_header = ""; // "Welcome to 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>"
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?"
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.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."
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"
out.policy_title = ""; // "CryptPad Privacy Policy"
out.policy_whatweknow = ""; // "What we know about you"
out.policy_whatweknow_p1 = ""; // "As an application that is hosted on the web, CryptPad has access to metadata exposed by the HTTP protocol. This includes your IP address, and various other HTTP headers that can be used to identify your particular browser. You can see what information your browser is sharing by visiting <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending\" title=\"what http headers is my browser sending\">WhatIsMyBrowser.com</a>."
out.policy_whatweknow_p2 = ""; // "We use <a href=\"https://www.elastic.co/products/kibana\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"open source analytics platform\">Kibana</a>, an open source analytics platform, to learn more about our users. Kibana tells us about how you found CryptPad, via direct entry, through a search engine, or via a referral from another web service like Reddit or Twitter."
out.policy_howweuse = ""; // "How we use what we learn"
out.policy_howweuse_p1 = ""; // "We use this information to make better decisions about promoting CryptPad, by evaluating which of our past efforts were successful. Information about your location lets us know whether we should consider providing better support for languages other than English."
out.policy_howweuse_p2 = ""; // "Information about your browser (whether it's a desktop or mobile operating system) helps us make decisions when prioritizing feature improvements. Our development team is small, and we try to make choices that will improve as many users' experience as possible."
out.policy_whatwetell = ""; // "What we tell others about you"
out.policy_whatwetell_p1 = ""; // "We do not furnish to third parties the information that we gather or that you provide to us unless we are legally required to do so."
out.policy_links = ""; // "Links to other sites"
out.policy_links_p1 = ""; // "This site contains links to other sites, including those produced by other organizations. We are not responsible for the privacy practices or the contents of any outside sites. As a general rule, links to outside sites are launched in a new browser window, to make clear that you are leaving CryptPad.fr."
out.policy_ads = ""; // "Advertisement"
out.policy_ads_p1 = ""; // "We do not display any online advertising, though we may link to the bodies which are financing our research."
out.policy_choices = ""; // "Choices you have"
out.policy_choices_open = ""; // "Our code is open source, so you always have the option of hosting your own instance of CryptPad."
out.policy_choices_vpn = ""; // "If you want to use our hosted instance, but don't want to expose your IP address, you can protect your IP using the <a href=\"https://www.torproject.org/projects/torbrowser.html.en\" title=\"downloads from the Tor project\" target=\"_blank\" rel=\"noopener noreferrer\">Tor browser bundle</a>, or a <a href=\"https://riseup.net/en/vpn\" title=\"VPNs provided by Riseup\" target=\"_blank\" rel=\"noopener noreferrer\">VPN</a>."
out.policy_choices_ads = ""; // "If you just want to block our analytics platform, you can use adblocking tools like <a href=\"https://www.eff.org/privacybadger\" title=\"download privacy badger\" target=\"_blank\" rel=\"noopener noreferrer\">Privacy Badger</a>."
out.tos_title = ""; // "CryptPad Terms of Service"
out.tos_legal = ""; // "Please don't be malicious, abusive, or do anything illegal."
out.tos_availability = ""; // "We hope you find this service useful, but availability or performance cannot be guaranteed. Please export your data regularly."
out.tos_e2ee = ""; // "CryptPad contents can be read or modified by anyone who can guess or otherwise obtain the pad's fragment identifier. We recommend that you use end-to-end-encrypted (e2ee) messaging technology to share links, and assume no liability in the event that such a link is leaked."
out.tos_logs = ""; // "Metadata provided by your browser to the server may be logged for the purpose of maintaining the service."
out.tos_3rdparties = ""; // "We do not provide individualized data to third parties unless required to by law."
out.bottom_france = ""; // "<a href=\"http://www.xwiki.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Made with <img class=\"bottom-bar-heart\" src=\"/customize/heart.png\" alt=\"love\" /> in <img class=\"bottom-bar-fr\" src=\"/customize/fr.png\" alt=\"France\" /></a>"
out.bottom_support = ""; // "<a href=\"http://labs.xwiki.com/\" title=\"XWiki Labs\" target=\"_blank\" rel=\"noopener noreferrer\">An <img src=\"/customize/logo-xwiki2.png\" alt=\"XWiki SAS\" class=\"bottom-bar-xwiki\"/> Labs Project </a> with the support of <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_france = ""; // "<a href=\"http://www.xwiki.com/\" target=\"_blank\" rel=\"noopener noreferrer\">With <img class=\"bottom-bar-heart\" src=\"/customize/heart.png\" alt=\"love\" /> from <img class=\"bottom-bar-fr\" src=\"/customize/fr.png\" title=\"France\" alt=\"France\"/> by <img src=\"/customize/logo-xwiki.png\" alt=\"XWiki SAS\" class=\"bottom-bar-xwiki\"/></a>"
out.header_support = ""; // "<a href=\"http://ng.open-paas.org/\" title=\"OpenPaaS::ng\" target=\"_blank\" rel=\"noopener noreferrer\"> <img src=\"/customize/openpaasng.png\" alt=\"OpenPaaS-ng\" class=\"bottom-bar-openpaas\" /></a>"
out.header_logoTitle = ""; // "Go to the main page"
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>"
out.codeInitialState = ""; // "/*\n This is the CryptPad Zero Knowledge collaborative code editor.\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.\n*/"
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\n1. Write your slides content using markdown syntax\n - Learn more about markdown syntax [here](http://www.markdowntutorial.com/)\n2. Separate your slides with ---\n3. Click on the \"Play\" button to see the result - Your slides are updated in realtime"
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."
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."
out.readme_cat1_l3_l1 = ""; // "You can click and drag files into folders in the {0} section of your drive and make new folders."
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."
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."
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"
out.tips = ""; // {"lag":"The green icon in the upper right shows the quality of your internet connection to the CryptPad server.","shortcuts":"`ctrl+b`, `ctrl+i` and `ctrl+u` are quick shortcuts for bold, italic and underline.","indent":"In numbered and bulleted lists, you can use tab or shift+tab to quickly increase or decrease indentation.","title":"You can set the title of your pad by clicking the top center.","store":"Every time you visit a pad, if you're logged in it will be saved to your CryptDrive.","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"
out.button_newpad = "Filă Text Nouă";
out.button_newcode = "Filă Cod Nouă";
out.button_newpoll = "Sondaj Nou";
out.button_newslide = "Prezentare Nouă";
out.button_newwhiteboard = "Fila Desen Nouă";
out.updated_0_common_connectionLost = "<b>Conexiunea la server este pierdută</b><br>Până la revenirea conexiunii, vei fi în modul citire";
out.common_connectionLost = out.updated_0_common_connectionLost;
out.websocketError = "Conexiune inexistentă către serverul websocket...";
out.typeError = "Această filă nu este compatibilă cu aplicația aleasă";
out.onLogout = "Nu mai ești autentificat, <a href=\"/\" target=\"_blank\">apasă aici</a> să te autentifici<br>sau apasă <em>Escape</em>să accesezi fila în modul citire.";
out.wrongApp = "Momentan nu putem arăta conținutul sesiunii în timp real în fereastra ta. Te rugăm reîncarcă pagina.";
out.loading = "Încarcă...";
out.error = "Eroare";
out.saved = "Salvat";
out.synced = "Totul a fost salvat";
out.deleted = "Pad șters din CryptDrive-ul tău";
out.disconnected = "Deconectat";
out.synchronizing = "Se sincronizează";
out.reconnecting = "Reconectare...";
out.lag = "Decalaj";
out.readonly = "Mod citire";
out.anonymous = "Anonim";
out.yourself = "Tu";
out.anonymousUsers = "editori anonimi";
out.anonymousUser = "editor anonim";
out.users = "Utilizatori";
out.and = "Și";
out.viewer = "privitor";
out.viewers = "privitori";
out.editor = "editor";
out.editors = "editori";
out.language = "Limbă";
out.upgrade = "Actualizare";
out.upgradeTitle = "Actualizează-ți contul pentru a mări limita de stocare";
out.MB = "MB";
out.greenLight = "Totul funcționează corespunzător";
out.orangeLight = "Conexiunea lentă la internet îți poate afecta experiența";
out.redLight = "Ai fost deconectat de la sesiune";
out.pinLimitReached = "Ai atins limita de stocare";
out.pinLimitReachedAlert = "Ai atins limita de stocare. Noile pad-uri nu vor mai fi stocate în CryptDrive.<br>Pentru a rezolva această problemă, poți să nlături pad-uri din CryptDrive-ul tău (incluzând gunoiul) sau să subscrii la un pachet premium pentru a-ți extinde spațiul de stocare.";
out.pinLimitNotPinned = "Ai atins limita de stocare.<br>Acest pad nu va fi stocat n CryptDrive-ul tău.";
out.pinLimitDrive = "Ai atins limita de stocare.<br>Nu poți să creezi alte pad-uri.";
out.importButtonTitle = "Importă un pad dintr-un fișier local";
out.exportButtonTitle = "Exportă pad-ul acesta către un fișier local";
out.exportPrompt = "Cum ai vrea să îți denumești fișierul?";
out.changeNamePrompt = "Schimbă-ți numele (lasă necompletat dacă vrei să fii anonim): ";
out.user_rename = "Schimbă numele afișat";
out.user_displayName = "Nume afișat";
out.user_accountName = "Nume cont";
out.clickToEdit = "Click pentru editare";
out.forgetButtonTitle = "Mută acest pad la gunoi";
out.forgetPrompt = "Click-ul pe OK va muta acest pad la gunoi. Ești sigur?";
out.movedToTrash = "Acest pad a fost mutat la gunoi.<br><a href=\"/drive/\">Acesează-mi Drive-ul</a>";
out.shareButton = "Distribuie";
out.shareSuccess = "Link copiat în clipboard";
out.newButton = "Nou";
out.newButtonTitle = "Crează un nou pad";
out.saveTemplateButton = "Salvează ca șablon";
out.saveTemplatePrompt = "Alege un titlu pentru șablon";
out.templateSaved = "Șablon salvat!";
out.selectTemplate = "Selectează un șablon sau apasă escape";
out.presentButtonTitle = "Intră în modul de prezentare";
out.presentSuccess = "Apasă ESC pentru a ieși din modul de prezentare";
out.backgroundButtonTitle = "Schimbă culoarea de fundal din prezentare";
out.colorButtonTitle = "Schimbă culoarea textului în modul de prezentare";
out.printButton = "Printează (enter)";
out.printButtonTitle = "Printează-ți slide-urile sau exportă-le ca fișier PDF";
out.printOptions = "Opțiuni schemă";
out.printSlideNumber = "Afișează numărul slide-ului";
out.printDate = "Afișează data";
out.printTitle = "Afișează titlul pad-ului";
out.printCSS = "Reguli de stil personalizate (CSS):";
out.printTransition = "Permite tranziția animațiilor";
out.slideOptionsTitle = "Personalizează-ți slide-urile";
out.slideOptionsButton = "Salvează (enter)";
out.editShare = "Editează link-ul";
out.editShareTitle = "Copiază link-ul de editare în clipboard";
out.editOpen = "Deschide link-ul de editare într-o nouă filă";
out.editOpenTitle = "Deschide acest pad în modul de editare într-o nouă filă";
out.viewShare = "Link în modul citire";
out.viewShareTitle = "Copiază link-ul în modul de citire în clipboard";
out.viewOpen = "Deschide link-ul în modul de citire într-o filă nouă";
out.viewOpenTitle = "Deschide acest pad în modul de citire într-o nouă filă";
out.notifyJoined = "{0} s-au alăturat sesiunii colaborative";
out.notifyRenamed = "{0} e cunoscut ca {1}";
out.notifyLeft = "{0} au părăsit sesiunea colaborativă";
out.okButton = "OK (enter)";
out.cancel = "Anulează";
out.cancelButton = "Anulează (esc)";
out.historyButton = "Afișează istoricul documentului";
out.history_next = "Mergi la versiunea următoare";
out.history_prev = "Mergi la versiunea trecută";
out.history_goTo = "Mergi la sesiunea selectată";
out.history_close = "Înapoi";
out.history_closeTitle = "Închide istoricul";
out.history_restore = "Restabilește";
out.history_restoreTitle = "Restabilește versiunea selectată a documentului";
out.history_restorePrompt = "Ești sigur că vrei să înlocuiești versiunea curentă a documentului cu cea afișată?";
out.history_restoreDone = "Document restabilit";
out.history_version = "Versiune:";
out.poll_title = "Zero Knowledge Selector Dată";
out.poll_subtitle = "Zero Knowledge, <em>realtime</em> programare";
out.poll_p_save = "Setările tale sunt actualizate instant, așa că tu nu trebuie să salvezi.";
out.poll_p_encryption = "Tot conținutul tău este criptat ca doar persoanele cărora tu le dai link-ul să aibă acces. Nici serverul nu poate să vadă ce modifici.";
out.wizardLog = "Click pe butonul din dreapta sus pentru a te ntoarce la sondajul tău";
out.wizardTitle = "Folosește wizard-ul pentru a crea sondajul tău";
out.wizardConfirm = "Ești pregătit să adaugi aceste opțiuni la sondajul tău?";
out.poll_publish_button = "Publică";
out.poll_admin_button = "Admin";
out.poll_create_user = "Adaugă un nou utilizator";
out.poll_create_option = "Adaugă o nouă opțiune";
out.poll_commit = "Comite";
out.poll_closeWizardButton = "Închide wizard-ul";
out.poll_closeWizardButtonTitle = "Închide wizard-ul";
out.poll_wizardComputeButton = "Calculează Opțiunile";
out.poll_wizardClearButton = "Curăță Tabelul";
out.poll_wizardDescription = "Crează automat un număr de opțiuni întroducând orice număr de zile sau intervale orare";
out.poll_wizardAddDateButton = "+ Zi";
out.poll_wizardAddTimeButton = "+ Ore";
out.poll_optionPlaceholder = "Opțiune";
out.poll_userPlaceholder = "Numele tău";
out.poll_removeOption = "Ești sigur că vrei să îndepărtezi această opțiune?";
out.poll_removeUser = "Ești sigur că vrei să îndepărtezi aceast utilizator?";
out.poll_titleHint = "Titlu";
out.poll_descriptionHint = "Descrie sondajul, și apoi folosește butonul 'publică' când ai terminat. Orice utilizator care are link-ul poate modifica descrierea, dar descurajăm această practică.";
out.canvas_clear = "Curăță";
out.canvas_delete = "Curăță selecția";
out.canvas_disable = "Dezactivează modul desen";
out.canvas_enable = "Activează modul desen";
out.canvas_width = "Lățime";
out.canvas_opacity = "Opacitate";
out.fm_rootName = "Documente";
out.fm_trashName = "Gunoi";
out.fm_unsortedName = "Fișiere nesortate";
out.fm_filesDataName = "Toate fișierele";
out.fm_templateName = "Șabloane";
out.fm_searchName = "Caută";
out.fm_searchPlaceholder = "Caută...";
out.fm_newButton = "Nou";
out.fm_newButtonTitle = "Crează un nou pad sau folder";
out.fm_newFolder = "Folder nou";
out.fm_newFile = "Pad nou";
out.fm_folder = "Folder";
out.fm_folderName = "Numele folderului";
out.fm_numberOfFolders = "# de foldere";
out.fm_numberOfFiles = "# of files";
out.fm_fileName = "Nume filă";
out.fm_title = "Titlu";
out.fm_type = "Tip";
out.fm_lastAccess = "Ultima accesare";
out.fm_creation = "Creare";
out.fm_forbidden = "Acțiune interzisă";
out.fm_originalPath = "Ruta inițială";
out.fm_openParent = "Arată în folder";
out.fm_noname = "Document nedenumit";
out.fm_emptyTrashDialog = "Ești sigur că vrei să golești coșul de gunoi?";
out.fm_removeSeveralPermanentlyDialog = "Ești sigur că vrei să ștergi pentru totdeauna aceste {0} elemente din coșul de gunoi?";
out.fm_removePermanentlyDialog = "Ești sigur că vrei să ștergi acest element pentru totdeauna?";
out.fm_removeSeveralDialog = "Ești sigur că vrei să muți aceste {0} elemente la coșul de gunoi?";
out.fm_removeDialog = "Ești sigur că vrei să muți {0} la gunoi?";
out.fm_restoreDialog = "Ești sigur că vrei să restabilești {0} în locația trecută?";
out.fm_unknownFolderError = "Ultima locație vizitată sau cea selectată nu mai există. Deschidem fișierul părinte...";
out.fm_contextMenuError = "Nu putem deschide meniul de context pentru acest element. Dacă problema persistă, reîncarcă pagina.";
out.fm_selectError = "Nu putem selecta elementul vizat. Dacă problema persistă, reîncarcă pagina.";
out.fm_categoryError = "Nu putem deschide categoria selectată, afișează sursa.";
out.fm_info_root = "Crează câte foldere tip cuib ai nevoie pentru a-ți sorta fișierele.";
out.fm_info_unsorted = "Conține toate fișierele pe care le-ai vizitat și nu sunt sortate în \"Documente\" sau mutate în \"Gunoi\".";
out.fm_info_template = "Conține toate pad-urile stocate ca șabloane și pe care le poți refolosi atunci când creezi un nou pad.";
out.fm_info_trash = "Fișierele șterse din gunoi vor fi șterse și din \"Toate fișierele\", făcând imposibilă recuperarea fișierelor din managerul de fișiere.";
out.fm_info_allFiles = "Conține toate fișierele din \"Documente\", \"Nesortate\" și \"Gunoi\". Poți să muți sau să ștergi fișierele aici.";
out.fm_info_login = "Loghează-te";
out.fm_info_register = "Înscrie-te";
out.fm_info_anonymous = "Nu ești logat cu un cont valid așa că aceste pad-uri vor fi șterse (<a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">află de ce</a>). <a href=\"/register/\">Înscrie-te</a> sau <a href=\"/login/\">Loghează-te</a> pentru a le salva.";
out.fm_alert_backupUrl = "Link copie de rezervă pentru acest drive.<br> Este <strong>foarte recomandat</strong> să o păstrezi pentru tine.<br>Poți să o folosești pentru a recupera toate fișierele în cazul în care memoria browserului tău este șterge..<br>Oricine are linkul poate să editeze sau să îndepărteze toate fișierele din managerul tău de documente.<br>";
out.fm_alert_anonymous = "Salut, momentan folosești CryptPad în mod anonim. Este ok, doar că fișierele tale vor fi șterse după o perioadă de inactivitate. Am dezactivat caracteristicile avansate ale drive-ului pentru utilizatorii anonimi pentru a face clar faptul că stocare documentelor acolo nu este o metodă sigură. Poți să <a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">citești mai multe</a> despre motivarea noastră și despre ce de trebuie să te <a href=\"/register/\">Înregistrezi</a> si sa te <a href=\"/login/\">Loghezi</a>.";
out.fm_backup_title = "Link de backup";
out.fm_nameFile = "Cum ai vrea să numești fișierul?";
out.fc_newfolder = "Folder nou";
out.fc_rename = "Redenumește";
out.fc_open = "Deschide";
out.fc_open_ro = "Deschide (modul citire)";
out.fc_delete = "Șterge";
out.fc_restore = "Restaurează";
out.fc_remove = "Șterge permanent";
out.fc_empty = "Curăță coșul";
out.fc_prop = "Proprietăți";
out.fc_sizeInKilobytes = "Dimensiune n Kilobytes";
out.fo_moveUnsortedError = "Nu poți să muți un folder la lista de pad-uri nesortate";
out.fo_existingNameError = "Numele ales este deja folosit în acest director. Te rugăm să alegi altul.";
out.fo_moveFolderToChildError = "Nu poți să muți un folder într-unul dintre descendenții săi";
out.fo_unableToRestore = "Nu am reușit să restaurăm fișierul în locația de origine. Poți să ncerci să îl muți într-o nouă locație.";
out.fo_unavailableName = "Un fișier sau un folder cu același nume există deja în locația nouă. Redenumește elementul și încearcă din nou.";
out.login_login = "Loghează-te";
out.login_makeAPad = "Crează un pad în modul anonim";
out.login_nologin = "Răsfoiește pad-urile locale";
out.login_register = "Înscrie-te";
out.logoutButton = "Deloghează-te";
out.settingsButton = "Setări";
out.login_username = "Nume utilizator";
out.login_password = "Parolă";
out.login_confirm = "Confirmă parola";
out.login_remember = "Ține-mă minte";
out.login_hashing = "Încriptăm parola, o să mai dureze.";
out.login_hello = "Salut {0},";
out.login_helloNoName = "Salut,";
out.login_accessDrive = "Acesează-ți drive-ul";
out.login_orNoLogin = "sau";
out.login_noSuchUser = "Nume de utilizator sau parolă invalide. Încearcă din nou sau înscrie-te.";
out.login_invalUser = "Nume utilizator cerut";
out.login_invalPass = "Parolă cerută";
out.login_unhandledError = "O eroare neașteptată a avut loc emoticon_unhappy";
out.register_importRecent = "Importă istoricul pad-ului (Recomandat)";
out.register_acceptTerms = "Accept <a href='/terms.html'>termenii serviciului</a>";
out.register_passwordsDontMatch = "Parolele nu se potrivesc!";
out.register_mustAcceptTerms = "Trebuie să accepți termenii serviciului";
out.register_mustRememberPass = "Nu putem să îți resetăm parola dacă o uiți. Este foarte important să o ții minte! Bifează căsuța pentru a confirma.";
out.register_header = "Bine ai venit în CryptPad";
out.register_explanation = "<p>Hai să stabilim câteva lucruri, mai întâi</p><ul><li>Parola ta este cheia secretă care criptează toate pad-urile tale. Dacă pierzi/uiți parola nu există nici-o metodă prin care îți putem recupera datele.</li><li>Poți importa pad-uri care au fost vizionate recent în browser pentru a le avea în cont.</li><li>Dacă folosești un computer împărțit, trebuie să te deloghezi, închiderea taburilor nu este de ajuns.</li></ul>";
out.register_writtenPassword = "Mi-am notat numele de utilizator și parola, înaintează.";
out.register_cancel = "Întoarce-te";
out.register_warning = "Zero Knowledge înseamnă că noi nu îți putem recupera datele dacă îți pierzi parola.";
out.register_alreadyRegistered = "Acest user există deja, vrei să te loghezi?";
out.settings_title = "Setări";
out.settings_save = "Salvează";
out.settings_backupTitle = "Fă o copie de rezervă sau restaurează toate datele";
out.settings_backup = "Copie de rezervă";
out.settings_restore = "Restaurează";
out.settings_resetTitle = "Curăță-ți drive-ul";
out.settings_reset = "Îndepărtează toate fișierele și folderele din CryptPad-ul tău.";
out.settings_resetPrompt = "Această acțiune va indepărta toate pad-urile din drive-ul tău.<br>Ești sigur că vrei să continui?<br>Tastează “<em>Iubesc CryptPad</em>” pentru a confirma.";
out.settings_resetDone = "Drive-ul tău este acum gol!";
out.settings_resetError = "Text de verificare incorect. CryptPad-ul tău nu a fost schimbat.";
out.settings_resetTips = "Sfaturi în CryptDrive";
out.settings_resetTipsButton = "Resetează sfaturile disponibile în CryptDrive";
out.settings_resetTipsDone = "Toate sfaturile sunt vizibile din nou.";
out.settings_importTitle = "Importă pad-urile recente ale acestui browser n CryptDrive-ul meu";
out.settings_import = "Importă";
out.settings_importConfirm = "Ești sigur că vrei să imporți pad-urile recente ale acestui browser în contul tău de CryptDrive?";
out.settings_importDone = "Import complet";
out.settings_userFeedbackHint1 = "CryptPad oferă niște feedback foarte simplu serverului, pentru a ne informa cum putem să îți îmbunătățim experiența voastră.";
out.settings_userFeedbackHint2 = "Conținutul pad-ului tău nu va fi împărțit cu serverele.";
out.settings_userFeedback = "Activează feedback";
out.settings_anonymous = "Nu ești logat. Setările sunt specifice browser-ului.";
out.settings_publicSigningKey = "Cheia de semnătură publică";
out.settings_usage = "Uzaj";
out.settings_usageTitle = "Vezi dimensiunea totală a pad-urilor fixate în MB";
out.settings_pinningNotAvailable = "Pad-urile fixate sunt disponibile doar utilizatorilor înregistrați.";
out.settings_pinningError = "Ceva nu a funcționat";
out.settings_usageAmount = "Pad-urile tale fixate ocupă {0}MB";
out.settings_logoutEverywhereTitle = "Deloghează-te peste tot";
out.settings_logoutEverywhere = "Deloghează-te din toate sesiunile web";
out.settings_logoutEverywhereConfirm = "Ești sigur? Va trebui să te loghezi, din nou, pe toate device-urile tale.";
out.upload_serverError = "Eroare de server: fișierele tale nu pot fi încărcate la momentul acesta.";
out.upload_uploadPending = "Ai deja o încărcare în desfășurare. Anulezi și încarci noul fișier?";
out.upload_success = "Fișierul tău ({0}) a fost ncărcat și adăugat la drive-ul tău cu succes.";
out.main_p2 = "Acest proiect folosește <a href=\"http://ckeditor.com/\">CKEditor</a> Visual Editor, <a href=\"https://codemirror.net/\">CodeMirror</a>, și <a href=\"https://github.com/xwiki-contrib/chainpad\">ChainPad</a> un motor în timp real.";
out.main_howitworks_p1 = "CryptPad folosește o variantă a algoritmului de <a href=\"https://en.wikipedia.org/wiki/Operational_transformation\">Operational transformation</a> care este capabil să găsescă consens distribuit folosind <a href=\"https://bitcoin.org/bitcoin.pdf\">Nakamoto Blockchain</a>, o construcție popularizată de <a href=\"https://en.wikipedia.org/wiki/Bitcoin\">Bitcoin</a>. Astfel algoritmul poate evita nevoia ca serverul central să rezove conflicte, iar serverul nu este interesat de conținutul care este editat în pad.";
out.main_about_p2 = "Dacă ai orice fel de întrebare sau comentariu, poți să ne <a href=\"https://twitter.com/cryptpad\">dai un tweet</a>, semnalezi o problemă <a href=\"https://github.com/xwiki-labs/cryptpad/issues/\" title=\"index de probleme\">on github</a>, spui salut pe IRC (<a href=\"http://webchat.freenode.net?channels=%23cryptpad&uio=MT1mYWxzZSY5PXRydWUmMTE9Mjg3JjE1PXRydWUe7\" title=\"freenode webchat\">irc.freenode.net</a>), sau <a href=\"research@xwiki.com\">trimiți un email</a>.";
out.main_info = "<h1>Colaborează în siguranță</h1><br> Dezvoltă-ți ideile împreună cu documentele partajate în timp ce tehnologia <strong>Zero Knowledge</strong> îți păstrează securitatea; chiar și de noi.";
out.main_howitworks = "Cum funcționează";
out.main_zeroKnowledge = "Zero Knowledge";
out.main_zeroKnowledge_p = "Nu trebuie să ne crezi că <em>nu ne uităm</em> la pad-urile tale, cu tehnologia revoluționară Zero Knowledge a CryptPad <em>nu putem</em>. Învață mai multe despre cum îți protejăm <a href=\"/privacy.html\" title='Intimitatea'>Intimitate și Securitate</a>.";
out.main_writeItDown = "Notează";
out.main_writeItDown_p = "Cele mai importante proiecte vin din idei mici. Notează-ți momentele de inspirație și ideile neașteptate pentru că nu știi niciodată care ar putea fi noua mare descoperire.";
out.main_share = "Partajează link-ul, partajează pad-ul";
out.main_share_p = "Dezvoltă-ți ideile împreună: organizează întâlniri eficiente, colaborează pe liste TODO și fă prezentări scurte cu toți prietenii tăi și device-urile tale.";
out.main_organize = "Organizează-te";
out.main_organize_p = "Cu CryptPad Drive, poți să stai cu ochii pe ce este important. Folderele îți permit să ții evidența proiectelor tale și să ai o viziune globală asupra evoluției lucrurilor.";
out.tryIt = "Testează!";
out.main_richText = "Rich Text editor";
out.main_richText_p = "Editează texte complexe în mod colaborativ cu Zero Knowledge în timp real. <a href=\"http://ckeditor.com\" target=\"_blank\">CkEditor</a> application.";
out.main_code = "Editor cod";
out.main_code_p = "Editează cod din softul tău, în mod colaborativ, cu Zero Knowledge în timp real.<a href=\"https://www.codemirror.net\" target=\"_blank\">CodeMirror</a> application.";
out.main_slide = "Editor slide-uri";
out.main_slide_p = "Crează-ți prezentări folosind sintaxa Markdown, și afișează-le în browser-ul tău.";
out.main_poll = "Sondaj";
out.main_poll_p = "Plănuiește întâlniri sau evenimente, sau votează pentru cea mai bună soluție pentru problema ta.";
out.main_drive = "CryptDrive";
out.footer_applications = "Aplicații";
out.footer_contact = "Contact";
out.footer_aboutUs = "Despre noi";
out.about = "Despre";
out.privacy = "Privacy";
out.contact = "Contact";
out.terms = "ToS";
out.blog = "Blog";
out.policy_title = "Politica de confidențialitate CryptPad";
out.policy_whatweknow = "Ce știm despre tine";
out.policy_whatweknow_p1 = "Ca o aplicație care este găzduită online, CryptPad are acces la metadatele expuse de protocolul HTTP. Asta include adresa IP-ului tău, și alte titluri HTTP care pot fi folosite ca să identifice un browser. Poți să vezi ce informații împărtășește browser-ul tău vizitând <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending\" title=\"what http headers is my browser sending\">WhatIsMyBrowser.com</a>.";
out.policy_whatweknow_p2 = "Folosim <a href=\"https://www.elastic.co/products/kibana\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"platforma de analiză open source\">Kibana</a>, o platformă open source, pentru a afla mai multe despre utilizatorii noștri. Kibana ne spune despre cum ai găsit CryptPad, căutare directă, printr-un motor de căutare, sau prin recomandare de la un alt serviciu online ca Reddit sau Twitter.";
out.policy_howweuse = "Cum folosim ce aflăm";
out.policy_howweuse_p1 = "Folosim aceste informații pentru a lua decizii mai bune în promovarea CryptPad, prin evaluarea eforturilor trecute care au fost de succes. Informațiile despre locația ta ne ajută să aflăm dacă ar trebui să oferim suport pentru alte limbi, pe lângă engleză.";
out.policy_howweuse_p2 = "Informațiile despre browser-ul tău (dacă este bazat pe un sistem de operare desktop sau mobil) ne ajută să luăm decizii când prioritizăm viitoarele îmbunătățiri. Echipa noastră de dezvoltare este mică, și încercăm să facem alegeri care să îmbunătățească experiența câtor mai mulți utilizatori.";
out.policy_whatwetell = "Ce le spunem altora despre tine";
out.policy_whatwetell_p1 = "Nu furnizăm informațiile obținute terților, decât dacă ne este cerut în mod legal.";
out.policy_links = "Link-uri către alte site-uri";
out.policy_links_p1 = "Acest site conține link-uri către alte site-uri, incluzându-le pe cele produse de alte organizații. Nu suntem responsabili pentru practicile de intimitate sau pentru conținutul site-urilor externe. Ca regulă generală, link-urile către site-uri externe sunt deschise ntr-o fereastră noup, pentru a face clar faptul că părăsiți CryptPad.fr.";
out.policy_ads = "Reclame";
out.policy_ads_p1 = "Nu afișăm nici o formă de publicitate online, dar s-ar putea să atașăm link-uri către instituțiile care ne finanțează cerecetarea.";
out.policy_choices = "Ce alegeri ai";
out.policy_choices_open = "Codul nostru este open source, așa că tu ai mereu posibilitatea de a-ți găzdui propria instanță de CryptPad.";
out.policy_choices_vpn = "Dacă vrei să folosești instanța găzduită de noi, dar nu vrei să îți expui IP-ul, poți să îl protejezi folosind <a href=\"https://www.torproject.org/projects/torbrowser.html.en\" title=\"downloads from the Tor project\" target=\"_blank\" rel=\"noopener noreferrer\">Tor browser bundle</a>, sau <a href=\"https://riseup.net/en/vpn\" title=\"VPNs provided by Riseup\" target=\"_blank\" rel=\"noopener noreferrer\">VPN</a>.";
out.policy_choices_ads = "Dacă vrei doar să blochezi platforma noastră de analiză, poți folosi soluții de adblocking ca <a href=\"https://www.eff.org/privacybadger\" title=\"download privacy badger\" target=\"_blank\" rel=\"noopener noreferrer\">Privacy Badger</a>.";
out.tos_title = "CryptPad Termeni de Utilizare";
out.tos_legal = "Te rugăm să nu fii rău intenționat, abuziv, sau să faci orice ilegal.";
out.tos_availability = "Sperăm că o să găsești acest serviciu util, dar disponibilitatea sau performanța nu poate fi garantată. Te rugăm să îți exporți datele n mod regulat.";
out.tos_e2ee = "Conținutul CryptPad poate fi citit sau modificat de oricine care poate ghici sau obține fragmentul identificator al pad-ului. Recomandăm să folosești soluții de comunicare criptate end-to-end-encrypted (e2ee) pentru a partaja link-uri, evitând orice risc în cazul unei scurgeri de informații.";
out.tos_logs = "Metadatele oferite de browser-ul tău serverului ar putea fi înscrise în scopul de a menține serviciul.";
out.tos_3rdparties = "Nu oferim date personale terților, decât dacă ne sunt solicitate prin lege.";
out.bottom_france = "<a href=\"http://www.xwiki.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Realizat cu <img class=\"bottom-bar-heart\" src=\"/customize/heart.png\" alt=\"love\" /> n <img class=\"bottom-bar-fr\" src=\"/customize/fr.png\" alt=\"Franța\" /></a>";
out.bottom_support = "<a href=\"http://labs.xwiki.com/\" title=\"XWiki Labs\" target=\"_blank\" rel=\"noopener noreferrer\">Un proiect al <img src=\"/customize/logo-xwiki2.png\" alt=\"XWiki SAS\" class=\"bottom-bar-xwiki\"/> Labs Project </a> cu susținerea <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_france = "<a href=\"http://www.xwiki.com/\" target=\"_blank\" rel=\"noopener noreferrer\">With <img class=\"bottom-bar-heart\" src=\"/customize/heart.png\" alt=\"love\" /> from <img class=\"bottom-bar-fr\" src=\"/customize/fr.png\" title=\"Franța\" alt=\"Franța\"/> by <img src=\"/customize/logo-xwiki.png\" alt=\"XWiki SAS\" class=\"bottom-bar-xwiki\"/></a>";
out.header_support = "<a href=\"http://ng.open-paas.org/\" title=\"OpenPaaS::ng\" target=\"_blank\" rel=\"noopener noreferrer\"> <img src=\"/customize/openpaasng.png\" alt=\"OpenPaaS-ng\" class=\"bottom-bar-openpaas\" /></a>";
out.header_logoTitle = "Mergi la pagina principală";
out.initialState = "<span style=\"font-size:16px;\"><p>Acesta este&nbsp;<strong>CryptPad</strong>, editorul colaborativ bazat pe tehnologia Zero Knowledge în timp real. Totul este salvat pe măsură ce scrii.<br>Partajează link-ul către acest pad pentru a edita cu prieteni sau folosește <span style=\"background-color:#5cb85c;color:#ffffff;\">&nbsp;Share&nbsp;</span> butonul pentru a partaja <em>read-only link</em>&nbsp;permițând vizualizarea dar nu și editarea.</p><p><span style=\"color:#808080;\"><em>Îndrăznește, începe să scrii...</em></span></p></span><p>&nbsp;<br></p>";
out.codeInitialState = "/*\n Acesta este editorul colaborativ de cod bazat pe tehnologia Zero Knowledge CryptPad.\n Ce scrii aici este criptat, așa că doar oamenii care au link-ul pot să-l acceseze.\n Poți să alegi ce limbaj de programare pus n evidență și schema de culori UI n dreapta sus.\n*/";
out.slideInitialState = "# CryptSlide\n* Acesta este un editor colaborativ bazat pe tehnologia Zero Knowledge.\n* Ce scrii aici este criptat, așa că doar oamenii care au link-ul pot să-l acceseze.\n* Nici măcar serverele nu au acces la ce scrii tu.\n* Ce vezi aici, ce auzi aici, atunci când pleci, lași aici.\n\n-\n# Cum se folosește\n1. Scrie-ți conținutul slide-urilor folosind sintaxa markdown\n - Află mai multe despre sintaxa markdown [aici](http://www.markdowntutorial.com/)\n2. Separă-ți slide-urile cu -\n3. Click pe butonul \"Play\" pentru a vedea rezultatele - Slide-urile tale sunt actualizate în timp real.";
out.driveReadmeTitle = "Ce este CryptDrive?";
out.readme_welcome = "Bine ai venit n CryptPad !";
out.readme_p1 = "Bine ai venit în CryptPad, acesta este locul unde îți poți lua notițe, singur sau cu prietenii.";
out.readme_p2 = "Acest pad o să îți ofere un scurt ghid în cum poți să folosești CryptPad pentru a lua notițe, a le ține organizate și a colabora pe ele.";
out.readme_cat1 = "Descoperă-ți CryptDrive-ul";
out.readme_cat1_l1 = "Crează un pad: În CryptDrive-ul tău, dă click {0} apoi {1} și poți să creezi un pad.";
out.readme_cat1_l2 = "Deschide pad-urile din CryptDrive-ul tău: doublu-click pe iconița unui pad pentru a-l deschide.";
out.readme_cat1_l3 = "Organizează-ți pad-urile: Când ești logat, orice pad accesezi va fi afișat ca în secțiunea {0} a drive-ului tău.";
out.readme_cat1_l3_l1 = "Poți să folosești funcția click and drag pentru a muta fișierele în folderele secțiunii {0} a drive-ului tău și pentru a crea noi foldere.";
out.readme_cat1_l3_l2 = "Ține minte să încerci click-dreapta pe iconițe pentru că există și meniuri adiționale.";
out.readme_cat1_l4 = "Pune pad-urile vechi în gunoi. Poți să folosești funcția click and drag pe pad-uri în categoria {0} la fel ca și în cazul folderelor.";
out.readme_cat2 = "Crează pad-uri ca un profesionist";
out.edit = "editează";
out.view = "vezi";
out.readme_cat2_l1 = "Butonul {0} din pad-ul tău dă accesul colaboratorilor tăi să {1} sau să {2} pad-ul.";
out.readme_cat2_l2 = "Schimbă titlul pad-ului dând click pe creion";
out.readme_cat3 = "Descoperă aplicațiile CryptPad";
out.readme_cat3_l1 = "Cu editorul de cod CryptPad, poți colabora pe cod ca Javascript și markdown ca HTML și Markdown";
out.readme_cat3_l2 = "Cu editorul de slide-uri CryptPad, poți să faci prezentări scurte folosind Markdown";
out.readme_cat3_l3 = "Cu CryptPoll poți să organizezi votări rapide, mai ales pentru a programa ntâlniri care se potrivesc calendarelor tuturor";
out.tips = { };
out.tips.lag = "Iconița verde din dreapta-sus arată calitatea conexiunii internetului tău la serverele CryptPad.";
out.tips.shortcuts = "`ctrl+b`, `ctrl+i` and `ctrl+u` sunt scurtături pentru bold, italic și underline.";
out.tips.indentare = "În listele cu bulină sau cele numerotate, poți folosi tab sau shift+tab pentru a mări sau micșora indentarea.";
out.tips.titlu = "Poți seta titlul pad-urilor tale prin click pe centru sus.";
out.tips.stocare = "De fiecare dată când vizitezi un pad, dacă ești logat va fi salvat pe CryptDrive-ul tău.";
out.tips.marker = "Poți sublinia text într-un pad folosind itemul \"marker\" n meniul de stiluri.";
out.feedback_about = "Dacă citești asta, probabil că ești curios de ce CryptPad cere pagini web atunci când întreprinzi anumite acțiuni";
out.feedback_privacy = "Ne pasă de intimitatea ta, si în același timp vrem să păstrăm CryptPad ușor de folosit. Folosim acest fișier pentru a ne da seama care beneficii UI contează cel mai mult pentru utilizatori, cerându-l alături de un parametru specific atunci când acțiunea se desfășoară";
out.feedback_optout = "Dacă vrei să ieși, vizitează <a href='/settings/'>setările de pe pagina ta de user</a>, unde vei găsi o căsuță pentru a activa sau dezactiva feedback-ul de la user";
return out; return out;
}); });

@ -0,0 +1,544 @@
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 = 'Chinese';
out.main_title = "CryptPad: 零知識, 即時協作編寫";
out.main_slogan = "團結就是力量 - 合作是關鍵"; // TODO remove?
out.type = {};
out.type.pad = '富文本';
out.type.code = '編碼';
out.type.poll = '投票';
out.type.slide = '投影片簡報';
out.type.drive = '磁碟';
out.type.whiteboard = '白板';
out.type.file = '檔案';
out.type.media = '多媒體';
out.button_newpad = '富文件檔案';
out.button_newcode = '新代碼檔案';
out.button_newpoll = '新投票調查';
out.button_newslide = '新簡報';
out.button_newwhiteboard = '新白板';
// 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>伺服器連線中斷</b><br>現在是唯讀狀態,直到連線恢復正常。";
out.common_connectionLost = out.updated_0_common_connectionLost;
out.websocketError = '無法連結上 websocket 伺服器...';
out.typeError = "這個編輯檔與所選的應用程式並不相容";
out.onLogout = '你已登出, <a href="/" target="_blank">點擊這裏</a> 來登入<br>或按<em>Escape</em> 來以唯讀模型使用你的編輯檔案';
out.wrongApp = "無法在瀏覽器顯示即時期間的內容,請試著再重新載入本頁。";
out.loading = "載入中...";
out.error = "錯誤";
out.saved = "儲存";
out.synced = "所有資料已儲存好了";
out.deleted = "自 CryptDrive 刪除檔案";
out.disconnected = '已斷線';
out.synchronizing = '同步中';
out.reconnecting = '重新連結...';
out.lag = 'Lag';
out.readonly = '唯讀';
out.anonymous = "匿名";
out.yourself = "你自己";
out.anonymousUsers = "匿名的編輯群";
out.anonymousUser = "匿名的編輯群者";
out.users = "用戶";
out.and = "與";
out.viewer = "檢視者";
out.viewers = "檢視群";
out.editor = "編輯者";
out.editors = "編輯群";
out.language = "語言";
out.comingSoon = "即將上市...";
out.newVersion = '<b>CryptPad 已更新!</b><br>' +
'檢查最新版本有什麼新功能:<br>'+
'<a href="https://github.com/xwiki-labs/cryptpad/releases/tag/{0}" target="_blank">CryptPad新發佈記事 {0}</a>';
out.upgrade = "昇級";
out.upgradeTitle = "昇級帳戶以取得更多的儲存空間";
out.MB = "MB";
out.GB = "GB";
out.KB = "KB";
out.formattedMB = "{0} MB";
out.formattedGB = "{0} GB";
out.formattedKB = "{0} KB";
out.greenLight = "每件事都很順利";
out.orangeLight = "連線速度慢可能會影響用戶體驗";
out.redLight = "你這段期間的連線已中斷";
out.pinLimitReached = "你已達到儲存容量上限";
out.updated_0_pinLimitReachedAlert = "你已達到儲存容量上限,新檔案不會儲存到你的 CryptDrive.<br>" +
'要嘛你可以自 CryptDrive 移除原有文件或是 <a href="https://accounts.cryptpad.fr/#!on={0}" target="_blank">昇級到付費版</a>增加你的儲存容量。';
out.pinLimitReachedAlert = out.updated_0_pinLimitReachedAlert;
out.pinLimitNotPinned = "你已達到容量使用上限<br>"+
"這個檔案無法儲存到你的 CryptDrive.";
out.pinLimitDrive = "你已達到容量使用上限<br>" +
"你不能建立新的編輯檔案";
out.importButtonTitle = '從電腦上傳滙入檔案';
out.exportButtonTitle = '將這個檔案滙出到電腦';
out.exportPrompt = '你希望怎麼命名你的檔案?';
out.changeNamePrompt = '更換你的名稱(若留空白則會成為無名氏): ';
out.user_rename = "改變顯示名稱";
out.user_displayName = "顯示名稱";
out.user_accountName = "帳號名稱";
out.clickToEdit = "點擊以編輯";
out.forgetButtonTitle = '將這個檔案移置垃圾筒';
out.forgetPrompt = '點擊 OK 將把這個檔案移置垃圾筒,確定要這樣做嗎';
out.movedToTrash = '這個檔案已被移置垃圾筒<br><a href="/drive/">讀取我的雲端硬碟</a>';
out.shareButton = '分享';
out.shareSuccess = '複製連結到剪貼版';
out.newButton = '新';
out.newButtonTitle = '建立新的工作檔案';
out.saveTemplateButton = "存成模版";
out.saveTemplatePrompt = "為這個模版選一個標題";
out.templateSaved = "模版已儲存!";
out.selectTemplate = "選擇一個模版或是按 escape 跳出";
out.previewButtonTitle = "顯示或隱藏 Markdown 預覽模式";
out.presentButtonTitle = "輸入簡報模式";
out.presentSuccess = '按 ESC 以退出簡報模式';
out.backgroundButtonTitle = '改變簡報的顏色背景';
out.colorButtonTitle = '在簡報模式下改變文字顏色';
out.printButton = "列印 (enter)";
out.printButtonTitle = "列印投影片或滙出成 PDF 檔案";
out.printOptions = "版型選項";
out.printSlideNumber = "顯示投影片號碼";
out.printDate = "顯示日期";
out.printTitle = "顯示檔案標題";
out.printCSS = "自定風格規則 (CSS):";
out.printTransition = "啟用轉場動畫";
out.slideOptionsTitle = "自定你的投影片";
out.slideOptionsButton = "儲存 (enter)";
out.editShare = "編輯連結";
out.editShareTitle = "複製所編輯的連結到剪貼版";
out.editOpen = "在新分頁開啟連結編輯";
out.editOpenTitle = "在新分頁開啟這個檔案為編輯模式";
out.viewShare = "唯讀連結";
out.viewShareTitle = "複製唯讀的連結到剪貼版";
out.viewOpen = "在新分頁開啟唯讀連結";
out.viewOpenTitle = "在新分頁開啟這個檔案為唯讀模式";
out.notifyJoined = "{0} 已加入此協作期間";
out.notifyRenamed = "{0} 現在改名為 {1}";
out.notifyLeft = "{0} 已離開了這個協作期間";
out.okButton = 'OK (enter)';
out.cancel = "取消";
out.cancelButton = '取消 (esc)';
out.historyButton = "顯示文件歷史";
out.history_next = "到下一個版本";
out.history_prev = "到之前的版本";
out.history_goTo = "到所選擇的版本";
out.history_close = "回到";
out.history_closeTitle = "關閉歷史記錄";
out.history_restore = "重建";
out.history_restoreTitle = "將此文件重建到所挑選的版本";
out.history_restorePrompt = "確定要將這個展現的版本來取代現有版本嗎?";
out.history_restoreDone = "文件已重建";
out.history_version = "版本:";
// Polls
out.poll_title = "零知識日期挑選";
out.poll_subtitle = "零知識, <em>即時</em> 排程";
out.poll_p_save = "你的設定會立即更新, 因此從不需要按鍵儲存或擔心遺失。";
out.poll_p_encryption = "你所有幹入的資料都會予以加密,只有取得連結者才可以讀取它。即便是伺服器也不能看到你作了什麼變動。";
out.wizardLog = "點擊左上方的按鍵以回到你的調查";
out.wizardTitle = "使用精靈來建立調查投票";
out.wizardConfirm = "你真的要新增這些問題到你的調查中嗎?";
out.poll_publish_button = "發佈";
out.poll_admin_button = "管理者";
out.poll_create_user = "新增使用者";
out.poll_create_option = "新增選項";
out.poll_commit = "投入";
out.poll_closeWizardButton = "關閉協助精靈";
out.poll_closeWizardButtonTitle = "關閉協助精靈";
out.poll_wizardComputeButton = "計算最適化";
out.poll_wizardClearButton = "清除表格";
out.poll_wizardDescription = "透過輸入任何日期或時間分段,可自動建立一些選項";
out.poll_wizardAddDateButton = "+ 日期";
out.poll_wizardAddTimeButton = "+ 時間";
out.poll_optionPlaceholder = "選項";
out.poll_userPlaceholder = "你的名稱";
out.poll_removeOption = "確定要移除這個選項嗎?";
out.poll_removeUser = "確定要移除這位使用者嗎?";
out.poll_titleHint = "標題";
out.poll_descriptionHint = "請簡述這個調查目的,完成時使用「發佈鍵」。任何知道此調查連結者可以更改這裏的描述內容,但我們不鼓勵這麼做。.";
// Canvas
out.canvas_clear = "清除";
out.canvas_delete = "刪除所選";
out.canvas_disable = "取消繪圖";
out.canvas_enable = "啟動繪圖";
out.canvas_width = "寛度";
out.canvas_opacity = "透明度";
// File manager
out.fm_rootName = "根目錄";
out.fm_trashName = "垃圾桶";
out.fm_unsortedName = "未整理的檔案";
out.fm_filesDataName = "所有檔案";
out.fm_templateName = "模版";
out.fm_searchName = "搜尋";
out.fm_searchPlaceholder = "搜尋...";
out.fm_newButton = "新的";
out.fm_newButtonTitle = "建立新工作檔案或資料夾";
out.fm_newFolder = "新資料夾";
out.fm_newFile = "新工作檔案";
out.fm_folder = "資料夾";
out.fm_folderName = "資料夾名稱";
out.fm_numberOfFolders = "# 個資料夾";
out.fm_numberOfFiles = "# 檔案";
out.fm_fileName = "檔案名";
out.fm_title = "標題";
out.fm_type = "類型";
out.fm_lastAccess = "上回使用";
out.fm_creation = "創建";
out.fm_forbidden = "禁止的行為";
out.fm_originalPath = "原始路徑";
out.fm_openParent = "顯示在目錄夾中";
out.fm_noname = "無標題文件";
out.fm_emptyTrashDialog = "確定要清理垃圾筒嗎?";
out.fm_removeSeveralPermanentlyDialog = "確定要將這些 {0} 東西永自垃圾筒移除嗎?";
out.fm_removePermanentlyDialog = "你確定要永久地移除這些項目嗎?";
out.fm_removeSeveralDialog = "確定要將這些 {0} 東西移至垃圾筒嗎?";
out.fm_removeDialog = "確定要將移動 {0} 至垃圾筒嗎?";
out.fm_restoreDialog = "確定要重置 {0} 到它之前的位置嗎?";
out.fm_unknownFolderError = "所選或上回訪問的目錄不再存在了,正開啟上層目錄中...";
out.fm_contextMenuError = "無法在此元件下打開文本選單。如果這個問題一直發生,請試著重新載入此頁。";
out.fm_selectError = "無法選取目標的要素。如果這個問題一直發生,請試著重新載入此頁。";
out.fm_categoryError = "無法打開所選的類別,正在顯示根目錄。";
out.fm_info_root = "在此建立任何巢狀目錄夾以便於整理分類你的檔案。";
out.fm_info_unsorted = '包含所有你曾訪問過的檔案,其尚未被整理在 "根目錄" 或移到到"垃圾筒".'; // "My Documents" should match with the "out.fm_rootName" key, and "Trash" with "out.fm_trashName"
out.fm_info_template = '包含所有工作檔案已存成模版,便於讓你在建立新工作檔案時套用。';
out.updated_0_fm_info_trash = '清空垃圾筒好讓 CryptDrive 多出一些空間';
out.fm_info_trash = out.updated_0_fm_info_trash;
out.fm_info_allFiles = '包含在 "根目錄", "未整理的" 和 "垃圾筒" 裏的所有檔案。這裏你無法移動或移除檔案。'; // Same here
out.fm_info_anonymous = '你尚未登入,因此這些工作檔案可能會被刪除。 (<a href="https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/" target="_blank">了解原因</a>). ' +
'<a href="/register/">註冊</a>或<a href="/login/">登入</a>以便保留它們。';
out.fm_alert_backupUrl = "這個雲端硬碟的備份連結<br>" +
"<strong>高度建議</strong>把自己的 IP 資訊保留成只有自己知道<br>" +
"萬一瀏覽器記憶被消除,你可以用它來接收所有的檔案。<br>" +
"任何知道此連結的人可以編輯或移除你檔案管理底下的所有檔案。<br>";
out.fm_alert_anonymous = "嗨你好, 你目前正以匿名方式在使用 CryptPad , 這也沒問題,不過你的東西過一段時間沒動靜後,就會自動被刪除。 " +
"匿名的用戶我們也取消其進階功能,因為我們要明確地讓用戶知道,這裏 " +
'不是一個安全存放東西的地方。你可以 <a href="https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/" target="_blank">進一步了解 </a> 關於 ' +
'為何我們這樣作,以及為何你最好能夠<a href="/register/">註冊</a> 以及 <a href="/login/">登錄</a>使用。';
out.fm_backup_title = '備份連結';
out.fm_nameFile = '你想要如何來命名這個檔案呢?';
out.fm_error_cantPin = "內部伺服器出錯,請重新載入本頁並再試一次。";
// File - Context menu
out.fc_newfolder = "新資料夾";
out.fc_rename = "重新命名";
out.fc_open = "打開";
out.fc_open_ro = "打開 (唯讀)";
out.fc_delete = "刪除";
out.fc_restore = "重置";
out.fc_remove = "永久刪除";
out.fc_empty = "清理垃圾筒";
out.fc_prop = "Properties";
out.fc_sizeInKilobytes = "容量大小 (Kilobytes)";
// fileObject.js (logs)
out.fo_moveUnsortedError = "你不能移動資料夾到未整理的工作檔案清單";
out.fo_existingNameError = "名稱已被使用,請選擇其它名稱";
out.fo_moveFolderToChildError = "你不能移動資料夾到它的子資料夾底下";
out.fo_unableToRestore = "無法將這個檔案重置到原始的位置。你可以試著將它移動到其它新位置。";
out.fo_unavailableName = "在新位置裏同名的檔案或資料夾名稱已存在,請重新命名後再試看看。";
// login
out.login_login = "登入";
out.login_makeAPad = '匿名地建立一個工作檔案';
out.login_nologin = "瀏覽本地的工作檔案";
out.login_register = "註冊";
out.logoutButton = "登出";
out.settingsButton = "設定";
out.login_username = "用戶名";
out.login_password = "密碼";
out.login_confirm = "確認你的密碼";
out.login_remember = "記住我";
out.login_hashing = "散列你的密碼中,這要花上一點時間";
out.login_hello = 'Hello {0},'; // {0} is the username
out.login_helloNoName = 'Hello,';
out.login_accessDrive = '取用你的磁碟';
out.login_orNoLogin = '或';
out.login_noSuchUser = '無效的用戶名或密碼,請再試一次或重新註冊';
out.login_invalUser = '要求用戶名';
out.login_invalPass = '要求密碼';
out.login_unhandledError = '發生了未預期的錯誤 :(';
out.register_importRecent = "滙入檔案記錄 (建議)";
out.register_acceptTerms = "我同意 <a href='/terms.html'>服務條款</a>";
out.register_passwordsDontMatch = "密碼不相符!";
out.register_mustAcceptTerms = "你必須同意我們的服務條款。";
out.register_mustRememberPass = "如果你忘了密碼,我們也無法為你重置。因此務必自行好好記住! 請在勾選處勾選確認。";
out.register_header = "歡迎來到 CryptPad";
out.register_explanation = [
"<p>首先讓我們先了解幾件事</p>",
"<ul>",
"<li>你的密碼是你用來加密所有工作檔案的密鑰。一旦遺失它,我們也沒辦法幫你恢復你的資料。</li>",
"<li>你可以滙入近期在瀏覽器下檢視的工作檔案到你的雲端硬碟裏。</li>",
"<li>如果你使用的是公用分享電腦,你需要在完成工作後進行登出,只是關閉分頁是不夠的。</li>",
"</ul>"
].join('');
out.register_writtenPassword = "我已記下了我的用戶名和密碼,請繼續";
out.register_cancel = "回去";
out.register_warning = "零知識表示如果你遺失了密碼,我們也無法還原你的資料";
out.register_alreadyRegistered = "這名用戶己存在了,你要登入嗎?";
// Settings
out.settings_title = "設定";
out.settings_save = "儲存";
out.settings_backupTitle = "備份或重建你所有的資料";
out.settings_backup = "備份";
out.settings_restore = "重建";
out.settings_resetTitle = "清除你的雲端硬碟";
out.settings_reset = "從你的 CryptDrive 移除所有的檔案和資料夾";
out.settings_resetPrompt = "這個動作會自你的雲端硬碟中移除所有工作檔案<br>"+
"確定要繼續嗎?<br>" +
"輸入 “<em>I love CryptPad</em>” 來確認。";
out.settings_resetDone = "你的目錄現已清空!";
out.settings_resetError = "不正確的認證文字,你的 CryptDrive 並未更改。";
out.settings_resetTips = "使用 CryptDrive 的竅門";
out.settings_resetTipsButton = "在 CryptDrive 下重置可用的訣竅";
out.settings_resetTipsDone = "所有的訣竅現在都可再次看到了。";
out.settings_importTitle = "滙入這個瀏覽器近期的工作檔案到我的 CryptDrive";
out.settings_import = "滙入";
out.settings_importConfirm = "確定要從這個瀏覽器滙入近期的工作檔案到你的 CryptDrive ";
out.settings_importDone = "滙入完成";
out.settings_userFeedbackHint1 = "CryptPad 會提供一些基本的反饋到伺服器,以讓我們知道如何改善用戶體驗。";
out.settings_userFeedbackHint2 = "你的工作檔案內容絕不會被分享到伺服器";
out.settings_userFeedback = "啟用用戶反饋功能";
out.settings_anonymous = "你尚未登入,在此瀏覽器上進行特別設定。";
out.settings_publicSigningKey = "公開金鑰簽署";
out.settings_usage = "用法";
out.settings_usageTitle = "查看所有置頂的工作檔案所佔的容量";
out.settings_pinningNotAvailable = "工作檔案置頂功能只開放給已註冊用戶";
out.settings_pinningError = "有點不對勁";
out.settings_usageAmount = "你置頂的工作檔案佔了 {0}MB";
out.settings_logoutEverywhereTitle = "自所有地點登出";
out.settings_logoutEverywhere = "自所有其它的網頁期間登出";
out.settings_logoutEverywhereConfirm = "你確定嗎?你將需要登入到所有用到設置。";
out.upload_serverError = "伺服器出錯:本次無法上傳你的檔案";
out.upload_uploadPending = "你欲上傳檔案正在傳輸中,要取消並上傳新檔案嗎?";
out.upload_success = "你的檔案 ({0}) 已成功地上傳並放入到你的網路磁碟中。";
out.upload_notEnoughSpace = "你的 CryptDrive 無足夠空間來存放這個檔案。";
out.upload_tooLarge = "此檔案超過了上傳單一檔案可允許的容量上限。";
out.upload_choose = "選擇一個檔案";
out.upload_pending = "待處理";
out.upload_cancelled = "已取消的";
out.upload_name = "檔案名";
out.upload_size = "大小";
out.upload_progress = "進度";
out.download_button = "解密 & 下載";
// general warnings
out.warn_notPinned = "這個工作檔案並不在任何人的 CryptDrive 裏,它將在 3 個月到期後刪除。 <a href='/about.html#pinning'>進一步了解...</a>";
// index.html
//about.html
out.main_p2 = '本專案使用 <a href="http://ckeditor.com/">CKEditor</a> 視覺編輯器, <a href="https://codemirror.net/">CodeMirror</a>, 以及 <a href="https://github.com/xwiki-contrib/chainpad">ChainPad</a> 即時引擊。';
out.main_howitworks_p1 = 'CryptPad 應用一種變體的 <a href="https://en.wikipedia.org/wiki/Operational_transformation">操作型變換 Operational transformation</a> 演算法,它利用<a href="https://bitcoin.org/bitcoin.pdf">Nakamoto Blockchain</a>來找到分散的共識, Nakamoto Blockchain 是一種建構當前流行的<a href="https://en.wikipedia.org/wiki/Bitcoin">比特幣</a>。這套演算法可避免需要一個中央的伺服器來解析操作型變換編輯衝突,而無須處理解析衝突,伺服器並不知道哪一個檔案被編輯。';
// contact.html
out.main_about_p2 = '若有任何問題和建議, 可以在<a href="https://twitter.com/cryptpad">tweet us</a>, <a href="https://github.com/xwiki-labs/cryptpad/issues/" title="our issue tracker">github</a>提出問題, 或是來到 irc (<a href="http://webchat.freenode.net?channels=%23cryptpad&uio=MT1mYWxzZSY5PXRydWUmMTE9Mjg3JjE1PXRydWUe7" title="freenode webchat">irc.freenode.net</a>)打聲招呼, 再或者 <a href="mailto:research@xwiki.com">寄封電郵給我們</a>.';
out.main_info = "<h1>Collaborate in Confidence</h1><br> 利用共同享文件發嚮點子,透過 <strong>零知識 </strong> 科技確保隱私安全; 對任何網路服務商都要加以提防。";
out.main_howitworks = '它如何運作';
out.main_zeroKnowledge = '零知識';
out.main_zeroKnowledge_p = "你不必相信我們所說的<em>並不會</em> 察看你的檔案, CryptPad 革命性的零知識技術讓我們 <em>真的不能看到</em>。 進一步了解在這裏,我們如何保護用戶的 <a href=\"/privacy.html\" title='Privacy'>隱私和安全</a>。";
out.main_writeItDown = '寫下它';
out.main_writeItDown_p = "偉大的專案來自不起眼的小點子。記下靈感與點子的瞬間,因為你從不會知道哪個會帶來重大突破。";
out.main_share = '分享連結, 分享工作檔案';
out.main_share_p = "一起來發響想法點子: 在任何設備上,與朋友一起執行有效率的會議, 協作待辦清單與快速製作簡報。";
out.main_organize = 'Get organized';
out.main_organize_p = "利用 CryptPad 空間, 你可以保留看管重要的東西。資料夾讓你可以追踪專案和全盤了解事情的走向狀況。";
out.tryIt = 'Try it out!';
out.main_richText = '富文字編輯器';
out.main_richText_p = '利用我們的即時零知識技術,集體協作地編輯富文本檔案 <a href="http://ckeditor.com" target="_blank">CkEditor</a> 應用程式application.';
out.main_code = '代碼編輯器';
out.main_code_p = '利用我們的即時零知識技術,集體協作地編輯程式代碼 <a href="https://www.codemirror.net" target="_blank">CodeMirror</a> 應用程式。';
out.main_slide = '投影片編輯器';
out.main_slide_p = '使用 Markdown 語法來建立投影片,並利用瀏覽器來展示投影片。';
out.main_poll = '調查';
out.main_poll_p = '規劃會議或活動,或是為問題舉行投最佳方案的投票。';
out.main_drive = 'CryptDrive';
out.footer_applications = "應用程式";
out.footer_contact = "聯繫";
out.footer_aboutUs = "關於 Cryptpad";
out.about = "關於";
out.privacy = "隱私";
out.contact = "聯繫";
out.terms = "服務條款";
out.blog = "Blog";
// privacy.html
out.policy_title = 'CryptPad 隱私政策';
out.policy_whatweknow = '我們會知道哪些關於你的資料';
out.policy_whatweknow_p1 = '作為一個網頁上的應用程式, CryptPad 可以接取 HTTP 協議所曝露的元數據。 這包括你的 IP 地址、各式其它的 HTTP 標頭,其用於識別你特定的瀏覽器。 你可以訪問 <a target="_blank" rel="noopener noreferrer" href="https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending" title="what http headers is my browser sending">WhatIsMyBrowser.com</a>這個網站,知道你的瀏覽器分享了哪些資訊。';
out.policy_whatweknow_p2 = '我們使用 <a href="https://www.elastic.co/products/kibana" target="_blank" rel="noopener noreferrer" title="open source analytics platform">Kibana</a>, 它是一個開源的流量數據分析平台, 以更了解用戶。Kibana 讓我們知道你是如何地發現 CryptPad, 是透過直接接入、攑搜尋引擊或是其它網站的介紹如 Reddit 和 Twitter。';
out.policy_howweuse = '我們如何利用我們知道的東西';
out.policy_howweuse_p1 = '我們利用這些資訊評估過去成功的效果,以更佳地決定如何推廣 CryptPad。有關你地理位置的資訊讓我們知道是否該提供英語之外的語言版本支援';
out.policy_howweuse_p2 = "有關你的瀏覽器資訊 (是桌面還是手機操作系統) 有助於讓我們決定要優先哪些功能改善。我們開發團隊人很少,我們試著挑選盡可能地提昇更多用戶的使用體驗。";
out.policy_whatwetell = '我們可以告訴別人關於你的哪些資料';
out.policy_whatwetell_p1 = '我們不會給第三人我們所收集的資訊,除非被依法要求配合。';
out.policy_links = '其它網站連結';
out.policy_links_p1 = '本站含有其它網站的連結包括其它組織的産品。我們無法對這些隱私實踐或任何本站以外的內容負責。一般而言連到外站的連結會另啟新視窗以明確讓你知道已離開了CryptPad.fr.';
out.policy_ads = '廣告';
out.policy_ads_p1 = '我們不會放置任何線上廣告,但會提供一些資助我們研究的機構與團體的網址連結';
out.policy_choices = '你有的選擇';
out.policy_choices_open = '我們的代碼是開放的,你可以選擇自行在自己的機器上來架設自己的 CryptPad.';
out.policy_choices_vpn = '如果你要使用我們架設的服務, 但不希望曝露自己的 IP 地址, 你可以利用<a href="https://www.torproject.org/projects/torbrowser.html.en" title="downloads from the Tor project" target="_blank" rel="noopener noreferrer">Tor 瀏覽器套件</a>來保護隱藏 IP 地址, 或是使用 <a href="https://riseup.net/en/vpn" title="VPNs provided by Riseup" target="_blank" rel="noopener noreferrer">VPN</a>。';
out.policy_choices_ads = '如果你只是想要封鎖我們的數據分析器, 你可以使用廣告封鎖工具如 <a hre="https://www.eff.org/privacybadger" title="download privacy badger" target="_blank" rel="noopener noreferrer">Privacy Badger</a>.';
// terms.html
out.tos_title = "CryptPad 服務條款";
out.tos_legal = "請不要惡意、濫用或從事非法活動。";
out.tos_availability = "希望你覺得我們的産品與服務對你有所幫助, 但我們並不能一直百分百保證它的表現穩定與可得性。請記得定期滙出你的資料。";
out.tos_e2ee = "CryptPad 的內容可以被任何猜出或取得工作檔案分段識別碼的人讀取與修改。我們建議你使用端對端加密 (e2ee) 訊息技術來分享工作檔案連結 以及假設如果一旦連結外漏不會背上任何責任。";
out.tos_logs = "你的瀏覽器提供給伺服器的元數據,可能會因為維護本服務之效能而被收集記錄。";
out.tos_3rdparties = "除非法令要求,我們不會提供任何個人資料給第三方。";
// BottomBar.html
out.bottom_france = '<a href="http://www.xwiki.com/" target="_blank" rel="noopener noreferrer">Made with <img class="bottom-bar-heart" src="/customize/heart.png" alt="love" /> in <img class="bottom-bar-fr" src="/customize/fr.png" alt="France" /></a>';
out.bottom_support = '<a href="http://labs.xwiki.com/" title="XWiki Labs" target="_blank" rel="noopener noreferrer">An <img src="/customize/logo-xwiki2.png" alt="XWiki SAS" class="bottom-bar-xwiki"/> Labs Project </a> with the support of <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">With <img class="bottom-bar-heart" src="/customize/heart.png" alt="love" /> from <img class="bottom-bar-fr" src="/customize/fr.png" title="France" alt="France"/> by <img src="/customize/logo-xwiki.png" alt="XWiki SAS" class="bottom-bar-xwiki"/></a>';
out.header_support = '<a href="http://ng.open-paas.org/" title="OpenPaaS::ng" target="_blank" rel="noopener noreferrer"> <img src="/customize/openpaasng.png" alt="OpenPaaS-ng" class="bottom-bar-openpaas" /></a>';
out.header_logoTitle = '回到主頁';
// Initial states
out.initialState = [
'<span style="font-size:16px;"><p>',
'這是&nbsp;<strong>CryptPad</strong>, 零知識即時協作編輯平台,當你輸入時一切已即存好。',
'<br>',
'分享這個工作檔案的網址連結給友人或是使用、 <span style="background-color:#5cb85c;color:#ffffff;">&nbsp;分享&nbsp;</span> 按鈕分享<em>唯讀的連結</em>&nbsp;其只能看不能編寫。',
'</p>',
'<p><span style="color:#808080;"><em>',
'來吧, 開始打字輸入吧...',
'</em></span></p></span>',
'<p>&nbsp;<br></p>'
].join('');
out.codeInitialState = [
'# CryptPad 零知識即時協作代碼編輯平台\n',
'\n',
'* 你所輸入的東西會予以加密,僅有知道此網頁連結者可以接取這份文件。\n',
'* 你可以在右上角選擇欲編寫的程式語言以及樣版配色風格。'
].join('');
out.slideInitialState = [
'# CryptSlide\n',
'* 它是零知識即時協作編輯平台。\n',
'* 你所輸入的東西會予以加密,僅有知道此網頁連結者可以接取這份文件。\n',
'* 即便是本站伺服器也不知道你輸入了什麼內容。\n',
'* 你在這裏看到的、你在這裏聽到的、當你離開本站時,讓它就留在這裏吧。\n',
'\n',
'---',
'\n',
'# 如何使用\n',
'1. 使用 markdown 語法來寫下你的投影片內容\n',
' - 進一步學習 markdown 語法 [here](http://www.markdowntutorial.com/)\n',
'2. 利用 --- 來區隔不同的投影片\n',
'3. 點擊下方 "Play" 鍵來查看成果',
' - 你的投影片會即時更新'
].join('');
// Readme
out.driveReadmeTitle = "什麼是 CryptDrive?";
out.readme_welcome = "歡迎來到 CryptPad !";
out.readme_p1 = "歡迎來到 CryptPad, 這裏你可以獨自作個人筆記或是和別人共享協作。";
out.readme_p2 = "這個工作檔案可以讓你快速地了解如何使用 CryptPad 作筆記,有效地整理管理文件工作檔案。";
out.readme_cat1 = "認識如何使用 CryptDrive";
out.readme_cat1_l1 = "建立一個工作檔案: 在 CryptDrive 底下, 點擊 {0} 然後 {1} 這樣就可以建立一個新的工作檔案。"; // 0: New, 1: Rich Text
out.readme_cat1_l2 = "從 CryptDrive 開啟工作檔案: 雙擊工作檔案的圖示來開啟它。";
out.readme_cat1_l3 = "分類你的工作檔案:登入之後,每一個你能接取使用的工作檔案會顯示在你雲端硬碟中的 {0} 部份。"; // 0: Unsorted files
out.readme_cat1_l3_l1 = "你可以點擊或是拉曳檔案到雲端硬碟 {0} 區,新增資料夾。"; // 0: Documents
out.readme_cat1_l3_l2 = "記得試著點擊圖示,以顯示更多的選項功能。";
out.readme_cat1_l4 = "把舊的工作檔案放到垃圾筒:點擊或是拉曳檔案到 {0} 如同把它們拉到文件目錄夾一樣的方法。"; // 0: Trash
out.readme_cat2 = "像個專業人士來編寫你的工作檔案";
out.edit = "編輯";
out.view = "檢視";
out.readme_cat2_l1 = "在工作檔案下的 {0} 按鍵可讓其它的協作者接取 {1} 或是 {2} 工作檔案"; // 0: Share, 1: edit, 2: view
out.readme_cat2_l2 = "若要更改工作檔案的名稱,只要點擊右上的鉛筆圖示即可";
out.readme_cat3 = "發現其它的 CryptPad 應用";
out.readme_cat3_l1 = "使用 CryptPad 代碼編輯器,你可以和其它人協作各種程式碼,如 Javascript、 markdown、 HTML 等等。";
out.readme_cat3_l2 = "使用 CryptPad 投影片編輯功能,你可以使用 Markdown 快速製作簡報檔。";
out.readme_cat3_l3 = "利用 CryptPoll 你可以快速作個線上調查,尤其是調查每個人有空的會議時間。";
// Tips
out.tips = {};
out.tips.lag = "右上角的綠色圖標顯示你連線至 CryptPad 伺服器的連線品質。";
out.tips.shortcuts = "`ctrl+b`, `ctrl+i` 和 `ctrl+u` 分別是粗體字、斜體、與加底線用法的快速鍵。";
out.tips.indent = "要使用數字以及符號列表, 可使用 tab 或 shift+tab 快速地增加或滅少縮排指令。";
out.tips.title = "點擊正上方來設定工作檔案的標題。";
out.tips.store = "每一回你造訪一個工作檔案, 如果是登入狀態,則這些檔案會自動儲存到你的 CryptDrive.";
out.tips.marker = "在格式下拉選單中使用 \"marker\" 可以標注反亮文字.";
out.feedback_about = "如果你讀了這裏,也許會好奇為何當你執行某些動作時 CryptPad 會請求網頁資訊。";
out.feedback_privacy = "我們注重你的隱私,同時也要讓 CryptPad 容易使用。我們利用這個檔案來了解哪一種介面設計為用戶所重視,透過它來請求特別的功能參數。";
out.feedback_optout = "如果欲退出客戶資料收集, 請到 <a href='/settings/'>用戶設定頁</a>, 可以找到勾選項目來啟用或關閉用戶回饋功能。";
return out;
});

@ -1,3 +1,9 @@
# This file is included strictly as an example of how Nginx can be configured
# to work with CryptPad. This example WILL NOT WORK AS IS. For best results,
# compare the sections of this configuration file against a working CryptPad
# installation (http server by the Nodejs process). If you are using CryptPad
# in production, contact sales@cryptpad.fr
server { server {
listen 443 ssl http2; listen 443 ssl http2;

@ -1,7 +1,7 @@
{ {
"name": "cryptpad", "name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server", "description": "realtime collaborative visual editor with zero knowlege server",
"version": "1.7.0", "version": "1.8.0",
"dependencies": { "dependencies": {
"chainpad-server": "^1.0.1", "chainpad-server": "^1.0.1",
"express": "~4.10.1", "express": "~4.10.1",
@ -18,7 +18,7 @@
"scripts": { "scripts": {
"lint": "jshint --config .jshintrc --exclude-path .jshintignore .", "lint": "jshint --config .jshintrc --exclude-path .jshintignore .",
"test": "node TestSelenium.js", "test": "node TestSelenium.js",
"style": "lessc ./customize.dist/src/less/cryptpad.less > ./customize.dist/main.css && lessc ./customize.dist/src/less/toolbar.less > ./customize.dist/toolbar.css && lessc ./www/drive/file.less > ./www/drive/file.css && lessc ./www/settings/main.less > ./www/settings/main.css && lessc ./www/slide/slide.less > ./www/slide/slide.css && lessc ./www/whiteboard/whiteboard.less > ./www/whiteboard/whiteboard.css && lessc ./www/poll/poll.less > ./www/poll/poll.css && lessc ./www/file/file.less > ./www/file/file.css", "style": "lessc ./customize.dist/src/less/cryptpad.less > ./customize.dist/main.css && lessc ./customize.dist/src/less/toolbar.less > ./customize.dist/toolbar.css && lessc ./www/drive/file.less > ./www/drive/file.css && lessc ./www/settings/main.less > ./www/settings/main.css && lessc ./www/slide/slide.less > ./www/slide/slide.css && lessc ./www/whiteboard/whiteboard.less > ./www/whiteboard/whiteboard.css && lessc ./www/poll/poll.less > ./www/poll/poll.css && lessc ./www/file/file.less > ./www/file/file.css && lessc ./www/code/code.less > ./www/code/code.css",
"template": "cd customize.dist/src && node build.js" "template": "cd customize.dist/src && node build.js"
} }
} }

@ -1,4 +1,4 @@
/* jshint esversion: 6 */ /* jshint esversion: 6, node: true */
const Fs = require('fs'); const Fs = require('fs');
const Semaphore = require('saferphore'); const Semaphore = require('saferphore');
const nThen = require('nthen'); const nThen = require('nthen');
@ -26,14 +26,14 @@ const hashesFromPinFile = (pinFile, fileName) => {
return Object.keys(pins); return Object.keys(pins);
}; };
const sizeForHashes = (hashes, dsFileSizes) => { const sizeForHashes = (hashes, dsFileStats) => {
let sum = 0; let sum = 0;
hashes.forEach((h) => { hashes.forEach((h) => {
const s = dsFileSizes[h]; const s = dsFileStats[h];
if (typeof(s) !== 'number') { if (typeof(s) !== 'number') {
//console.log('missing ' + h + ' ' + typeof(s)); //console.log('missing ' + h + ' ' + typeof(s));
} else { } else {
sum += s; sum += s.size;
} }
}); });
return sum; return sum;
@ -43,8 +43,9 @@ const sema = Semaphore.create(20);
let dirList; let dirList;
const fileList = []; const fileList = [];
const dsFileSizes = {}; const dsFileStats = {};
const out = []; const out = [];
const pinned = {};
nThen((waitFor) => { nThen((waitFor) => {
Fs.readdir('./datastore', waitFor((err, list) => { Fs.readdir('./datastore', waitFor((err, list) => {
@ -65,7 +66,7 @@ nThen((waitFor) => {
sema.take((returnAfter) => { sema.take((returnAfter) => {
Fs.stat(f, waitFor(returnAfter((err, st) => { Fs.stat(f, waitFor(returnAfter((err, st) => {
if (err) { throw err; } if (err) { throw err; }
dsFileSizes[f.replace(/^.*\/([^\/]*)\.ndjson$/, (all, a) => (a))] = st.size; dsFileStats[f.replace(/^.*\/([^\/]*)\.ndjson$/, (all, a) => (a))] = st;
}))); })));
}); });
}); });
@ -90,12 +91,25 @@ nThen((waitFor) => {
Fs.readFile(f, waitFor(returnAfter((err, content) => { Fs.readFile(f, waitFor(returnAfter((err, content) => {
if (err) { throw err; } if (err) { throw err; }
const hashes = hashesFromPinFile(content.toString('utf8'), f); const hashes = hashesFromPinFile(content.toString('utf8'), f);
const size = sizeForHashes(hashes, dsFileSizes); const size = sizeForHashes(hashes, dsFileStats);
out.push([f, Math.floor(size / (1024 * 1024))]); if (process.argv.indexOf('--unpinned') > -1) {
hashes.forEach((x) => { pinned[x] = 1; });
} else {
out.push([f, Math.floor(size / (1024 * 1024))]);
}
}))); })));
}); });
}); });
}).nThen(() => { }).nThen(() => {
out.sort((a,b) => (a[1] - b[1])); if (process.argv.indexOf('--unpinned') > -1) {
out.forEach((x) => { console.log(x[0] + ' ' + x[1] + ' MB'); }); Object.keys(dsFileStats).forEach((f) => {
if (!(f in pinned)) {
console.log("./datastore/" + f.slice(0,2) + "/" + f + ".ndjson " +
dsFileStats[f].size + " " + (+dsFileStats[f].mtime));
}
});
} else {
out.sort((a,b) => (a[1] - b[1]));
out.forEach((x) => { console.log(x[0] + ' ' + x[1] + ' MB'); });
}
}); });

@ -1,4 +1,5 @@
/*@flow*/ /*@flow*/
/*jshint esversion: 6 */
/* Use Nacl for checking signatures of messages */ /* Use Nacl for checking signatures of messages */
var Nacl = require("tweetnacl"); var Nacl = require("tweetnacl");
@ -8,15 +9,17 @@ var Nacl = require("tweetnacl");
var Fs = require("fs"); var Fs = require("fs");
var Path = require("path"); var Path = require("path");
var Https = require("https"); var Https = require("https");
const Package = require('./package.json');
var RPC = module.exports; var RPC = module.exports;
var Store = require("./storage/file"); var Store = require("./storage/file");
var DEFAULT_LIMIT = 50 * 1024 * 1024; var DEFAULT_LIMIT = 50 * 1024 * 1024;
var SESSION_EXPIRATION_TIME = 60 * 1000;
var isValidId = function (chan) { var isValidId = function (chan) {
return /^[a-fA-F0-9]/.test(chan) || return chan && chan.length && /^[a-fA-F0-9]/.test(chan) ||
[32, 48].indexOf(chan.length) !== -1; [32, 48].indexOf(chan.length) !== -1;
}; };
@ -75,13 +78,14 @@ var parseCookie = function (cookie) {
}; };
var escapeKeyCharacters = function (key) { var escapeKeyCharacters = function (key) {
return key.replace(/\//g, '-'); return key && key.replace && key.replace(/\//g, '-');
}; };
var unescapeKeyCharacters = function (key) { var unescapeKeyCharacters = function (key) {
return key.replace(/\-/g, '/'); return key.replace(/\-/g, '/');
}; };
// TODO Rename to getSession ?
var beginSession = function (Sessions, key) { var beginSession = function (Sessions, key) {
var safeKey = escapeKeyCharacters(key); var safeKey = escapeKeyCharacters(key);
if (Sessions[safeKey]) { if (Sessions[safeKey]) {
@ -216,6 +220,7 @@ var loadUserPins = function (Env, publicKey, cb) {
var parsed; var parsed;
try { try {
parsed = JSON.parse(msg); parsed = JSON.parse(msg);
session.hasPinned = true;
switch (parsed[0]) { switch (parsed[0]) {
case 'PIN': case 'PIN':
@ -272,7 +277,11 @@ var getUploadSize = function (Env, channel, cb) {
} }
Fs.stat(path, function (err, stats) { Fs.stat(path, function (err, stats) {
if (err) { return void cb(err); } if (err) {
// if a file was deleted, its size is 0 bytes
if (err.code === 'ENOENT') { return cb(void 0, 0); }
return void cb(err);
}
cb(void 0, stats.size); cb(void 0, stats.size);
}); });
}; };
@ -371,6 +380,10 @@ var getHash = function (Env, publicKey, cb) {
// To each key is associated an object containing the 'limit' value and a 'note' explaining that limit // To each key is associated an object containing the 'limit' value and a 'note' explaining that limit
var limits = {}; var limits = {};
var updateLimits = function (config, publicKey, cb) { var updateLimits = function (config, publicKey, cb) {
if (config.adminEmail === false) {
if (config.allowSubscriptions === false) { return; }
throw new Error("allowSubscriptions must be false if adminEmail is false");
}
if (typeof cb !== "function") { cb = function () {}; } if (typeof cb !== "function") { cb = function () {}; }
var defaultLimit = typeof(config.defaultStorageLimit) === 'number'? var defaultLimit = typeof(config.defaultStorageLimit) === 'number'?
@ -382,8 +395,10 @@ var updateLimits = function (config, publicKey, cb) {
} }
var body = JSON.stringify({ var body = JSON.stringify({
domain: config.domain, domain: config.myDomain,
subdomain: config.subdomain subdomain: config.mySubdomain,
adminEmail: config.adminEmail,
version: Package.version
}); });
var options = { var options = {
host: 'accounts.cryptpad.fr', host: 'accounts.cryptpad.fr',
@ -561,7 +576,16 @@ var resetUserPins = function (Env, publicKey, channelList, cb) {
console.error(e); console.error(e);
return void cb(e); return void cb(e);
} }
if (pinSize > free) { return void(cb('E_OVER_LIMIT')); }
/* we want to let people pin, even if they are over their limit,
but they should only be able to do this once.
This prevents data loss in the case that someone registers, but
does not have enough free space to pin their migrated data.
They will not be able to pin additional pads until they upgrade
or delete enough files to go back under their limit. */
if (pinSize > free && session.hasPinned) { return void(cb('E_OVER_LIMIT')); }
pinStore.message(publicKey, JSON.stringify(['RESET', channelList]), pinStore.message(publicKey, JSON.stringify(['RESET', channelList]),
function (e) { function (e) {
if (e) { return void cb(e); } if (e) { return void cb(e); }
@ -617,6 +641,7 @@ var makeFileStream = function (root, id, cb) {
var stream = Fs.createWriteStream(full, { var stream = Fs.createWriteStream(full, {
flags: 'a', flags: 'a',
encoding: 'binary', encoding: 'binary',
highWaterMark: Math.pow(2, 16),
}); });
stream.on('open', function () { stream.on('open', function () {
cb(void 0, stream); cb(void 0, stream);
@ -629,12 +654,15 @@ var makeFileStream = function (root, id, cb) {
var upload = function (Env, publicKey, content, cb) { var upload = function (Env, publicKey, content, cb) {
var paths = Env.paths; var paths = Env.paths;
var dec = new Buffer(Nacl.util.decodeBase64(content)); // jshint ignore:line var dec;
try { dec = Buffer.from(content, 'base64'); }
catch (e) { return void cb(e); }
var len = dec.length; var len = dec.length;
var session = beginSession(Env.Sessions, publicKey); var session = beginSession(Env.Sessions, publicKey);
if (typeof(session.currentUploadSize) !== 'number') { if (typeof(session.currentUploadSize) !== 'number' ||
typeof(session.currentUploadSize) !== 'number') {
// improperly initialized... maybe they didn't check before uploading? // improperly initialized... maybe they didn't check before uploading?
// reject it, just in case // reject it, just in case
return cb('NOT_READY'); return cb('NOT_READY');
@ -662,6 +690,12 @@ var upload = function (Env, publicKey, content, cb) {
var upload_cancel = function (Env, publicKey, cb) { var upload_cancel = function (Env, publicKey, cb) {
var paths = Env.paths; var paths = Env.paths;
var session = beginSession(Env.Sessions, publicKey);
delete session.currentUploadSize;
delete session.pendingUploadSize;
if (session.blobstage) { session.blobstage.close(); }
var path = makeFilePath(paths.staging, publicKey); var path = makeFilePath(paths.staging, publicKey);
if (!path) { if (!path) {
console.log(paths.staging, publicKey); console.log(paths.staging, publicKey);
@ -777,6 +811,24 @@ var upload_status = function (Env, publicKey, filesize, cb) {
}); });
}; };
var isAuthenticatedCall = function (call) {
return [
'COOKIE',
'RESET',
'PIN',
'UNPIN',
'GET_HASH',
'GET_TOTAL_SIZE',
'GET_FILE_SIZE',
'UPDATE_LIMITS',
'GET_LIMIT',
'GET_MULTIPLE_FILE_SIZE',
//'UPLOAD',
'UPLOAD_COMPLETE',
'UPLOAD_CANCEL',
].indexOf(call) !== -1;
};
/*::const ConfigType = require('./config.example.js');*/ /*::const ConfigType = require('./config.example.js');*/
RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)=>void*/) { RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)=>void*/) {
// load pin-store... // load pin-store...
@ -784,7 +836,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
var warn = function (e, output) { var warn = function (e, output) {
if (e && !config.suppressRPCErrors) { if (e && !config.suppressRPCErrors) {
console.error('[' + e + ']', output); console.error(new Date().toISOString() + ' [' + e + ']', output);
} }
}; };
@ -832,7 +884,6 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
beginSession(Sessions, publicKey); beginSession(Sessions, publicKey);
var cookie = msg[0]; var cookie = msg[0];
if (!isValidCookie(Sessions, publicKey, cookie)) { if (!isValidCookie(Sessions, publicKey, cookie)) {
// no cookie is fine if the RPC is to get a cookie // no cookie is fine if the RPC is to get a cookie
if (msg[1] !== 'COOKIE') { if (msg[1] !== 'COOKIE') {
@ -846,8 +897,10 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
return void respond('INVALID_MESSAGE_OR_PUBLIC_KEY'); return void respond('INVALID_MESSAGE_OR_PUBLIC_KEY');
} }
if (checkSignature(serialized, signature, publicKey) !== true) { if (isAuthenticatedCall(msg[1])) {
return void respond("INVALID_SIGNATURE_OR_PUBLIC_KEY"); if (checkSignature(serialized, signature, publicKey) !== true) {
return void respond("INVALID_SIGNATURE_OR_PUBLIC_KEY");
}
} }
var safeKey = escapeKeyCharacters(publicKey); var safeKey = escapeKeyCharacters(publicKey);
@ -855,6 +908,9 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
public key which you provided. public key which you provided.
We can safely modify the state for that key We can safely modify the state for that key
OR it's an unauthenticated call, which must not modify the state
for that key in a meaningful way.
*/ */
// discard validated cookie from message // discard validated cookie from message
@ -908,8 +964,8 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
Respond(e, size); Respond(e, size);
}); });
case 'GET_FILE_SIZE': case 'GET_FILE_SIZE':
return void getFileSize(Env, msg[2], function (e, size) { return void getFileSize(Env, msg[1], function (e, size) {
warn(e, msg[2]); warn(e, msg[1]);
Respond(e, size); Respond(e, size);
}); });
case 'UPDATE_LIMITS': case 'UPDATE_LIMITS':
@ -1017,7 +1073,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
// expire old sessions once per minute // expire old sessions once per minute
setInterval(function () { setInterval(function () {
expireSessions(Sessions); expireSessions(Sessions);
}, 60000); }, SESSION_EXPIRATION_TIME);
}); });
}); });
}); });

@ -34,6 +34,11 @@ var setHeaders = (function () {
const headers = clone(config.httpHeaders); const headers = clone(config.httpHeaders);
if (config.contentSecurity) { if (config.contentSecurity) {
headers['Content-Security-Policy'] = clone(config.contentSecurity); headers['Content-Security-Policy'] = clone(config.contentSecurity);
if (headers['Content-Security-Policy'].indexOf('frame-ancestors') === -1) {
// backward compat for those who do not merge the new version of the config
// when updating. This prevents endless spinner if someone clicks donate.
headers['Content-Security-Policy'] += "frame-ancestors 'self' accounts.cryptpad.fr;";
}
} }
const padHeaders = clone(headers); const padHeaders = clone(headers);
if (config.padContentSecurity) { if (config.padContentSecurity) {
@ -121,6 +126,9 @@ app.get('/api/config', function(req, res){
waitSeconds: 60, waitSeconds: 60,
urlArgs: 'ver=' + Package.version + (DEV_MODE? '-' + (+new Date()): ''), urlArgs: 'ver=' + Package.version + (DEV_MODE? '-' + (+new Date()): ''),
}, },
removeDonateButton: (config.removeDonateButton === true),
allowSubscriptions: (config.allowSubscriptions === true),
websocketPath: config.useExternalWebsocket ? undefined : config.websocketPath, websocketPath: config.useExternalWebsocket ? undefined : config.websocketPath,
websocketURL:'ws' + ((useSecureWebsockets) ? 's' : '') + '://' + host + ':' + websocketURL:'ws' + ((useSecureWebsockets) ? 's' : '') + '://' + host + ':' +
websocketPort + '/cryptpad_websocket', websocketPort + '/cryptpad_websocket',

@ -4,8 +4,9 @@ define([
'/bower_components/textpatcher/TextPatcher.amd.js', '/bower_components/textpatcher/TextPatcher.amd.js',
'json.sortify', 'json.sortify',
'/common/cryptpad-common.js', '/common/cryptpad-common.js',
'/drive/tests.js' '/drive/tests.js',
], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad, Drive) { '/common/test.js'
], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad, Drive, Test) {
window.Hyperjson = Hyperjson; window.Hyperjson = Hyperjson;
window.TextPatcher = TextPatcher; window.TextPatcher = TextPatcher;
window.Sortify = Sortify; window.Sortify = Sortify;
@ -29,6 +30,7 @@ define([
ASSERTS.forEach(function (f, index) { ASSERTS.forEach(function (f, index) {
f(function (err) { f(function (err) {
console.log("test " + index);
done(err, index); done(err, index);
}, index); }, index);
}); });
@ -271,6 +273,12 @@ The test returned:
var $report = $('.report'); var $report = $('.report');
$report.addClass(failed?'failure':'success'); $report.addClass(failed?'failure':'success');
if (failed) {
Test.failed();
} else {
Test.passed();
}
}); });
}); });

@ -1,8 +1,9 @@
define([ define([
'jquery', 'jquery',
'/common/cryptpad-common.js', '/common/cryptpad-common.js',
'/common/test.js',
'/bower_components/tweetnacl/nacl-fast.min.js' '/bower_components/tweetnacl/nacl-fast.min.js'
], function ($, Cryptpad) { ], function ($, Cryptpad, Test) {
var Nacl = window.nacl; var Nacl = window.nacl;
var signMsg = function (msg, privKey) { var signMsg = function (msg, privKey) {
@ -18,8 +19,16 @@ define([
/^http(s)?:\/\/localhost\:/ /^http(s)?:\/\/localhost\:/
]; ];
// Safari is weird about localStorage in iframes but seems to let sessionStorage slide.
localStorage.User_hash = localStorage.User_hash || sessionStorage.User_hash;
Cryptpad.ready(function () { Cryptpad.ready(function () {
console.log('IFRAME READY'); console.log('IFRAME READY');
Test(function () {
// This is only here to maybe trigger an error.
window.drive = Cryptpad.getStore().getProxy().proxy['drive'];
Test.passed();
});
$(window).on("message", function (jqe) { $(window).on("message", function (jqe) {
var evt = jqe.originalEvent; var evt = jqe.originalEvent;
var data = JSON.parse(evt.data); var data = JSON.parse(evt.data);

@ -0,0 +1,74 @@
html,
body {
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
overflow: hidden;
box-sizing: border-box;
position: relative;
}
body {
display: flex;
flex-flow: column;
max-height: 100%;
min-height: auto;
}
.CodeMirror {
display: inline-block;
height: 100%;
width: 50%;
transition: width 500ms, min-width 500ms, max-width 500ms;
min-width: 20%;
max-width: 80%;
resize: horizontal;
}
.CodeMirror.fullPage {
min-width: 100%;
max-width: 100%;
resize: none;
}
.CodeMirror-focused .cm-matchhighlight {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
background-position: bottom;
background-repeat: repeat-x;
}
#editorContainer {
flex: 1;
display: flex;
flex-flow: row;
height: 100%;
overflow: hidden;
}
#previewContainer {
flex: 1;
padding: 5px 20px;
overflow: auto;
display: inline-block;
height: 100%;
border-left: 1px solid black;
box-sizing: border-box;
font-family: Calibri, Ubuntu, sans-serif;
word-wrap: break-word;
}
#preview {
max-width: 40vw;
margin: auto;
}
#preview table {
border-collapse: collapse;
}
#preview table tr th {
border: 3px solid black;
padding: 15px;
}
@media (max-width: 600px) {
.CodeMirror {
flex: 1;
max-width: 100%;
resize: none;
}
#previewContainer {
display: none !important;
}
}

@ -0,0 +1,84 @@
@import "../../customize.dist/src/less/variables.less";
@import "../../customize.dist/src/less/mixins.less";
html, body{
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
overflow: hidden;
box-sizing: border-box;
position: relative;
}
body {
display: flex;
flex-flow: column;
max-height: 100%;
min-height: auto;
}
@slideTime: 500ms;
.CodeMirror {
display: inline-block;
height: 100%;
width: 50%;
transition: width @slideTime, min-width @slideTime, max-width @slideTime;
min-width: 20%;
max-width: 80%;
resize: horizontal;
}
.CodeMirror.fullPage {
min-width: 100%;
max-width: 100%;
resize: none;
}
.CodeMirror-focused .cm-matchhighlight {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
background-position: bottom;
background-repeat: repeat-x;
}
#editorContainer {
flex: 1;
display: flex;
flex-flow: row;
height: 100%;
overflow: hidden;
}
#previewContainer {
flex: 1;
padding: 5px 20px;
overflow: auto;
display: inline-block;
height: 100%;
border-left: 1px solid black;
box-sizing: border-box;
font-family: Calibri,Ubuntu,sans-serif;
word-wrap: break-word;
}
#preview {
max-width: 40vw;
margin: auto;
table {
border-collapse: collapse;
tr {
th {
border: 3px solid black;
padding: 15px;
}
}
}
}
@media (max-width: @media-medium-screen) {
.CodeMirror {
flex: 1;
max-width: 100%;
resize: none;
}
#previewContainer {
display: none !important;
}
}

@ -5,6 +5,7 @@
<meta content="text/html; charset=utf-8" http-equiv="content-type"/> <meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css"> <link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
<meta name="referrer" content="no-referrer" />
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script> <script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
<link rel="icon" type="image/png" <link rel="icon" type="image/png"
href="/customize/main-favicon.png" href="/customize/main-favicon.png"

@ -8,6 +8,7 @@
<link rel="stylesheet" href="/bower_components/codemirror/lib/codemirror.css"> <link rel="stylesheet" href="/bower_components/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="/bower_components/codemirror/addon/dialog/dialog.css"> <link rel="stylesheet" href="/bower_components/codemirror/addon/dialog/dialog.css">
<link rel="stylesheet" href="/bower_components/codemirror/addon/fold/foldgutter.css" /> <link rel="stylesheet" href="/bower_components/codemirror/addon/fold/foldgutter.css" />
<link rel="stylesheet" href="/code/code.css" />
<script src="/bower_components/codemirror/mode/javascript/javascript.js"></script> <script src="/bower_components/codemirror/mode/javascript/javascript.js"></script>
<script src="/bower_components/codemirror/addon/mode/loadmode.js"></script> <script src="/bower_components/codemirror/addon/mode/loadmode.js"></script>
<script src="/bower_components/codemirror/mode/meta.js"></script> <script src="/bower_components/codemirror/mode/meta.js"></script>
@ -31,70 +32,6 @@
<script src="/bower_components/codemirror/addon/fold/markdown-fold.js"></script> <script src="/bower_components/codemirror/addon/fold/markdown-fold.js"></script>
<script src="/bower_components/codemirror/addon/fold/comment-fold.js"></script> <script src="/bower_components/codemirror/addon/fold/comment-fold.js"></script>
<script src="/bower_components/codemirror/addon/display/placeholder.js"></script> <script src="/bower_components/codemirror/addon/display/placeholder.js"></script>
<style>
html, body{
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
overflow: hidden;
box-sizing: border-box;
position: relative;
}
body {
display: flex;
flex-flow: column;
max-height: 100%;
min-height: auto;
}
.CodeMirror {
display: inline-block;
height: 100%;
width: 50%;
min-width: 20%;
max-width: 80%;
resize: horizontal;
}
.CodeMirror.fullPage {
min-width: 100%;
max-width: 100%;
resize: none;
}
.CodeMirror-focused .cm-matchhighlight {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
background-position: bottom;
background-repeat: repeat-x;
}
#editorContainer {
flex: 1;
display: flex;
flex-flow: row;
height: 100%;
overflow: hidden;
}
#previewContainer {
flex: 1;
padding: 5px 20px;
overflow: auto;
display: inline-block;
height: 100%;
border-left: 1px solid black;
box-sizing: border-box;
font-family: Calibri,Ubuntu,sans-serif;
word-wrap: break-word;
}
#preview {
max-width: 40vw;
margin: auto;
}
#preview table tr td, #preview table tr th {
border: 1px solid black;
padding: 15px;
}
#preview table tr th {
border: 3px solid black;
}
</style>
</head> </head>
<body> <body>
<div id="cme_toolbox" class="toolbar-container"></div> <div id="cme_toolbox" class="toolbar-container"></div>

@ -53,6 +53,7 @@ define([
var andThen = function (CMeditor) { var andThen = function (CMeditor) {
var CodeMirror = Cryptpad.createCodemirror(CMeditor, ifrw, Cryptpad); var CodeMirror = Cryptpad.createCodemirror(CMeditor, ifrw, Cryptpad);
$iframe.find('.CodeMirror').addClass('fullPage');
editor = CodeMirror.editor; editor = CodeMirror.editor;
var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox'); var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox');
@ -110,8 +111,16 @@ define([
return stringify(obj); return stringify(obj);
}; };
var forceDrawPreview = function () {
try {
DiffMd.apply(DiffMd.render(editor.getValue()), $preview);
} catch (e) { console.error(e); }
};
var drawPreview = Cryptpad.throttle(function () { var drawPreview = Cryptpad.throttle(function () {
DiffMd.apply(DiffMd.render(editor.getValue()), $preview); if (CodeMirror.highlightMode !== 'markdown') { return; }
if (!$previewContainer.is(':visible')) { return; }
forceDrawPreview();
}, 150); }, 150);
var onLocal = config.onLocal = function () { var onLocal = config.onLocal = function () {
@ -137,8 +146,13 @@ define([
var $codeMirror = $iframe.find('.CodeMirror'); var $codeMirror = $iframe.find('.CodeMirror');
if (mode === "markdown") { if (mode === "markdown") {
APP.$previewButton.show(); APP.$previewButton.show();
$previewContainer.show(); Cryptpad.getPadAttribute('previewMode', function (e, data) {
$codeMirror.removeClass('fullPage'); if (e) { return void console.error(e); }
if (data !== false) {
$previewContainer.show();
$codeMirror.removeClass('fullPage');
}
});
return; return;
} }
APP.$previewButton.hide(); APP.$previewButton.hide();
@ -155,7 +169,7 @@ define([
Metadata = Cryptpad.createMetadata(UserList, Title); Metadata = Cryptpad.createMetadata(UserList, Title);
var configTb = { var configTb = {
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'], displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit', 'upgrade'],
userList: UserList.getToolbarConfig(), userList: UserList.getToolbarConfig(),
share: { share: {
secret: secret, secret: secret,
@ -183,8 +197,8 @@ define([
/* add a history button */ /* add a history button */
var histConfig = { var histConfig = {
onLocal: config.onLocal(), onLocal: config.onLocal,
onRemote: config.onRemote(), onRemote: config.onRemote,
setHistory: setHistory, setHistory: setHistory,
applyVal: function (val) { applyVal: function (val) {
var remoteDoc = JSON.parse(val || '{}').content; var remoteDoc = JSON.parse(val || '{}').content;
@ -235,9 +249,16 @@ define([
} }
$previewContainer.toggle(); $previewContainer.toggle();
if ($previewContainer.is(':visible')) { if ($previewContainer.is(':visible')) {
forceDrawPreview();
$codeMirror.removeClass('fullPage'); $codeMirror.removeClass('fullPage');
Cryptpad.setPadAttribute('previewMode', true, function (e) {
if (e) { return console.log(e); }
});
} else { } else {
$codeMirror.addClass('fullPage'); $codeMirror.addClass('fullPage');
Cryptpad.setPadAttribute('previewMode', false, function (e) {
if (e) { return console.log(e); }
});
} }
}); });
$rightside.append($previewButton); $rightside.append($previewButton);
@ -251,6 +272,7 @@ define([
CodeMirror.configureTheme(); CodeMirror.configureTheme();
} }
// set the hash // set the hash
if (!readOnly) { Cryptpad.replaceHash(editHash); } if (!readOnly) { Cryptpad.replaceHash(editHash); }
}; };
@ -302,6 +324,13 @@ define([
Title.updateTitle(Cryptpad.initialName); Title.updateTitle(Cryptpad.initialName);
} }
Cryptpad.getPadAttribute('previewMode', function (e, data) {
if (e) { return void console.error(e); }
if (data === false && APP.$previewButton) {
APP.$previewButton.click();
}
});
Cryptpad.removeLoadingScreen(); Cryptpad.removeLoadingScreen();
setEditable(true); setEditable(true);
initializing = false; initializing = false;

@ -7,7 +7,9 @@ define([], function () {
// jquery declares itself as literally "jquery" so it cannot be pulled by path :( // jquery declares itself as literally "jquery" so it cannot be pulled by path :(
"jquery": "/bower_components/jquery/dist/jquery.min", "jquery": "/bower_components/jquery/dist/jquery.min",
// json.sortify same // json.sortify same
"json.sortify": "/bower_components/json.sortify/dist/JSON.sortify" "json.sortify": "/bower_components/json.sortify/dist/JSON.sortify",
"pdfjs-dist/build/pdf": "/bower_components/pdfjs-dist/build/pdf",
"pdfjs-dist/build/pdf.worker": "/bower_components/pdfjs-dist/build/pdf.worker"
} }
}); });

@ -194,10 +194,17 @@ define([
}; };
UI.removeLoadingScreen = function (cb) { UI.removeLoadingScreen = function (cb) {
$('#' + LOADING).fadeOut(750, cb); $('#' + LOADING).fadeOut(750, cb);
$('#loadingTip').css('top', ''); var $tip = $('#loadingTip').css('top', '')
window.setTimeout(function () { // loading.less sets transition-delay: $wait-time
$('#loadingTip').fadeOut(750); // and transition: opacity $fadeout-time
}, 3000); .css({
'opacity': 0,
'pointer-events': 'none',
});
setTimeout(function () {
$tip.remove();
}, 3750);
// jquery.fadeout can get stuck
}; };
UI.errorLoadingScreen = function (error, transparent) { UI.errorLoadingScreen = function (error, transparent) {
if (!$('#' + LOADING).is(':visible')) { UI.addLoadingScreen(undefined, true); } if (!$('#' + LOADING).is(':visible')) { UI.addLoadingScreen(undefined, true); }

@ -25,7 +25,10 @@ define([
*/ */
var common = window.Cryptpad = { var common = window.Cryptpad = {
Messages: Messages, Messages: Messages,
Clipboard: Clipboard Clipboard: Clipboard,
donateURL: 'https://accounts.cryptpad.fr/#/donate?on=' + window.location.hostname,
upgradeURL: 'https://accounts.cryptpad.fr/#/?on=' + window.location.hostname,
account: {},
}; };
// constants // constants
@ -216,6 +219,7 @@ define([
userNameKey, userNameKey,
userHashKey, userHashKey,
'loginToken', 'loginToken',
'plan',
].forEach(function (k) { ].forEach(function (k) {
sessionStorage.removeItem(k); sessionStorage.removeItem(k);
localStorage.removeItem(k); localStorage.removeItem(k);
@ -244,6 +248,11 @@ define([
var getUserHash = common.getUserHash = function () { var getUserHash = common.getUserHash = function () {
var hash = localStorage[userHashKey]; var hash = localStorage[userHashKey];
if (['undefined', 'undefined/'].indexOf(hash) !== -1) {
localStorage.removeItem(userHashKey);
return;
}
if (hash) { if (hash) {
var sHash = common.serializeHash(hash); var sHash = common.serializeHash(hash);
if (sHash !== hash) { localStorage[userHashKey] = sHash; } if (sHash !== hash) { localStorage[userHashKey] = sHash; }
@ -575,7 +584,7 @@ define([
var data = makePad(href, name); var data = makePad(href, name);
getStore().pushData(data, function (e, id) { getStore().pushData(data, function (e, id) {
if (e) { if (e) {
if (e === 'E_OVER_LIMIT' && AppConfig.enablePinLimit) { if (e === 'E_OVER_LIMIT') {
common.alert(Messages.pinLimitNotPinned, null, true); common.alert(Messages.pinLimitNotPinned, null, true);
return; return;
} }
@ -741,7 +750,7 @@ define([
}; };
common.isOverPinLimit = function (cb) { common.isOverPinLimit = function (cb) {
if (!common.isLoggedIn() || !AppConfig.enablePinLimit) { return void cb(null, false); } if (!common.isLoggedIn()) { return void cb(null, false); }
var usage; var usage;
var andThen = function (e, limit, plan) { var andThen = function (e, limit, plan) {
if (e) { return void cb(e); } if (e) { return void cb(e); }
@ -775,7 +784,6 @@ define([
}; };
var LIMIT_REFRESH_RATE = 30000; // milliseconds var LIMIT_REFRESH_RATE = 30000; // milliseconds
var limitReachedDisplayed = false;
common.createUsageBar = function (cb, alwaysDisplayUpgrade) { common.createUsageBar = function (cb, alwaysDisplayUpgrade) {
var todo = function (err, state, data) { var todo = function (err, state, data) {
var $container = $('<span>', {'class':'limit-container'}); var $container = $('<span>', {'class':'limit-container'});
@ -797,7 +805,10 @@ define([
var width = Math.floor(Math.min(quota, 1)*200); // the bar is 200px width var width = Math.floor(Math.min(quota, 1)*200); // the bar is 200px width
var $usage = $('<span>', {'class': 'usage'}).css('width', width+'px'); var $usage = $('<span>', {'class': 'usage'}).css('width', width+'px');
if ((quota >= 0.8 || alwaysDisplayUpgrade) && data.plan !== "power") { if (Config.noSubscriptionButton !== true &&
(quota >= 0.8 || alwaysDisplayUpgrade) &&
data.plan !== "power")
{
var origin = encodeURIComponent(window.location.hostname); var origin = encodeURIComponent(window.location.hostname);
var $upgradeLink = $('<a>', { var $upgradeLink = $('<a>', {
href: "https://accounts.cryptpad.fr/#!on=" + origin, href: "https://accounts.cryptpad.fr/#!on=" + origin,
@ -823,13 +834,7 @@ define([
if (quota < 0.8) { $usage.addClass('normal'); } if (quota < 0.8) { $usage.addClass('normal'); }
else if (quota < 1) { $usage.addClass('warning'); } else if (quota < 1) { $usage.addClass('warning'); }
else { else { $usage.addClass('above'); }
$usage.addClass('above');
if (!limitReachedDisplayed) {
limitReachedDisplayed = true;
common.alert(Messages._getKey('pinAboveLimitAlert', [prettyUsage, encodeURIComponent(window.location.hostname)]), null, true);
}
}
var $text = $('<span>', {'class': 'usageText'}); var $text = $('<span>', {'class': 'usageText'});
$text.text(usage + ' / ' + prettyLimit); $text.text(usage + ' / ' + prettyLimit);
$limit.append($usage).append($text); $limit.append($usage).append($text);
@ -1405,6 +1410,14 @@ define([
console.log('RPC handshake complete'); console.log('RPC handshake complete');
rpc = common.rpc = env.rpc = call; rpc = common.rpc = env.rpc = call;
common.getPinLimit(function (e, limit, plan, note) {
if (e) { return void console.error(e); }
common.account.limit = limit;
localStorage.plan = common.account.plan = plan;
common.account.note = note;
cb();
});
common.arePinsSynced(function (err, yes) { common.arePinsSynced(function (err, yes) {
if (!yes) { if (!yes) {
common.resetPins(function (err) { common.resetPins(function (err) {
@ -1413,7 +1426,6 @@ define([
}); });
} }
}); });
cb();
}); });
} else if (PINNING_ENABLED) { } else if (PINNING_ENABLED) {
console.log('not logged in. pads will not be pinned'); console.log('not logged in. pads will not be pinned');

@ -170,6 +170,10 @@ define([
proxy[tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); proxy[tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
} }
// copy User_hash into sessionStorage because cross-domain iframes
// on safari replaces localStorage with sessionStorage or something
if (sessionStorage) { sessionStorage.setItem('User_hash', localStorage.getItem('User_hash')); }
var localToken = tryParsing(localStorage.getItem(tokenKey)); var localToken = tryParsing(localStorage.getItem(tokenKey));
if (localToken === null) { if (localToken === null) {
// if that number hasn't been set to localStorage, do so. // if that number hasn't been set to localStorage, do so.

File diff suppressed because one or more lines are too long

@ -92,8 +92,8 @@ define([
exp.getFileSize = function (file, cb) { exp.getFileSize = function (file, cb) {
rpc.send('GET_FILE_SIZE', file, function (e, response) { rpc.send('GET_FILE_SIZE', file, function (e, response) {
if (e) { return void cb(e); } if (e) { return void cb(e); }
if (response && response.length) { if (response && response.length && typeof(response[0]) === 'number') {
cb(void 0, response[0]); return void cb(void 0, response[0]);
} else { } else {
cb('INVALID_RESPONSE'); cb('INVALID_RESPONSE');
} }

@ -27,7 +27,11 @@ types of messages:
var hkn = network.historyKeeper; var hkn = network.historyKeeper;
var txid = uid(); var txid = uid();
ctx.pending[txid] = cb; var pending = ctx.pending[txid] = function (err, response) {
cb(err, response);
};
pending.data = data;
pending.called = 0;
return network.sendto(hkn, JSON.stringify([txid, data])); return network.sendto(hkn, JSON.stringify([txid, data]));
}; };
@ -60,7 +64,22 @@ types of messages:
if (typeof(pending) === 'function') { if (typeof(pending) === 'function') {
if (parsed[1] === 'ERROR') { if (parsed[1] === 'ERROR') {
return void pending(parsed[2]); if (parsed[2] === 'NO_COOKIE') {
return void ctx.send('COOKIE', "", function (e) {
if (e) {
console.error(e);
return void pending(e);
}
// resend the same command again
// give up if you've already tried resending
if (ctx.resend(txid)) { delete ctx.pending[txid]; }
});
}
pending(parsed[2]);
delete ctx.pending[txid];
return;
} else { } else {
// update the cookie // update the cookie
if (/\|/.test(cookie)) { if (/\|/.test(cookie)) {
@ -70,8 +89,13 @@ types of messages:
} }
} }
pending(void 0, response); pending(void 0, response);
// if successful, delete the callback...
delete ctx.pending[txid];
}
else {
console.log("received message for txid with no callback");
} }
//else { console.log("No callback provided"); }
}; };
var create = function (network, edPrivateKey, edPublicKey, cb) { var create = function (network, edPrivateKey, edPublicKey, cb) {
@ -104,7 +128,7 @@ types of messages:
connected: true, connected: true,
}; };
var send = function (type, msg, cb) { var send = ctx.send = function (type, msg, cb) {
if (!ctx.connected && type !== 'COOKIE') { if (!ctx.connected && type !== 'COOKIE') {
return void window.setTimeout(function () { return void window.setTimeout(function () {
cb('DISCONNECTED'); cb('DISCONNECTED');
@ -129,6 +153,44 @@ types of messages:
return sendMsg(ctx, data, cb); return sendMsg(ctx, data, cb);
}; };
ctx.resend = function (txid) {
var pending = ctx.pending[txid];
if (pending.called) {
console.error("[%s] called too many times", txid);
return true;
}
pending.called++;
// update the cookie and signature...
pending.data[2] = ctx.cookie;
pending.data[0] = signMsg(pending.data.slice(2), signKey);
try {
return ctx.network.sendto(ctx.network.historyKeeper,
JSON.stringify([txid, pending.data]));
} catch (e) {
console.log("failed to resend");
console.error(e);
}
};
send.unauthenticated = function (type, msg, cb) {
if (!ctx.connected) {
return void window.setTimeout(function () {
cb('DISCONNECTED');
});
}
// construct an unsigned message
var data = [null, edPublicKey, null, type, msg];
if (ctx.cookie && ctx.cookie.join) {
data[2] = ctx.cookie.join('|');
} else {
data[2] = ctx.cookie;
}
return sendMsg(ctx, data, cb);
};
network.on('message', function (msg) { network.on('message', function (msg) {
onMsg(ctx, msg); onMsg(ctx, msg);
}); });

@ -0,0 +1,75 @@
define([], function () {
var out = function () { };
out.passed = out.failed = out;
if (window.location.hash.indexOf("?test=test") > -1) {
var cpt = window.__CRYPTPAD_TEST__ = {
data: [],
getData: function () {
var data = JSON.stringify(cpt.data);
cpt.data = [];
return data;
}
};
// jshint -W103
var errProto = (new Error()).__proto__;
var doLog = function (o) {
var s;
if (typeof(o) === 'object' && o.__proto__ === errProto) {
s = JSON.stringify([ o.message, o.stack ]);
} else if (typeof(s) !== 'string') {
try {
s = JSON.stringify(o);
} catch (e) {
s = String(o);
}
}
var out = [s];
try { throw new Error(); } catch (e) { out.push(e.stack.split('\n')[3]); }
cpt.data.push({ type: 'log', val: out.join('') });
};
window.console._error = window.console.error;
window.console._log = window.console.log;
window.console.error = function (e) { window.console._error(e); doLog(e); };
window.console.log = function (l) { window.console._log(l); doLog(l); };
window.onerror = function (msg, url, lineNo, columnNo, e) {
cpt.data.push({
type: 'report',
val: 'failed',
error: {
message: e ? e.message : msg,
stack: e ? e.stack : (url + ":" + lineNo)
}
});
};
require.onError = function (e) {
cpt.data.push({
type: 'report',
val: 'failed',
error: { message: e.message, stack: e.stack }
});
};
out = function (f) { f(); };
out.testing = true;
out.passed = function () {
cpt.data.push({
type: 'report',
val: 'passed'
});
};
out.failed = function (reason) {
var e;
try { throw new Error(reason); } catch (err) { e = err; }
cpt.data.push({
type: 'report',
val: 'failed',
error: { message: e.message, stack: e.stack }
});
};
} else {
out.testing = false;
}
return out;
});

@ -500,8 +500,12 @@ define([
var todo = function (e, overLimit) { var todo = function (e, overLimit) {
if (e) { return void console.error("Unable to get the pinned usage"); } if (e) { return void console.error("Unable to get the pinned usage"); }
if (overLimit) { if (overLimit) {
var message = Messages.pinLimitReachedAlert;
if (ApiConfig.noSubscriptionButton === true) {
message = Messages.pinLimitReachedAlertNoAccounts;
}
$limit.show().click(function () { $limit.show().click(function () {
Cryptpad.alert(Messages.pinLimitReachedAlert, null, true); Cryptpad.alert(message, null, true);
}); });
} }
}; };

@ -10,7 +10,7 @@ define([
constants: {}, constants: {},
}; };
var SPINNER_DISAPPEAR_TIME = 3000; var SPINNER_DISAPPEAR_TIME = 1000;
// Toolbar parts // Toolbar parts
var TOOLBAR_CLS = Bar.constants.toolbar = 'cryptpad-toolbar'; var TOOLBAR_CLS = Bar.constants.toolbar = 'cryptpad-toolbar';
@ -33,6 +33,7 @@ define([
var LIMIT_CLS = Bar.constants.lag = 'cryptpad-limit'; var LIMIT_CLS = Bar.constants.lag = 'cryptpad-limit';
var TITLE_CLS = Bar.constants.title = "cryptpad-title"; var TITLE_CLS = Bar.constants.title = "cryptpad-title";
var NEWPAD_CLS = Bar.constants.newpad = "cryptpad-newpad"; var NEWPAD_CLS = Bar.constants.newpad = "cryptpad-newpad";
var UPGRADE_CLS = Bar.constants.upgrade = "cryptpad-upgrade";
// User admin menu // User admin menu
var USERADMIN_CLS = Bar.constants.user = 'cryptpad-user-dropdown'; var USERADMIN_CLS = Bar.constants.user = 'cryptpad-user-dropdown';
@ -70,6 +71,7 @@ define([
var $userContainer = $('<span>', { var $userContainer = $('<span>', {
'class': USER_CLS 'class': USER_CLS
}).appendTo($topContainer); }).appendTo($topContainer);
$('<button>', {'class': UPGRADE_CLS + ' buttonSuccess'}).hide().appendTo($userContainer);
$('<span>', {'class': SPINNER_CLS}).hide().appendTo($userContainer); $('<span>', {'class': SPINNER_CLS}).hide().appendTo($userContainer);
$('<span>', {'class': STATE_CLS}).hide().appendTo($userContainer); $('<span>', {'class': STATE_CLS}).hide().appendTo($userContainer);
$('<span>', {'class': LAG_CLS}).hide().appendTo($userContainer); $('<span>', {'class': LAG_CLS}).hide().appendTo($userContainer);
@ -595,7 +597,6 @@ define([
'class': 'synced fa fa-check', 'class': 'synced fa fa-check',
title: Messages.synced title: Messages.synced
}).appendTo($spin); }).appendTo($spin);
toolbar.$userAdmin.prepend($spin);
if (config.realtime) { if (config.realtime) {
config.realtime.onPatch(ks(toolbar, config)); config.realtime.onPatch(ks(toolbar, config));
config.realtime.onMessage(ks(toolbar, config, true)); config.realtime.onMessage(ks(toolbar, config, true));
@ -616,8 +617,12 @@ define([
var todo = function (e, overLimit) { var todo = function (e, overLimit) {
if (e) { return void console.error("Unable to get the pinned usage"); } if (e) { return void console.error("Unable to get the pinned usage"); }
if (overLimit) { if (overLimit) {
var key = 'pinLimitReachedAlert';
if (ApiConfig.noSubscriptionButton === true) {
key = 'pinLimitReachedAlertNoAccounts';
}
$limit.show().click(function () { $limit.show().click(function () {
Cryptpad.alert(Messages._getKey('pinLimitReachedAlert', [encodeURIComponent(window.location.hostname)]), null, true); Cryptpad.alert(Messages._getKey(key, [encodeURIComponent(window.location.hostname)]), null, true);
}); });
} }
}; };
@ -631,6 +636,8 @@ define([
var pads_options = []; var pads_options = [];
Config.availablePadTypes.forEach(function (p) { Config.availablePadTypes.forEach(function (p) {
if (p === 'drive') { return; } if (p === 'drive') { return; }
if (!Cryptpad.isLoggedIn() && Config.registeredOnlyTypes &&
Config.registeredOnlyTypes.indexOf(p) !== -1) { return; }
pads_options.push({ pads_options.push({
tag: 'a', tag: 'a',
attributes: { attributes: {
@ -692,6 +699,33 @@ define([
return $userAdmin; return $userAdmin;
}; };
var createUpgrade = function (toolbar) {
if (ApiConfig.removeDonateButton) { return; }
if (Cryptpad.account.plan) { return; }
var text;
var feedback;
var url;
if (ApiConfig.allowSubscriptions && Cryptpad.isLoggedIn()) {
text = Messages.upgradeAccount;
feedback = "UPGRADE_ACCOUNT";
url = Cryptpad.upgradeURL;
} else {
text = Messages.supportCryptpad;
feedback = "SUPPORT_CRYPTPAD";
url = Cryptpad.donateURL;
}
var $upgrade = toolbar.$top.find('.' + UPGRADE_CLS).attr({
'title': Messages.supportCryptpad
}).text(text).show()
.click(function () {
Cryptpad.feedback(feedback);
window.open(url,'_blank');
});
return $upgrade;
};
// Events // Events
var initClickEvents = function (toolbar, config) { var initClickEvents = function (toolbar, config) {
var removeDropdowns = function () { var removeDropdowns = function () {
@ -849,10 +883,10 @@ define([
tb['spinner'] = createSpinner; tb['spinner'] = createSpinner;
tb['state'] = createState; tb['state'] = createState;
tb['limit'] = createLimit; tb['limit'] = createLimit;
tb['upgrade'] = createUpgrade;
tb['newpad'] = createNewPad; tb['newpad'] = createNewPad;
tb['useradmin'] = createUserAdmin; tb['useradmin'] = createUserAdmin;
var addElement = toolbar.addElement = function (arr, additionnalCfg, init) { var addElement = toolbar.addElement = function (arr, additionnalCfg, init) {
if (typeof additionnalCfg === "object") { $.extend(true, config, additionnalCfg); } if (typeof additionnalCfg === "object") { $.extend(true, config, additionnalCfg); }
arr.forEach(function (el) { arr.forEach(function (el) {

@ -1373,6 +1373,10 @@ define([
} }
AppConfig.availablePadTypes.forEach(function (type) { AppConfig.availablePadTypes.forEach(function (type) {
if (type === 'drive') { return; } if (type === 'drive') { return; }
if (!Cryptpad.isLoggedIn() && AppConfig.registeredOnlyTypes &&
AppConfig.registeredOnlyTypes.indexOf(type) !== -1) {
return;
}
var attributes = { var attributes = {
'class': 'newdoc', 'class': 'newdoc',
'data-type': type, 'data-type': type,
@ -2674,13 +2678,11 @@ define([
} }
/* add the usage */ /* add the usage */
if (AppConfig.enablePinLimit) { Cryptpad.createUsageBar(function (err, $limitContainer) {
Cryptpad.createUsageBar(function (err, $limitContainer) { if (err) { return void logError(err); }
if (err) { return void logError(err); } $leftside.html('');
$leftside.html(''); $leftside.append($limitContainer);
$leftside.append($limitContainer); }, true);
});
}
/* add a history button */ /* add a history button */
var histConfig = { var histConfig = {

@ -50,8 +50,18 @@ define([
if (queue.inProgress) { return; } if (queue.inProgress) { return; }
queue.inProgress = true; queue.inProgress = true;
var $cancelCell = $table.find('tr[id="'+id+'"]').find('.upCancel'); var $row = $table.find('tr[id="'+id+'"]');
$cancelCell.html('-');
$row.find('.upCancel').html('-');
var $pv = $row.find('.progressValue');
var $pb = $row.find('.progressContainer');
var updateProgress = function (progressValue) {
$pv.text(Math.round(progressValue*100)/100 + '%');
$pb.css({
width: (progressValue/100)*188+'px'
});
};
var u8 = new Uint8Array(blob); var u8 = new Uint8Array(blob);
@ -59,13 +69,10 @@ define([
var next = FileCrypto.encrypt(u8, metadata, key); var next = FileCrypto.encrypt(u8, metadata, key);
var estimate = FileCrypto.computeEncryptedSize(blob.byteLength, metadata); var estimate = FileCrypto.computeEncryptedSize(blob.byteLength, metadata);
var chunks = [];
var sendChunk = function (box, cb) { var sendChunk = function (box, cb) {
var enc = Nacl.util.encodeBase64(box); var enc = Nacl.util.encodeBase64(box);
Cryptpad.rpc.send.unauthenticated('UPLOAD', enc, function (e, msg) {
chunks.push(box);
Cryptpad.rpc.send('UPLOAD', enc, function (e, msg) {
console.log(box); console.log(box);
cb(e, msg); cb(e, msg);
}); });
@ -77,16 +84,10 @@ define([
if (box) { if (box) {
actual += box.length; actual += box.length;
var progressValue = (actual / estimate * 100); var progressValue = (actual / estimate * 100);
updateProgress(progressValue);
return void sendChunk(box, function (e) { return void sendChunk(box, function (e) {
if (e) { return console.error(e); } if (e) { return console.error(e); }
var $pv = $table.find('tr[id="'+id+'"]').find('.progressValue');
$pv.text(Math.round(progressValue*100)/100 + '%');
var $pb = $table.find('tr[id="'+id+'"]').find('.progressContainer');
$pb.css({
width: (progressValue/100)*188+'px'
});
next(again); next(again);
}); });
} }
@ -222,7 +223,7 @@ define([
Title = Cryptpad.createTitle({}, function(){}, Cryptpad); Title = Cryptpad.createTitle({}, function(){}, Cryptpad);
var displayed = ['title', 'useradmin', 'newpad', 'limit']; var displayed = ['title', 'useradmin', 'newpad', 'limit', 'upgrade'];
if (secret && hexFileName) { if (secret && hexFileName) {
displayed.push('fileshare'); displayed.push('fileshare');
} }
@ -301,7 +302,12 @@ define([
} }
if (!Cryptpad.isLoggedIn()) { if (!Cryptpad.isLoggedIn()) {
return Cryptpad.alert("You must be logged in to upload files"); return Cryptpad.alert(Messages.upload_mustLogin, function () {
if (sessionStorage) {
sessionStorage.redirectTo = window.location.href;
}
window.location.href = '/login/';
});
} }
$form.css({ $form.css({

@ -62,6 +62,10 @@
<input type="text" id="name" name="name" class="form-control" data-localization-placeholder="login_username" autofocus> <input type="text" id="name" name="name" class="form-control" data-localization-placeholder="login_username" autofocus>
<input type="password" id="password" name="password" class="form-control" data-localization-placeholder="login_password"> <input type="password" id="password" name="password" class="form-control" data-localization-placeholder="login_password">
<button class="btn btn-primary login first" data-localization="login_login"></button> <button class="btn btn-primary login first" data-localization="login_login"></button>
<div class="extra">
<p data-localization="login_notRegistered"></p>
<button id="register" class="btn btn-success register first" data-localization="login_register"></button>
</div>
</div> </div>
</div> </div>
</div> </div>

@ -128,5 +128,16 @@ define([
}, 0); }, 0);
}, 100); }, 100);
}); });
$('#register').on('click', function () {
if (sessionStorage) {
if ($uname.val()) {
sessionStorage.login_user = $uname.val();
}
if ($passwd.val()) {
sessionStorage.login_pass = $passwd.val();
}
}
window.location.href = '/register/';
});
}); });
}); });

@ -6,6 +6,8 @@ define([
'/common/cryptpad-common.js', '/common/cryptpad-common.js',
//'/common/visible.js', //'/common/visible.js',
//'/common/notify.js', //'/common/notify.js',
'pdfjs-dist/build/pdf',
'pdfjs-dist/build/pdf.worker',
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
'/bower_components/file-saver/FileSaver.min.js', '/bower_components/file-saver/FileSaver.min.js',
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad /*, Visible, Notify*/) { ], function ($, Crypto, realtimeInput, Toolbar, Cryptpad /*, Visible, Notify*/) {
@ -28,7 +30,7 @@ define([
var cryptKey = secret.keys && secret.keys.fileKeyStr; var cryptKey = secret.keys && secret.keys.fileKeyStr;
var fileId = secret.channel; var fileId = secret.channel;
var hexFileName = Cryptpad.base64ToHex(fileId); var hexFileName = Cryptpad.base64ToHex(fileId);
var type = "image/png"; // var type = "image/png";
var parsed = Cryptpad.parsePadUrl(window.location.href); var parsed = Cryptpad.parsePadUrl(window.location.href);
var defaultName = Cryptpad.getDefaultName(parsed); var defaultName = Cryptpad.getDefaultName(parsed);
@ -57,7 +59,7 @@ define([
var $mt = $iframe.find('#encryptedFile'); var $mt = $iframe.find('#encryptedFile');
$mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName); $mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName);
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey); $mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
$mt.attr('data-type', type); // $mt.attr('data-type', type);
$(window.document).on('decryption', function (e) { $(window.document).on('decryption', function (e) {
var decrypted = e.originalEvent; var decrypted = e.originalEvent;
@ -98,6 +100,30 @@ define([
updateTitle(Cryptpad.initialName || getTitle() || defaultName); updateTitle(Cryptpad.initialName || getTitle() || defaultName);
/**
* Allowed mime types that have to be set for a rendering after a decryption.
*
* @type {Array}
*/
var allowedMediaTypes = [
'image/png',
'image/jpeg',
'image/jpg',
'image/gif',
'audio/mp3',
'audio/ogg',
'audio/wav',
'audio/webm',
'video/mp4',
'video/ogg',
'video/webm',
'application/pdf',
'application/dash+xml',
'download'
];
MediaTag.CryptoFilter.setAllowedMediaTypes(allowedMediaTypes);
MediaTag($mt[0]); MediaTag($mt[0]);
Cryptpad.removeLoadingScreen(); Cryptpad.removeLoadingScreen();

@ -4,6 +4,7 @@
<title>CryptPad</title> <title>CryptPad</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/> <meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css"> <link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script> <script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
<link rel="icon" type="image/png" <link rel="icon" type="image/png"

@ -453,7 +453,7 @@ define([
Metadata = Cryptpad.createMetadata(UserList, Title); Metadata = Cryptpad.createMetadata(UserList, Title);
var configTb = { var configTb = {
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'], displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit', 'upgrade'],
userList: UserList.getToolbarConfig(), userList: UserList.getToolbarConfig(),
share: { share: {
secret: secret, secret: secret,
@ -500,8 +500,8 @@ define([
/* add a history button */ /* add a history button */
var histConfig = { var histConfig = {
onLocal: realtimeOptions.onLocal(), onLocal: realtimeOptions.onLocal,
onRemote: realtimeOptions.onRemote(), onRemote: realtimeOptions.onRemote,
setHistory: setHistory, setHistory: setHistory,
applyVal: function (val) { applyHjson(val || '["BODY",{},[]]'); }, applyVal: function (val) { applyHjson(val || '["BODY",{},[]]'); },
$toolbar: $bar $toolbar: $bar

@ -15,8 +15,6 @@ define([
$(function () { $(function () {
var unlockHTML = '<i class="fa fa-unlock" aria-hidden="true"></i>';
var lockHTML = '<i class="fa fa-lock" aria-hidden="true"></i>';
var HIDE_INTRODUCTION_TEXT = "hide_poll_text"; var HIDE_INTRODUCTION_TEXT = "hide_poll_text";
var defaultName; var defaultName;
@ -100,12 +98,10 @@ define([
// Enable the checkboxes for the user's column (committed or not) // Enable the checkboxes for the user's column (committed or not)
$('input[disabled="disabled"][data-rt-id^="' + id + '"]').removeAttr('disabled'); $('input[disabled="disabled"][data-rt-id^="' + id + '"]').removeAttr('disabled');
$('input[type="checkbox"][data-rt-id^="' + id + '"]').addClass('enabled'); $('input[type="checkbox"][data-rt-id^="' + id + '"]').addClass('enabled');
$('[data-rt-id="' + id + '"] ~ .edit').css('visibility', 'hidden'); $('.lock[data-rt-id="' + id + '"]').addClass('fa-unlock').removeClass('fa-lock').attr('title', Messages.poll_unlocked);
$('.lock[data-rt-id="' + id + '"]').html(unlockHTML);
if (isOwnColumnCommitted()) { return; } if (isOwnColumnCommitted()) { return; }
$('[data-rt-id^="' + id + '"]').closest('td').addClass("uncommitted"); $('[data-rt-id^="' + id + '"]').closest('td').addClass("uncommitted");
$('td.uncommitted .remove, td.uncommitted .edit').css('visibility', 'hidden');
$('td.uncommitted .cover').addClass("uncommitted"); $('td.uncommitted .cover').addClass("uncommitted");
$('.uncommitted input[type="text"]').attr("placeholder", Messages.poll_userPlaceholder); $('.uncommitted input[type="text"]').attr("placeholder", Messages.poll_userPlaceholder);
}; };
@ -118,8 +114,7 @@ define([
APP.editable.col.forEach(function (id) { APP.editable.col.forEach(function (id) {
$('input[disabled="disabled"][data-rt-id^="' + id + '"]').removeAttr('disabled'); $('input[disabled="disabled"][data-rt-id^="' + id + '"]').removeAttr('disabled');
$('input[type="checkbox"][data-rt-id^="' + id + '"]').addClass('enabled'); $('input[type="checkbox"][data-rt-id^="' + id + '"]').addClass('enabled');
$('span.edit[data-rt-id="' + id + '"]').css('visibility', 'hidden'); $('.lock[data-rt-id="' + id + '"]').addClass('fa-unlock').removeClass('fa-lock').attr('title', Messages.poll_unlocked);
$('.lock[data-rt-id="' + id + '"]').html(unlockHTML);
}); });
}; };
@ -276,7 +271,6 @@ define([
switch (type) { switch (type) {
case 'text': case 'text':
debug("text[rt-id='%s'] [%s]", id, input.value); debug("text[rt-id='%s'] [%s]", id, input.value);
if (!input.value) { return void debug("Hit enter?"); }
Render.setValue(object, id, input.value); Render.setValue(object, id, input.value);
change(null, null, null, 50); change(null, null, null, 50);
break; break;
@ -295,12 +289,26 @@ define([
} }
}; };
var hideInputs = function (target, isKeyup) {
if (!isKeyup && $(target).is('[type="text"]')) {
return;
}
$('.lock[data-rt-id!="' + APP.userid + '"]').addClass('fa-lock').removeClass('fa-unlock').attr('title', Messages.poll_locked);
var $cells = APP.$table.find('thead td:not(.uncommitted), tbody td');
$cells.find('[type="text"][data-rt-id!="' + APP.userid + '"]').attr('disabled', true);
$('.edit[data-rt-id!="' + APP.userid + '"]').css('visibility', 'visible');
APP.editable.col = [APP.userid];
APP.editable.row = [];
};
/* Called whenever an event is fired on a span */ /* Called whenever an event is fired on a span */
var handleSpan = function (span) { var handleSpan = function (span) {
var id = span.getAttribute('data-rt-id'); var id = span.getAttribute('data-rt-id');
var type = Render.typeofId(id); var type = Render.typeofId(id);
var isRemove = span.className && span.className.split(' ').indexOf('remove') !== -1; var isRemove = span.className && span.className.split(' ').indexOf('remove') !== -1;
var isEdit = span.className && span.className.split(' ').indexOf('edit') !== -1; var isEdit = span.className && span.className.split(' ').indexOf('edit') !== -1;
var isLock = span.className && span.className.split(' ').indexOf('lock') !== -1;
var isLocked = span.className && span.className.split(' ').indexOf('fa-lock') !== -1;
if (type === 'row') { if (type === 'row') {
if (isRemove) { if (isRemove) {
Cryptpad.confirm(Messages.poll_removeOption, function (res) { Cryptpad.confirm(Messages.poll_removeOption, function (res) {
@ -310,6 +318,7 @@ define([
}); });
}); });
} else if (isEdit) { } else if (isEdit) {
hideInputs(span);
unlockRow(id, function () { unlockRow(id, function () {
change(null, null, null, null, function() { change(null, null, null, null, function() {
$('input[data-rt-id="' + id + '"]').focus(); $('input[data-rt-id="' + id + '"]').focus();
@ -324,7 +333,8 @@ define([
change(); change();
}); });
}); });
} else if (isEdit) { } else if (isLock && isLocked) {
hideInputs(span);
unlockColumn(id, function () { unlockColumn(id, function () {
change(null, null, null, null, function() { change(null, null, null, null, function() {
$('input[data-rt-id="' + id + '"]').focus(); $('input[data-rt-id="' + id + '"]').focus();
@ -338,48 +348,34 @@ define([
} }
}; };
var hideInputs = function (e, isKeyup) {
if (!isKeyup && $(e.target).is('[type="text"]')) {
return;
}
$('.lock[data-rt-id!="' + APP.userid + '"]').html(lockHTML);
var $cells = APP.$table.find('thead td:not(.uncommitted), tbody td');
$cells.find('[type="text"][data-rt-id!="' + APP.userid + '"]').attr('disabled', true);
$('.edit[data-rt-id!="' + APP.userid + '"]').css('visibility', 'visible');
APP.editable.col = [APP.userid];
APP.editable.row = [];
};
$(window).click(hideInputs);
var handleClick = function (e, isKeyup) { var handleClick = function (e, isKeyup) {
e.stopPropagation(); e.stopPropagation();
if (!APP.ready) { return; } if (!APP.ready) { return; }
var target = e && e.target; var target = e && e.target;
if (isKeyup) {
debug("Keyup!");
}
if (!target) { return void debug("NO TARGET"); } if (!target) { return void debug("NO TARGET"); }
var nodeName = target && target.nodeName; var nodeName = target && target.nodeName;
var shouldLock = $(target).hasClass('fa-unlock');
if (!$(target).parents('#table tbody').length || $(target).hasClass('edit')) { if ((!$(target).parents('#table tbody').length && $(target).hasClass('lock'))) {
hideInputs(e); hideInputs(e);
} }
switch (nodeName) { switch (nodeName) {
case 'INPUT': case 'INPUT':
if (isKeyup && (e.keyCode === 13 || e.keyCode === 27)) { if (isKeyup && (e.keyCode === 13 || e.keyCode === 27)) {
hideInputs(e, isKeyup); hideInputs(target, isKeyup);
return; break;
} }
handleInput(target); handleInput(target);
break; break;
case 'SPAN': case 'SPAN':
//case 'LABEL': //case 'LABEL':
if (shouldLock) {
break;
}
handleSpan(target); handleSpan(target);
break; break;
case undefined: case undefined:
@ -459,7 +455,6 @@ var ready = function (info, userid, readOnly) {
var $table = APP.$table = $(Render.asHTML(displayedObj, null, colsOrder, readOnly)); var $table = APP.$table = $(Render.asHTML(displayedObj, null, colsOrder, readOnly));
APP.$createRow = $('#create-option').click(function () { APP.$createRow = $('#create-option').click(function () {
//console.error("BUTTON CLICKED! LOL");
Render.createRow(proxy, function (empty, id) { Render.createRow(proxy, function (empty, id) {
change(null, null, null, null, function() { change(null, null, null, null, function() {
$('.edit[data-rt-id="' + id + '"]').click(); $('.edit[data-rt-id="' + id + '"]').click();
@ -470,7 +465,7 @@ var ready = function (info, userid, readOnly) {
APP.$createCol = $('#create-user').click(function () { APP.$createCol = $('#create-user').click(function () {
Render.createColumn(proxy, function (empty, id) { Render.createColumn(proxy, function (empty, id) {
change(null, null, null, null, function() { change(null, null, null, null, function() {
$('.edit[data-rt-id="' + id + '"]').click(); $('.lock[data-rt-id="' + id + '"]').click();
}); });
}); });
}); });
@ -532,6 +527,8 @@ var ready = function (info, userid, readOnly) {
.click(handleClick) .click(handleClick)
.on('keyup', function (e) { handleClick(e, true); }); .on('keyup', function (e) { handleClick(e, true); });
$(window).click(hideInputs);
proxy proxy
.on('change', ['info'], function (o, n, p) { .on('change', ['info'], function (o, n, p) {
if (p[1] === 'title') { if (p[1] === 'title') {
@ -612,7 +609,7 @@ var create = function (info) {
Title = Cryptpad.createTitle({}, onLocalTitle, Cryptpad); Title = Cryptpad.createTitle({}, onLocalTitle, Cryptpad);
var configTb = { var configTb = {
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'], displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit', 'upgrade'],
userList: UserList.getToolbarConfig(), userList: UserList.getToolbarConfig(),
share: { share: {
secret: secret, secret: secret,

@ -32,6 +32,9 @@ textarea[disabled] {
font: white; font: white;
border: 0px; border: 0px;
} }
input[type="text"]::placeholder {
color: #666;
}
table#table { table#table {
margin: 0px; margin: 0px;
} }
@ -66,7 +69,7 @@ table#table {
#tableScroll { #tableScroll {
overflow-y: hidden; overflow-y: hidden;
overflow-x: auto; overflow-x: auto;
margin-left: calc(30% - 50px + 29px); margin-left: calc(30% - 50px + 31px);
max-width: 70%; max-width: 70%;
width: auto; width: auto;
display: inline-block; display: inline-block;
@ -104,6 +107,9 @@ table {
tbody { tbody {
border: 1px solid #555; border: 1px solid #555;
} }
tbody * {
box-sizing: border-box;
}
tbody tr { tbody tr {
text-align: center; text-align: center;
} }
@ -260,23 +266,37 @@ div.realtime table input[type="text"] {
border: 1px solid #fff; border: 1px solid #fff;
width: 80%; width: 80%;
} }
form.realtime table span,
div.realtime table span {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
form.realtime table thead td, form.realtime table thead td,
div.realtime table thead td { div.realtime table thead td {
padding: 0px 5px; padding: 0px 5px;
background: #aaa; background: #aaa;
border-radius: 20px 20px 0 0; border-radius: 20px 20px 0 0;
text-align: center; }
form.realtime table thead td:nth-of-type(2),
div.realtime table thead td:nth-of-type(2) {
background: #999;
}
form.realtime table thead td:nth-of-type(2) .lock,
div.realtime table thead td:nth-of-type(2) .lock {
cursor: default;
} }
form.realtime table thead td input[type="text"], form.realtime table thead td input[type="text"],
div.realtime table thead td input[type="text"] { div.realtime table thead td input[type="text"] {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 1px 5px;
} }
form.realtime table thead td input[type="text"][disabled], form.realtime table thead td input[type="text"][disabled],
div.realtime table thead td input[type="text"][disabled] { div.realtime table thead td input[type="text"][disabled] {
color: #000; color: #000;
padding: 1px 5px; border: 1px solid transparent;
border: none;
} }
form.realtime table tbody .text-cell, form.realtime table tbody .text-cell,
div.realtime table tbody .text-cell { div.realtime table tbody .text-cell {
@ -296,9 +316,9 @@ div.realtime table tbody .text-cell .remove {
float: left; float: left;
margin: 0 0 0 10px; margin: 0 0 0 10px;
} }
form.realtime table tbody td label, form.realtime table tbody tr:not(:first-child) td:not(:first-child) label,
div.realtime table tbody td label { div.realtime table tbody tr:not(:first-child) td:not(:first-child) label {
border: 0.5px solid #555; border-top: 1px solid #555;
} }
form.realtime table .edit, form.realtime table .edit,
div.realtime table .edit { div.realtime table .edit {
@ -307,6 +327,13 @@ div.realtime table .edit {
float: left; float: left;
margin-left: 10px; margin-left: 10px;
} }
form.realtime table .lock,
div.realtime table .lock {
margin-left: calc(50% - 0.5em);
cursor: pointer;
width: 1em;
text-align: center;
}
form.realtime table .remove, form.realtime table .remove,
div.realtime table .remove { div.realtime table .remove {
float: right; float: right;

@ -2,7 +2,9 @@
@import "../../customize.dist/src/less/mixins.less"; @import "../../customize.dist/src/less/mixins.less";
@poll-th-bg: #aaa; @poll-th-bg: #aaa;
@poll-th-user-bg: #999;
@poll-td-bg: #aaa; @poll-td-bg: #aaa;
@poll-placeholder: #666;
@poll-border-color: #555; @poll-border-color: #555;
@poll-cover-color: #000; @poll-cover-color: #000;
@poll-fg: #000; @poll-fg: #000;
@ -42,6 +44,13 @@ input[type="text"][disabled], textarea[disabled] {
font: white; font: white;
border: 0px; border: 0px;
} }
// The placeholder color only seems to effect Safari when not set
input[type="text"]::placeholder {
color: @poll-placeholder;
}
table#table { table#table {
margin: 0px; margin: 0px;
} }
@ -75,7 +84,7 @@ table#table {
#tableScroll { #tableScroll {
overflow-y: hidden; overflow-y: hidden;
overflow-x: auto; overflow-x: auto;
margin-left: calc(~"30% - 50px + 29px"); margin-left: calc(~"30% - 50px + 31px");
max-width: 70%; max-width: 70%;
width: auto; width: auto;
display: inline-block; display: inline-block;
@ -118,6 +127,9 @@ table {
} }
tbody { tbody {
border: 1px solid @poll-border-color; border: 1px solid @poll-border-color;
* {
box-sizing: border-box;
}
tr { tr {
text-align: center; text-align: center;
&:first-of-type th{ &:first-of-type th{
@ -282,20 +294,32 @@ form.realtime, div.realtime {
width: 80%; width: 80%;
} }
} }
span {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
thead { thead {
td { td {
padding: 0px 5px; padding: 0px 5px;
background: @poll-th-bg; background: @poll-th-bg;
border-radius: 20px 20px 0 0; border-radius: 20px 20px 0 0;
text-align: center; //text-align: center;
&:nth-of-type(2) {
background: @poll-th-user-bg;
.lock {
cursor: default;
}
}
input { input {
&[type="text"] { &[type="text"] {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 1px 5px;
&[disabled] { &[disabled] {
color: @poll-fg; color: @poll-fg;
padding: 1px 5px; border: 1px solid transparent;
border: none;
} }
} }
} }
@ -318,9 +342,11 @@ form.realtime, div.realtime {
margin: 0 0 0 10px; margin: 0 0 0 10px;
} }
} }
td { tr:not(:first-child) {
label { td:not(:first-child) {
border: .5px solid @poll-border-color; label {
border-top: 1px solid @poll-border-color;
}
} }
} }
} }
@ -331,6 +357,12 @@ form.realtime, div.realtime {
margin-left: 10px; margin-left: 10px;
} }
.lock {
margin-left: ~"calc(50% - 0.5em)";
cursor: pointer;
width: 1em;
text-align: center;
}
.remove { .remove {
float: right; float: right;
margin-right: 10px; margin-right: 10px;

@ -252,6 +252,7 @@ var Renderer = function (Cryptpad) {
var makeRemoveElement = Render.makeRemoveElement = function (id) { var makeRemoveElement = Render.makeRemoveElement = function (id) {
return ['SPAN', { return ['SPAN', {
'data-rt-id': id, 'data-rt-id': id,
'title': Cryptpad.Messages.poll_remove,
class: 'remove', class: 'remove',
}, ['✖']]; }, ['✖']];
}; };
@ -259,6 +260,7 @@ var Renderer = function (Cryptpad) {
var makeEditElement = Render.makeEditElement = function (id) { var makeEditElement = Render.makeEditElement = function (id) {
return ['SPAN', { return ['SPAN', {
'data-rt-id': id, 'data-rt-id': id,
'title': Cryptpad.Messages.poll_edit,
class: 'edit', class: 'edit',
}, ['✐']]; }, ['✐']];
}; };
@ -266,25 +268,18 @@ var Renderer = function (Cryptpad) {
var makeLockElement = Render.makeLockElement = function (id) { var makeLockElement = Render.makeLockElement = function (id) {
return ['SPAN', { return ['SPAN', {
'data-rt-id': id, 'data-rt-id': id,
class: 'lock', 'title': Cryptpad.Messages.poll_locked,
}, [['i', { class: 'lock fa fa-lock',
class: 'fa fa-lock', }, []];
'aria-hidden': true,
}, []]
]];
}; };
var makeHeadingCell = Render.makeHeadingCell = function (cell, readOnly) { var makeHeadingCell = Render.makeHeadingCell = function (cell, readOnly) {
if (!cell) { return ['TD', {}, []]; } if (!cell) { return ['TD', {}, []]; }
if (cell.type === 'text') { if (cell.type === 'text') {
var removeElement = makeRemoveElement(cell['data-rt-id']);
var editElement = makeEditElement(cell['data-rt-id']);
var lockElement = makeLockElement(cell['data-rt-id']);
var elements = [['INPUT', cell, []]]; var elements = [['INPUT', cell, []]];
if (!readOnly) { if (!readOnly) {
elements.unshift(removeElement); elements.unshift(makeRemoveElement(cell['data-rt-id']));
elements.unshift(lockElement); elements.unshift(makeLockElement(cell['data-rt-id']));
elements.unshift(editElement);
} }
return ['TD', {}, elements]; return ['TD', {}, elements];
} }
@ -321,12 +316,10 @@ var Renderer = function (Cryptpad) {
var makeBodyCell = Render.makeBodyCell = function (cell, readOnly) { var makeBodyCell = Render.makeBodyCell = function (cell, readOnly) {
if (cell && cell.type === 'text') { if (cell && cell.type === 'text') {
var removeElement = makeRemoveElement(cell['data-rt-id']);
var editElement = makeEditElement(cell['data-rt-id']);
var elements = [['INPUT', cell, []]]; var elements = [['INPUT', cell, []]];
if (!readOnly) { if (!readOnly) {
elements.push(removeElement); elements.push(makeRemoveElement(cell['data-rt-id']));
elements.push(editElement); elements.push(makeEditElement(cell['data-rt-id']));
} }
return ['TD', {}, [ return ['TD', {}, [
['DIV', {class: 'text-cell'}, elements] ['DIV', {class: 'text-cell'}, elements]

@ -2,8 +2,9 @@ define([
'jquery', 'jquery',
'/common/login.js', '/common/login.js',
'/common/cryptpad-common.js', '/common/cryptpad-common.js',
'/common/credential.js' // preloaded for login.js '/common/test.js',
], function ($, Login, Cryptpad) { '/common/credential.js', // preloaded for login.js
], function ($, Login, Cryptpad, Test) {
var Messages = Cryptpad.Messages; var Messages = Cryptpad.Messages;
$(function () { $(function () {
@ -63,6 +64,11 @@ define([
var $register = $('button#register'); var $register = $('button#register');
var logMeIn = function (result) { var logMeIn = function (result) {
if (Test.testing) {
Test.passed();
window.alert("Test passed!");
return;
}
localStorage.User_hash = result.userHash; localStorage.User_hash = result.userHash;
var proxy = result.proxy; var proxy = result.proxy;
@ -153,6 +159,9 @@ define([
} }
return; return;
} }
if (Test.testing) { return void logMeIn(result); }
Cryptpad.eraseTempSessionValues(); Cryptpad.eraseTempSessionValues();
if (shouldImport) { if (shouldImport) {
sessionStorage.migrateAnonDrive = 1; sessionStorage.migrateAnonDrive = 1;
@ -176,5 +185,18 @@ define([
$dialog.find('> div').addClass('half'); $dialog.find('> div').addClass('half');
}); });
}); });
Test(function () {
$uname.val('test' + Math.random());
$passwd.val('test');
$confirm.val('test');
$checkImport[0].checked = true;
$checkAcceptTerms[0].checked = true;
$register.click();
window.setTimeout(function () {
Cryptpad.findOKButton().click();
}, 1000);
});
}); });
}); });

@ -40,6 +40,9 @@
<span class="link right"> <span class="link right">
<a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a> <a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a>
</span> </span>
<span class="link right">
<button id="upgrade" class="upgrade btn buttonSuccess" style="display: none;"></button>
</span>
</div> </div>
@ -105,7 +108,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="version-footer">CryptPad v1.7.0 (Hodag)</div> <div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
</footer> </footer>
</body> </body>

@ -3,7 +3,8 @@ define([
'/common/cryptpad-common.js', '/common/cryptpad-common.js',
'/common/cryptget.js', '/common/cryptget.js',
'/common/mergeDrive.js', '/common/mergeDrive.js',
'/bower_components/file-saver/FileSaver.min.js' '/bower_components/file-saver/FileSaver.min.js',
'/customize/header.js',
], function ($, Cryptpad, Crypt, Merge) { ], function ($, Cryptpad, Crypt, Merge) {
var saveAs = window.saveAs; var saveAs = window.saveAs;
@ -328,19 +329,6 @@ define([
$(function () { $(function () {
var $main = $('#mainBlock'); var $main = $('#mainBlock');
// Language selector
var $sel = $('#language-selector');
Cryptpad.createLanguageSelector(undefined, $sel);
$sel.find('button').addClass('btn').addClass('btn-secondary');
$sel.show();
// User admin menu
var $userMenu = $('#user-menu');
var userMenuCfg = {
$initBlock: $userMenu
};
var $userAdmin = Cryptpad.createUserAdminMenu(userMenuCfg);
$userAdmin.find('button').addClass('btn').addClass('btn-secondary');
$(window).click(function () { $(window).click(function () {
$('.cryptpad-dropdown').hide(); $('.cryptpad-dropdown').hide();

@ -4,6 +4,7 @@
<title>CryptPad</title> <title>CryptPad</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/> <meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script> <script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
<link rel="icon" type="image/png" <link rel="icon" type="image/png"
href="/customize/main-favicon.png" href="/customize/main-favicon.png"

@ -284,7 +284,7 @@ define([
Metadata = Cryptpad.createMetadata(UserList, Title, metadataCfg); Metadata = Cryptpad.createMetadata(UserList, Title, metadataCfg);
var configTb = { var configTb = {
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'], displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit', 'upgrade'],
userList: UserList.getToolbarConfig(), userList: UserList.getToolbarConfig(),
share: { share: {
secret: secret, secret: secret,
@ -313,8 +313,8 @@ define([
/* add a history button */ /* add a history button */
var histConfig = { var histConfig = {
onLocal: config.onLocal(), onLocal: config.onLocal,
onRemote: config.onRemote(), onRemote: config.onRemote,
setHistory: setHistory, setHistory: setHistory,
applyVal: function (val) { applyVal: function (val) {
var remoteDoc = JSON.parse(val || '{}').content; var remoteDoc = JSON.parse(val || '{}').content;

@ -40,6 +40,9 @@
<span class="link right"> <span class="link right">
<a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a> <a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a>
</span> </span>
<span class="link right">
<button id="upgrade" class="upgrade btn buttonSuccess" style="display: none;"></button>
</span>
</div> </div>
@ -105,7 +108,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="version-footer">CryptPad v1.7.0 (Hodag)</div> <div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
</footer> </footer>
</body> </body>

@ -296,7 +296,7 @@ window.canvas = canvas;
Metadata = Cryptpad.createMetadata(UserList, Title, metadataCfg); Metadata = Cryptpad.createMetadata(UserList, Title, metadataCfg);
var configTb = { var configTb = {
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'], displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit', 'upgrade'],
userList: UserList.getToolbarConfig(), userList: UserList.getToolbarConfig(),
share: { share: {
secret: secret, secret: secret,

Loading…
Cancel
Save