diff --git a/.jshintignore b/.jshintignore
index c9c5d3eb8..aac65f7de 100644
--- a/.jshintignore
+++ b/.jshintignore
@@ -1,6 +1,8 @@
node_modules/
www/bower_components/
www/common/pdfjs/
+www/common/tippy/
+www/common/jquery-ui/
server.js
www/common/media-tag.js
@@ -8,7 +10,6 @@ www/scratch
www/common/toolbar.js
www/common/hyperscript.js
-www/common/tippy.min.js
www/pad/wysiwygarea-plugin.js
www/pad/mediatag-plugin.js
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..8c6d7bbe9
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,47 @@
+# 1.29.0
+
+**Goals**
+
+For this release we wanted to direct our effort towards improving user experience issues surrounding user accounts.
+
+**Update notes**
+
+This release features breaking changes to some clientside dependencies. Administrators must make sure to deploy the
+latest server with npm update before updating your clientside dependencies with bower update.
+
+**What's new**
+
+ * newly registered users are now able to delete their accounts automatically, along with any personal
+ information which had been created:
+ * ToDo list data is automatically deleted, along with user profiles
+ * all of a user's owned pads are also removed immediately in their account deletion process
+ * users who predate account deletion will not benefit from automatic account deletion, since the server
+ does not have sufficient knowledge to guarantee that the information they could request to have deleted is strictly
+ their own. For this reason, we've started working on scripts for validating user requests, so as to enable manual
+ deletion by the server administrator.
+ * the script can be found in cryptpad/check-account-deletion.js, and it will be a part of an ongoing
+ effort to improve administrator tooling for situations like this
+ * users who have not logged in, but wish to use their drive now see a ghost icon which they can use to create pads.
+ We hope this makes it easier to get started as a new user.
+ * registered users who have saved templates in their drives can now use those templates at any time, rather than only
+ using them to create new pads
+ * we've updated our file encryption code such that it does not interfere with other scripts which may be running at
+ the same time (synchronous blocking, for those who are interested)
+ * we now validate message signatures clientside, except when they are coming from the history keeper because clients
+ trust that the server has already validated those signatures
+
+**Bug fixes**
+ * we've removed some dependencies from our home page that were introduced when we updated to use bootstrap4
+ * we now import fontawesome as css, and not less, which saves processing time and saves room in our localStorage cache
+ * templates which do not have a 'type' attribute set are migrated such that the pads which are created with their
+ content are valid
+ * thumbnail creation for pads is now disabled by default, due to poor performance
+ * users can enable thumbnail creation in their settings page
+ * we've fixed a significant bug in how our server handles checkpoints (special patches in history which contain the
+ entire pads content)
+ * it was possible for two users to independently create checkpoints in close proximity while the document was in a
+ forked state. New users joining while the session was in this state would get stuck on one side of the fork,
+ and could lose data if the users on the opposing fork overrode their changes
+ * we've updated our tests, which have been failing for some time because their success conditions were no longer valid
+ * while trying to register a previously registered user, users could cancel the prompt to login as that user.
+ If they did so, the registration form remained locked. This has been fixed.
\ No newline at end of file
diff --git a/bower.json b/bower.json
index f9ee60c20..8e6d5cb79 100644
--- a/bower.json
+++ b/bower.json
@@ -29,10 +29,10 @@
"json.sortify": "~2.1.0",
"secure-fabric.js": "secure-v1.7.9",
"hyperjson": "~1.4.0",
- "chainpad-crypto": "^0.1.3",
- "chainpad-listmap": "^0.4.2",
- "chainpad": "^5.0.0",
- "chainpad-netflux": "^0.6.1",
+ "chainpad-crypto": "^0.2.0",
+ "chainpad-listmap": "^0.5.0",
+ "chainpad": "^5.1.0",
+ "chainpad-netflux": "^0.7.0",
"file-saver": "1.3.1",
"alertifyjs": "1.0.11",
"scrypt-async": "1.2.0",
@@ -46,7 +46,8 @@
"localforage": "^1.5.2",
"html2canvas": "^0.4.1",
"croppie": "^2.5.0",
- "sortablejs": "#^1.6.0"
+ "sortablejs": "#^1.6.0",
+ "saferphore": "^0.0.1"
},
"resolutions": {
"bootstrap": "^v4.0.0"
diff --git a/check-account-deletion.js b/check-account-deletion.js
new file mode 100644
index 000000000..39bbf02f5
--- /dev/null
+++ b/check-account-deletion.js
@@ -0,0 +1,76 @@
+/* jshint esversion: 6, node: true */
+const Fs = require('fs');
+const nThen = require('nthen');
+const Pinned = require('./pinned');
+const Nacl = require('tweetnacl');
+
+const hashesFromPinFile = (pinFile, fileName) => {
+ var pins = {};
+ pinFile.split('\n').filter((x)=>(x)).map((l) => JSON.parse(l)).forEach((l) => {
+ switch (l[0]) {
+ case 'RESET': {
+ pins = {};
+ if (l[1] && l[1].length) { l[1].forEach((x) => { pins[x] = 1; }); }
+ //jshint -W086
+ // fallthrough
+ }
+ case 'PIN': {
+ l[1].forEach((x) => { pins[x] = 1; });
+ break;
+ }
+ case 'UNPIN': {
+ l[1].forEach((x) => { delete pins[x]; });
+ break;
+ }
+ default: throw new Error(JSON.stringify(l) + ' ' + fileName);
+ }
+ });
+ return Object.keys(pins);
+};
+
+var escapeKeyCharacters = function (key) {
+ return key && key.replace && key.replace(/\//g, '-');
+};
+
+
+const dataIdx = process.argv.indexOf('--data');
+let edPublic;
+if (dataIdx === -1) {
+ const hasEdPublic = process.argv.indexOf('--ed');
+ if (hasEdPublic === -1) { return void console.error("Missing ed argument"); }
+ edPublic = escapeKeyCharacters(process.argv[hasEdPublic+1]);
+} else {
+ const deleteData = JSON.parse(process.argv[dataIdx+1]);
+ if (!deleteData.toSign || !deleteData.proof) { return void console.error("Invalid arguments"); }
+ // Check sig
+ const ed = Nacl.util.decodeBase64(deleteData.toSign.edPublic);
+ const signed = Nacl.util.decodeUTF8(JSON.stringify(deleteData.toSign));
+ const proof = Nacl.util.decodeBase64(deleteData.proof);
+ if (!Nacl.sign.detached.verify(signed, proof, ed)) { return void console.error("Invalid signature"); }
+ edPublic = escapeKeyCharacters(deleteData.toSign.edPublic);
+}
+
+let data = [];
+let pinned = [];
+
+nThen((waitFor) => {
+ let f = './pins/' + edPublic.slice(0, 2) + '/' + edPublic + '.ndjson';
+ Fs.readFile(f, waitFor((err, content) => {
+ if (err) { throw err; }
+ pinned = hashesFromPinFile(content.toString('utf8'), f);
+ }));
+}).nThen((waitFor) => {
+ Pinned.load(waitFor((d) => {
+ data = Object.keys(d);
+ }), {
+ exclude: [edPublic + '.ndjson']
+ });
+}).nThen(() => {
+ console.log('Pads pinned by this user and not pinned by anybody else:');
+ pinned.forEach((p) => {
+ if (data.indexOf(p) === -1) {
+ console.log(p);
+ }
+ });
+});
+
diff --git a/config.example.js b/config.example.js
index 3aace0d4a..166fd74a8 100644
--- a/config.example.js
+++ b/config.example.js
@@ -2,7 +2,7 @@
/*
globals module
*/
-var domain = ' http://localhost:3000/';
+var domain = 'http://localhost:3000/';
// You can `kill -USR2` the node process and it will write out a heap dump.
// If your system doesn't support dumping, comment this out and install with
@@ -12,6 +12,10 @@ var domain = ' http://localhost:3000/';
// to enable this feature, uncomment the line below:
// require('heapdump');
+
+// we prepend a space because every usage expects it
+// requiring admins to preserve it is unnecessarily confusing
+domain = ' ' + domain;
module.exports = {
// the address you want to bind to, :: means all ipv4 and ipv6 addresses
diff --git a/customize.dist/loading-logo.png b/customize.dist/loading-logo.png
new file mode 100644
index 000000000..23753684f
Binary files /dev/null and b/customize.dist/loading-logo.png differ
diff --git a/customize.dist/loading.js b/customize.dist/loading.js
new file mode 100644
index 000000000..c08b82681
--- /dev/null
+++ b/customize.dist/loading.js
@@ -0,0 +1,201 @@
+// dark #326599
+// light #4591c4
+define([], function () {
+ var loadingStyle = (function(){/*
+#cp-loading {
+ transition: opacity 0.75s, visibility 0s 0.75s;
+ visibility: visible;
+ position: fixed;
+ z-index: 10000000;
+ top: 0px;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
+ background: linear-gradient(to right, #326599 0%, #326599 50%, #4591c4 50%, #4591c4 100%);
+ color: #fafafa;
+ font-size: 1.5em;
+ opacity: 1;
+ display: flex;
+ flex-flow: column;
+ justify-content: center;
+ align-items: center;
+}
+#cp-loading.cp-loading-hidden {
+ opacity: 0;
+ visibility: hidden;
+}
+#cp-loading .cp-loading-logo {
+ height: 300px;
+ width: 300px;
+ margin-top: 50px;
+ flex: 0 1 auto;
+ min-height: 0;
+ text-align: center;
+}
+#cp-loading .cp-loading-logo img {
+ max-width: 100%;
+ max-height: 100%;
+}
+#cp-loading .cp-loading-container {
+ width: 700px;
+ max-width: 90vw;
+ height: 500px;
+ max-height: calc(100vh - 20px);
+ margin: 50px;
+ flex-shrink: 0;
+ display: flex;
+ flex-flow: column;
+ justify-content: center;
+ justify-content: space-evenly;
+ align-items: center;
+}
+@media screen and (max-height: 800px) {
+ #cp-loading .cp-loading-container {
+ height: auto;
+ }
+}
+@media screen and (max-width: 600px) {
+ #cp-loading .cp-loading-container {
+ height: auto;
+ }
+}
+#cp-loading .cp-loading-cryptofist {
+ margin-left: auto;
+ margin-right: auto;
+ //height: 300px;
+ max-width: 90vw;
+ max-height: 300px;
+ width: auto;
+ height: auto;
+ margin-bottom: 2em;
+}
+@media screen and (max-height: 500px) {
+ #cp-loading .cp-loading-logo {
+ display: none;
+ }
+}
+#cp-loading-message {
+ background: #FFF;
+ padding: 20px;
+ width: 100%;
+ color: #000;
+ text-align: center;
+ display: none;
+}
+#cp-loading-password-prompt {
+ font-size: 18px;
+}
+#cp-loading-password-prompt .cp-password-error {
+ color: white;
+ background: #9e0000;
+ padding: 5px;
+ margin-bottom: 15px;
+}
+#cp-loading-password-prompt .cp-password-info {
+ text-align: left;
+ margin-bottom: 15px;
+}
+#cp-loading-password-prompt .cp-password-form {
+ display: flex;
+ justify-content: space-around;
+ flex-wrap: wrap;
+}
+#cp-loading-password-prompt .cp-password-form button,
+#cp-loading-password-prompt .cp-password-form .cp-password-input {
+ background-color: #4591c4;
+ color: white;
+ border: 1px solid #4591c4;
+}
+#cp-loading-password-prompt .cp-password-form .cp-password-container {
+ flex-shrink: 1;
+ min-width: 0;
+}
+#cp-loading-password-prompt .cp-password-form input {
+ flex: 1;
+ padding: 0 5px;
+ min-width: 0;
+ text-overflow: ellipsis;
+}
+#cp-loading-password-prompt .cp-password-form button:hover {
+ background-color: #326599;
+}
+#cp-loading .cp-loading-spinner-container {
+ position: relative;
+ height: 100px;
+}
+#cp-loading .cp-loading-spinner-container > div {
+ height: 100px;
+}
+#cp-loading-tip {
+ position: fixed;
+ z-index: 10000000;
+ top: 80%;
+ left: 0;
+ right: 0;
+ text-align: center;
+ transition: opacity 750ms;
+ transition-delay: 3000ms;
+}
+@media screen and (max-height: 600px) {
+ #cp-loading-tip {
+ display: none;
+ }
+}
+#cp-loading-tip span {
+ background: #222;
+ color: #fafafa;
+ text-align: center;
+ font-size: 1.5em;
+ opacity: 0.7;
+ font-family: 'Open Sans', 'Helvetica Neue', sans-serif;
+ padding: 15px;
+ max-width: 60%;
+ display: inline-block;
+}
+.cp-loading-progress {
+ width: 100%;
+ margin: 20px;
+}
+.cp-loading-progress p {
+ margin: 5px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.cp-loading-progress-bar {
+ height: 24px;
+ background: white;
+}
+.cp-loading-progress-bar-value {
+ height: 100%;
+ background: #5cb85c;
+}
+*/}).toString().slice(14, -3);
+ var urlArgs = window.location.href.replace(/^.*\?([^\?]*)$/, function (all, x) { return x; });
+ var elem = document.createElement('div');
+ elem.setAttribute('id', 'cp-loading');
+ elem.innerHTML = [
+ '',
+ '
',
+ '',
+ '
',
+ '
',
+ '
',
+ '',
+ '
',
+ '',
+ '
'
+ ].join('');
+ return function () {
+ var intr;
+ var append = function () {
+ if (!document.body) { return; }
+ clearInterval(intr);
+ document.body.appendChild(elem);
+ };
+ intr = setInterval(append, 100);
+ append();
+ };
+});
diff --git a/customize.dist/login.js b/customize.dist/login.js
index 1f8cebab5..4d2855b16 100644
--- a/customize.dist/login.js
+++ b/customize.dist/login.js
@@ -154,6 +154,7 @@ define([
proxy.login_name = uname;
proxy[Constants.displayNameKey] = uname;
sessionStorage.createReadme = 1;
+ if (!shouldImport) { proxy.version = 6; }
Feedback.send('REGISTRATION', true);
} else {
Feedback.send('LOGIN', true);
@@ -212,6 +213,7 @@ define([
loadingText: Messages.login_hashing,
hideTips: true,
});
+
// We need a setTimeout(cb, 0) otherwise the loading screen is only displayed
// after hashing the password
window.setTimeout(function () {
@@ -256,7 +258,10 @@ define([
// logMeIn should reset registering = false
UI.removeLoadingScreen(function () {
UI.confirm(Messages.register_alreadyRegistered, function (yes) {
- if (!yes) { return; }
+ if (!yes) {
+ hashing = false;
+ return;
+ }
proxy.login_name = uname;
if (!proxy[Constants.displayNameKey]) {
diff --git a/customize.dist/pages.js b/customize.dist/pages.js
index 50a5eae35..2a75ed7ad 100644
--- a/customize.dist/pages.js
+++ b/customize.dist/pages.js
@@ -73,7 +73,7 @@ define([
])
])
]),
- h('div.cp-version-footer', "CryptPad v1.28.0 (toString)")
+ h('div.cp-version-footer', "CryptPad v2.0.0 (Alpaca)")
]);
};
@@ -93,16 +93,25 @@ define([
]);
}
+ var button = h('button.navbar-toggler', {
+ 'type':'button',
+ /*'data-toggle':'collapse',
+ 'data-target':'#menuCollapse',
+ 'aria-controls': 'menuCollapse',
+ 'aria-expanded':'false',
+ 'aria-label':'Toggle navigation'*/
+ }, h('i.fa.fa-bars '));
+
+ $(button).click(function () {
+ if ($('#menuCollapse').is(':visible')) {
+ return void $('#menuCollapse').slideUp();
+ }
+ $('#menuCollapse').slideDown();
+ });
+
return h('nav.navbar.navbar-expand-lg',
h('a.navbar-brand', { href: '/index.html'}),
- h('button.navbar-toggler', {
- 'type':'button',
- 'data-toggle':'collapse',
- 'data-target':'#menuCollapse',
- 'aria-controls': 'menuCollapse',
- 'aria-expanded':'false',
- 'aria-label':'Toggle navigation'
- }, h('i.fa.fa-bars ')),
+ button,
h('div.collapse.navbar-collapse.justify-content-end#menuCollapse', [
//h('a.nav-item.nav-link', { href: '/what-is-cryptpad.html'}, Msg.topbar_whatIsCryptpad), // Moved the FAQ
h('a.nav-item.nav-link', { href: '/faq.html'}, Msg.faq_link),
@@ -619,23 +628,87 @@ define([
];
};
- var loadingScreen = Pages.loadingScreen = function () {
- return h('div#cp-loading',
- h('div.cp-loading-container', [
- h('img.cp-loading-cryptofist', {
- src: '/customize/cryptpad-new-logo-colors-logoonly.png?' + urlArgs
- }),
- h('div.cp-loading-spinner-container',
- h('span.fa.fa-circle-o-notch.fa-spin.fa-4x.fa-fw')),
- h('p'),
- ])
- );
+ Pages.createCheckbox = function (id, labelTxt, checked, opts) {
+ opts = opts|| {};
+ // Input properties
+ var inputOpts = {
+ type: 'checkbox',
+ id: id
+ };
+ if (checked) { inputOpts.checked = 'checked'; }
+ $.extend(inputOpts, opts.input || {});
+
+ // Label properties
+ var labelOpts = {};
+ $.extend(labelOpts, opts.label || {});
+ if (labelOpts.class) { labelOpts.class += ' cp-checkmark'; }
+
+ // Mark properties
+ var markOpts = { tabindex: 0 };
+ $.extend(markOpts, opts.mark || {});
+
+ var input = h('input', inputOpts);
+ var mark = h('span.cp-checkmark-mark', markOpts);
+ var label = h('span.cp-checkmark-label', labelTxt);
+
+ $(mark).keydown(function (e) {
+ if (e.which === 32) {
+ e.stopPropagation();
+ e.preventDefault();
+ $(input).prop('checked', !$(input).is(':checked'));
+ $(input).change();
+ }
+ });
+
+ $(input).change(function () { $(mark).focus(); });
+
+ return h('label.cp-checkmark', labelOpts, [
+ input,
+ mark,
+ label
+ ]);
};
- var hiddenLoader = function () {
- var loader = loadingScreen();
- loader.style.display = 'none';
- return loader;
+ Pages.createRadio = function (name, id, labelTxt, checked, opts) {
+ opts = opts|| {};
+ // Input properties
+ var inputOpts = {
+ type: 'radio',
+ id: id,
+ name: name
+ };
+ if (checked) { inputOpts.checked = 'checked'; }
+ $.extend(inputOpts, opts.input || {});
+
+ // Label properties
+ var labelOpts = {};
+ $.extend(labelOpts, opts.label || {});
+ if (labelOpts.class) { labelOpts.class += ' cp-checkmark'; }
+
+ // Mark properties
+ var markOpts = { tabindex: 0 };
+ $.extend(markOpts, opts.mark || {});
+
+ var input = h('input', inputOpts);
+ var mark = h('span.cp-radio-mark', markOpts);
+ var label = h('span.cp-checkmark-label', labelTxt);
+
+ $(mark).keydown(function (e) {
+ if (e.which === 32) {
+ e.stopPropagation();
+ e.preventDefault();
+ $(input).prop('checked', !$(input).is(':checked'));
+ $(input).change();
+ }
+ });
+
+ $(input).change(function () { $(mark).focus(); });
+
+ return h('label.cp-radio', labelOpts, [
+ input,
+ mark,
+ label
+ ]);
};
Pages['/user/'] = Pages['/user/index.html'] = function () {
@@ -681,27 +754,10 @@ define([
placeholder: Msg.login_confirm,
}),
h('div.checkbox-container', [
- h('input#import-recent', {
- name: 'import-recent',
- type: 'checkbox',
- checked: true
- }),
- // hscript doesn't generate for on label for some
- // reason... use jquery as a temporary fallback
- setHTML($('')[0], Msg.register_importRecent)
- /*h('label', {
- 'for': 'import-recent',
- }, Msg.register_importRecent),*/
+ Pages.createCheckbox('import-recent', Msg.register_importRecent, true)
]),
h('div.checkbox-container', [
- h('input#accept-terms', {
- name: 'accept-terms',
- type: 'checkbox'
- }),
- setHTML($('')[0], Msg.register_acceptTerms)
- /*setHTML(h('label', {
- 'for': 'accept-terms',
- }), Msg.register_acceptTerms),*/
+ $(Pages.createCheckbox('accept-terms')).find('.cp-checkmark-label').append(Msg.register_acceptTerms).parent()[0]
]),
h('button#register.btn.cp-login-register', Msg.login_register)
])
@@ -716,7 +772,6 @@ define([
]),
infopageFooter(),
- hiddenLoader(),
])];
};
@@ -743,17 +798,7 @@ define([
placeholder: Msg.login_password,
}),
h('div.checkbox-container', [
- h('input#import-recent', {
- name: 'import-recent',
- type: 'checkbox',
- checked: true
- }),
- // hscript doesn't generate for on label for some
- // reason... use jquery as a temporary fallback
- setHTML($('')[0], Msg.register_importRecent)
- /*h('label', {
- 'for': 'import-recent',
- }, Msg.register_importRecent),*/
+ Pages.createCheckbox('import-recent', Msg.register_importRecent, true),
]),
h('div.extra', [
h('button.login.first.btn', Msg.login_login)
@@ -762,7 +807,6 @@ define([
]),
]),
infopageFooter(),
- hiddenLoader(),
])];
};
diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less
index b845a5d6f..24c63e570 100644
--- a/customize.dist/src/less2/include/alertify.less
+++ b/customize.dist/src/less2/include/alertify.less
@@ -12,14 +12,11 @@
@alertify-btn-fg: @alertify-fore;
- @alertify-btn-bg: rgba(200, 200, 200, 0.1);
- @alertify-btn-bg-hover: rgba(200, 200, 200, .3);
-
@alertify-bg: @colortheme_modal-dim;
@alertify-fg: @alertify-fore;
@alertify-input-bg: @colortheme_modal-input;
- @alertify-input-fg: @colortheme_modal-fg;
+ @alertify-input-fg: @colortheme_modal-input-fg;
@alertify_padding-base: @variables_padding;
@alertify_box-shadow: @variables_shadow;
@@ -34,7 +31,7 @@
}
> * {
padding: @alertify_padding-base @alertify_padding-base * 4;
- color: @alertify-fore;
+ color: @colortheme_notification-color;
font-family: @colortheme_font;
font-size: large;
@@ -65,6 +62,8 @@
width: 100%;
height: 100%;
z-index: 100000; // alertify container
+ font: @colortheme_app-font;
+
&.forefront {
z-index: @max-z-index; // alertify max forefront
}
@@ -112,10 +111,6 @@
}
.dialog, .alert {
- .bright {
- color: @colortheme_light-base;
- }
-
& > div {
background-color: @alertify-dialog-bg;
&.half {
@@ -227,7 +222,7 @@
button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button) {
- background-color: @alertify-btn-bg;
+ background-color: @colortheme_alertify-cancel;
box-sizing: border-box;
position: relative;
outline: 0;
@@ -247,7 +242,7 @@
border-radius: 0;
color: @alertify-btn-fg;
- border: 1px solid transparent;
+ border: 1px solid @colortheme_alertify-cancel-border;
&.safe, &.danger {
color: @colortheme_old-base;
@@ -256,32 +251,40 @@
}
&.danger {
background-color: @colortheme_alertify-red;
+ border-color: @colortheme_alertify-red-border;
+ color: @colortheme_alertify-red-color;
&:hover, &:active {
- background-color: lighten(@colortheme_alertify-red, 5%);
+ background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-red, 10%), lighten(@colortheme_alertify-red, 10%));
}
}
&.safe {
background-color: @colortheme_alertify-green;
+ border-color: @colortheme_alertify-green-border;
+ color: @colortheme_alertify-green-color;
&:hover, &:active {
- background-color: lighten(@colortheme_alertify-green, 10%);
+ background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-green, 10%), lighten(@colortheme_alertify-green, 10%));
}
}
&.primary {
background-color: @colortheme_alertify-primary;
color: @colortheme_alertify-primary-text;
+ border-color: @colortheme_alertify-primary-border;
+ font-weight: bold;
&:hover, &:active {
- background-color: darken(@colortheme_alertify-primary, 10%);
+ background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-primary, 10%), lighten(@colortheme_alertify-primary, 10%));
}
}
&:hover, &:active {
- background-color: @alertify-btn-bg-hover;
+ background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-cancel, 10%), lighten(@colortheme_alertify-cancel, 10%));
}
&:focus {
- border: 1px dotted @alertify-base;
+ //border: 1px dotted @alertify-base;
+ box-shadow: 0px 0px 5px @colortheme_alertify-primary;
+ outline: none;
}
&::-moz-focus-inner {
border: 0;
diff --git a/customize.dist/src/less2/include/app-noscroll.less b/customize.dist/src/less2/include/app-noscroll.less
index c66019d61..f0a760b5f 100644
--- a/customize.dist/src/less2/include/app-noscroll.less
+++ b/customize.dist/src/less2/include/app-noscroll.less
@@ -7,6 +7,7 @@
overflow: hidden;
box-sizing: border-box;
position: relative;
+ border: 0;
body {
height: 100%;
width: 100%;
@@ -15,6 +16,7 @@
overflow: hidden;
box-sizing: border-box;
position: relative;
+ border: 0;
}
}
diff --git a/customize.dist/src/less2/include/checkmark.less b/customize.dist/src/less2/include/checkmark.less
index 30c6e7d4a..adbc70750 100644
--- a/customize.dist/src/less2/include/checkmark.less
+++ b/customize.dist/src/less2/include/checkmark.less
@@ -5,7 +5,7 @@
@width: round(@size / 8);
@dim1: round(@size / 3);
@dim2: round(2 * @size / 3);
- @top: round(@size / 12);
+ @top: round(@size / 12) - 1;
// Text
.cp-checkmark {
margin: 0;
@@ -17,6 +17,10 @@
-ms-user-select: none;
user-select: none;
+ & > a {
+ margin-left: 0.25em;
+ }
+
&.cp-checkmark-secondary {
.cp-checkmark-mark {
&:after {
@@ -26,6 +30,7 @@
input {
&:checked ~ .cp-checkmark-mark {
background-color: @colortheme_checkmark-back2;
+ border-color: @colortheme_checkmark-back2;
}
}
}
@@ -37,12 +42,19 @@
display: none;
&:checked ~ .cp-checkmark-mark {
background-color: @colortheme_checkmark-back1;
+ border-color: @colortheme_checkmark-back1;
&:after {
display: block;
}
}
}
+ .cp-checkmark-label {
+ cursor: default;
+ &:empty {
+ display: none;
+ }
+ }
.cp-checkmark-mark {
margin-right: 10px;
position: relative;
@@ -51,6 +63,8 @@
background-color: @colortheme_checkmark-back0;
display: flex;
justify-content: center;
+ border: 1px solid @colortheme_form-border;
+ flex-shrink: 0;
&:after {
content: "";
display: none;
@@ -60,6 +74,90 @@
transform: rotate(45deg);
border: solid @colortheme_checkmark-col1;
border-width: 0 @width @width 0;
+ position: absolute;
+ }
+ &:focus {
+ //border-color: #FF007C !important;
+ box-shadow: 0px 0px 5px @colortheme_checkmark-back1;
+ outline: none;
+ }
+ }
+
+ }
+
+ .cp-radio {
+ margin: 0;
+ display: flex;
+ align-items: center;
+ position: relative;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+
+ &.cp-radio-secondary {
+ .cp-radio-mark {
+ &:after {
+ border-color: @colortheme_checkmark-col2;
+ }
+ }
+ input {
+ &:checked ~ .cp-radio-mark {
+ background-color: @colortheme_checkmark-back2;
+ }
+ }
+ }
+ &:hover .cp-radio-mark {
+ background-color: @colortheme_checkmark-back0-active;
+ }
+
+ input {
+ display: none;
+ &:checked ~ .cp-radio-mark {
+ background-color: @colortheme_checkmark-back1;
+ border-color: @colortheme_checkmark-back1;
+ &:after {
+ display: block;
+ }
+ }
+ }
+
+ .cp-checkmark-label {
+ cursor: default;
+ &:empty {
+ display: none;
+ }
+ }
+
+ @radio-size: @dim1 * 3;
+ .cp-radio-mark {
+ margin-right: 10px;
+ position: relative;
+ height: @radio-size;
+ width: @radio-size;
+ background-color: @colortheme_checkmark-back0;
+ border-radius: 50%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: 1px solid @colortheme_form-border;
+ flex-shrink: 0;
+ &:after {
+ display: none;
+ content: "";
+ border-radius: 50%;
+ background: white;
+ width: @dim1;
+ height: @dim1;
+
+ //transform: rotate(45deg);
+ //border: solid @colortheme_checkmark-col1;
+ //border-width: 0 @width @width 0;
+ }
+ &:focus {
+ //border-color: #FF007C !important;
+ box-shadow: 0px 0px 5px @colortheme_checkmark-back1;
+ outline: none;
}
}
diff --git a/customize.dist/src/less2/include/colortheme.less b/customize.dist/src/less2/include/colortheme.less
index fdf4ea972..06dadab39 100644
--- a/customize.dist/src/less2/include/colortheme.less
+++ b/customize.dist/src/less2/include/colortheme.less
@@ -2,6 +2,9 @@
@colortheme_app-font-size: 16px;
@colortheme_app-font: @colortheme_app-font-size @colortheme_font;
+@colortheme_logo-1: #326599;
+@colortheme_logo-2: #4591c4;
+
@colortheme_link-color: #0275D8;
@colortheme_link-color-visited: #005999;
@colortheme_info-background: #fafafa;
@@ -15,23 +18,42 @@
@colortheme_cp-red: #FA5858; // remove red
@colortheme_cp-green: #46E981;
-@colortheme_modal-bg: #222;
-@colortheme_modal-fg: #fff;
-@colortheme_modal-link: #eee;
+@colortheme_form-border: #bbbbbb;
+@colortheme_form-bg: @colortheme_logo-2;
+@colortheme_form-color: #ffffff;
+@colortheme_form-bg-alt: #ffffff;
+@colortheme_form-color-alt: @colortheme_logo-1;
+@colortheme_form-warning: #f49842;
+@colortheme_form-warning-hov: darken(@colortheme_form-warning, 5%);
+
+@colortheme_modal-bg: @colortheme_form-bg-alt; // TODO Modals bg
+@colortheme_modal-fg: @colortheme_form-color-alt;
+@colortheme_modal-link: @colortheme_link-color;
@colortheme_modal-link-visited: lighten(@colortheme_modal-link, 10%);
-@colortheme_modal-dim: rgba(0, 0, 0, 0.4);
+@colortheme_modal-dim: fade(@colortheme_logo-2, 50%); // TODO transparent background behind modals
+@colortheme_modal-input: @colortheme_form-bg;
+@colortheme_modal-input-fg: @colortheme_form-color;
-@colortheme_loading-bg: #222;
+@colortheme_loading-bg: @colortheme_logo-1;
+@colortheme_loading-bg-alt: @colortheme_logo-2;
@colortheme_loading-color: @colortheme_old-fore;
-@colortheme_modal-input: #111;
+// TODO modals buttons
@colortheme_alertify-red: #E55236;
+@colortheme_alertify-red-color: #FFF;
+@colortheme_alertify-red-border: transparent;
@colortheme_alertify-green: #77C825;
-@colortheme_alertify-primary: #fff;
-@colortheme_alertify-primary-text: #000;
-
-@colortheme_notification-log: rgba(0, 0, 0, 0.8);
+@colortheme_alertify-green-color: #FFF;
+@colortheme_alertify-green-border: transparent;
+@colortheme_alertify-primary: @colortheme_form-bg;
+@colortheme_alertify-primary-text: @colortheme_form-color;
+@colortheme_alertify-primary-border: transparent;
+@colortheme_alertify-cancel: @colortheme_modal-bg;
+@colortheme_alertify-cancel-border: #ccc;
+
+@colortheme_notification-log: fade(@colortheme_logo-1, 90%);
+@colortheme_notification-color: #fff;;
@colortheme_notification-warn: rgba(205, 37, 50, 0.8);
@colortheme_dropdown-bg: #f9f9f9;
@@ -117,9 +139,9 @@
@cryptpad_header_col: #1E1F1F;
@cryptpad_text_col: #3F4141;
-@colortheme_checkmark-back0: #ffffff;
-@colortheme_checkmark-back0-active: #bbbbbb;
-@colortheme_checkmark-back1: #FF0073;
-@colortheme_checkmark-col1: #ffffff;
-@colortheme_checkmark-back2: #FFFFFF;
-@colortheme_checkmark-col2: #000000;
+@colortheme_checkmark-back0: @colortheme_form-bg-alt;
+@colortheme_checkmark-back0-active: @colortheme_form-border;
+@colortheme_checkmark-back1: @colortheme_form-bg;
+@colortheme_checkmark-col1: @colortheme_form-color;
+@colortheme_checkmark-back2: @colortheme_form-bg-alt;
+@colortheme_checkmark-col2: @colortheme_form-color-alt;
diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less
index 4fd0a5a1d..57509455e 100644
--- a/customize.dist/src/less2/include/creation.less
+++ b/customize.dist/src/less2/include/creation.less
@@ -1,47 +1,78 @@
@import (once) "./colortheme-all.less";
@import (once) "./tools.less";
-@import (once) "./checkmark.less";
@import (once) './icon-colors.less';
-.creation_main() {
- .tippy-popper {
- z-index: 100000001 !important;
- }
+.creation_main(
+ @color: @colortheme_default-color, // Color of the text for the toolbar
+ @bg-color: @colortheme_default-bg, // color of the toolbar background
+ @warn-color: @colortheme_default-warn, // color of the warning text in the toolbar
+) {
+ @colortheme_creation-modal-bg: #fff;
+ @colortheme_creation-modal: #666;
+ @colortheme_creation-modal-title: @colortheme_loading-bg;
+
#cp-creation-container {
position: absolute;
z-index: 100000000; // #loading * 10
top: 0px;
- background: @colortheme_loading-bg;
+ //background: @colortheme_loading-bg;
+ background: linear-gradient(to right, @colortheme_loading-bg 0%, @colortheme_loading-bg 50%, @colortheme_loading-bg-alt 50%, @colortheme_loading-bg-alt 100%);
color: @colortheme_loading-color;
display: flex;
flex-flow: column; /* we need column so that the child can shrink vertically */
justify-content: center;
+ align-items: center;
width: 100%;
height: 100%;
overflow: auto;
+ .cp-creation-logo {
+ height: 300px;
+ width: 300px;
+ margin-top: 50px;
+ flex: 0 1 auto; /* allows shrink */
+ min-height: 0;
+ text-align: center;
+ img {
+ max-width: 100%;
+ max-height: 100%;
+ }
+ }
}
#cp-creation {
- flex: 0 1 auto; /* allows shrink */
- min-height: 0;
overflow: auto;
text-align: center;
+ background: @colortheme_creation-modal-bg;
+ color: @colortheme_creation-modal;
font: @colortheme_app-font;
- width: 100%;
outline: none;
+ width: 700px;
+ max-width: 90vw;
+ height: 500px;
+ max-height: calc(~"100vh - 20px");
+ margin: 50px;
+ flex-shrink: 0;
+ display: flex;
+ flex-flow: column;
+
& > div {
- width: 60vw;
+ width: 100%;
max-width: 100%;
- margin: 40px auto;
+ margin: auto;
text-align: left;
}
- .cp-creation-create, .cp-creation-settings {
+ .cp-creation-title {
+ color: @colortheme_creation-modal-title;
+ font-weight: bold;
+ margin: 15px;
+ }
+
+ .cp-creation-create {
margin-top: 0px;
- @creation-button: #30B239;
button {
.tools_unselectable();
padding: 15px;
- background: @creation-button;
+ background: linear-gradient(to right, @colortheme_logo-2, @colortheme_logo-1);
color: #FFF;
font-weight: bold;
margin: 3px 10px;
@@ -50,15 +81,16 @@
outline: none;
width: 100%;
&:hover {
+ background: linear-gradient(to right, lighten(@colortheme_logo-2, 5%), lighten(@colortheme_logo-1, 5%));
//background: darken(@creation-button, 5%);
- background: lighten(@creation-button, 5%);
+ //background: lighten(@creation-button, 5%);
}
}
}
.cp-creation-create {
text-align: center;
- margin: auto;
- margin-top: 20px;
+ //margin: auto;
+ //margin-top: 20px;
width: 400px;
max-width: 100%;
button {
@@ -70,6 +102,8 @@
display: flex;
flex-flow: column;
align-items: center;
+ flex: 1 0 auto;
+ justify-content: space-evenly;
& > div {
width: 400px;
max-width: 100%;
@@ -77,7 +111,9 @@
align-items: center;
flex-wrap: wrap;
font-size: 16px;
- margin: 10px 0;
+ //margin: 10px 0;
+ min-height: 28px;
+ line-height: 28px;
label {
flex: 1;
}
@@ -88,31 +124,68 @@
padding: 0 10px;
}
}
- .cp-creation-help {
- font-size: 18px;
- color: white;
- &:hover {
- color: #AAA;
- text-decoration: none;
- }
+ }
+ .cp-creation-help, .cp-creation-warning {
+ font-size: 18px;
+ color: @colortheme_form-warning;
+ &:hover {
+ color: @colortheme_form-warning-hov;
+ text-decoration: none;
}
}
.cp-creation-slider {
display: block;
overflow: hidden;
max-height: 0px;
- transition: max-height 0.5s ease-in-out;
- width: 100%;
- margin-top: 10px;
+ max-width: 0px;
+ //margin-top: 10px;
&.active {
- max-height: 40px;
+ transition: max-height 0.5s ease-in-out;
+ max-width: unset;
+ max-height: 100px;
}
}
+
+ input, select {
+ font-size: 14px;
+ border: 1px solid @colortheme_form-border;
+ height: 26px;
+ background-color: @colortheme_form-bg;
+ color: @colortheme_form-color;
+ }
+
.cp-creation-expire {
.cp-creation-expire-picker {
text-align: center;
input {
- width: 100px;
+ width: 50px;
+ margin: 0 5px;
+ }
+ select {
+ margin-right: 5px;
+ }
+ }
+ &.active {
+ label {
+ flex: unset;
+ }
+ .cp-creation-slider {
+ flex: 1;
+ }
+ }
+ }
+ .cp-creation-password {
+ .cp-creation-password-picker {
+ text-align: center;
+ width: 100%;
+ .cp-password-container {
+ input {
+ width: 150px;
+ padding: 0 5px;
+ }
+ label {
+ flex: unset;
+ }
}
}
}
@@ -125,31 +198,51 @@
}
div.cp-creation-remember {
- margin-top: 30px;
.cp-creation-remember-help {
- font-style: italic;
+ width: 100%;
+ //font-style: italic;
+ font-size: 12px;
+ font-weight: bold;
+ color: @colortheme_form-bg;
+ line-height: 20px;
+ .fa {
+ margin-right: 10px;
+ }
}
}
div.cp-creation-template {
width: 100%;
- background-color: darken(@colortheme_modal-bg, 3%);
- padding: 20px;
- margin: 30px 0;
- .cp-creation-title {
- padding: 0 0 10px 10px;
- margin: auto;
+ //flex: 1 0 auto;
+ flex-wrap: nowrap;
+ .cp-creation-template-more {
+ font-size: 30px;
+ cursor: pointer;
+ margin: 0 5px;
+ text-align: center;
+ &:first-child {
+ left: 5px;
+ }
+ &:last-child {
+ right: 5px;
+ }
+ &:hover {
+ color: #888;
+ }
+ &.hidden {
+ visibility: hidden;
+ }
}
}
.cp-creation-template-container {
width: 100%;
+ flex: 1;
display: flex;
flex-wrap: wrap;
justify-content: center;
- overflow-y: auto;
+ //overflow-y: auto;
align-items: center;
.cp-creation-template-element {
- @darker: darken(@colortheme_modal-fg, 30%);
-
+ box-shadow: 2px 2px 7px @colortheme_form-border;
width: 135px;
padding: 5px;
margin: 5px;
@@ -162,19 +255,23 @@
line-height: 1em;
cursor: pointer;
- background-color: #111;
- color: @darker;
+ color: black;
border: 1px solid transparent;
&.cp-creation-template-selected {
- border: 1px solid white;
- background-color: #222;
+ color: @color !important;
+ background-color: @bg-color !important;
+ .fa {
+ color: @color;
+ }
}
transition: all 0.1s;
&:hover {
- color: @colortheme_modal-fg;
+ //color: @colortheme_modal-fg;
+ background-color: @colortheme_form-border;
+ box-shadow: none;
}
align-items: center;
@@ -196,6 +293,7 @@
max-width: 100%;
}
.fa {
+ color: @bg-color;
cursor: pointer;
width: 100px;
height: 100px;
@@ -210,52 +308,78 @@
.cp-creation-deleted-container {
text-align: center;
.cp-creation-deleted {
- background: #111;
+ margin: 0 10px;
+ background: @colortheme_loading-bg;
+ color: @colortheme_loading-color;
padding: 10px;
text-align: center;
font-weight: bold;
display: inline-block;
}
}
+ }
- .checkmark_main(30px);
-
- @media screen and (max-width: @browser_media-narrow-screen) {
- & > div {
- width: 95%;
- margin: 10px auto;
+ @media screen and (max-height: 700px) {
+ #cp-creation-container {
+ .cp-creation-logo {
+ //flex-shrink: 0;
+ display: none;
+ }
}
}
- @media screen and (max-width: @browser_media-medium-screen) {
- #cp-creation-form {
- div.cp-creation-template {
- margin: 0;
- padding: 5px;
- .cp-creation-template-container {
- .cp-creation-template-element {
- flex-flow: row;
- margin: 1px;
- padding: 5px;
- width: 155px;
- img {
- display: none;
+ @media screen and (max-width: 500px) {
+ #cp-creation {
+ #cp-creation-form {
+ & > div {
+ width: 95%;
+ margin: 10px auto;
+ }
+ .cp-creation-expire {
+ &.active {
+ label {
+ flex: 1;
}
- .fa {
- font-size: 18px;
- width: 20px;
- height: 20px;
- line-height: 20px;
- display: inline !important;
+ .cp-creation-slider {
+ flex: unset;
+ order: 10;
+ width: 100%;
}
- .cp-creation-template-element-name {
- margin: 0;
- margin-left: 5px;
+ }
+ }
+ }
+ }
+ }
+ @media screen and (max-width: @browser_media-medium-screen) {
+ #cp-creation {
+ height: auto;
+ #cp-creation-form {
+ div.cp-creation-template {
+ margin: 0;
+ padding: 5px;
+ .cp-creation-template-container {
+ .cp-creation-template-element {
+ flex-flow: row;
+ margin: 1px;
+ padding: 5px;
+ width: 155px;
+ img {
+ display: none;
+ }
+ .fa {
+ font-size: 18px;
+ width: 20px;
+ height: 20px;
+ line-height: 20px;
+ display: inline !important;
+ }
+ .cp-creation-template-element-name {
+ margin: 0;
+ margin-left: 5px;
+ }
}
}
}
}
}
}
-
- }
}
diff --git a/customize.dist/src/less2/include/dropdown.less b/customize.dist/src/less2/include/dropdown.less
index 3cf32537e..0d3552450 100644
--- a/customize.dist/src/less2/include/dropdown.less
+++ b/customize.dist/src/less2/include/dropdown.less
@@ -55,9 +55,23 @@
user-select: none;
float: none;
text-align: left;
- font: @dropdown_font;
line-height: 1em;
+ align-items: center;
+ &:not(.fa) {
+ font: @dropdown_font;
+ }
+ &.fa {
+ font-size: 18px;
+ &::before {
+ width: 40px;
+ margin-left: -10px;
+ text-align: center;
+ }
+ * {
+ font: @dropdown_font;
+ }
+ }
.fa {
width: 20px;
diff --git a/customize.dist/src/less2/include/framework.less b/customize.dist/src/less2/include/framework.less
index 4bdb2b0b8..286870991 100644
--- a/customize.dist/src/less2/include/framework.less
+++ b/customize.dist/src/less2/include/framework.less
@@ -1,8 +1,12 @@
+@import (once) "./colortheme-all.less";
@import (once) "./toolbar.less";
@import (once) './fileupload.less';
@import (once) './alertify.less';
@import (once) './tokenfield.less';
@import (once) './creation.less';
+@import (once) './tippy.less';
+@import (once) "./checkmark.less";
+@import (once) "./password-input.less";
.framework_main(@bg-color, @warn-color, @color) {
.toolbar_main(
@@ -13,6 +17,33 @@
.fileupload_main();
.alertify_main();
.tokenfield_main();
- .creation_main();
+ .tippy_main();
+ .checkmark_main(20px);
+ .password_main();
+ .creation_main(
+ @bg-color: @bg-color,
+ @warn-color: @warn-color,
+ @color: @color
+ );
+ font: @colortheme_app-font;
+}
+
+.framework_min_main(
+ @color: @colortheme_default-color, // Color of the text for the toolbar
+ @bg-color: @colortheme_default-bg, // color of the toolbar background
+ @warn-color: @colortheme_default-warn, // color of the warning text in the toolbar
+) {
+ .toolbar_main(
+ @bg-color: @bg-color,
+ @warn-color: @warn-color,
+ @color: @color
+ );
+ .fileupload_main();
+ .alertify_main();
+ .tippy_main();
+ .checkmark_main(20px);
+ .password_main();
+ font: @colortheme_app-font;
}
+
diff --git a/customize.dist/src/less2/include/icons.less b/customize.dist/src/less2/include/icons.less
index 03a6af5ab..c2ddfef32 100644
--- a/customize.dist/src/less2/include/icons.less
+++ b/customize.dist/src/less2/include/icons.less
@@ -10,7 +10,6 @@
text-overflow: ellipsis;
padding-top: 5px;
padding-bottom: 5px;
- border: 1px solid white;
.cp-icons-name {
width: 100%;
@@ -26,7 +25,7 @@
word-wrap: break-word;
}
&.cp-icons-element-selected {
- background-color: white;
+ background-color: rgba(0,0,0,0.2);
color: #666;
}
.fa {
diff --git a/customize.dist/src/less2/include/infopages.less b/customize.dist/src/less2/include/infopages.less
index b07b90aa8..4282b3499 100644
--- a/customize.dist/src/less2/include/infopages.less
+++ b/customize.dist/src/less2/include/infopages.less
@@ -161,6 +161,7 @@
background-size: contain;
height: 50px;
width: 250px;
+ margin-right: 0;
}
a {
border: 2px solid transparent;
@@ -169,7 +170,8 @@
.nav-link {
padding: 0.5em 0.7em;
&:hover {
- transform: scale(1.05);
+ font-size: 1.05em;
+ //transform: scale(1.05);
};
}
.cp-register-btn {
@@ -184,9 +186,18 @@
color: #4591C4;
}
}
-@media (max-width: 991px) {
+@media (max-width: 1000px) {
#menuCollapse {
text-align: right;
+/* @media (min-width: 576px) {
+ top: 100%;
+ background: rgba(255,255,255,0.8);
+ position: absolute;
+ right: 0px;
+ padding: 0 20px;
+ z-index: 1;
+ }
+*/
}
.navbar-nav a {
text-align: right !important;
@@ -194,7 +205,7 @@
.cp-register-btn {
margin-right: 13px;
text-align: center;
- }
+ }
}
//footer general styles
diff --git a/customize.dist/src/less2/include/modal.less b/customize.dist/src/less2/include/modal.less
index 3bd674527..e7e39ad96 100644
--- a/customize.dist/src/less2/include/modal.less
+++ b/customize.dist/src/less2/include/modal.less
@@ -57,7 +57,7 @@
input {
background-color: @colortheme_modal-input;
- color: @colortheme_modal-fg;
+ color: @colortheme_modal-input-fg;
border: 0;
padding: 8px 12px;
margin: 1em;
diff --git a/customize.dist/src/less2/include/password-input.less b/customize.dist/src/less2/include/password-input.less
new file mode 100644
index 000000000..8836476fd
--- /dev/null
+++ b/customize.dist/src/less2/include/password-input.less
@@ -0,0 +1,13 @@
+.password_main() {
+ .cp-password-container {
+ display: flex;
+ align-items: center;
+ input {
+ flex: 1;
+ min-width: 0;
+ }
+ label, .fa {
+ margin-left: 10px;
+ }
+ }
+}
diff --git a/customize.dist/src/less2/include/sidebar-layout.less b/customize.dist/src/less2/include/sidebar-layout.less
index ede03942e..5640e014b 100644
--- a/customize.dist/src/less2/include/sidebar-layout.less
+++ b/customize.dist/src/less2/include/sidebar-layout.less
@@ -52,6 +52,12 @@
margin-bottom: 0;
}
}
+ label.noTitle {
+ display: inline-flex;
+ .fa {
+ margin-left: 10px;
+ }
+ }
margin-bottom: 20px;
}
[type="text"], button {
diff --git a/customize.dist/src/less2/include/tippy.less b/customize.dist/src/less2/include/tippy.less
new file mode 100644
index 000000000..afec36e96
--- /dev/null
+++ b/customize.dist/src/less2/include/tippy.less
@@ -0,0 +1,14 @@
+@import (once) './colortheme-all.less';
+
+.tippy_main() {
+ .tippy-tooltip.cryptpad-theme {
+ /* Your styling here. Example: */
+ background-color: white;
+ box-shadow: 2px 2px 10px #000;
+ font-weight: bold;
+ color: #333;
+ [x-circle] {
+ background-color: unset;
+ }
+ }
+}
diff --git a/customize.dist/src/less2/include/tokenfield.less b/customize.dist/src/less2/include/tokenfield.less
index 6604b4481..46ba3af09 100644
--- a/customize.dist/src/less2/include/tokenfield.less
+++ b/customize.dist/src/less2/include/tokenfield.less
@@ -1,25 +1,31 @@
@import (once) "./tools.less";
.tokenfield_main () {
+ .ui-autocomplete {
+ z-index: 100001; // alertify + 1
+ }
.tokenfield {
.tools_unselectable();
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-evenly;
height: auto;
min-height: 34px;
padding-bottom: 0px;
background-color: unset;
border: none;
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- padding: 0 10px;
+ margin: 0 10px;
+ padding: 0;
+ width: ~"calc(100% - 20px)";
.token {
box-sizing: border-box;
border-radius: 3px;
- display: inline-block;
+ display: inline-flex;
+ align-items: center;
border: 1px solid #d9d9d9;
background-color: #ededed;
white-space: nowrap;
- margin: 10px 5px;
+ margin: 2px 0;
height: 24px;
vertical-align: middle;
cursor: default;
@@ -50,7 +56,7 @@
.close {
font-family: Arial;
display: inline-block;
- line-height: 24px;
+ line-height: 1.49em;
font-size: 1.1em;
margin-left: 5px;
float: none;
@@ -73,6 +79,8 @@
margin: 0 !important; // Override alertify
box-shadow: none;
max-width: 100%;
+ width: 100%;
+ min-width: 100% !important;
&:focus {
border-color: transparent;
outline: 0;
diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less
index 50c51f611..602868fe4 100644
--- a/customize.dist/src/less2/include/toolbar.less
+++ b/customize.dist/src/less2/include/toolbar.less
@@ -183,8 +183,13 @@
#cp-app-toolbar-creation-dialog.cp-modal-container {
.icons_main();
- li:hover {
- border: 1px solid white;
+ li {
+ border: 1px solid @colortheme_modal-fg;
+ &:hover {
+ //border: 1px solid @colortheme_modal-fg;
+ background: @colortheme_modal-fg;
+ color: @colortheme_modal-bg;
+ }
}
.cp-modal {
display: flex;
diff --git a/customize.dist/src/less2/loading.less b/customize.dist/src/less2/loading.less
deleted file mode 100644
index 8c7e8f5e4..000000000
--- a/customize.dist/src/less2/loading.less
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
-WARNING: THIS FILE DOES NOTHING
-It exists only as a proposal of what CSS you should use in loading.js
-The CSS inside of loading.js is precompiled in order to save 200ish milliseconds to the loading screen.
-*/
-@import (once) "./include/colortheme-all.less";
-@import (once) "./include/browser.less";
-
-#cp-loading {
- transition: opacity 0.75s, visibility 0s 0.75s;
- visibility: visible;
- opacity: 1;
- position: fixed;
- z-index: 10000000; // #loading
- top: 0px;
- bottom: 0px;
- left: 0px;
- right: 0px;
- background: @colortheme_loading-bg;
- color: @colortheme_loading-color;
- text-align: center;
- font-size: 1.5em;
- .cp-loading-container {
- margin-top: 50vh;
- transform: translateY(-50%);
- }
- .cp-loading-cryptofist {
- margin-left: auto;
- margin-right: auto;
- height: 300px;
- margin-bottom: 2em;
- @media screen and (max-height: @browser_media-short-screen) {
- display: none;
- }
- }
- .cp-loading-spinner-container {
- position: relative;
- height: 100px;
- > div {
- height: 100px;
- }
- }
- &.cp-loading-hidden {
- opacity: 0;
- visibility: hidden;
- }
-}
-#cp-loading-tip {
- position: fixed;
- z-index: 10000000; // loading tip
- top: 80%;
- left: 0;
- right: 0;
- text-align: center;
-
- transition: opacity 750ms;
- transition-delay: 3000ms;
- @media screen and (max-height: @browser_media-medium-screen) {
- display: none;
- }
- span {
- background: @colortheme_loading-bg;
- color: @colortheme_loading-color;
- text-align: center;
- font-size: 1.5em;
- opacity: 0.7;
- font-family: @colortheme_font;
- padding: 15px;
- max-width: 60%;
- display: inline-block;
- }
-}
-
diff --git a/customize.dist/src/less2/pages/page-login.less b/customize.dist/src/less2/pages/page-login.less
index 77dead84c..5ad874d0f 100644
--- a/customize.dist/src/less2/pages/page-login.less
+++ b/customize.dist/src/less2/pages/page-login.less
@@ -1,11 +1,12 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme-all.less";
@import (once) "../include/alertify.less";
-@import (once) "../loading.less";
+@import (once) "../include/checkmark.less";
.infopages_main();
.infopages_topbar();
.alertify_main();
+.checkmark_main(20px);
.form-group {
.extra {
diff --git a/customize.dist/src/less2/pages/page-register.less b/customize.dist/src/less2/pages/page-register.less
index 6336c6aa3..84944f4df 100644
--- a/customize.dist/src/less2/pages/page-register.less
+++ b/customize.dist/src/less2/pages/page-register.less
@@ -1,11 +1,12 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme-all.less";
@import (once) "../include/alertify.less";
-@import (once) "../loading.less";
+@import (once) "../include/checkmark.less";
.infopages_main();
.infopages_topbar();
.alertify_main();
+.checkmark_main(20px);
.cp-container {
.form-group {
@@ -23,11 +24,7 @@
}
margin-top: 16px;
font-size: 1.25em;
- min-width: 30%; // conflict?
- width: 30%;
- @media (max-width: 500px) {
- width: 45%;
- }
+ min-width: 30%;
}
}
padding-bottom: 3em;
diff --git a/customize.dist/template.js b/customize.dist/template.js
index ddfd1c1d8..0ee34d407 100644
--- a/customize.dist/template.js
+++ b/customize.dist/template.js
@@ -3,7 +3,7 @@ define([
'/common/hyperscript.js',
'/customize/pages.js',
- 'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
+ 'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
], function ($, h, Pages) {
$(function () {
var $body = $('body');
@@ -27,8 +27,7 @@ $(function () {
window.Tether = function () {};
require([
'less!/customize/src/less2/main.less',
- 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
- '/bower_components/bootstrap/dist/js/bootstrap.bundle.min.js'
+ 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css'
], function () {
$body.append($main);
diff --git a/customize.dist/translations/messages.de.js b/customize.dist/translations/messages.de.js
index 57e94ac16..06b86968c 100644
--- a/customize.dist/translations/messages.de.js
+++ b/customize.dist/translations/messages.de.js
@@ -5,75 +5,251 @@
// the language dropdowns that are shown throughout Cryptpad's interface
out._languageName = 'German';
- out.main_title = "Cryptpad: Echtzeitzusammenarbeit, ohne Preisgabe von Informationen";
+ out.main_title = "Cryptpad: Echtzeitzusammenarbeit ohne Preisgabe von Informationen";
out.main_slogan = "Einigkeit ist Stärke - Zusammenarbeit der Schlüssel"; // Der Slogan sollte evtl. besser englisch bleiben.
out.type = {};
out.type.pad = 'Pad';
out.type.code = 'Code';
- out.type.poll = 'Umfrage';
- out.type.slide = 'Präsentation';
-
- out.common_connectionLost = 'Serververbindung verloren';
-
- out.disconnected = 'Getrennt';
- out.synchronizing = 'Synchronisiert';
- out.reconnecting = 'Verbindung wird neu aufgebaut...';
- out.lag = 'Lag';
- out.readonly = 'Nur-Lesen';
- out.anonymous = "Anonymous";
- out.yourself = "Du";
- out.anonymousUsers = "anonyme Nutzer*innen";
- out.anonymousUser = "anonyme Nutzer*in";
- out.users = "Nutzer*innen";
- out.and = "Und";
- out.viewer = "Betrachter*in";
- out.viewers = "Betrachter*innen";
- out.editor = "Bearbeiter*in";
- out.editors = "Bearbeiter*innen";
+ out.type.poll = 'Umfrage';
+ out.type.slide = 'Präsentation';
+ out.type.drive = 'CryptDrive';
+ out.type.whiteboard = 'Whiteboard';
+ out.type.file = 'Datei';
+ out.type.media = 'Medien';
+ out.type.todo = 'Aufgabe';
+ out.type.contacts = 'Kontakte';
+
+ out.button_newpad = 'Neues Pad';
+ out.button_newcode = 'Neues Code Pad';
+ out.button_newpoll = 'Neue Umfrage';
+ out.button_newslide = 'Neue Präsentation';
+ out.button_newwhiteboard = 'Neues Whiteboard';
+
+ // NOTE: Remove updated_0_ if we need an updated_1_
+ out.updated_0_common_connectionLost = "Die Verbindung zum Server ist abgebrochen Du verwendest jetzt das Dokument schreibgeschützt, bis die Verbindung wieder funktioniert.";
+ out.common_connectionLost = out.updated_0_common_connectionLost;
+
+ out.websocketError = 'Verbindung zum Websocket fehlgeschlagen...';
+ out.typeError = "Dieses Dokument ist nicht mit dem Programm kompatibel";
+ out.onLogout = 'Du bist ausgeloggt. {0}Klicke hier{1}, um wieder einzuloggen, oder drucke die Escapetaste, um dein Dokument schreibgeschützt zu benutzen.';
+ out.wrongApp = "Der Inhalt dieser Echtzeitsitzung kann nicht in deinem Browser angezeigt werden. Bitte lade die Seite neu.";
+ out.padNotPinned = 'Dieses Dokument wird nach 3 Monaten ohne Zugang auslaufen, {0}logge dich ein{1} or {2}registriere dich{3}, um das Auslaufen zu verhindern.';
+ out.anonymousStoreDisabled = "Der Webmaster dieses CryptPad Server hat die anonyme Verwendung deaktiviert. Du muss dich einloggen, um CryptDrive zu verwenden.";
+ out.expiredError = 'Dieses Dokument ist abgelaufen und ist nicht mehr verfügbar.';
+ out.deletedError = 'Dieses Dokument wurde von seinem Besitzer gelöscht und nicht mehr verfügbar.';
+ out.inactiveError = 'Dieses Dokument ist wegen Inaktivität gelöscht worden. Drucke auf die Esc-Taste, um ein neues Dokument zu gestalten.';
+ out.chainpadError = 'Ein kritischer Fehler hat stattgefunden, bei den Updates deines Dokuments. Dieses Dokument ist schreibgeschützt, damit du sicher machen kannst, dass keine Inhalt verloren geht. '+
+ 'Druck auf Esc, um das Dokument schreibgeschützt zu lesen, oder lade es neu, um das Editierien wiederanzufangen.';
+ out.errorCopy = ' Du kannst noch den Inhalt woanders kopieren, nachdem du Esc drucken. Wenn du die Seite verlässt, verschwindet der Inhalt für immer!';
+
+ out.loading = "Laden...";
+ out.error = "Fehler";
+ out.saved = "Gespeichert";
+ out.synced = "Alles gespeichert";
+ out.deleted = "Dokumente, die von deinem CryptDrive gelöscht wurden";
+ out.deletedFromServer = "Dokumente, die vom Server gelöscht wurden";
+
+ out.realtime_unrecoverableError = "Das Echtzeitengine hat ein nicht-reparierbaren Fehler getroffen. Klicke auf OK, um neuzuladen.";
+
+ out.disconnected = 'Getrennt';
+ out.synchronizing = 'Synchronisieren';
+ out.reconnecting = 'Verbindung wird aufgebaut';
+ out.typing = "Es wird getippt";
+ out.initializing = "Starten...";
+ out.forgotten = 'Zum Papierkorb verschoben';
+ out.errorState = 'Kritischer Fehler: {0}';
+ out.lag = 'Verspätung';
+ out.readonly = 'schreibgeschützt';
+ out.anonymous = "Anonym";
+ out.yourself = "Du";
+ out.anonymousUsers = "anonyme Nutzer*innen";
+ out.anonymousUser = "anonyme Nutzer*in";
+ out.users = "Nutzer*innen";
+ out.and = "Und";
+ out.viewer = "Betrachter*in";
+ out.viewers = "Betrachter*innen";
+ out.editor = "Bearbeiter*in";
+ out.editors = "Bearbeiter*innen";
+ out.userlist_offline = "Du bist aktuell offline, die Benutzerliste ist nicht verfügbar.";
+
+ out.language = "Sprache";
+
+ out.comingSoon = "Kommt bald...";
+
+ out.newVersion = 'CryptPad wurde aktualisiert! ' +
+ 'Entdecke, was neu in dieser Version ist: '+
+ 'Release notes for CryptPad {0}';
+
+ out.upgrade = "aufrüsten";
+ out.upgradeTitle = "Rüste dein Konto auf, um mehr Speicherplatz zu haben";
+
+ out.upgradeAccount = "Konto aufrüsten";
+ out.MB = "MB";
+ out.GB = "GB";
+ out.KB = "KB";
+
+ out.supportCryptpad = "CryptPad unterstützen";
+
+ out.formattedMB = "{0} MB";
+ out.formattedGB = "{0} GB";
+ out.formattedKB = "{0} KB";
out.greenLight = "Alles funktioniert bestens";
- out.orangeLight = "Deine langsame Verbindung kann die Nutzung beinträchtigen";
+ out.orangeLight = "Deine langsame Verbindung kann die Nutzung beeinträchtigen";
out.redLight = "Du wurdest von dieser Sitzung getrennt";
+ out.pinLimitReached = "Du hast deine Speicherplatzgrenze erreicht";
+ out.updated_0_pinLimitReachedAlert = "Du hast deine Speicherplatzgrenze erreicht. Neue Dokumente werden nicht mehr in deinem CryptDrive gespeichert. " +
+ 'Du kannst entweder Dokument von deinem CryptDrive entfernen oder ein Premiumagenbot anfordern, damit deine Grenze erhöht wird.';
+ out.pinLimitReachedAlert = out.updated_0_pinLimitReachedAlert;
+ out.pinLimitReachedAlertNoAccounts = out.pinLimitReached;
+ out.pinLimitNotPinned = "Du hast deine Speicherplatzgrenze erreicht. "+
+ "Dieses Dokument ist nicht in deinem CryptDrive gespeichert.";
+ out.pinLimitDrive = "Du hast deine Speicherplatzgrenze erreicht. " +
+ "Du kannst keine neue Dokumente gestalten.";
+
+ out.moreActions = "Mehr Aktionen";
+
+ out.importButton = "Importieren";
out.importButtonTitle = 'Importiere eine lokale Datei in dieses Dokument';
+ out.exportButton = "Exportieren";
out.exportButtonTitle = 'Exportiere dieses Dokument in eine lokale Datei';
out.exportPrompt = 'Wie möchtest du die Datei nennen?';
-
- out.changeNamePrompt = 'Ändere deinen Namen (oder lasse dieses Feld leer um anonym mitzuarbeiten): ';
+ out.changeNamePrompt = 'Ändere deinen Namen (oder lasse dieses Feld leer, um anonym mitzuarbeiten): ';
+ out.user_rename = "Bearbeite deinen Name";
+ out.user_displayName = "Name";
+ out.user_accountName = "Kontoname";
out.clickToEdit = "Zum Bearbeiten klicken";
+ out.saveTitle = "Bitte gebe das Titel ein (enter)";
out.forgetButtonTitle = 'Entferne dieses Dokument von deiner Startseitenliste';
+ out.forgetButtonTitle = 'Dieses Dokument zum Papierkorb verschieben';
out.forgetPrompt = 'Mit dem Klick auf OK wird das Dokument aus deinem lokalen Speicher gelöscht. Fortfahren?';
+ out.movedToTrash = 'Dieses Dokument liegt im Papierkorb. Du kannst zum CryptDrive navigieren';
out.shareButton = 'Teilen';
out.shareSuccess = 'Die URL wurde in die Zwischenablage kopiert';
- out.presentButtonTitle = "Präsentationsmodus starten";
+ out.userListButton = "Benutzerliste";
+
+ out.userAccountButton = "Dein Konto";
+
+ out.newButton = 'Neu';
+ out.newButtonTitle = 'Neues Dokument gestalten';
+ out.uploadButton = 'Hochladen';
+ out.uploadButtonTitle = 'Eine neue Datei ins aktuelle Ordner hochladen';
+
+ out.saveTemplateButton = "Als Vorlage speichern";
+ out.saveTemplatePrompt = "Bitte gib ein Titel für die Vorlag ein";
+ out.templateSaved = "Vorlage gespeichert!";
+ out.selectTemplate = "Bitte wähle eine Vorlage oder drucke die Esc Taste";
+ out.useTemplate = "Mit einer Vorlage starten?"; //Would you like to "You have available templates for this type of pad. Do you want to use one?";
+ out.useTemplateOK = 'Wähle ein Template (Enter)';
+ out.useTemplateCancel = 'Frisch starten (Esc)';
+ out.template_import = "Eine Vorlage importieren";
+ out.template_empty = "Keine Vorlage verfügbar";
+
+ out.previewButtonTitle = "Der Markdownvorschau (un)sichtbar machen";
+
+ out.presentButtonTitle = "Zum Präsentationsmodus wechseln";
+
+ out.backgroundButtonTitle = 'Hintergrundfarbe';
+ out.colorButtonTitle = 'Die Hintergrundfrabe des Präsentationsmodus bearbeiten';
- out.backgroundButtonTitle = 'Die Hintergrundfarbe der Präsentation ändern';
- out.colorButtonTitle = 'Die Textfarbe im Präsentationsmodus ändern';
+ out.propertiesButton = "Eigenschaften";
+ out.propertiesButtonTitle = 'Die Eigenschaften des Dokuments ansehen';
- // Hierfür fehlt eine passende Übersetzung...
+ out.printText = "Drucken";
+ out.printButton = "Drucken (enter)";
+ out.printButtonTitle = "Deine Präsentation ausdrucken oder als PDF Dateien exportieren";
+ out.printOptions = "Druckeinstellungen";
+ out.printSlideNumber = "Foliennummer anzeigen";
+ out.printDate = "Datum anzeigen";
+ out.printTitle = "Titel der Präsentation anzeigen";
+ out.printCSS = "Custom CSS Regeln (CSS):";
+ out.printTransition = "Animierte Transitionen aktivieren";
+ out.printBackground = "Ein Hintergrundbild verwenden";
+ out.printBackgroundButton = "Bitte ein Bild wählen";
+ out.printBackgroundValue = "Aktueller Hintergrund:{0}";
+ out.printBackgroundNoValue = "Kein Hintergrundbild gewählt";
+ out.printBackgroundRemove = "Das Hintergrundbild wählen";
- out.editShare = "Mitarbeits-URL teilen";
- out.editShareTitle = "Mitarbeits-URL in die Zwischenablage kopieren";
- out.viewShare = "Nur-Lesen-URL teilen";
- out.viewShareTitle = "Nur-Lesen-URL in die Zwischenablage kopieren";
+ out.filePickerButton = "Eine Datei deines CryptDrives einbetten";
+ out.filePicker_close = "Schliessen";
+ out.filePicker_description = "Bitte wähle eine Datei aus deinem CryptDrive oder lade eine neue hoch";
+ out.filePicker_filter = "Namensfilter";
+ out.or = 'oder';
+
+ out.tags_title = "Tags (for you only)";
+ out.tags_add = "Die Tags dieser Seite bearbeiten";
+ out.tags_searchHint = "Dateien mit Tags in deinem CryptDrive suchen";
+ out.tags_searchHint = "Die Suche mit dem Tag # in deinem CryptDrive starten.";
+ out.tags_notShared = "Deine Tags sind nicht mit anderen Benutzern geteilt";
+ out.tags_duplicate = "Doppeltes Tag: {0}";
+ out.tags_noentry = "Du kannst ein Tag auf einem gelöschten Dokument nicht hinzufügen!";
+
+ out.slideOptionsText = "Einstellungen";
+ out.slideOptionsTitle = "Präsentationseinstellungen";
+ out.slideOptionsButton = "Speichern (enter)";
+ out.slide_invalidLess = "Ungültiges Custom-Stil";
+
+ out.languageButton = "Sprache";
+ out.languageButtonTitle = "Bitte wähle die Sprache für die Syntaxhervorhebung";
+ out.themeButton = "Farbschema";
+ out.themeButtonTitle = "Wähle das Farbschema um Kode und Folieneditor darzustellen";
+
+ out.editShare = "Mitarbeit-URL teilen";
+ out.editShareTitle = "Mitarbeit-URL in die Zwischenablage kopieren";
+ out.editOpen = "Die Mitarbeit-URL in ein neues Tab öffnen";
+ out.editOpenTitle = "Öffne dieses Dokument in Mitarbeitmodus in einem neuem Tab";
+ out.viewShare = "Schreibgeschützt-URL teilen";
+ out.viewShareTitle = "Schreibgeschützt-URL in die Zwischenablage kopieren";
out.viewOpen = "In neuem Tab anzeigen";
- out.viewOpenTitle = "Dokument im Nur-Lesen-Modus in neuem Tab öffnen.";
+ out.viewOpenTitle = "Dokument schreibgeschützt in neuem Tab öffnen.";
+ out.fileShare = "Link kopieren";
+ out.getEmbedCode = "Einbettungscode anzeigen";
+ out.viewEmbedTitle = "Das Dokument in einer externe Webseite einbetten";
+ out.viewEmbedTag = "Um dieses Dokument einzubetten, benutzt du dieses iframe in deiner HTML Seite, wie du wilsst. Du kannst mit CSS oder HTML Attributen das Stil erweitern";
+ out.fileEmbedTitle = "Die Datei in einer externen Seite einbetten";
+ out.fileEmbedScript = "Um diese Datei einzubetten, bringst du dieses Skript einmal in deiner Webseite, damit das Media-Tag geladen wird:";
+ out.fileEmbedTag = "Dann kannst du das Media-Tag, wo du willst auf einder Seite platzieren:";
+
+ out.notifyJoined = "{0} ist in der Mitarbeit-Sitzung ";
+ out.notifyRenamed = "{0} ist jetzt als {1} bekannt";
+ out.notifyLeft = "{0} hat die Mitarbeit-Sitzung verlassen";
+
+ out.okButton = 'OK (enter)';
+
+ out.cancel = "Abbrechen";
+ out.cancelButton = 'Abbrechen (esc)';
+ out.doNotAskAgain = "Nicht mehr fragen (Esc)";
- out.notifyJoined = "{0} ist der gemeinsamen Sitzung beigetreten";
- out.notifyRenamed = "{0} heißt nun {1}";
- out.notifyLeft = "{0} hat die gemeinsame Sitzung verlassen";
+ out.show_help_button = "Hilfe anzeigen";
+ out.hide_help_button = "Hilfe verbergen";
+ out.help_button = "Hilfe";
- out.tryIt = 'Probier\'s aus!';
+ out.historyText = "Verlauf";
+ out.historyButton = "Den Dokumentverlauf anzeigen";
+ out.history_next = "früher";
+ out.history_prev = "Zur früheren Version wechseln";
+ out.history_goTo = "Zur genannten Version wechseln";
+ out.history_close = "Zurück";
+ out.history_closeTitle = "Verlauf schliessen";
+ out.history_restore = "wiederherstellen";
+ out.history_restoreTitle = "Die gewählte Version des Dokuments wiederherstellen";
+ out.history_restorePrompt = "Bist du sicher, dass du die aktuelle Version mit der angezeigten ersetzen möchtest?";
+ out.history_restoreDone = "Version wiederhergestellt";
+ out.history_version = "Version:";
- out.okButton = 'OK (Enter)';
- out.cancelButton = 'Abbrechen (ESC)';
+ // Ckeditor
+ out.openLinkInNewTab = "Link im neuen Tab öffnen";
+ out.pad_mediatagTitle = "Media-Tag Einstellungen";
+ out.pad_mediatagWidth = "Breite (px)";
+ out.pad_mediatagHeight = "Höhe (px)";
// Polls
@@ -108,20 +284,440 @@
out.poll_removeUser = "Bist du sicher, dass du diese(n) Nutzer*in entfernen möchtest?";
out.poll_titleHint = "Titel";
- out.poll_descriptionHint = "Beschreibe deine Abstimmung und publiziere sie mit dem 'Veröffentlichen'-Knopf wenn du fertig bis. Jeder mit dem Link kann die Beschreibung ändern.";
+ out.poll_descriptionHint = "Beschreibe deine Abstimmung und publiziere sie mit dem 'Veröffentlichen'-Knopf wenn du fertig bis."+
+ " Die Beschreibung kann mit dem Markdown Syntax geschrieben werden und du kannst Media-Elemente von deinem CryptPad einbetten." +
+ "Jeder mit dem Link kann die Beschreibung ändern, aber es ist keine gute Praxis.";
+
+ out.poll_remove = "Entfernen";
+ out.poll_edit = "Bearbeiten";
+ out.poll_locked = "Gesperrt";
+ out.poll_unlocked = "Editierbar";
+
+ out.poll_bookmark_col = 'Setze ein Lesezeichen auf dieser Spalte, damit sie immer editierbar und links immer für dich erscheint.';
+ out.poll_bookmarked_col = 'Dieses ist die Splate mit Lesezeichen für dich. Sie wird immer editierbar und links für dich angezeigt.';
+ out.poll_total = 'SUMME';
+
+ out.poll_comment_list = "Komentare";
+ out.poll_comment_add = "Ein Kommentar hinzufügen";
+ out.poll_comment_submit = "Schicken";
+ out.poll_comment_remove = "Dieses Kommentar entfernen";
+ out.poll_comment_placeholder = "Dein Kommentar";
+
+ out.poll_comment_disabled = "Diese Umfrage mit dem ✓ Knopf veröffentlichen, damit die Kommentare möglich sind.";
+
+ // Canvas
+ out.canvas_clear = "Löschen";
+ out.canvas_delete = "Abschnitt entfernen";
+ out.canvas_disable = "Zeichnung deaktivieren";
+ out.canvas_enable = "Zeichnung aktivieren";
+ out.canvas_width = "Breite";
+ out.canvas_opacity = "Deckkraft";
+ out.canvas_opacityLabel = "Deckkraft: {0}";
+ out.canvas_widthLabel = "Breite: {0}";
+ out.canvas_saveToDrive = "Dieses Bild in deinem CryptDrive speichern";
+ out.canvas_currentBrush = "Aktueller Pinsel";
+ out.canvas_chooseColor = "Eine Farbe wählen";
+ out.canvas_imageEmbed = "Ein Bild aus deinem Rechner einbetten";
+
+ // Profile
+ out.profileButton = "Profil"; // dropdown menu
+ out.profile_urlPlaceholder = 'URL';
+ out.profile_namePlaceholder = 'Angezeigte name';
+ out.profile_avatar = "Avatar";
+ out.profile_upload = " Ein neues Avatar hochladen";
+ out.profile_uploadSizeError = "Fehler: Dein Avatar muss kleiner als {0} sein";
+ out.profile_uploadTypeError = "Fehler: Das Typ dieses Bild ist nicht unterestützt. Unterstütze Typen sind: {0}";
+ out.profile_error = "Fehler bei der Erstellung deines Profils: {0}";
+ out.profile_register = "Du muss dich einloggen, um ein Profil zu erstellen!";
+ out.profile_create = "Ein Profil erstellen";
+ out.profile_description = "Beschreibung";
+ out.profile_fieldSaved = 'Neuer Wert gespeichert: {0}';
+
+ out.profile_inviteButton = "Sich in Verbindung setzen";
+ out.profile_inviteButtonTitle ='Ein Link erstellen, damit dieser Benutzer sich mit dir in Verbindung setzt.';
+ out.profile_inviteExplanation = "Ein Klick auf OK wird ein Link erstellen, dass eine sichere Chatsession nur mit {0} erlaubt. Dieses Link kann öffentlich gepostet werden.";
+ out.profile_viewMyProfile = "Mein Profil anzeigen";
+
+ // contacts/userlist
+ out.userlist_addAsFriendTitle = 'Benutzer "{0}" als Kontakt hinzufügen';
+ out.userlist_thisIsYou = 'Das bist du ("{0}")';
+ out.userlist_pending = "Wartet...";
+ out.contacts_title = "Kontakte";
+ out.contacts_addError = 'Fehler bei dem Hinzufügen des Kontakts in die Liste';
+ out.contacts_added = 'Verbindungeinladung angenommen.';
+ out.contacts_rejected = 'Verbindungeinladung abgelehnt';
+ out.contacts_request = 'Benutzer {0} möchtet dich als Kontakt hinzufügen. Annehmen?';
+ out.contacts_send = 'Schicken';
+ out.contacts_remove = 'Dieses Kontakt entfernen';
+ out.contacts_confirmRemove = 'Bist du sicher, dass du {0} von der Kontaktliste entfernen möchtest?';
+ out.contacts_typeHere = "Gebe eine Nachricht ein...";
+
+ out.contacts_info1 = "Diese ist deine Kontaktliste. Ab hier, kannst du:";
+ out.contacts_info2 = "Auf dem Avatar eines Kontakts klicken, um mit diesem Benutzer zu chatten";
+ out.contacts_info3 = "Das Avatar doppelklicken, um sein Profil anzuzeigen";
+ out.contacts_info4 = "Jede Teilnehmer, kann den Chatverlauf löschen";
+
+ out.contacts_removeHistoryTitle = 'Den Chatverlauf löschen';
+ out.contacts_confirmRemoveHistory = 'Bist du sicher, den Chatverlauf komplett zu löschen? Die Daten sind dann weg.';
+ out.contacts_removeHistoryServerError = 'Es gab ein Fehler bei dem Löschen des Chatverlaufs. Versuche es noch einmal später';
+ out.contacts_fetchHistory = "Den früheren Verlauf laden";
+
+ // File manager
+
+ out.fm_rootName = "Dokumente";
+ out.fm_trashName = "Papierkorb";
+ out.fm_unsortedName = "Dateien (ohne Ordnung)";
+ out.fm_filesDataName = "Alle Dateien";
+ out.fm_templateName = "Vorlagen";
+ out.fm_searchName = "Suchen";
+ out.fm_recentPadsName = "Zuletzt geöffnete Dokumente";
+ out.fm_ownedPadsName = "Eigene";
+ out.fm_searchPlaceholder = "Suchen...";
+ out.fm_newButton = "Neu";
+ out.fm_newButtonTitle = "Ein neues Dokument oder Ordner gestalten, oder eine Datei in dem aktuellen Ordner importiere";
+ out.fm_newFolder = "Neuer Ordner";
+ out.fm_newFile = "Neues Dokument";
+ out.fm_folder = "Ordner";
+ out.fm_folderName = "Ordnername";
+ out.fm_numberOfFolders = "# von Ordnern";
+ out.fm_numberOfFiles = "# von Dateien";
+ out.fm_fileName = "Dateiname";
+ out.fm_title = "Titel";
+ out.fm_type = "Typ";
+ out.fm_lastAccess = "Zuletzt besucht";
+ out.fm_creation = "Erstellung";
+ out.fm_forbidden = "Verbotene Aktion";
+ out.fm_originalPath = "Herkunft Pfad";
+ out.fm_openParent = "Im Ordner zeigen";
+ out.fm_noname = "Dokument ohne Titel";
+ out.fm_emptyTrashDialog = "Soll der Papierkorb wirklich gelöscht werden?";
+ out.fm_removeSeveralPermanentlyDialog = "Bist sicher, diese {0} Elemente dauerhaft aus deinem CryptDrive zu entfernen?";
+ out.fm_removePermanentlyDialog = "Bist sicher, dieses Element dauerhaft aus deinem CryptDrive zu entfernen?";
+ out.fm_removeSeveralDialog = "Bist du sicher, diese {0} Elemente aus dem Papierkorb zu entfernen?";
+ out.fm_removeDialog = "Bist sicher, {0} zum Papierkorb zu verschieben?";
+ out.fm_deleteOwnedPad = "Bist du sicher, dieses Dokument aus dem Server dauerhaft zu löschen?";
+ out.fm_deleteOwnedPads = "Bist du sicher, dass du diese Dokumente dauerhaft aus dem Server entfernen möchtest?";
+ out.fm_restoreDialog = "Bist du sicher, dass du {0} zurück zum originalen Ordner verschieben möchtests?";
+ out.fm_unknownFolderError = "Der Ordner, der gerade gewählt oder letzlich besucht wurde, existiert nicht mehr. Der Parentordner wird geöffnet...";
+ out.fm_contextMenuError = "Fehler bei der Öfnnung des Kontextmenü für dieses Element. Wenn dieses Problem wieder erscheint, versuche die Seite neuzuladen.";
+ out.fm_selectError = "Fehler bei der Selektierung des Zielelements. Wenn dieses Problem wieder erscheint, versuche die Seite neuzuladen.";
+ out.fm_categoryError = "Fehler bei dem Öffnen der selektierten Kategorie. Der Wurzel wird angezeigt.";
+ out.fm_info_root = "Gestalte hier soviele Ordnern, wie du willst, um deine Dateien und Dokumente zu organisieren.";
+ out.fm_info_unsorted = 'Hier sind alle Dateien, die du besucht hast, noch nicht in "Dokumente" sortiert sind oder zum Papierkorb verschoben wurden.';
+ out.fm_info_template = 'Hier sind alle Dokumente, die als Vorlage gespeichert wurden und die du wiederverwenden kannst, um ein neues Dokument zu erstellen.';
+ out.fm_info_recent = "Liste der zuletzt geöffnete Dokumente.";
+ out.updated_0_fm_info_trash = 'Leere den Papierkorb, um mehr freien Platz in deinem CryptDrive zu erhalten.';
+ out.fm_info_trash = out.updated_0_fm_info_trash;
+ out.fm_info_allFiles = 'Beinhaltet alle Dateien von "Dokumente", "Unklassifiziert" und "Papierkorb". Dateien können hier nicht verschoben werden.';
+ out.fm_info_anonymous = 'Du bist nicht eingeloggt, daher laufen die Dokumente nach 3 Monaten aus (mehr dazu lesen). ' +
+ 'Zugang zu den Dokumenten ist in deinem Browser gespeichert, daher wir das Löschen des Browserverlaufs auch die Dokumente verschwinden lassen. ' +
+ 'Registriere dich oder logge dich ein, um sie dauerhaft zu machen. ';
+ out.fm_info_owned = "Diese Dokumente sind deine eigene. Das heisst, dass du sie vom Server entfernen kannst, wann du willst. Wenn du das machst, dann wird es auch keinen Zugriff zu diesem für andere Benutzer geben.";
+ out.fm_alert_backupUrl = "Backuplink für dieses CryptDrive. " +
+ "Es ist hoch empfohlen dieses Link geheim zu halten. " +
+ "Du kannst es benutzen, um deine gesamte Dateien abzurufen, wenn dein Browserspeicher gelöscht wurde. " +
+ "Jede Person, die dieses Link hat, kann die Dateien in deinem CryptDrive bearbeiten oder löschen. ";
+ out.fm_alert_anonymous = "Hallo, du benutzt CryptPad anonym. Das ist in Ordnung aber Dokumente können nach einer Inaktivitätsperiode gelöscht werden. " +
+ "Wir haben fortgeschrittene Aktionen aus dem anonymen CryptDrive entfernt, weil wir klar machen wollen, dass es kein sicherer Platz ist, Dinge zu lagern." +
+ 'Du kannst lesen, weshalb wir das machen und weshalb du wirklich ' +
+ 'registrieren oder eingloggen solltest.';
+ out.fm_backup_title = 'Backup link';
+ out.fm_nameFile = 'Wie soll diese Datei heissen?';
+ out.fm_error_cantPin = "Interner Serverfehler. Bitte lade die Seite neu und versuche es wieder.";
+ out.fm_viewListButton = "Listeansicht";
+ out.fm_viewGridButton = "Kachelnansicht";
+ out.fm_renamedPad = "Du hast ein spezielle Name für dieses Dokument gesetzt. Seine geteiltes Titel ist: {0}";
+ out.fm_prop_tagsList = "Tags";
+ out.fm_burnThisDriveButton = "Alle Informationen löschen , die CryptPad in deinem Browser hält";
+ out.fm_burnThisDrive = "Bist du sicher, dass du alles, was CryptPad in deinem Browser hält löschen möchtest? " +
+ "Das wird dein CryptDrive und seinen Verlauf von deinem Browser löschen, Dokumente werden noch auf unseres Server (verschlüsselt) bleiben.";
+ out.fm_padIsOwned = "Dieses Dokument ist dein Eigenes";
+ out.fm_padIsOwnedOther = "Dieses Dokument ist von einem anderen Benutzer";
+ out.fm_deletedPads = "Dieses Dokument existiert nicht mehr auf dem Server, es wurde von deinem CryptDrive gelöscht: {0}";
+ // File - Context menu
+ out.fc_newfolder = "Neuer Ordner";
+ out.fc_rename = "Unbenennen";
+ out.fc_open = "Öffnen";
+ out.fc_open_ro = "Öffnen (schreibgeschützt)";
+ out.fc_delete = "Zum Papierkorb verschieben";
+ out.fc_delete_owned = "Vom Server löschen";
+ out.fc_restore = "Restaurieren";
+ out.fc_remove = "Von deinem CryptDrive entfernen";
+ out.fc_empty = "Den Papierkorb leeren";
+ out.fc_prop = "Eigenschaften";
+ out.fc_hashtag = "Tags";
+ out.fc_sizeInKilobytes = "Grösse in Kilobytes";
+ // fileObject.js (logs)
+ out.fo_moveUnsortedError = "Du kannst, ein Ordner nicht in die Liste von allen Pads verschieben";
+ out.fo_existingNameError = "Dieser Dokumentname ist in diesem Verzeichnis schon da. Bitte wähle einen Anderen.";
+ out.fo_moveFolderToChildError = "Du kannst ein Ordner nicht in eine seine Nachfolgern verchieben";
+ out.fo_unableToRestore = "Ein Fehler ist aufgetreten, um diese Datei zu seinem Herkunftordner zu verschieben. Du kannst probieren, diese zu einem anderen Ordner zu verschieben.";
+ out.fo_unavailableName = "Ein Dokument oder Ordner mit dem selben Name existiert in diesem Ordner schon. Bitte benenne es zuerst um, und versucht wieder zu verschieben.";
+
+ out.fs_migration = "Dein CryptDrive wird gerade zu einer neueren Version aktualisiert. Daher muss die Seite neugeladen werden. Bite lade die Seite neu, um sie weiter zu verwenden.";
+
+ // login
+ out.login_login = "Einloggen";
+ out.login_makeAPad = 'Ein Dokument anonym erstellen';
+ out.login_nologin = "Lokale Dokumente ansehen";
+ out.login_register = "Registrieren";
+ out.logoutButton = "Ausloggen";
+ out.settingsButton = "Einstellungen";
+
+ out.login_username = "Benutzername";
+ out.login_password = "Passwort";
+ out.login_confirm = "Passwort bestätigen";
+ out.login_remember = "Mein Login speichern";
+
+ out.login_hashing = "Dein Passwort wird gerade durchgerechnet, das kann etwas dauert.";
+
+ out.login_hello = 'Hallo {0},'; // {0} is the username
+ out.login_helloNoName = 'Hallo,';
+ out.login_accessDrive = 'Dein CryptDrive ansehen';
+ out.login_orNoLogin = 'oder';
+
+ out.login_noSuchUser = 'Ungültiger Benutzername oder Passwort. Versuche es erneute oder registriere';
+ out.login_invalUser = 'Der Benutzername kann nicht leer sein';
+ out.login_invalPass = 'Der Passwort kann nicht leer sein';
+ out.login_unhandledError = 'Ein Fehler ist aufgetreten:(';
+
+ out.register_importRecent = "Die Dokumente aus deiner anonymen Sitzung importieren";
+ out.register_acceptTerms = "Ich bin mit den Servicebedingungen einverstanden";
+ out.register_passwordsDontMatch = "Passwörter sind nicht gleich!";
+ out.register_passwordTooShort = "Passwörter müssen mindestens {0} Buchstaben haben.";
+
+ out.register_mustAcceptTerms = "Du musst mit den Servicebedingungen einverstanden sein.";
+ out.register_mustRememberPass = "Wir können dein Passwort nicht zurücksetzen, im Fall dass du dieses vergisst. Es ist äusserst wichtig, dass du dieses erinnerst! Bitte ticke das Kästchen ein.";
+
+ out.register_whyRegister = "Wieso sollst du dich registrieren?";
+ out.register_header = "Willkommen zu CryptPad";
+ out.register_explanation = [
+ "
Lass uns ein Paar Punkte überprüfen:
",
+ "
",
+ "
Dein Passwort ist dein Geheimnis, um alle deine Dokumente zu verschlüsseln. Wenn du es verlierst, gibt es keine Methode die Daten zurückzufinden.
",
+ "
Du kannst die Dokumente, die du letzlich angesehen hast importieren, damit sind sie in deinem CryptDrive.
",
+ "
Wenn du auf einem geteilten Rechner bist, muss du ausloggen, wenn du fertig bist. Es ist nicht ausreichend, die Browserfensters (oder das Browser) zu schliessen.
",
+ "
"
+ ].join('');
+
+ out.register_writtenPassword = "Ich habe meinen Benutzername und Passwort notiert. Weiter geht's.";
+ out.register_cancel = "Zurück";
+
+ out.register_warning = "\"Ohne Preisgabe von Information\" heisst, dass wir keine Methode haben, wenn du dein Passwort verlierst.";
+
+ out.register_alreadyRegistered = "Dieser Benutzer existiert schon, willst du dich einloggen?";
+
+ // Settings
+ out.settings_cat_account = "Konto";
+ out.settings_cat_drive = "CryptDrive";
+ out.settings_cat_code = "Code";
+ out.settings_cat_pad = "Rich text";
+ out.settings_cat_creation = "Neues Dokument";
+ out.settings_cat_subscription = "Registrierung";
+ out.settings_title = "Einstellungen";
+ out.settings_save = "Speichern";
+
+ out.settings_backupCategory = "Backup";
+ out.settings_backupTitle = "Eine Backup erstellen oder die Daten restaurieren";
+ out.settings_backup = "Backup";
+ out.settings_restore = "Restaurieren";
+
+ out.settings_resetNewTitle = "CryptDrive säubern";
+ out.settings_resetButton = "Löschen";
+ out.settings_reset = "Alle Dateien und Ordnern aus deinem CryptDrive löschen";
+ out.settings_resetPrompt = "Diese Aktion wird alle Dokumente deines CryptDrives entfernen. "+
+ "Bist du sicher, dass du es tun möchtest? " +
+ "Gebe I love CryptPad ein, um zu bestätigen."; // TODO: I love CryptPad should be localized
+ out.settings_resetDone = "Dein CryptDrive ist jetzt leer!";
+ out.settings_resetError = "Prüftext inkorrekt. Dein CryptDrive wurde nicht verändert.";
+
+ out.settings_resetTipsAction = "Zurücksetzen";
+ out.settings_resetTips = "Tipps";
+ out.settings_resetTipsButton = "Die Tipps für CryptDrive zurücksetzen";
+ out.settings_resetTipsDone = "Alle Tipps sind wieder sichtbar.";
+
+ out.settings_thumbnails = "Vorschaubilder";
+ out.settings_disableThumbnailsAction = "Die Gestaltung von Vorschaubilder in deinem CryptPad deaktivieren";
+ out.settings_disableThumbnailsDescription = "Vorschaubilder sind automatisch erstellt und gespeichert in deinem Browser, wenn du ein Dokument besuchst. Du kannst dieses Feature hier deaktivieren.";
+ out.settings_resetThumbnailsAction = "Entfernen";
+ out.settings_resetThumbnailsDescription = "Alle Vorschaubilder entfernen, die in deinem Browser gespeichert sind.";
+ out.settings_resetThumbnailsDone = "Alle Vorschaubilder sind entfern worden.";
+
+ out.settings_importTitle = "Importierie die neulich besuchte Dokumente in deinem CryptDrive";
+ out.settings_import = "Importieren";
+ out.settings_importConfirm = "Bist du sicher, dass du die neulich besuchte Dokumente in deinem Konto importieren möchtest??";
+ out.settings_importDone = "Import erledigt";
+
+ out.settings_userFeedbackTitle = "Rückmeldung";
+ out.settings_userFeedbackHint1 = "CryptPad gibt grundlegende Rückmeldungen zum Server. Es erlaubt uns, wie wir deine Erfahrung verbessern können.";
+ out.settings_userFeedbackHint2 = "Der Inhalt deiner Dokumente wird nie mit dem Server geteilt.";
+ out.settings_userFeedback = "Rückmeldungen aktivieren";
+
+ out.settings_deleteTitle = "Löschung des Kontos";
+ out.settings_deleteHint = "Die Löschung eines Kontos ist dauerhaft. Dein CryptDrive und eigene Dokumente werden alle von dem Server gelöscht. Die restliche Dokumente werden nach 90 Tage gelöscht, wenn keine andere diese bei sich gelagert haben.";
+ out.settings_deleteButton = "Dein Konto löschen";
+ out.settings_deleteModal = "Gebe die folgende Information zu deinem CryptPad Adminstrator, damit er die Daten vom Server löschen kann.";
+ out.settings_deleteConfirm = "OK klicken wird dein Konto dauerhaft löschen. Bist du sicher?";
+ out.settings_deleted = "Dein Konto ist jetzt gelöscht. Drucke OK, um zum Homepage zu gelingen.";
+
+ out.settings_anonymous = "Du bist nicht eingeloggt. Die Einstellungen hier sind spezifisch zu deinem Browser.";
+ out.settings_publicSigningKey = "Öffentliche Schlüssel zum Unterschreiben";
+
+ out.settings_usage = "Verbrauch";
+ out.settings_usageTitle = "Die Gesamtgrösse deiner Dokumente in MB"; // TODO: pinned ??
+ out.settings_pinningNotAvailable = "Genagelte Dokumente sind nur für angemeldete Benutzer verfügbar.";
+ out.settings_pinningError = "Etwas ging schief";
+ out.settings_usageAmount = "Deine genagelte Dokumente verwenden {0}MB";
+
+ out.settings_logoutEverywhereButton = "Ausloggen";
+ out.settings_logoutEverywhereTitle = "Von jeden Browsers ausloggen";
+ out.settings_logoutEverywhere = "Das Ausloggen in alle andere Websitzungen erzwingen";
+ out.settings_logoutEverywhereConfirm = "Bist du sicher? Du wirdst auf allen deinen Geräten dich wieder einloggen müssen.";
+
+ out.settings_codeIndentation = 'Einrücken für das Codeeditor (Leerzeichen)';
+ out.settings_codeUseTabs = "Mit Tabs einrücken (anstatt mit Leerzeichen)";
+
+ out.settings_padWidth = "Maximumgrösse des Editors";
+ out.settings_padWidthHint = "Rich-text Dokumente benutzen normalerweise die grösste verfügbare Breite und es kann manchmal schwer lesebar sein. Du kannst die Breite des Editors hier reduzieren.";
+ out.settings_padWidthLabel = "Die Breite des Editors reduzieren";
+
+ out.settings_creationSkip = "Das Erstellungsdialg für neue Dokumente vermeiden";
+ out.settings_creationSkipHint = "Dieses Erstellungsdialog erlaubt Einstellungen vorzunehmen, um mehr Sicherheit und Kontroll für deine Dokumente zu geben. Aber es kann manchaml dir verlangsam, da es eine zusätzliche Stufe verlange. Mit dieser Option kannst du die dieses Dialog vermeiden und die default-Einstellungen wählen.";
+ out.settings_creationSkipTrue = "vermeiden";
+ out.settings_creationSkipFalse = "anzeigen";
+
+ out.settings_templateSkip = "Die Wahl der Vorlage vermeiden";
+ out.settings_templateSkipHint = "Wenn du ein neues Dokument erstellt, und wenn Vorlagen da sind, erscheint ein Dialog, wo du die Vorlage wählen kannst. Hier kannst du dieses Dialog vermeiden und somit keine Vorlage verwenden.";
+
+ out.upload_title = "Datei hochladen";
+ out.upload_rename = "Willst du einen neuen Name für {0} geben, bevor es zum Server hochgeladen wird? " +
+ "Die Dateieendung ({1}) wird automatisch hinzugefügt. "+
+ "Dieser Name bleibt für immer und wird für die andere Benutzer sichtbar.";
+ out.upload_serverError = "Serverfehler: Die Datei kann nicht aktuell hochgeladen werden. ";
+ out.upload_uploadPending = "Ein anderes Hochladen passiert gerade. Willst du es abbrechen und deine neue Datei hochladen?";
+ out.upload_success = "Deine Datei ({0}) wurde erfolgreich hochgeladen und in deinem CryptDrive hinzugefügt.";
+ out.upload_notEnoughSpace = "Der verfügbare Speicherplatz auf deinem CryptDrive reicht leider nicht für diese Datei.";
+ out.upload_notEnoughSpaceBrief = "Unsaureichende Volumen";
+ out.upload_tooLarge = "Diese Datei ist zu gross, um hochgeladen zu werden.";
+ out.upload_tooLargeBrief = 'Datei zu gross';
+ out.upload_choose = "Eine Datei wählen";
+ out.upload_pending = "In der Warteschlange";
+ out.upload_cancelled = "Abgebrochen";
+ out.upload_name = "Dateiname";
+ out.upload_size = "Grösse";
+ out.upload_progress = "Fortschritt";
+ out.upload_mustLogin = "Du muss eingeloggt sein, um Dateien hochzuladen";
+ out.download_button = "Entschlüsseln und runterladen";
+ out.download_mt_button = "Runterladen";
+ out.download_resourceNotAvailable = "Diese Ressource war nicht verfügbar..";
+
+ out.todo_title = "CryptTodo";
+ out.todo_newTodoNamePlaceholder = "Die Aufgabe prüfen...";
+ out.todo_newTodoNameTitle = "Diese Aufgabe zu deiner ToDo-Liste hinzufügen";
+ out.todo_markAsCompleteTitle = "Diese Aufgabe als erledigt markieren";
+ out.todo_markAsIncompleteTitle = "Diese Aufgabe als nicht erledigt markieren";
+ out.todo_removeTaskTitle = "Diese Aufgabe aus deiner ToDo-Liste entfernen";
+
+ // pad
+ out.pad_showToolbar = "Werkzeugsleiste anzeigen";
+ out.pad_hideToolbar = "Werkzeugsleiste verbergen";
+
+ // markdown toolbar
+ out.mdToolbar_button = "Die Markdown Werkzeugsleiste anzeigen oder verbergen";
+ out.mdToolbar_defaultText = "Dein Text hier";
+ out.mdToolbar_help = "Hilfe";
+ out.mdToolbar_tutorial = "http://www.markdowntutorial.com/";
+ out.mdToolbar_bold = "Fett";
+ out.mdToolbar_italic = "Kursiv";
+ out.mdToolbar_strikethrough = "Durchgestrichen";
+ out.mdToolbar_heading = "Kopfzeile";
+ out.mdToolbar_link = "Link";
+ out.mdToolbar_quote = "Zitat";
+ out.mdToolbar_nlist = "Nummerierte Liste";
+ out.mdToolbar_list = "Aufählung";
+ out.mdToolbar_check = "Aufgabenliste";
+ out.mdToolbar_code = "Code";
// index.html
- out.main_howitworks = 'Wie es funktioniert';
- out.main_p2 = 'Dieses Projekt nutzt den CKEditor visuellen Editor, CodeMirror, und die ChainPad realtime engine.';
- out.main_howitworks_p1 = 'CryptPad nutzt eine Variante des Operational transformation Algorithmus. Dieser kann mit Hilfe der Nakamoto Blockchain, einem Konstrukt, das durch das Bitcoin-Projekt Bekanntheit erlangte, verteilten Konsens (distributed consensus) finden. Damit ist der Algorithmus nicht auf einen zentralen Server angewiesen um Konflikte zu lösen — der Server muss also nichts vom Inhalt der Pads wissen.';
- out.main_about_p2 = 'Für Fragen und Kommentare kannst du uns tweeten, ein Ticket auf Github öffnen, hi auf irc sagen (irc.freenode.net), oder eine Mail zukommen lassen.';
+ //about.html
+ out.main_about_p2 = 'Dieses Projekt verwendet CKEditor WYSIWYG Editor, CodeMirror, sowie das ChainPad echtzeit Engine.';
+
+ out.main_howitworks_p1 = 'CryptPad verwendet ein alternative Operational transformation Algorithmus, der verteilt Konsens mit einem Nakamoto Blockchain erreicht, eine Informationskonstrukt, was für Bitcoin bekannt wurde. Damit kann der Algorithmus ohne die Mitarbeit eines zentrales Server die Konflikte von Operational Transform lösen; dadurch kann der Server auch ohne Kenntnisse des Inhalts der Dokumente bleiben.';
- out.button_newpad = 'Neues WYSIWYG-Pad erstellen';
- out.button_newcode = 'Neues Code-Pad erstellen';
- out.button_newpoll = 'Neue Abstimmung erstellen';
- out.button_newslide = 'Neue Präsentation erstellen';
+ // contact.html
+ out.main_about_p2 = 'Wenn du Fragen oder Kommentare hast, hören wir sie gern! Du kannst uns antweeten, ein Issue öffnen on GitHub. Komm und sag hallo auf our der Matrix Kanal or IRC (#cryptpad on irc.freenode.net), or schick uns ein Email.';
+ out.main_about_p22 = 'Uns antweeten';
+ out.main_about_p23 = 'ein Issue auf GitHub aufnehmen';
+ out.main_about_p24 = 'Hallo sagen (Matrix)';
+ out.main_about_p25 = 'uns ein Email schicken';
+ out.main_about_p26 = 'Wenn du Fragen oder Kommentare hast, freuen wir davon zu hören!';
+
+ out.main_info = "
Vertrauenswürdige Kollaboration
Lass deine Ideen wachsen während die ohne Preisgabe deiner Informationen Technologie deinen Datenschutz sogar gegenüber uns sichert.";
+ out.main_catch_phrase = "Das Cloud ohne Preisgabe deiner Informationen";
+
+ out.main_howitworks = 'Wie fukntioniert es';
+ out.main_zeroKnowledge = 'Ohne Preisgabe deiner Informationen';
+ out.main_zeroKnowledge_p = "Du brauchst nicht uns dein Vertrauen geben, dass wir deine Dokumente nicht angucken werden: Mit der Technologie können wir das einfach nicht. Erfahre mehr, wie wir dein Datenschutz und Sicherheit sichern.";
+
+ out.main_writeItDown = 'Runterschreiben';
+
+ out.main_writeItDown_p = "Die grösste Projekte stammen aus den kleinsten Ideen. Schreibe runter deine Inspirationsmomente und unerwartete Ideen, da du nie weisst, welche ein Durchbruch sein wird.";
+ out.main_share = 'Teile das Link, teile das Dokument';
+ out.main_share_p = "Lasse deine Ideen gemeinsam wachsen: Führe effektive Treffen, kooperiere auf ToDo-Listen und mache kurze Vorträge mit alle deinen Bekannten und mit alle deinen Geräten.";
+ out.main_organize = 'Organisiere dich';
+ out.main_organize_p = "Mit CryptPad Drive kannst du deinen Übersicht auf das Wichtiges behalten. Ordnern erlauben, deine Projekte zu organisieren und einen Übersicht behalten, über was geht wo.";
+ out.tryIt = 'Versuche es!';
+ out.main_richText = 'Rich Text Editor';
+ out.main_richText_p = 'Bearbeite Rich-Text kollaborativ mit unserem echtzeit CkEditor ohne Preisgabe deiner Informationen app.';
+ out.main_code = 'Code Editor';
+ out.main_code_p = 'Bearbeite Code kollaborativ mit unseren echtzeit CodeMirror app ohne Preisgabe deiner Informationen.';
+ out.main_slide = 'Präsentationeneditor';
+ out.main_slide_p = 'Gestalte Präsentationen mit der Markdown Syntax und zeige sid im Browser an.';
+ out.main_poll = 'Umfragen';
+ out.main_poll_p = 'Plane ein Treffen oder ein Event, und lass die beste Wahl online treffen.';
+ out.main_drive = 'CryptDrive';
+
+ out.main_richTextPad = 'Rich Text Dokument';
+ out.main_codePad = 'Markdown/Code Dokument';
+ out.main_slidePad = 'Markdown Präsentation';
+ out.main_pollPad = 'Umfrage oder Terminabstimmung';
+ out.main_whiteboardPad = 'Whiteboard';
+ out.main_localPads = 'Lokale Dokumente';
+ out.main_yourCryptDrive = 'Dein CryptDrive';
+ out.main_footerText = "Mit CryptPad, du kannst schnell kollaborative Dokumente erstellen, um Notizzen oder Ideen zusammen runterzuschreiben.";
+
+ out.footer_applications = "Apps";
+ out.footer_contact = "Kontakt";
+ out.footer_aboutUs = "Über uns";
+
+ out.about = "Über uns";
+ out.privacy = "Datenschutz";
+ out.contact = "Kontakt";
+ out.terms = "Servicebendingungen";
+ out.blog = "Blog";
+
+ out.topbar_whatIsCryptpad = "Was ist CryptPad";
+
+ // what-is-cryptpad.html
+
+ out.whatis_title = 'Was ist CryptPad';
+ out.whatis_collaboration = 'Effektive und und leichte Kollaboration';
+ out.whatis_collaboration_p1 = 'Mit CryptPad kannst du kollaborative Dokumente erstellen, um Notizzen und Ideen zusammen runterzuschreiben. Wenn du dich registrierst und loggst dich ein, kriegst die Möglichkeit Dateien hochzuladen, und Ordnern um alle deine Dokumente zu organisieren.';
+ out.whatis_collaboration_p2 = 'Du kannst Zugang zu einem CryptPad teilen, indem du das Link teilst. Du kannst auch einen schreibgeschützten Zugang, um die Ergebnisse deiner kollaborativen Arbeit zu veröffentlichen, während du sie noch bearbeiten kannst.';
+ out.whatis_collaboration_p3 = 'Du kannst Rich-Text Dokumente mit dem CKEditor sowie Markdown Dokumente, die in Echtzeit angezeigt werden, während du tipps. Du kannst auch die Umfrage App verwenden, um Ereignisse unter mehrere Teilnehmern zu synchroniseren.';
+ out.whatis_zeroknowledge = 'Ohne Preisgabe von Informationen';
+ out.whatis_zeroknowledge_p1 = "Wir wollen nicht wissen, was du gerade tippst. Und mit modernen Verschlüsselungstechnologie, du kannst sicher sein, dass wir nicht es nicht können. CryptPad verwendet 100% Clientseitige Verschlüsselung, um den Inhalt von uns zu schützen, wir die Personen die das Website hosten.";
+ out.whatis_zeroknowledge_p2 = 'Wenn du dich registrierst und logge dich ein, dein Benutzername und Passwort sind in einem Schlüssel umgerechnet mit einer Scrypt Ableitungsfunktion. Weder ist dieser Schlüssel noch der Benutzername oder Passwort sind zum Server geschickt. Anstatt dessen sind sie benutzt clientseitig, um den Inhalt deinese CryptDrives zu entschlüsseln. Dieses beinhaltet alle Dokumente, die die zugänglich sind.';
+ out.whatis_zeroknowledge_p3 = 'Wenn du ein Dokument teilst, teilst auch den kryptografischen Schlüssel, der Zugang zu diesem Dokument gibt. Da dieser Schlüssel im fragment identifier ist, ist das nie direkt zum Server geschickt. Bitte lese unsere Blogeintrag über Datenschutz um mehr zu erfahren, welche Typen von Kontextinformation wir zugänglich und nicht zugänglich haben.';
+ out.whatis_drive = 'Organisieren mit CryptDrive';
+ out.whatis_drive_p1 = 'Sobald ein Dokument mit CryptPad zugegriffen wird, ist deses automatisch zu deinem CryptDrive hinzugefügt, im Stamm Ordner. Später kannst du diese Dokumente in Ordnern organisieren oder du kannst es im Papierkorb verschieben. CryptDrive erlaubt die Suche durch deine Dokumente, wie und wann du willst.';
+ out.whatis_drive_p2 = 'Mit dem einfachsten Ziehen und Schieben Gesten kannst du die Dokumente herum von deinem Drive umplatzieren. Die Links zu diesen Dokumenten bleiben erhalten damit Kollaboratoren nie Zugang verlieren.';
+ out.whatis_drive_p3 = 'Du kannst auch Dateien in dein CryptDrive hochladen und mit deinen Kollegen teilen. Hochgeladene Dateien können genau so wie kollaborative Dokumente organisiert werden.';
+ out.whatis_business = 'CryptPad im Business';
+ out.whatis_business_p1 = 'Der Grundprinzip von CryptPad (Verschlüsselung ohne Preisgabe der Information) ist ausgezeichnet, um die Effektivität von existierenden Protokolle, indem die Zugangsberechtigungen des Unternehmens in die Kryptografie umgesetzt werden. Weil hochsensible Medien nur mit Angestelltenzugang entschlüsselt werden kann, kann CryptPad das Jackpot der Hackers wegnehmen, was in der Natur von tradionellen IT Servers liegt. Lese das CryptPad Whitepaper, um mehr zu erfahren, wir CryptPad dein Unternehmen helfen kann.';
+ out.whatis_business_p2 = 'CryptPad kann auf eigenen Rechnern installiert werden. CryptPad Entwicklers at XWiki SAS können kommerzielle Unterstützung, Customisierung und Entwicklung anbieten. Bitte schicke ein Email zu sales@cryptpad.fr, um mehr zu erfahren.';
// privacy.html
@@ -143,6 +739,251 @@
out.policy_choices_vpn = 'Wenn du unsere gehostete Instanz nutzen möchtest bitten wir dich darum IP-Adresse zu verschleiern, das geht zum Beispiel mit dem Tor browser bundle, oder einem VPN-Zugang.';
out.policy_choices_ads = 'Wenn du unsere Analysesoftware blockieren möchtest kannst du Adblock-Software wie Privacy Badger verwenden.';
+ // features.html
+
+ out.features = "Funktionen";
+ out.features_title = "Tabelle der Funktionen";
+ out.features_feature = "Funktion";
+ out.features_anon = "Anonymer Benutzer";
+ out.features_registered = "Angemeldete Benutzer";
+ out.features_notes = "Notizzen";
+ out.features_f_pad = "Ein Dokument erstellen/bearbeiten/ansehen";
+ out.features_f_pad_notes = "Rich Text, Code, Präsentation, Umfrage und Whiteboard Apps";
+ out.features_f_history = "Verlauf";
+ out.features_f_history_notes = "Jegliche Version deines Dokuments ansehen und zurückbringen";
+ out.features_f_todo = "Eine ToDo-Liste erstellen";
+ out.features_f_drive = "CryptDrive";
+ out.features_f_drive_notes = "Einfache Funktionen für anonyme Benutzer";
+ out.features_f_export = "Export/Import";
+ out.features_f_export_notes = "Für Dokumente und CryptDrive";
+ out.features_f_viewFiles = "Dateien ansehen";
+ out.features_f_uploadFiles = "Dateien hochladen";
+ out.features_f_embedFiles = "Dateien einbetten";
+ out.features_f_embedFiles_notes = "Eine Datei in ein Dokument einbetten, die im CryptDrive steht";
+ out.features_f_multiple = "Verwendung auf mehrere Geräte";
+ out.features_f_multiple_notes = "Eine leichte Methode, deine Dokumente von jeglichem Gerät zu verwenden";
+ out.features_f_logoutEverywhere = "Auf allen Geräten ausloggen";
+ out.features_f_logoutEverywhere_notes = ""; // Used in the French translation to explain
+ out.features_f_templates = "Vorlagen verwenden";
+ out.features_f_templates_notes = "Neue Vorlagen erstellen und neue Dokumente aus den Vorlagen erstellen";
+ out.features_f_profile = "Ein Profil erstellen";
+ out.features_f_profile_notes = "Persönliche Seite, mit ein Benutzerbild und eine Beschreibung";
+ out.features_f_tags = "Tags anwenden";
+ out.features_f_tags_notes = "Erlaubt dich in CryptDrive anhand Tags zu suchen";
+ out.features_f_contacts = "Kontakte App";
+ out.features_f_contacts_notes = "Kontakte hinzufügen und mit den in einer verschlüsselte Sitzung chatten";
+ out.features_f_storage = "Speicherplatz";
+ out.features_f_storage_anon = "Dokumente sind nach 3 Monate gelöscht";
+ out.features_f_storage_registered = "Frei: 50MB Premium: 5GB/20GB/50GB";
+
+ // faq.html
+
+ out.faq_link = "FAQ";
+ out.faq_title = "Häufigste Fragen";
+ out.faq_whatis = "Was ist CryptPad?";
+ out.faq = {};
+ out.faq.keywords = {
+ title: 'Schlüsselkonzepte',
+ pad: {
+ q: "Was ist ein CryptPad Dokument?",
+ a: "Ein CryptPad Dokument ist manchmal als Pad genannt. Diese Bennung wurde von Etherpad bekannt gemacht, ein kollaboratives Editor in Echtzeit\n"+
+ "Es beschreibt ein Dokument, das du in deinem Browser bearbeiten kannst, normalerweise mit der Möglichkeit für andere Personen, die Veränderungen nah zu direkt zu sehen."
+ },
+ owned: {
+ q: "What ist ein eigenes Dokument?",
+ a: "Ein eigenes Dokument ist ein Dokument mit einem definierten Eigentümer, der anhand ein einer Unterschrift mit öffentlichen Schlüssel erkannt wird." +
+ "Der Eigentümer eines Dokuments kann entscheiden, das Dokument zu löschen. In diesem Fall macht er das Dokument unverfügbar für weitere Kollaboration, egal ob das Dokument in deinem CryptDrive war oder nicht."
+ },
+ expiring: {
+ q: "What is der Ablaufsdatum eines Dokuments?",
+ a: "Ein Dokument kann mit einem Ablaufsdatum versehen werden. Nach diesem Datum wird es automatisch vom Server gelöscht" +
+ " Das Ablaufdatum kann sowohl sehr nah (ein Paar Stunden) als sehr weit sein (hunterte Monate)." +
+ " Das Dokument und sein gesamter Verlauf wird dauerhaut unverfügbar werden, auch wenn er gerade noch bearbeitet wird.
" +
+ " Wenn ein Dokument ein Ablaufsadtum hat, kann mann dieses Datum in der Eigenschaften lesen: Entweder mit einem Recht-klick in CryptDrive oder mit der Properties Ansicht, wenn das Dokument geöffnet ist."
+ },
+ tag: {
+ q: "Wie kann ich Tags verwenden?",
+ a: "Du kannst Dokumente und CryptDrive-hochgeladene Dateien taggen. Das heisst mit einem Tag zu versehen. In Bearbeitung ist es durch das tag Knopf (" +
+ " Suche die Dokumente und Dateien in deinem CryptDrive mithilfe der Suchfunktion mit Suchterme, die mit einem Hashtag starten, zB #crypto."
+ },
+ template: {
+ q: "Was ist eine Vorlage?",
+ a: "Eine Vorlage ist ein Dokument, dass du benutzen kannst, um der Anfangsinhalt für zukünftige Dokumente zu definieren." +
+ " Jedes existes existierende Dokument kan eine Vorlage werden, indem es zum Vorlagen Abschnitt des CryptDrives geschoben wird." +
+ " Du kannst auch eine Kopie eines Dokuments erstellen, die ales Vorlage wird, indem du auf der Vorlagen Knop () des Toolbars des Editors druckst."
+ },
+ };
+ out.faq.privacy = {
+ title: 'Privacy',
+ different: {
+ q: "Wie unterscheidet sich CryptPad von anderen online kollaborative Editoren?",
+ a: "CryptPad verschlüsselt Veränderungen deiner Dokumente, bevor diese Information zum Server geschickt wird. Somit können wir nicht lesen, was du getippt hast."
+ },
+ me: {
+ q: "Welche Information kennt der Server über mich?",
+ a: "Die Administratoren des Servers können die IP-Adresse der Personen sehen, die CryptPad besuchen." +
+ " Wir speichern nicht welche Adresse besucht welches Dokument, aber wir konnten es tun, auch ohne Zugang zu den Inhalt des Dokuments zu kennen." +
+ " Wenn du besorgt bist, dass wir diese Information analysieren, ist es am sichersten davon auszugehen, dass wir es tun, da wir nicht beweisen können, dass wir es nicht tun.
" +
+
+ " Wir sammeln elementare technische Informationen über wie CryptPad benutzt wird, wie die Grösse des Bildschirms auf der Gerät und welche Knöpfe werden meist geklickt." +
+ " Das hilft uns, unser Software besser zu machen. Aber du kannst die diese Sammlung für dich vermeiden, in dem du Rückmeldung aktivieren kein Haken setzt.
" +
+
+ " Die Speicherungsgrössen und deren Grenzen sind mit dem öffentlichen Schlüssel eines Benutzers verbunden, aber wir verbinden nicht Namen oder Emailadressen mit dieser öffentlichen Schlüsseln.
" +
+
+ " Du kannst mehr Informationen darüber auf diesem Blogeintrag lesen."
+ },
+ register: {
+ q: "Weisst der Server mehr über mich, wenn ich registriere?",
+ a: "Wir verlangen nicht deine Emailadresse zu bestätigen und der Server kennt der Benutzername und Passwort nicht, wenn du dich regstriests. " +
+ " Anstatt dessen, der Registrierungs- und Anmeldeformular generiert ein Schlüsselpaar mit deiner Eingabe. Nur der öffentliche Schlüssel dieses Schlüsselpaars wird zum Server geschickt." +
+ " Mit diesem öffentlichen Schlüssel könenn wir Informationen kontrollieren wie die Menge der Daten, die du benutzt; damit können wir den Verbrauch von jedem Benutzer im Quota beschränken.
" +
+
+ " Wir benutzen die Rückmeldung Funktion, um den Server zu informieren, dass jemand mit deinem IP ein Konto registriert hat." +
+ " Damit können wir messen, wie viele Benutzer CryptPad Konten registrieren registrieren, und von welchen Regionen. Somit können wir wissen, welche Sprache braucht ein besseres Support.
" +
+
+ " Wenn du registrierst, du erstellest einen öffentlichen Schlüssel, das benutzt wird, um den Server zu informieren, dass die Dokumente nicht löschen sollte, wenn sie nicht aktiv benutzt werden." +
+ " Diese Information zeigt mehr zum Server, über wie du CryptPad benutzt, aber das System erlaubt uns, die Dokumente zu löschen, wofür keine sich die Mühe gegeben hat, was zu tun, um sie zu behalten."
+ },
+ other: {
+ q: "Was können andere Benutzer über micht erfahren?",
+ a: "Wenn die ein Dokument jemanden anderen bearbeitest, du kommunizierst mit dem Server. Nur wir kennen deine IP-Adresse. " +
+ " Andere Benutzern können dein Benutzername, dein Benutzerbild, das Link deines Profils (wenn du ein hast), und deinen öffentlichen Schlüssel (um die Nachrichten zu den Benutzern zu verschlüsseln)."
+ },
+ anonymous: {
+ q: "Macht mich CryptPad anonym?",
+ a: "Auch wenn CryptPad so konzipiert wurde, dass es so wenig wie möglich über dicht kenn, es liefert keine strenge Anonymität" +
+ " Unsere Servers haben einen Zugang zu deiner IP-Adresse, allerdings kannst du diese Information verbergen, indem du Tor verwendets." +
+ " Einfach Tor zu verwenden, ohne dein Verhältnis zu ändern, garnatiert auch nicht deine Anonymität, da der Server Benutzern noch mit deren einzige öffentlichen Schlüsseln identifizeren kann." +
+ " Wenn du denselben Schlüssel benutzt, wenn du nicht Tor benutzt. Es wird möglich, deine Sitzung zu de-anonimisieren.
" +
+
+ " Für Benutzer die einen niedrigeren Grad Datenschutz brauchen, CryptPad, im Gegenteil zu anderen Onlinservers, verlangt sein Benutzer nicht, sich mit Namen, Telefonnummer oder Emailadressen zu identifizieren."
+ },
+ policy: {
+ q: "Habt ihr eine Datenschutzerklärung?",
+ a: "Ja! Es ist hier verfügbar."
+ }
+ };
+ out.faq.security = {
+ title: 'Sicherheit',
+ proof: {
+ q: "Wie benutzt ihr Zero Knowledge Beweise?",
+ a: "Wir benutzen das Term Ohne Preisgabe von Informationen (Zero knowledge) nicht im Sinn eines Zero knowledge Beweis aber im Sinn eines Zero Knowledge Webdienst " +
+ " Zero Knowledge Webdienst verschlüsseln die Benutzerdaten im Browser, ohne dass der Server je Zugang zu den unverschlüsselten Daten oder zu dem Verschlüsselungschlüsseln hat.
" +
+ " Wir haben eine kurze Liste von Zero-Knowledge Webdienste hier gesammelt."
+ },
+ why: {
+ q: "Wieso soll ich CryptPad verwenden?",
+ a: "Unsere Position ist, dass Clouddienst nicht Zugang zu deinen Daten verlangen sollten, damit du sie zu deinen Kontakten und Mitarbeitern teilen kannst. " +
+ " Wenn du ein Webdienst benutzt, der nicht explizit eine Ankündigung macht, dass die keinen Zugang zu deinen Information haben, ist es sehr wahrscheinlich, dass sie diese Information für andere Zwecke verwerten."
+ },
+ compromised: {
+ q: "Liefert mich CryptPad einen Schutz, wenn mein Gerät zugegriffen wird?",
+ a: "Im Fall, dass dein Gerät gestolen wird, erlaubt CryptPad ein Knopf zu drucken, damit alle Geräte, ausser das wo du gerade geloggt bist, ausgeloggt wird. " +
+ " Alle andere Geräten, die mit diesem Konto verbunden sind, werden auch ausgeloggt. " +
+ " Alle früher verbundene Geräte, werden ausgeloggt, sobald sie CryptPad besuchen.
" +
+
+ " Die Fernlogout Funktion, wie oben beschrieben, ist im Browser implementiert und nicht im Server. " +
+ " Somit schützt diese nicht von Regierungsagenturen. Aber es sollte ausreichend sein, wenn du ein Logout vergessen hast, wenn du auf einem geteiltes Rechner warst."
+ },
+ crypto: {
+ q: "Welche Kryptografie benutzt ihr?",
+ a: "CryptPad basiert auf zwei open-source Kryptografiebibliotheken: " +
+ " tweetnacl.js und scrypt-async.js.
" +
+ " Scrypt ist ein Passwort-basiert Schlüsselableitungsalgorithmus. Wir benutzen es, um dein Benutzername und Kennwort in einem Schlüsselpaar umzuwandeln, das deinen Zugang zum CryptDrive, und daher deine gesamte Dokumente, sichert.
" +
+
+ " Wir verwenden die Verschlüsselung xsalsa20-poly1305 und x25519-xsalsa20-poly1305 von tweetnacl, um, bzw, Dokumente und Chat-Historie zu verschlüsseln."
+ }
+ };
+ out.faq.usability = {
+ title: 'Usability',
+ register: {
+ q: "Was kriege ich, wenn ich registriere?",
+ a: "Registrierte Benutzer können eine Menge Funktionen verwenden, die unregistrierten nicht verwenden können. Es gibt eine Tabelle hier."
+ },
+ share: {
+ q: "Wie kann ich Zugang zu einem verschlüsselten Dokument mit Freunden teilen?",
+ a: "CryptPad macht den Verschlüsselungsschlüssel zu deinem Pad nach dem # Buchstabe in dem URL." +
+ " Alles was nach diesem Buchstabe kommt, ist nicht zum Server geschickt; also haben wir nie Zugang zu deinem Verschlüsselungsschlüssel." +
+ " Wenn du das Link deines Dokuments teilst, teilst du auch die Fähigkeit zum Lesen und zum Bearbeiten."
+ },
+ remove: {
+ q: "Ich habe ein Dokument aus meinem CryptDrive gelöst, aber den Inhalt ist noch verfügbar. Wie kann ich es entfernen?",
+ a: "Nur eigene Dokumente, die erst in Februar 2018 eingeführt wurden, können gelöscht werden. Dazu können diese Dokument nur von deren Eigentümer gelöscht werden" +
+ " (der Benutzer, der das Dokument original gestaltet hat). Wenn du nicht der Eigentümer eines Dokuments bist, musst du noch den Eigentümer bitten, dass er dieses löscht." +
+ " Für ein Dokument, wovon du den Eigentümer bist, kannst du auf dem Dokument in CryptDrive rechtsklicken und Vom Server löschen wählen. "
+ },
+ forget: {
+ q: "Was passiert, wenn ich mein Passwort vergesse?",
+ a: " Leider: Wenn wir dein Passwort zurückerstellen könnten, könnten wir auch Zugang zu deinen Daten selber haben. " +
+ " Wenn du dein Passwort nicht registriert hast, und kann es auch nicht erinnern, kannst du vielleicht die vergangene Dokumente von deinem Browserverlauf zurück gewinnen. "
+ },
+ change: {
+ q: "Was ist, wenn ich mein Passwort wechseln möchte?",
+ a: "Es ist aktuell nicht möglich, dein CryptPad Passwort zu wechseln, obwohl wir diese Funktion bald planen."
+ },
+ devices: {
+ q: "Ich bin auf zwei Geräte eingeloggt und sehe zwei unterschiedliche CryptDrives. Wie ist das möglich?",
+ a: "Es ist möglich, dass du zweimal derselben Name registriert hast, mit unterschiedlichen Passwörter." +
+ " Weil der CyrptPad Server dicht mit deinem kryptografische Unterschrift identifiziert, es kann nicht dich verhindern, mit demselben Name einzuloggen." +
+ " Somit hat jede Benutzerkonto ein einzigartiges Beutzername und Passwortkombination. " +
+ " Angemeldete Benutzer können ihre Benutzername im oberen Teil der Einstellungsseite sehen."
+ },
+ folder: {
+ q: "Kann ich meine ganze Ordnern in CryptDrive teilen?",
+ a: "Wir arbeiten daran, eine Arbeitgruppenfunktion anzubieten, die Mitglieder erlauben würde, ganze Ordnern sowie alle Dokumente darin, zu teilen."
+ },
+ feature: {
+ q: "Könnt ihr diese Funktion hinzufügen, das ich brauche?",
+ a: "Viele Funktionen existieren in CryptPad, weil Benutzern haben dafür gebeten." +
+ " Unsere Kontaktseite gibt eine Liste de Methoden, um mit uns in Kontakt zu treten.
" +
+
+ "Leider können wir aber nicht garantieren, dass wir alle Funktionen entwickeln, die Benutzern bitten." +
+ " Wenn eine Funktion kritisch für ihre Organisation ist, kannst du Sponsor der Entwicklung dieser Funktion werden, und somit deren Realisierung sichern." +
+ " Bitte kontaktiere sales@cryptpad.fr für mehr Informationen.
" +
+
+ "Auch wenn du nicht die Entwicklung einer Funktion sponsorieren kannst, sind wir zu Rückmeldungen interessiert, damit es uns hilft CryptPad zu verbessern." +
+ " Du bist willkommen, mit uns in Kontakt zu treten, mit eine der Methoden oben."
+ }
+ };
+
+ out.faq.other = {
+ title: "Andere Fragen?",
+ pay: {
+ q: "Wieso soll ich zahlen, wenn so viele Funktionen sowieso kostenfrei sind?",
+ a: "Wir geben Sponsoren zusätzliche Speicherplatzmöglichkeiten sowie die Möglichkeit, die Speicherplatzgrenzen ihrer Freunde zu erhören (lese mehr).
" +
+
+ " Weiter als diese kurzfristige Vorteile kannst du, wenn du ein Premiumangebot annimmst, die aktive Weiterentwicklung von CryptPad. Dieses beinhaltet Bugs reparieren, neue Funktionen gestalten, und es leichter für andere zu machen, dass sie CryptPad auf eigenen Servers installieren." +
+ " Zusätzlich hilfst du das den Anderen zu beweisen, dass Leute datenschutzschonende Technologien verbessern wollen. Wir hoffen, dass am Ende Geschäftmodelle ein Aspekt der vergangene Geschichte ist.
" +
+
+ " Am Ende glauben wir, dass es gut ist, die Funktionen von CryptPad kostenfrei anzubieten, weil jeder persönlichen Datenschutz braucht, nicht nur diejenige mit Extraeinkommen." +
+ " Durch ihre Unterstützung hilfst du uns, zu ermöglichen, dass Bevölkerung mit weniger Einkommen diese grundlegende Funktionen geniessen können, ohne dass ein Preisetikette daran klebt."
+ },
+ goal: {
+ q: "Was ist ihr Ziel?",
+ a: "Durch die Verbesserung von Datenschutzschonende Technologie möchten wir die Erwartungen der Benutzern erhöhen, was der Datenschutz auf Cloudplattformen angeht." +
+ "Wir hoffen, dass unsere Arbeit andere Dienstanbietern in allen Domänen ähnliche oder bessere Dienste anbieten können. " +
+ "Trotz unser Optimismu wissen wir, dass vieles vom Web aus gezielte Werbung gesponsert wird. " +
+ "Es gibt viel mehr Arbeit in der Richtung zu tun, als wir selber schaffen, und wir erkennen die Unterstützung der Gemeinschaft für Promotion, Support und andere Beiträge für dieses Zweck."
+ },
+ jobs: {
+ q: "Stellt ihr an?",
+ a: "Ja! Bitte schicke eine kurze Einführung zu dir auf jobs@xwiki.com."
+ },
+ host: {
+ q: "Könnt ihr mich helfen, meine eigene Installation von CryptPad zu erledigen?",
+ a: "Wir sind froh, dich zu unterstützen, das interne CryptPad deiner Firma zu installieren. Setze dich bitte mit sales@cryptpad.fr in Kontakt für mehr Information.",
+ },
+ revenue: {
+ q: "Wie kann ich ein geteilttes Einkommen Modell erreichen?",
+ a: " Wenn du deine eigene Installation von CrytPad betreibst, und du möchtests die Einkommen für deine bezahlte Konten mit Entwicklern teilen, wird dein Server als Partnerservice konfugriert werden müssen.
" +
+
+ "In deinem CryptPad Verzeichnis befinden sich config.example.js, die eine Erklärung liefert, wie du dein Server dafür konfigurieren muss. "+
+ "Danakch solltest du sales@cryptpad.fr ein Email schicken, damit es geprüft wird, dass dein Server richtig mit HTTPS konfiguriert wird und damit die Bezahlungsmethoden diskutiert werden. "
+ },
+ };
+
+
+
// terms.html
out.tos_title = "Cryptpad Nutzungsbedingungen";
@@ -152,20 +993,176 @@
out.tos_logs = "Metadaten, die dein Browser bereitstellt, können geloggt werden, um den Service aufrechtzuerahlten.";
out.tos_3rdparties = "Wir geben keine Individualdaten an dritte Weiter, außer auf richterliche Anordnung.";
+ // 404 page
+ out.four04_pageNotFound = "Wir konnten nicht die Seite finden, die du angefordert hast.";
+
// BottomBar.html
- out.bottom_france = 'Mit in gemacht';
- out.bottom_support = 'Ein Labs Project mit Hilfe von ';
+ // out.bottom_france = 'Mit in gemacht';
+ // out.bottom_support = 'Ein Labs Project mit Hilfe von ';
// Header.html
- out.header_france = 'Mit von und ';
+ out.updated_0_header_logoTitle = 'Zu deinem CryptDrive';
+ out.header_logoTitle = out.updated_0_header_logoTitle;
+ out.header_homeTitle = 'Zu der CryptPad Homeseite';
+
+ // Initial states
+
+ out.help = {};
+
+ out.help.title = "Mit CryptPad anfängen";
+ out.help.generic = {
+ more: 'Erfahre mehr wie CryptPad für dich arbeiten kann, indem du unsere FAQ liest.',
+ share: 'Benutze das Teilen Menü (), um Links zu schicken, die zu Kooperationen im Lesen oder Bearbeiten einladen.',
+ stored: 'Jedes Dokument, dass du besuchst, ist automatisch in deinem CryptDrive gespeichert.',
+ };
+
+ out.help.text = {
+ formatting: 'Du kannst das Toolbar anzeigen oder verbergen indem du auf oder klickst.',
+ embed: 'Registrierte Benutzern können mit Bilder oder Dateien einbetten, die in deren CryptDrive gespeichert sind.',
+ history: 'Du kannst das Menü Verlauf benutzen, um frühere Version anzusehen oder zurückbringen.',
+ };
+
+ out.help.pad = {
+ export: 'Du kannst export als PDF benutzen, indem du auf dem Knopf in dem Formattierungstoolbar druckst.',
+ };
+
+ out.help.code = {
+ modes: 'Benutze das Dropdown Menü im Submenü , um die Syntaxherhorhebung oder das Farbschema zu wechseln.',
+ };
+
+ out.help.slide = {
+ markdown: 'Schreibe Folien in Markdown and separiere sie mit der Zeile ---.',
+ present: 'Starte die Präsentation mit dem Knopf .',
+ settings: 'Verändere die Präsentationseinstellung (Hintergrund, Transition, Anzeige der Seitenummer, etc) mit dem Knopf in dem Submenü .',
+ colors: 'Verändere Text- und Hintergrundfarbe mit den Knöpfen und .',
+ };
+
+ out.help.poll = {
+ decisions: 'Treffen Entscheidung privat, unter Bekannte',
+ options: 'Schlage zuätzliche Optionen, und mache deine bevorzugte Optionen laut',
+ choices: 'Klicke Zellen in deiner Spalte, um zwischen ja (✔), viellecht (~), oder nein (✖) zu wählen',
+ submit: 'Klicke auf schicken, damit deine Wahlen anderen Sichtbar wird',
+ };
+
+ out.help.whiteboard = {
+ colors: 'Ein Doppelklick auf Farben erlaubt die Palette zu verändern',
+ mode: 'Deaktiviere das Zeichenmodus, um die Strichen zu ziehen und verlängern',
+ embed: 'Einbette Bilder von deiner Festplatte oder von deinem CryptDrive und exportiere sie als PNG zu deiner Festplatte oder zu deinem CryptDrive , um ein schreibgeschütztes Link zu teilen. Es erlaubt der Ansicht ohne die Bearbeitung.',
+ '',
+ ].join('');
+
+ out.codeInitialState = [
+ '# CryptPad: Kollaboratives Code Editor ohne Preisgabe deiner Information\n',
+ '\n',
+ '* Was du hier tippst, ist Verschlüsselt. Nur Personen die das vollen Link haben können es zugreifen.\n',
+ '* Du kannst die Programmierungsprache für die Syntaxhervorhebung sowie das Farbschema oben rechts wählen.'
+ ].join('');
+
+ out.slideInitialState = [
+ '# CryptSlide\n',
+ '1. SChreibe deine Präsentation mit der Markdown Syntax\n',
+ ' - Mehr über Markdwon [hier](http://www.markdowntutorial.com/) erfahren\n',
+ '2. Trenne deine Folien mit ---\n',
+ '3. Klicke auf dem "Abspielen" Knopf, um das Ergebnis zu sehen.',
+ ' - Deine Folien sind in Echtzeit aktualisiert'
+ ].join('');
+
+ // Readme
+
+ out.driveReadmeTitle = "Was ist CryptPad?";
+ out.readme_welcome = "Willkommen zu CryptPad !";
+ out.readme_p1 = "Willkommen zu CryptPad, hier kannst du deine Notizzen runterschreiben, allein oder mit Bekannten.";
+ out.readme_p2 = "Dieses Dokument gibt dir einen kurzen Durchblick, wie du CryptPad verwenden kann, um Notizzen zu schreiben und zusammen zu arbeiten.";
+ out.readme_cat1 = "Kenne CryptDrive lernen";
+ out.readme_cat1_l1 = "Ein Dokument erstellen: In deinem CryptDrive, klicke {0} dann {1} und kannst ein ein Dokuemnt erstellen."; // 0: New, 1: Rich Text
+ out.readme_cat1_l2 = "Ein Dokument deines CryptDrives öffnen: Doppelklick auf der Ikone eines Dokument, um es zu öffnen.";
+ out.readme_cat1_l3 = "Deine Dokumente organisieren: Wenn du eingeloggst bist, wird jedes Dokument, das du beuuchst in {0} Abschnitt deines CryptDrive";
+ out.readme_cat1_l3_l1 = "Du kannst Dateien zwischen Ordnern Ziehen und Schieben in dem Abschnitt {0} deines CryptDrives oder neue Ordnern gestalten."; // 0: Documents
+ out.readme_cat1_l3_l2 = "Erinnere dich daran, ein Rechtklick auf Ikonen zu geben, da es zusätzlichen Menüfunktionen gibt.";
+ out.readme_cat1_l4 = "Verschiebe deine alte Dokumente zum Papierkorb: Du kannst deine Dokumente zu {0} verschieben, genauso, wie du es zu einem Ordner machst."; // 0: Trash
+ out.readme_cat2 = "Dokumente wie ein Profi gestalten";
+ out.edit = "bearbeiten";
+ out.view = "ansehen";
+ out.readme_cat2_l1 = "Der Knopf {0} in deinem Dokument erlaubt dich, anderen eine Mitarbeitzugang zu geben (entweder zu {1} oder {2}).";
+ out.readme_cat2_l2 = "Der Titel eines Dokuments kann mit einem Klick auf dem Stift verändert werden.";
+ out.readme_cat3 = "Entdecke CryptPad apps";
+ out.readme_cat3_l1 = "Mit dem CryptPad Codeeditor kannst du auf Code wie JavaScript, Markdown, oder HTML";
+ out.readme_cat3_l2 = "Mit dem CryptPad Präsentationseditor kannst du schnelle Vorträge mithilfe von Markdwon gestalten";
+ out.readme_cat3_l3 = "Mit der CryptPad Umfrage kannst du schnell Abstrimmungen treffen, insbesonders, um Meetings zu planen, die dem Kalender von allen passen.";
+
+ // Tips
+ out.tips = {};
+ out.tips.shortcuts = "`ctrl+b`, `ctrl+i` and `ctrl+u` sind Tatstenkürzeln um fett, kurziv, oder unterschrieben zu markieren.";
+ out.tips.indent = "In gezifferten oder einfache Listen kannst du TAB und SHIFT-TAB benutzen, um die Identierung zu erhöhen oder reduzieren.";
+ out.tips.store = "Jedes Mal, dass du ein Dokument besuchsts, und eingeloggt bist, wird es in deinem CryptDrive gespeichert.";
+ out.tips.marker = "Du kannst Text in einem Dokument mit \"Marker\" Menü in dem Stilmenü markieren.";
+ out.tips.driveUpload = "Registrierte Benutzer können verschlüsselte Dateien aus ihrer Festplatten hochladen, indem sie einfach Schieben und in ihrem CryptDrive ablegen.";
+ out.tips.filenames = "Du kannst Dateien in deinem CryptDrive neubenennen. Dieser Name ist nur für dich.";
+ out.tips.drive = "Eingeloggte Benutzern können ihre Dateien in ihrem CryptDrive organisieren. Dieses ist mit einem Klick auf der CryptPad Ikone oben links erreichbar, wenn mann in einem Dokument ist.";
+ out.tips.profile = "Registrierte Benuzter können ein Profil gestalten mit dem Benutzer Menü oben rechts.";
+ out.tips.avatars = "Du kannst ein Benutzerbild in deinem Profil hochladen. Andere sehen es, wenn die in einem Dokument zusammenarbeiten.";
+ out.tips.tags = "Bringe Tags auf deinen Dokumenten und starte eine Suche-bei-Tags mit dem # Zeichen in dem CryptDrive Suche.";
+
+ out.feedback_about = "Wenn du das liest, fragst du dich weshalb Anfragen an Webseiten schickt, wenn manche Aktionen geführt werden.";
+ out.feedback_privacy = "Wir wollen deinen Datenschutz schonen, aber gleichzeitig wollen wir, dass die Benutzung von CryptPad sehr leicht ist, zB indem wir erfahren, welche UI-Funktion am wichtigsten für unsere Benutzen ist. Dieses wird nachgefragt mit einer genauen Parameterbeschreibung, welche Aktion war gemacht.";
+ out.feedback_optout = "Wenn du es aber nicht möchtest. besuche deine Einstellungen, dort findest du ein Haken, wo du es deaktivieren kannst.";
+ // Creation page
+ out.creation_404 = "Dieses Dokument existiert nicht mehr. Benutze das folgende Formular, um ein neues Dokument zu gestalten.";
+ out.creation_ownedTitle = "Dokumenttyp";
+ out.creation_owned = "Eigenes Dokument"; // Creation page
+ out.creation_ownedTrue = "Eigenes Dokument"; // Settings
+ out.creation_ownedFalse = "Dokument von jemanden anders";
+ out.creation_owned1 = "Ein eigenes Dokument kann vom Server gelöscht werden, wenn der Eigentümer es entscheidet. Die Löschung eines eigenes Dokuments verursacht die Löschung aus allen anderen CryptDrives. ";
+ out.creation_owned2 = "Ein offenes Dokument hat kein Eigentümer, also kann es nicht löschen, ausser es hat sein Auslaufdatum erreicht.";
+ out.creation_expireTitle = "Lebenszyklus";
+ out.creation_expire = "Auslaufende Dokument";
+ out.creation_expireTrue = "Ein Lebenszyklus hinzufügen";
+ out.creation_expireFalse = "Unbegrenz";
+ out.creation_expireHours = "Stunde(n)";
+ out.creation_expireDays = "Tag(en)";
+ out.creation_expireMonths = "Monat(e)";
+ out.creation_expire1 = "An unlimited pad will not be removed from the server until its owner deletes it.";
+ out.creation_expire2 = "An expiring pad has a set lifetime, after which it will be automatically removed from the server and other users' CryptDrives.";
+ out.creation_noTemplate = "Keine Vorlage";
+ out.creation_newTemplate = "Neue Vorlage";
+ out.creation_create = "Erstellen";
+ out.creation_saveSettings = "Dieses Dialog nicht mehr anzeigen";
+ out.creation_settings = "Mehr Einstellungen zeigen";
+ out.creation_rememberHelp = "Geh zu deiner Einstellungen, um diese Einstellung wieder vorzunehmen";
+ // Properties about creation data
+ out.creation_owners = "Eigentümer";
+ out.creation_ownedByOther = "Eigentum eines anderen Benutzer";
+ out.creation_noOwner = "Kein Eigentümer";
+ out.creation_expiration = "Auslaufdatum";
+ out.creation_propertiesTitle = "Verfügbarkeit";
+ out.creation_appMenuName = "Fortgeschrittenes Modus (Ctrl + E)";
+ out.creation_newPadModalDescription = "Klicke auf einem Padtyp, um es zu erstellen. Du kannst auch die Tab-Taste benutzen, um zu navigieren, und die Enter-Taste zu bestätigen. ";
+ out.creation_newPadModalDescriptionAdvanced = "Du kannst das Kästchen ticken (oder auf der Leertaste drucken um den Wert zu ändern), um das Einstellungsdialog bei der Dokumenterstellung anzuzeigen (für eigene oder auslaufende Dokumente).";
+ out.creation_newPadModalAdvanced = "Das Einstellungdialog bei der Dokumenterstellung anzeigen";
- // TODO Hardcode cause YOLO
- //out.header_xwiki = '';
- out.header_support = '';
- out.header_logoTitle = 'Zur Hauptseite';
+ // New share modal
+ out.share_linkCategory = "Link teilen";
+ out.share_linkAccess = "Zugangsrechte";
+ out.share_linkEdit = "Bearbeiten";
+ out.share_linkView = "Ansehen";
+ out.share_linkOptions = "Linkoptionen";
+ out.share_linkEmbed = "Einbettungsmodus (Toolbar und Benutzerliste sind verborgen)";
+ out.share_linkPresent = "Anzeigemodus (Bearbeitbare Abschnuttte sind verborgen)";
+ out.share_linkOpen = "In einem neuen Tab öffnen";
+ out.share_linkCopy = "Zur Zwischenablage kopieren.";
+ out.share_embedCategory = "Einbetten";
+ out.share_mediatagCopy = "Mediatag zur Zwischenablage kopieren";
return out;
});
diff --git a/customize.dist/translations/messages.el.js b/customize.dist/translations/messages.el.js
index 36a19da87..493be541d 100644
--- a/customize.dist/translations/messages.el.js
+++ b/customize.dist/translations/messages.el.js
@@ -31,7 +31,7 @@ define(function () {
out.websocketError = 'Αδυναμία σύνδεσης στον διακομιστή...';
out.typeError = "Αυτό το pad δεν είναι συμβατό με την επιλεγμένη εφαρμογή";
- out.onLogout = 'Έχετε αποσυνδεθεί, κάντε "κλικ" εδώ για να συνδεθείτε ή πατήστε Escape για να προσπελάσετε το έγγραφο σε λειτουργία ανάγνωσης μόνο.';
+ out.onLogout = 'Έχετε αποσυνδεθεί, {0}κάντε "κλικ" εδώ{1} για να συνδεθείτε ή πατήστε Escape για να προσπελάσετε το έγγραφο σε λειτουργία ανάγνωσης μόνο.';
out.wrongApp = "Αδυναμία προβολής του περιεχομένου αυτής της συνεδρίας στον περιηγητή σας. Παρακαλώ δοκιμάστε επαναφόρτωση της σελίδας.";
out.loading = "Φόρτωση...";
diff --git a/customize.dist/translations/messages.es.js b/customize.dist/translations/messages.es.js
index 9e246294f..fc905870a 100644
--- a/customize.dist/translations/messages.es.js
+++ b/customize.dist/translations/messages.es.js
@@ -152,7 +152,7 @@ define(function () {
out.websocketError = "Error al conectarse al servidor WebSocket";
out.typeError = "Este documento no es compatible con la aplicación seleccionada";
- out.onLogout = "Tu sesión está cerrada, haz clic aquí para iniciar sesión o pulsa Escape para acceder al documento en modo sólo lectura.";
+ out.onLogout = "Tu sesión está cerrada, {0}haz clic aquí{1} para iniciar sesión o pulsa Escape para acceder al documento en modo sólo lectura.";
out.loading = "Cargando...";
out.error = "Error";
out.language = "Idioma";
diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js
index 37e1cfb18..67b8ebea6 100644
--- a/customize.dist/translations/messages.fr.js
+++ b/customize.dist/translations/messages.fr.js
@@ -27,7 +27,7 @@ define(function () {
out.websocketError = 'Impossible de se connecter au serveur WebSocket...';
out.typeError = "Ce pad n'est pas compatible avec l'application sélectionnée";
- out.onLogout = 'Vous êtes déconnecté de votre compte utilisateur, cliquez ici pour vous authentifier ou appuyez sur Échap pour accéder au pad en mode lecture seule.';
+ out.onLogout = 'Vous êtes déconnecté de votre compte utilisateur, {0}cliquez ici{1} pour vous authentifier ou appuyez sur Échap pour accéder au pad en mode lecture seule.';
out.wrongApp = "Impossible d'afficher le contenu de ce document temps-réel dans votre navigateur. Vous pouvez essayer de recharger la page.";
out.padNotPinned = 'Ce pad va expirer après 3 mois d\'inactivité, {0}connectez-vous{1} ou {2}enregistrez-vous{3} pour le préserver.';
out.anonymousStoreDisabled = "L'administrateur de cette instance de CryptPad a désactivé le drive pour les utilisateurs non enregistrés. Vous devez vous connecter pour pouvoir utiliser CryptDrive.";
@@ -37,6 +37,7 @@ define(function () {
out.chainpadError = 'Une erreur critique est survenue lors de la mise à jour du contenu. Le pad est désormais en mode lecture seule afin de s\'assurer que vous ne perdiez pas davantage de données. ' +
'Appuyez sur Échap pour voir le pad ou rechargez la page pour pouvoir le modifier à nouveau.';
out.errorCopy = ' Vous pouvez toujours copier son contenu ailleurs en appuyant sur Échap. Dés que vous aurez quitté la page, il sera impossible de le récupérer.';
+ out.errorRedirectToHome = 'Appuyez sur Échap pour retourner vers votre CryptDrive.';
out.loading = "Chargement...";
out.error = "Erreur";
@@ -145,6 +146,8 @@ define(function () {
out.useTemplate = "Commencer avec un modèle?";
out.useTemplateOK = 'Choisir un modèle (Entrée)';
out.useTemplateCancel = 'Document vierge (Échap)';
+ out.template_import = "Importer un modèle";
+ out.template_empty = "Aucun modèle disponible";
out.previewButtonTitle = "Afficher ou cacher la prévisualisation de Markdown";
@@ -364,6 +367,7 @@ define(function () {
out.fm_searchName = "Recherche";
out.fm_recentPadsName = "Pads récents";
out.fm_ownedPadsName = "Pads en votre possession";
+ out.fm_tagsName = "Mots-clés";
out.fm_searchPlaceholder = "Rechercher...";
out.fm_newButton = "Nouveau";
out.fm_newButtonTitle = "Créer un nouveau pad ou un dossier, importer un fichier dans le dossier courant";
@@ -426,6 +430,8 @@ define(function () {
out.fm_padIsOwned = "Vous êtes le propriétaire de ce pad";
out.fm_padIsOwnedOther = "Ce pad est la propriété d'un autre utilisateur";
out.fm_deletedPads = "Ces pads n'existent plus sur le serveur, ils ont été supprimés de votre CryptDrive: {0}";
+ out.fm_tags_name = "Mot-clé";
+ out.fm_tags_used = "Nombre d'utilisations";
// File - Context menu
out.fc_newfolder = "Nouveau dossier";
out.fc_rename = "Renommer";
@@ -546,6 +552,8 @@ define(function () {
out.settings_deleteHint = "La suppression de votre compte utilisateur est permanente. Votre CryptDrive et votre liste de pads seront supprimés du serveur. Le reste de vos pads sera supprimé après 90 jours d'inactivité si personne ne les a stockés dans leur CryptDrive.";
out.settings_deleteButton = "Supprimer votre compte";
out.settings_deleteModal = "Veuillez envoyer les informations suivantes à votre administrateur CryptPad afin que vos données soient supprimées du serveur.";
+ out.settings_deleteConfirm = "Êtes-vous sûr de vouloir supprimer votre compte utilisateur ? Cette action est irréversible.";
+ out.settings_deleted = "Votre compte utilisateur a été supprimé. Appuyez sur OK pour être rédirigé(e) vers la page d'accueil.";
out.settings_anonymous = "Vous n'êtes pas connecté. Ces préférences seront utilisées pour ce navigateur.";
out.settings_publicSigningKey = "Clé publique de signature";
@@ -609,9 +617,6 @@ define(function () {
out.pad_showToolbar = "Afficher la barre d'outils";
out.pad_hideToolbar = "Cacher la barre d'outils";
- // general warnings
- out.warn_notPinned = "Ce pad n'est stocké dans aucun CryptDrive. Il va expirer après 3 mois d'inactivité. En savoir plus...";
-
// markdown toolbar
out.mdToolbar_button = "Afficher ou cacher la barre d'outils Markdown";
out.mdToolbar_defaultText = "Votre texte ici";
@@ -950,8 +955,6 @@ define(function () {
// Header.html
- out.header_france = 'Fait avec en par ';
- out.header_support = '';
out.updated_0_header_logoTitle = 'Retourner vers votre CryptDrive';
out.header_logoTitle = out.updated_0_header_logoTitle;
out.header_homeTitle = "Aller sur la page d'accueil";
@@ -1051,7 +1054,7 @@ define(function () {
out.tips = {};
out.tips.shortcuts = "`ctrl+b`, `ctrl+i` et `ctrl+u` sont des raccourcis rapides pour mettre en gras, en italique ou souligner.";
out.tips.indent = "Dans les listes à puces ou numérotées, vous pouvez utiliser `Tab` ou `Maj+Tab` pour augmenter ou réduire rapidement l'indentation.";
- out.tips.store = "Dès que vous ouvrez un nouveau pad, il est automatiquement stocké dans votre CryptDrive si vous êtes connectés.";
+ out.tips.store = "Dès que vous ouvrez un nouveau pad, il est automatiquement stocké dans votre CryptDrive si vous êtes connecté.";
out.tips.marker = "Vous pouvez surligner du texte dans un pad en utilisant l'option \"marker\" dans le menu déroulant des styles.";
out.tips.driveUpload = "Les utilisateurs enregistrés peuvent importer des fichiers en les faisant glisser et en les déposant dans leur CryptDrive.";
out.tips.filenames = "Vous pouvez renommer les fichiers de votre CryptDrive, ce nom ne sera visible que par vous.";
@@ -1081,6 +1084,7 @@ define(function () {
out.creation_expireMonths = "Mois";
out.creation_expire1 = "Un pad illimité ne sera pas supprimé du serveur à moins que son propriétaire ne le décide.";
out.creation_expire2 = "Un pad à durée de vie sera supprimé automatiquement du serveur et du CryptDrive des utilisateurs lorsque cette durée sera dépassée.";
+ out.creation_password = "Ajouter un mot de passe";
out.creation_noTemplate = "Pas de modèle";
out.creation_newTemplate = "Nouveau modèle";
out.creation_create = "Créer";
@@ -1092,12 +1096,20 @@ define(function () {
out.creation_ownedByOther = "Appartient à un autre utilisateur";
out.creation_noOwner = "Pas de propriétaire";
out.creation_expiration = "Date d'expiration";
+ out.creation_passwordValue = "Mot de passe";
out.creation_propertiesTitle = "Disponibilité";
out.creation_appMenuName = "Mode avancé (Ctrl + E)";
out.creation_newPadModalDescription = "Cliquez sur un type de pad pour le créer. Vous pouvez aussi appuyer sur Tab pour sélectionner un type et appuyer sur Entrée pour valider.";
out.creation_newPadModalDescriptionAdvanced = "Cochez la case si vous souhaitez voir l'écran de création de pads (pour les pads avec propriétaire ou à durée de vie). Vous pouvez appuyer sur Espace pour changer sa valeur.";
out.creation_newPadModalAdvanced = "Afficher l'écran de création de pads";
+ // Password prompt on the loadind screen
+ out.password_info = "Le pad auquel vous essayez d'accéder est protégé par un mot de passe. Entrez le bon mot de passe pour accéder à son contenu.";
+ out.password_error = "Pad introuvable ! Cette erreur peut provenir de deux facteurs. Soit le mot de passe est faux, soit le pad a été supprimé du serveur.";
+ out.password_placeholder = "Tapez le mot de passe ici...";
+ out.password_submit = "Valider";
+ out.password_show = "Afficher";
+
// New share modal
out.share_linkCategory = "Partage";
out.share_linkAccess = "Droits d'accès";
@@ -1111,5 +1123,12 @@ define(function () {
out.share_embedCategory = "Intégration";
out.share_mediatagCopy = "Copier le mediatag";
+ // Loading info
+ out.loading_pad_1 = "Initialisation du pad";
+ out.loading_pad_2 = "Chargement du contenu du pad";
+ out.loading_drive_1 = "Chargement des données";
+ out.loading_drive_2 = "Mise à jour du format des données";
+ out.loading_drive_3 = "Vérification de l'intégrité des données";
+
return out;
});
diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js
index d6d186a15..5fdee8948 100644
--- a/customize.dist/translations/messages.js
+++ b/customize.dist/translations/messages.js
@@ -30,7 +30,7 @@ define(function () {
out.websocketError = 'Unable to connect to the websocket server...';
out.typeError = "This pad is not compatible with the selected application";
- out.onLogout = 'You are logged out, click here to log in or press Escape to access your pad in read-only mode.';
+ out.onLogout = 'You are logged out, {0}click here{1} to log in or press Escape to access your pad in read-only mode.';
out.wrongApp = "Unable to display the content of that realtime session in your browser. Please try to reload that page.";
out.padNotPinned = 'This pad will expire after 3 months of inactivity, {0}login{1} or {2}register{3} to preserve it.';
out.anonymousStoreDisabled = "The webmaster of this CryptPad instance has disabled the store for anonymous users. You have to log in to be able to use CryptDrive.";
@@ -40,6 +40,7 @@ define(function () {
out.chainpadError = 'A critical error occurred when updating your content. This page is in read-only mode to make sure you won\'t lose your work. ' +
'Hit Esc to continue to view this pad, or reload to try editing again.';
out.errorCopy = ' You can still copy the content to another location by pressing Esc. Once you leave this page, it will disappear forever!';
+ out.errorRedirectToHome = 'Press Esc to be redirected to your CryptDrive.';
out.loading = "Loading...";
out.error = "Error";
@@ -148,6 +149,8 @@ define(function () {
out.useTemplate = "Start with a template?"; //Would you like to "You have available templates for this type of pad. Do you want to use one?";
out.useTemplateOK = 'Pick a template (Enter)';
out.useTemplateCancel = 'Start fresh (Esc)';
+ out.template_import = "Import a template";
+ out.template_empty = "No template available";
out.previewButtonTitle = "Display or hide the Markdown preview mode";
@@ -370,6 +373,7 @@ define(function () {
out.fm_searchName = "Search";
out.fm_recentPadsName = "Recent pads";
out.fm_ownedPadsName = "Owned";
+ out.fm_tagsName = "Tags";
out.fm_searchPlaceholder = "Search...";
out.fm_newButton = "New";
out.fm_newButtonTitle = "Create a new pad or folder, import a file in the current folder";
@@ -432,6 +436,8 @@ define(function () {
out.fm_padIsOwned = "You are the owner of this pad";
out.fm_padIsOwnedOther = "This pad is owned by another user";
out.fm_deletedPads = "These pads no longer exist on the server, they've been removed from your CryptDrive: {0}";
+ out.fm_tags_name = "Tag name";
+ out.fm_tags_used = "Number of uses";
// File - Context menu
out.fc_newfolder = "New folder";
out.fc_rename = "Rename";
@@ -555,6 +561,8 @@ define(function () {
out.settings_deleteHint = "Account deletion is permanent. Your CryptDrive and your list of pads will be deleted from the server. The rest of your pads will be deleted in 90 days if nobody else has stored them in their CryptDrive.";
out.settings_deleteButton = "Delete your account";
out.settings_deleteModal = "Share the following information with your CryptPad administrator in order to have your data removed from their server.";
+ out.settings_deleteConfirm = "Clicking OK will delete your account permanently. Are you sure?";
+ out.settings_deleted = "Your user account is now deleted. Press OK to go to the home page.";
out.settings_anonymous = "You are not logged in. Settings here are specific to this browser.";
out.settings_publicSigningKey = "Public Signing Key";
@@ -618,9 +626,6 @@ define(function () {
out.pad_showToolbar = "Show toolbar";
out.pad_hideToolbar = "Hide toolbar";
- // general warnings
- out.warn_notPinned = "This pad is not in anyone's CryptDrive. It will expire after 3 months. Learn more...";
-
// markdown toolbar
out.mdToolbar_button = "Show or hide the Markdown toolbar";
out.mdToolbar_defaultText = "Your text here";
@@ -999,9 +1004,6 @@ define(function () {
// Header.html
- out.header_france = 'With from by ';
-
- out.header_support = '';
out.updated_0_header_logoTitle = 'Go to your CryptDrive';
out.header_logoTitle = out.updated_0_header_logoTitle;
out.header_homeTitle = 'Go to CryptPad homepage';
@@ -1133,6 +1135,7 @@ define(function () {
out.creation_expireMonths = "Month(s)";
out.creation_expire1 = "An unlimited pad will not be removed from the server until its owner deletes it.";
out.creation_expire2 = "An expiring pad has a set lifetime, after which it will be automatically removed from the server and other users' CryptDrives.";
+ out.creation_password = "Add a password";
out.creation_noTemplate = "No template";
out.creation_newTemplate = "New template";
out.creation_create = "Create";
@@ -1144,12 +1147,20 @@ define(function () {
out.creation_ownedByOther = "Owned by another user";
out.creation_noOwner = "No owner";
out.creation_expiration = "Expiration time";
+ out.creation_passwordValue = "Password";
out.creation_propertiesTitle = "Availability";
out.creation_appMenuName = "Advanced mode (Ctrl + E)";
out.creation_newPadModalDescription = "Click on a pad type to create it. You can also press Tab to select the type and press Enter to confirm.";
out.creation_newPadModalDescriptionAdvanced = "You can check the box (or press Space to change its value) if you want to display the pad creation screen (for owned pads, expiring pads, etc.).";
out.creation_newPadModalAdvanced = "Display the pad creation screen";
+ // Password prompt on the loadind screen
+ out.password_info = "The pad you're tyring to open is protected with a password. Enter the correct password to access its content.";
+ out.password_error = "Pad not found! This error can be caused by two factors: either the password in invalid, or the pad has been deleted from the server.";
+ out.password_placeholder = "Type the password here...";
+ out.password_submit = "Submit";
+ out.password_show = "Show";
+
// New share modal
out.share_linkCategory = "Share link";
out.share_linkAccess = "Access rights";
@@ -1163,6 +1174,12 @@ define(function () {
out.share_embedCategory = "Embed";
out.share_mediatagCopy = "Copy mediatag to clipboard";
+ // Loading info
+ out.loading_pad_1 = "Initializing pad";
+ out.loading_pad_2 = "Loading pad content";
+ out.loading_drive_1 = "Loading data";
+ out.loading_drive_2 = "Updating data format";
+ out.loading_drive_3 = "Verifying data integrity";
return out;
});
diff --git a/customize.dist/translations/messages.pt-br.js b/customize.dist/translations/messages.pt-br.js
index fbf63d067..a2feea5f9 100644
--- a/customize.dist/translations/messages.pt-br.js
+++ b/customize.dist/translations/messages.pt-br.js
@@ -38,7 +38,7 @@ define(function () {
out.websocketError = 'Incapaz de se conectar com o servidor websocket...';
out.typeError = "Este bloco não é compatível com a aplicação selecionada";
- out.onLogout = 'você foi desconectado, clique aqui para se conectar, ou pressione ESC para acessar seu bloco em modo somente leitura.';
+ out.onLogout = 'você foi desconectado, {0}clique aqui{1} para se conectar, ou pressione ESC para acessar seu bloco em modo somente leitura.';
out.wrongApp = "Incapaz de mostrar o conteúdo em tempo real no seu navegador. Por favor tente recarregar a página.";
out.loading = "Carregando...";
diff --git a/customize.dist/translations/messages.ro.js b/customize.dist/translations/messages.ro.js
index 56ec8b05d..fe24c7057 100644
--- a/customize.dist/translations/messages.ro.js
+++ b/customize.dist/translations/messages.ro.js
@@ -13,7 +13,7 @@ define(function () {
out.common_connectionLost = out.updated_0_common_connectionLost;
out.websocketError = "Conexiune inexistentă către serverul websocket...";
out.typeError = "Această filă nu este compatibilă cu aplicația aleasă";
- out.onLogout = "Nu mai ești autentificat, apasă aici să te autentifici sau apasă Escapesă accesezi fila în modul citire.";
+ out.onLogout = "Nu mai ești autentificat, {0}apasă aici{1} să te autentifici sau apasă Escapesă 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";
diff --git a/customize.dist/translations/messages.zh.js b/customize.dist/translations/messages.zh.js
index 25b7fe8c8..422f00f2c 100644
--- a/customize.dist/translations/messages.zh.js
+++ b/customize.dist/translations/messages.zh.js
@@ -31,7 +31,7 @@ define(function () {
out.websocketError = '無法連結上 websocket 伺服器...';
out.typeError = "這個編輯檔與所選的應用程式並不相容";
- out.onLogout = '你已登出, 點擊這裏 來登入 或按Escape 來以唯讀模型使用你的編輯檔案';
+ out.onLogout = '你已登出, {0}點擊這裏{1} 來登入 或按Escape 來以唯讀模型使用你的編輯檔案';
out.wrongApp = "無法在瀏覽器顯示即時期間的內容,請試著再重新載入本頁。";
out.loading = "載入中...";
diff --git a/delete-inactive.js b/delete-inactive.js
index 16f41da45..9acf15f69 100644
--- a/delete-inactive.js
+++ b/delete-inactive.js
@@ -33,7 +33,7 @@ nThen(function (waitFor) {
sem.take(function (give) {
Fs.unlink(f.filename, give(function (err) {
if (err) { return void console.error(err + " " + f.filename); }
- console.log(f.filename + " " + f.size + " " + (+f.atime) + " " + (+new Date()));
+ console.log(f.filename + " " + f.size + " " + (+f.mtime) + " " + (+new Date()));
}));
});
});
diff --git a/docs/example.nginx.conf b/docs/example.nginx.conf
index d56920bf5..44b12ade8 100644
--- a/docs/example.nginx.conf
+++ b/docs/example.nginx.conf
@@ -75,6 +75,12 @@ server {
}
location ^~ /blob/ {
+ add_header Cache-Control max-age=31536000;
+ try_files $uri =404;
+ }
+
+ location ^~ /datastore/ {
+ add_header Cache-Control max-age=0;
try_files $uri =404;
}
diff --git a/package.json b/package.json
index 7f19ca955..c58111e31 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server",
- "version": "1.28.0",
+ "version": "2.0.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"chainpad-server": "^2.0.0",
diff --git a/pinned.js b/pinned.js
index 41a832241..d5df9373a 100644
--- a/pinned.js
+++ b/pinned.js
@@ -15,6 +15,7 @@ const hashesFromPinFile = (pinFile, fileName) => {
switch (l[0]) {
case 'RESET': {
pins = {};
+ if (l[1] && l[1].length) { l[1].forEach((x) => { pins[x] = 1; }); }
//jshint -W086
// fallthrough
}
@@ -32,7 +33,7 @@ const hashesFromPinFile = (pinFile, fileName) => {
return Object.keys(pins);
};
-module.exports.load = function (cb) {
+module.exports.load = function (cb, config) {
nThen((waitFor) => {
Fs.readdir('./pins', waitFor((err, list) => {
if (err) {
@@ -49,7 +50,10 @@ module.exports.load = function (cb) {
sema.take((returnAfter) => {
Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => {
if (err) { throw err; }
- list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); });
+ list2.forEach((ff) => {
+ if (config && config.exclude && config.exclude.indexOf(ff) > -1) { return; }
+ fileList.push('./pins/' + f + '/' + ff);
+ });
})));
});
});
@@ -76,4 +80,4 @@ if (!module.parent) {
console.log(x + ' ' + JSON.stringify(data[x]));
});
});
-}
\ No newline at end of file
+}
diff --git a/pinneddata.js b/pinneddata.js
index 378f34ad7..346062fb6 100644
--- a/pinneddata.js
+++ b/pinneddata.js
@@ -3,14 +3,21 @@ const Fs = require('fs');
const Semaphore = require('saferphore');
const nThen = require('nthen');
+/*
+ takes contents of a pinFile (UTF8 string)
+ and the pin file's name
+ returns an array of of channel ids which are pinned
+
+ throw errors on pin logs with invalid pin data
+*/
const hashesFromPinFile = (pinFile, fileName) => {
var pins = {};
pinFile.split('\n').filter((x)=>(x)).map((l) => JSON.parse(l)).forEach((l) => {
switch (l[0]) {
case 'RESET': {
pins = {};
- //jshint -W086
- // fallthrough
+ if (l[1] && l[1].length) { l[1].forEach((x) => { pins[x] = 1; }); }
+ break;
}
case 'PIN': {
l[1].forEach((x) => { pins[x] = 1; });
@@ -26,6 +33,11 @@ const hashesFromPinFile = (pinFile, fileName) => {
return Object.keys(pins);
};
+/*
+ takes an array of pinned file names
+ and a global map of stats indexed by public keys
+ returns the sum of the size of those pinned files
+*/
const sizeForHashes = (hashes, dsFileStats) => {
let sum = 0;
hashes.forEach((h) => {
@@ -39,23 +51,31 @@ const sizeForHashes = (hashes, dsFileStats) => {
return sum;
};
+// do twenty things at a time
const sema = Semaphore.create(20);
let dirList;
-const fileList = [];
-const dsFileStats = {};
-const out = [];
-const pinned = {};
+const fileList = []; // array which we reuse for a lot of things
+const dsFileStats = {}; // map of stats
+const out = []; // what we return at the end
+const pinned = {}; // map of pinned files
+// define a function: 'load' which takes a config
+// and a callback
module.exports.load = function (config, cb) {
nThen((waitFor) => {
+ // read the subdirectories in the datastore
Fs.readdir('./datastore', waitFor((err, list) => {
if (err) { throw err; }
dirList = list;
}));
}).nThen((waitFor) => {
+ // iterate over all subdirectories
dirList.forEach((f) => {
+ // process twenty subdirectories simultaneously
sema.take((returnAfter) => {
+ // get the list of files in every subdirectory
+ // and push them to 'fileList'
Fs.readdir('./datastore/' + f, waitFor(returnAfter((err, list2) => {
if (err) { throw err; }
list2.forEach((ff) => { fileList.push('./datastore/' + f + '/' + ff); });
@@ -63,14 +83,19 @@ module.exports.load = function (config, cb) {
});
});
}).nThen((waitFor) => {
-
+ // read the subdirectories in 'blob'
Fs.readdir('./blob', waitFor((err, list) => {
if (err) { throw err; }
+ // overwrite dirList
dirList = list;
}));
}).nThen((waitFor) => {
+ // iterate over all subdirectories
dirList.forEach((f) => {
+ // process twenty subdirectories simultaneously
sema.take((returnAfter) => {
+ // get the list of files in every subdirectory
+ // and push them to 'fileList'
Fs.readdir('./blob/' + f, waitFor(returnAfter((err, list2) => {
if (err) { throw err; }
list2.forEach((ff) => { fileList.push('./blob/' + f + '/' + ff); });
@@ -78,24 +103,34 @@ module.exports.load = function (config, cb) {
});
});
}).nThen((waitFor) => {
+ // iterate over the fileList
fileList.forEach((f) => {
+ // process twenty files simultaneously
sema.take((returnAfter) => {
+ // get the stats of each files
Fs.stat(f, waitFor(returnAfter((err, st) => {
if (err) { throw err; }
st.filename = f;
+ // push them to a big map of stats
dsFileStats[f.replace(/^.*\/([^\/\.]*)(\.ndjson)?$/, (all, a) => (a))] = st;
})));
});
});
}).nThen((waitFor) => {
+ // read the subdirectories in the pinstore
Fs.readdir('./pins', waitFor((err, list) => {
if (err) { throw err; }
dirList = list;
}));
}).nThen((waitFor) => {
+ // set file list to an empty array
+ // fileList = [] ??
fileList.splice(0, fileList.length);
dirList.forEach((f) => {
+ // process twenty directories at a time
sema.take((returnAfter) => {
+ // get the list of files in every subdirectory
+ // and push them to 'fileList' (which is empty because we keep reusing it)
Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => {
if (err) { throw err; }
list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); });
@@ -103,70 +138,147 @@ module.exports.load = function (config, cb) {
});
});
}).nThen((waitFor) => {
+ // iterate over the list of pin logs
fileList.forEach((f) => {
+ // twenty at a time
sema.take((returnAfter) => {
+ // read the full content
Fs.readFile(f, waitFor(returnAfter((err, content) => {
if (err) { throw err; }
+ // get the list of channels pinned by this log
const hashes = hashesFromPinFile(content.toString('utf8'), f);
- const size = sizeForHashes(hashes, dsFileStats);
if (config.unpinned) {
hashes.forEach((x) => { pinned[x] = 1; });
} else {
+ // get the size of files pinned by this log
+ // but only if we're gonna use it
+ let size = sizeForHashes(hashes, dsFileStats);
+ // we will return a list of values
+ // [user_public_key, size_of_files_they_have_pinned]
out.push([f, Math.floor(size / (1024 * 1024))]);
}
})));
});
});
}).nThen(() => {
+ // handle all the information you've processed so far
if (config.unpinned) {
+ // the user wants data about what has not been pinned
+
+ // by default we concern ourselves with pads and files older than infinity (everything)
let before = Infinity;
+
+ // but you can override this with config
if (config.olderthan) {
before = config.olderthan;
- if (isNaN(before)) {
+ // FIXME validate inputs before doing the heavy lifting
+ if (isNaN(before)) { // make sure the supplied value is a number
return void cb('--olderthan error [' + config.olderthan + '] not a valid date');
}
}
+
+ // you can specify a different time for blobs...
let blobsbefore = before;
if (config.blobsolderthan) {
+ // use the supplied date if it exists
blobsbefore = config.blobsolderthan;
if (isNaN(blobsbefore)) {
return void cb('--blobsolderthan error [' + config.blobsolderthan + '] not a valid date');
}
}
let files = [];
+ // iterate over all the stats that you've saved
Object.keys(dsFileStats).forEach((f) => {
+ // we only care about files which are not in the pin map
if (!(f in pinned)) {
+ // check if it's a blob or a 'pad'
const isBlob = dsFileStats[f].filename.indexOf('.ndjson') === -1;
- if ((+dsFileStats[f].atime) >= ((isBlob) ? blobsbefore : before)) { return; }
+
+ // if the mtime is newer than the specified value for its file type, ignore this file
+
+ if ((+dsFileStats[f].mtime) >= ((isBlob) ? blobsbefore : before)) { return; }
+
+ // otherwise push it to the list of files, with its filename, size, and mtime
files.push({
filename: dsFileStats[f].filename,
size: dsFileStats[f].size,
- atime: dsFileStats[f].atime
+ mtime: dsFileStats[f].mtime
});
}
});
+
+ // return the list of files
cb(null, files);
} else {
+ // if you're not in 'unpinned' mode, sort by size (ascending)
out.sort((a,b) => (a[1] - b[1]));
+ // and return the sorted data
cb(null, out.slice());
}
});
};
+
+// This script can be called directly on its own
+// or required as part of another script
if (!module.parent) {
- let config = {};
+ // if no parent, it is being invoked directly
+ let config = {}; // build the config from command line arguments...
+
+ // --unpinned gets the list of unpinned files
+ // if you don't pass this, it will list the size of pinned data per user
if (process.argv.indexOf('--unpinned') > -1) { config.unpinned = true; }
+
+ // '--olderthan' must be used in conjunction with '--unpinned'
+ // if you pass '--olderthan' with a string date or number, it will limit
+ // results only to pads older than the supplied time
+ // it defaults to 'infinity', or no filter at all
const ot = process.argv.indexOf('--olderthan');
- config.olderthan = ot > -1 && new Date(process.argv[ot+1]);
+ if (ot > -1) {
+ config.olderthan = Number(process.argv[ot+1]) ? new Date(Number(process.argv[ot+1]))
+ : new Date(process.argv[ot+1]);
+ }
+
+ // '--blobsolderthan' must be used in conjunction with '--unpinned'
+ // if you pass '--blobsolderthan with a string date or number, it will limit
+ // results only to blobs older than the supplied time
+ // it defaults to using the same value passed '--olderthan'
const bot = process.argv.indexOf('--blobsolderthan');
- config.blobsolderthan = bot > -1 && new Date(process.argv[bot+1]);
+ if (bot > -1) {
+ config.blobsolderthan = Number(process.argv[bot+1]) ? new Date(Number(process.argv[bot+1]))
+ : new Date(process.argv[bot+1]);
+ }
+
+ // call our big function directly
+ // pass our constructed configuration and a callback
module.exports.load(config, function (err, data) {
- if (err) { throw new Error(err); }
- if (!Array.isArray(data)) { return; }
+ if (err) { throw new Error(err); } // throw errors
+ if (!Array.isArray(data)) { return; } // if the returned value is not an array, you're done
if (config.unpinned) {
- data.forEach((f) => { console.log(f.filename + " " + f.size + " " + (+f.atime)); });
+ // display the list of unpinned files with their size and mtime
+ data.forEach((f) => { console.log(f.filename + " " + f.size + " " + (+f.mtime)); });
} else {
+ // display the list of public keys and the size of the data they have pinned in megabytes
data.forEach((x) => { console.log(x[0] + ' ' + x[1] + ' MB'); });
}
});
}
+
+
+/* Example usage of this script...
+
+# display the list of public keys and the size of the data the have pinned in megabytes
+node pinneddata.js
+
+# display the list of unpinned pads and blobs with their size and mtime
+node pinneddata.js --unpinned
+
+# display the list of unpinned pads and blobs older than 12345 with their size and mtime
+node pinneddata.js --unpinned --olderthan 12345
+
+
+# display the list of unpinned pads older than 12345 and unpinned blobs older than 123
+# each with their size and mtime
+node pinneddata.js --unpinned --olderthan 12345 --blobsolderthan 123
+
+*/
diff --git a/rpc.js b/rpc.js
index ae4971ae0..88228fdac 100644
--- a/rpc.js
+++ b/rpc.js
@@ -326,6 +326,24 @@ var getFileSize = function (Env, channel, cb) {
});
};
+var getMetadata = function (Env, channel, cb) {
+ if (!isValidId(channel)) { return void cb('INVALID_CHAN'); }
+
+ if (channel.length === 32) {
+ if (typeof(Env.msgStore.getChannelMetadata) !== 'function') {
+ return cb('GET_CHANNEL_METADATA_UNSUPPORTED');
+ }
+
+ return void Env.msgStore.getChannelMetadata(channel, function (e, data) {
+ if (e) {
+ if (e.code === 'INVALID_METADATA') { return void cb(void 0, {}); }
+ return void cb(e.code);
+ }
+ cb(void 0, data);
+ });
+ }
+};
+
var getMultipleFileSize = function (Env, channels, cb) {
if (!Array.isArray(channels)) { return cb('INVALID_PIN_LIST'); }
if (typeof(Env.msgStore.getChannelSize) !== 'function') {
@@ -1139,6 +1157,7 @@ var isNewChannel = function (Env, channel, cb) {
var isUnauthenticatedCall = function (call) {
return [
'GET_FILE_SIZE',
+ 'GET_METADATA',
'GET_MULTIPLE_FILE_SIZE',
'IS_CHANNEL_PINNED',
'IS_NEW_CHANNEL',
@@ -1260,12 +1279,14 @@ RPC.create = function (
}
case 'GET_FILE_SIZE':
return void getFileSize(Env, msg[1], function (e, size) {
- if (e) {
- console.error(e);
- }
WARN(e, msg[1]);
respond(e, [null, size, null]);
});
+ case 'GET_METADATA':
+ return void getMetadata(Env, msg[1], function (e, data) {
+ WARN(e, msg[1]);
+ respond(e, [null, data, null]);
+ });
case 'GET_MULTIPLE_FILE_SIZE':
return void getMultipleFileSize(Env, msg[1], function (e, dict) {
if (e) {
@@ -1458,9 +1479,9 @@ RPC.create = function (
Respond(void 0, "OK");
});
case 'REMOVE_PINS':
- return void removePins(Env, safeKey, function (e, response) {
+ return void removePins(Env, safeKey, function (e) {
if (e) { return void Respond(e); }
- Respond(void 0, response);
+ Respond(void 0, "OK");
});
// restricted to privileged users...
case 'UPLOAD':
diff --git a/server.js b/server.js
index a7830cbaf..aac8d7513 100644
--- a/server.js
+++ b/server.js
@@ -123,6 +123,9 @@ app.get(mainPagePattern, Express.static(__dirname + '/customize.dist'));
app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob')), {
maxAge: DEV_MODE? "0d": "365d"
}));
+app.use("/datastore", Express.static(Path.join(__dirname, (config.filePath || './datastore')), {
+ maxAge: "0d"
+}));
app.use("/customize", Express.static(__dirname + '/customize'));
app.use("/customize", Express.static(__dirname + '/customize.dist'));
@@ -188,6 +191,7 @@ var custom_four04_path = Path.resolve(__dirname + '/customize/404.html');
var send404 = function (res, path) {
if (!path && path !== four04_path) { path = four04_path; }
Fs.exists(path, function (exists) {
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
if (exists) { return Fs.createReadStream(path).pipe(res); }
send404(res);
});
@@ -250,4 +254,4 @@ var nt = nThen(function (w) {
if (config.debugReplName) {
require('replify')({ name: config.debugReplName, app: debuggableStore });
-}
\ No newline at end of file
+}
diff --git a/www/assert/main.js b/www/assert/main.js
index eb470eb35..f8177a03f 100644
--- a/www/assert/main.js
+++ b/www/assert/main.js
@@ -223,6 +223,33 @@ define([
hd.type === 'invite');
}, "test support for invite urls");
+ // test support for V2
+ assert(function (cb) {
+ var parsed = Hash.parsePadUrl('/pad/#/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/');
+ var secret = Hash.getSecrets('pad', '/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/');
+ return cb(parsed.hashData.version === 2 &&
+ parsed.hashData.mode === "edit" &&
+ parsed.hashData.type === "pad" &&
+ parsed.hashData.key === "oRE0oLCtEXusRDyin7GyLGcS" &&
+ secret.channel === "d8d51b4aea863f3f050f47f8ad261753" &&
+ window.nacl.util.encodeBase64(secret.keys.cryptKey) === "0Ts1M6VVEozErV2Nx/LTv6Im5SCD7io2LlhasyyBPQo=" &&
+ secret.keys.validateKey === "f5A1FM9Gp55tnOcM75RyHD1oxBG9ZPh9WDA7qe2Fvps=" &&
+ !parsed.hashData.present);
+ }, "test support for version 2 hash failed to parse");
+ assert(function (cb) {
+ var parsed = Hash.parsePadUrl('/pad/#/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed');
+ var secret = Hash.getSecrets('pad', '/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed', 'pewpew');
+ return cb(parsed.hashData.version === 2 &&
+ parsed.hashData.mode === "edit" &&
+ parsed.hashData.type === "pad" &&
+ parsed.hashData.key === "HGu0tK2od-2BBnwAz2ZNS-t4" &&
+ secret.channel === "3fb6dc93807d903aff390b5f798c92c9" &&
+ window.nacl.util.encodeBase64(secret.keys.cryptKey) === "EeCkGJra8eJgVu7v4Yl2Hc3yUjrgpKpxr0Lcc3bSWVs=" &&
+ secret.keys.validateKey === "WGkBczJf2V6vQZfAScz8V1KY6jKdoxUCckrD+E75gGE=" &&
+ parsed.hashData.embed &&
+ parsed.hashData.password);
+ }, "test support for password in version 2 hash failed to parse");
+
assert(function (cb) {
var url = '/pad/?utm_campaign=new_comment&utm_medium=email&utm_source=thread_mailer#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/';
var secret = Hash.parsePadUrl(url);
diff --git a/www/code/inner.js b/www/code/inner.js
index c2e5851bf..0a6b11abc 100644
--- a/www/code/inner.js
+++ b/www/code/inner.js
@@ -334,6 +334,7 @@ define([
//var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')';
+ // PASSWORD_FILES
var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
diff --git a/www/common/boot2.js b/www/common/boot2.js
index a9d0606db..fc3968fb3 100644
--- a/www/common/boot2.js
+++ b/www/common/boot2.js
@@ -34,7 +34,7 @@ define([
url: '/common/feedback.html?NO_LOCALSTORAGE=' + (+new Date()),
});
});
- window.alert("CryptPad needs localStorage to work, try a different browser");
+ window.alert("CryptPad needs localStorage to work. Try changing your cookie permissions, or using a different browser");
};
window.onerror = function (e) {
diff --git a/www/common/common-hash.js b/www/common/common-hash.js
index 870b19dfe..5ba9ec5b3 100644
--- a/www/common/common-hash.js
+++ b/www/common/common-hash.js
@@ -19,7 +19,50 @@ define([
.decodeUTF8(JSON.stringify(list))));
};
- var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) {
+ var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) {
+ var version = secret.version;
+ var data = secret.keys;
+ if (version === 0) {
+ return secret.channel + secret.key;
+ }
+ if (version === 1) {
+ if (!data.editKeyStr) { return; }
+ return '/1/edit/' + hexToBase64(secret.channel) +
+ '/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/';
+ }
+ if (version === 2) {
+ if (!data.editKeyStr) { return; }
+ var pass = secret.password ? 'p/' : '';
+ return '/2/' + secret.type + '/edit/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/' + pass;
+ }
+ };
+ var getViewHashFromKeys = Hash.getViewHashFromKeys = function (secret) {
+ var version = secret.version;
+ var data = secret.keys;
+ if (version === 0) { return; }
+ if (version === 1) {
+ if (!data.viewKeyStr) { return; }
+ return '/1/view/' + hexToBase64(secret.channel) +
+ '/'+Crypto.b64RemoveSlashes(data.viewKeyStr)+'/';
+ }
+ if (version === 2) {
+ if (!data.viewKeyStr) { return; }
+ var pass = secret.password ? 'p/' : '';
+ return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass;
+ }
+ };
+ var getFileHashFromKeys = Hash.getFileHashFromKeys = function (secret) {
+ var version = secret.version;
+ var data = secret.keys;
+ if (version === 0) { return; }
+ if (version === 1) {
+ return '/1/' + hexToBase64(secret.channel) + '/' +
+ Crypto.b64RemoveSlashes(data.fileKeyStr) + '/';
+ }
+ };
+
+ // V1
+ /*var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) {
if (typeof keys === 'string') {
return chanKey + keys;
}
@@ -34,7 +77,7 @@ define([
};
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) {
return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/';
- };
+ };*/
Hash.getUserHrefFromKeys = function (origin, username, pubkey) {
return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
};
@@ -43,6 +86,24 @@ define([
return s.replace(/\/+/g, '/');
};
+ Hash.createChannelId = function () {
+ var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
+ if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
+ throw new Error('channel ids must consist of 32 hex characters');
+ }
+ return id;
+ };
+
+ Hash.createRandomHash = function (type, password) {
+ var cryptor = Crypto.createEditCryptor2(void 0, void 0, password);
+ return getEditHashFromKeys({
+ password: Boolean(password),
+ version: 2,
+ type: type,
+ keys: { editKeyStr: cryptor.editKeyStr }
+ });
+ };
+
/*
Version 0
/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy
@@ -56,25 +117,56 @@ Version 1
var hashArr = fixDuplicateSlashes(hash).split('/');
if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) {
parsed.type = 'pad';
- if (hash.slice(0,1) !== '/' && hash.length >= 56) {
+ if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0
// Old hash
parsed.channel = hash.slice(0, 32);
parsed.key = hash.slice(32, 56);
parsed.version = 0;
+ parsed.getHash = function () { return hash; };
return parsed;
}
- if (hashArr[1] && hashArr[1] === '1') {
+ var options;
+ if (hashArr[1] && hashArr[1] === '1') { // Version 1
parsed.version = 1;
parsed.mode = hashArr[2];
parsed.channel = hashArr[3];
- parsed.key = hashArr[4].replace(/-/g, '/');
- var options = hashArr.slice(5);
+ parsed.key = Crypto.b64AddSlashes(hashArr[4]);
+
+ options = hashArr.slice(5);
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
+
+ parsed.getHash = function (opts) {
+ var hash = hashArr.slice(0, 5).join('/') + '/';
+ if (opts.embed) { hash += 'embed/'; }
+ if (opts.present) { hash += 'present/'; }
+ return hash;
+ };
+ return parsed;
+ }
+ if (hashArr[1] && hashArr[1] === '2') { // Version 2
+ parsed.version = 2;
+ parsed.app = hashArr[2];
+ parsed.mode = hashArr[3];
+ parsed.key = hashArr[4];
+
+ options = hashArr.slice(5);
+ parsed.password = options.indexOf('p') !== -1;
+ parsed.present = options.indexOf('present') !== -1;
+ parsed.embed = options.indexOf('embed') !== -1;
+
+ parsed.getHash = function (opts) {
+ var hash = hashArr.slice(0, 5).join('/') + '/';
+ if (parsed.password) { hash += 'p/'; }
+ if (opts.embed) { hash += 'embed/'; }
+ if (opts.present) { hash += 'present/'; }
+ return hash;
+ };
return parsed;
}
return parsed;
}
+ parsed.getHash = function () { return hashArr.join('/'); };
if (['media', 'file'].indexOf(type) !== -1) {
parsed.type = 'file';
if (hashArr[1] && hashArr[1] === '1') {
@@ -125,17 +217,9 @@ Version 1
url += ret.type + '/';
if (!ret.hashData) { return url; }
if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; }
- if (ret.hashData.version !== 1) { return url + '#' + ret.hash; }
- url += '#/' + ret.hashData.version +
- '/' + ret.hashData.mode +
- '/' + ret.hashData.channel.replace(/\//g, '-') +
- '/' + ret.hashData.key.replace(/\//g, '-') +'/';
- if (options.embed) {
- url += 'embed/';
- }
- if (options.present) {
- url += 'present/';
- }
+ if (ret.hashData.version === 0) { return url + '#' + ret.hash; }
+ var hash = ret.hashData.getHash(options);
+ url += '#' + hash;
return url;
};
@@ -153,12 +237,13 @@ Version 1
return '';
});
idx = href.indexOf('/#');
+ if (idx === -1) { return ret; }
ret.hash = href.slice(idx + 2);
ret.hashData = parseTypeHash(ret.type, ret.hash);
return ret;
};
- var getRelativeHref = Hash.getRelativeHref = function (href) {
+ Hash.getRelativeHref = function (href) {
if (!href) { return; }
if (href.indexOf('#') === -1) { return; }
var parsed = parsePadUrl(href);
@@ -170,11 +255,13 @@ Version 1
* - no argument: use the URL hash or create one if it doesn't exist
* - secretHash provided: use secretHash to find the keys
*/
- Hash.getSecrets = function (type, secretHash) {
+ Hash.getSecrets = function (type, secretHash, password) {
var secret = {};
var generate = function () {
- secret.keys = Crypto.createEditCryptor();
- secret.key = Crypto.createEditCryptor().editKeyStr;
+ secret.keys = Crypto.createEditCryptor2(void 0, void 0, password);
+ secret.channel = base64ToHex(secret.keys.chanId);
+ secret.version = 2;
+ secret.type = type;
};
if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) {
generate();
@@ -191,7 +278,6 @@ Version 1
parsed = pHref.hashData;
hash = pHref.hash;
}
- //var parsed = parsePadUrl(window.location.href);
//var hash = secretHash || window.location.hash.slice(1);
if (hash.length === 0) {
generate();
@@ -203,9 +289,10 @@ Version 1
// Old hash
secret.channel = parsed.channel;
secret.key = parsed.key;
- }
- else if (parsed.version === 1) {
+ secret.version = 0;
+ } else if (parsed.version === 1) {
// New hash
+ secret.version = 1;
if (parsed.type === "pad") {
secret.channel = base64ToHex(parsed.channel);
if (parsed.mode === 'edit') {
@@ -229,49 +316,63 @@ Version 1
// version 2 hashes are to be used for encrypted blobs
throw new Error("User hashes can't be opened (yet)");
}
+ } else if (parsed.version === 2) {
+ // New hash
+ secret.version = 2;
+ secret.type = type;
+ secret.password = password;
+ if (parsed.type === "pad") {
+ if (parsed.mode === 'edit') {
+ secret.keys = Crypto.createEditCryptor2(parsed.key, void 0, password);
+ secret.channel = base64ToHex(secret.keys.chanId);
+ secret.key = secret.keys.editKeyStr;
+ if (secret.channel.length !== 32 || secret.key.length !== 24) {
+ throw new Error("The channel key and/or the encryption key is invalid");
+ }
+ }
+ else if (parsed.mode === 'view') {
+ secret.keys = Crypto.createViewCryptor2(parsed.key, password);
+ secret.channel = base64ToHex(secret.keys.chanId);
+ if (secret.channel.length !== 32) {
+ throw new Error("The channel key is invalid");
+ }
+ }
+ } else if (parsed.type === "file") {
+ throw new Error("File hashes should be version 1");
+ } else if (parsed.type === "user") {
+ throw new Error("User hashes can't be opened (yet)");
+ }
}
}
return secret;
};
- Hash.getHashes = function (channel, secret) {
+ Hash.getHashes = function (secret) {
var hashes = {};
- if (!secret.keys) {
+ secret = JSON.parse(JSON.stringify(secret));
+
+ if (!secret.keys && !secret.key) {
console.error('e');
return hashes;
+ } else if (!secret.keys) {
+ secret.keys = {};
}
- if (secret.keys.editKeyStr) {
- hashes.editHash = getEditHashFromKeys(channel, secret.keys);
+
+ if (secret.keys.editKeyStr || (secret.version === 0 && secret.key)) {
+ hashes.editHash = getEditHashFromKeys(secret);
}
if (secret.keys.viewKeyStr) {
- hashes.viewHash = getViewHashFromKeys(channel, secret.keys);
+ hashes.viewHash = getViewHashFromKeys(secret);
}
if (secret.keys.fileKeyStr) {
- hashes.fileHash = getFileHashFromKeys(channel, secret.keys.fileKeyStr);
+ hashes.fileHash = getFileHashFromKeys(secret);
}
return hashes;
};
- var createChannelId = Hash.createChannelId = function () {
- var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
- if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
- throw new Error('channel ids must consist of 32 hex characters');
- }
- return id;
- };
-
- Hash.createRandomHash = function () {
- // 16 byte channel Id
- var channelId = Util.hexToBase64(createChannelId());
- // 18 byte encryption key
- var key = Crypto.b64RemoveSlashes(Crypto.rand64(18));
- return '/1/edit/' + [channelId, key].join('/') + '/';
- };
-
// STORAGE
- Hash.findWeaker = function (href, recents) {
- var rHref = href || getRelativeHref(window.location.href);
- var parsed = parsePadUrl(rHref);
+ Hash.findWeaker = function (href, channel, recents) {
+ var parsed = parsePadUrl(href);
if (!parsed.hash) { return false; }
var weaker;
Object.keys(recents).some(function (id) {
@@ -279,6 +380,8 @@ Version 1
var p = parsePadUrl(pad.href);
if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
+ if (channel !== pad.channel) { return; } // Not the same channel
+
var pHash = p.hashData;
var parsedHash = parsed.hashData;
if (!parsedHash || !pHash) { return; }
@@ -287,18 +390,16 @@ Version 1
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
if (pHash.version !== parsedHash.version) { return; }
- if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'view' && parsedHash.mode === 'edit') {
- weaker = pad.href;
+ weaker = pad;
return true;
}
return;
});
return weaker;
};
- var findStronger = Hash.findStronger = function (href, recents) {
- var rHref = href || getRelativeHref(window.location.href);
- var parsed = parsePadUrl(rHref);
+ Hash.findStronger = function (href, channel, recents) {
+ var parsed = parsePadUrl(href);
if (!parsed.hash) { return false; }
// We can't have a stronger hash if we're already in edit mode
if (parsed.hashData && parsed.hashData.mode === 'edit') { return; }
@@ -308,6 +409,8 @@ Version 1
var p = parsePadUrl(pad.href);
if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
+ if (channel !== pad.channel) { return; } // Not the same channel
+
var pHash = p.hashData;
var parsedHash = parsed.hashData;
if (!parsedHash || !pHash) { return; }
@@ -316,37 +419,20 @@ Version 1
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
if (pHash.version !== parsedHash.version) { return; }
- if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'edit' && parsedHash.mode === 'view') {
- stronger = pad.href;
+ stronger = pad;
return true;
}
return;
});
return stronger;
};
- Hash.isNotStrongestStored = function (href, recents) {
- return findStronger(href, recents);
- };
- Hash.hrefToHexChannelId = function (href) {
+ Hash.hrefToHexChannelId = function (href, password) {
var parsed = Hash.parsePadUrl(href);
if (!parsed || !parsed.hash) { return; }
-
- parsed = parsed.hashData;
- if (parsed.version === 0) {
- return parsed.channel;
- } else if (parsed.version !== 1 && parsed.version !== 2) {
- console.error("parsed href had no version");
- console.error(parsed);
- return;
- }
-
- var channel = parsed.channel;
- if (!channel) { return; }
-
- var hex = base64ToHex(channel);
- return hex;
+ var secret = Hash.getSecrets(parsed.type, parsed.hash, password);
+ return secret.channel;
};
Hash.getBlobPathFromHex = function (id) {
diff --git a/www/common/common-interface.js b/www/common/common-interface.js
index 24c7098c0..5af92df64 100644
--- a/www/common/common-interface.js
+++ b/www/common/common-interface.js
@@ -6,15 +6,18 @@ define([
'/common/common-notifier.js',
'/customize/application_config.js',
'/bower_components/alertifyjs/dist/js/alertify.js',
- '/common/tippy.min.js',
+ '/common/tippy/tippy.min.js',
'/customize/pages.js',
'/common/hyperscript.js',
+ '/customize/loading.js',
'/common/test.js',
+ '/common/jquery-ui/jquery-ui.min.js',
'/bower_components/bootstrap-tokenfield/dist/bootstrap-tokenfield.js',
- 'css!/common/tippy.css',
+ 'css!/common/tippy/tippy.css',
+ 'css!/common/jquery-ui/jquery-ui.min.css'
], function ($, Messages, Util, Hash, Notifier, AppConfig,
- Alertify, Tippy, Pages, h, Test) {
+ Alertify, Tippy, Pages, h, Loading, Test) {
var UI = {};
/*
@@ -182,11 +185,17 @@ define([
]);
};
- UI.tokenField = function (target) {
+ UI.tokenField = function (target, autocomplete) {
var t = {
element: target || h('input'),
};
- var $t = t.tokenfield = $(t.element).tokenfield();
+ var $t = t.tokenfield = $(t.element).tokenfield({
+ autocomplete: {
+ source: autocomplete,
+ delay: 100
+ },
+ showAutocompleteOnFocus: false
+ });
t.getTokens = function (ignorePending) {
var tokens = $t.tokenfield('getTokens').map(function (token) {
@@ -209,10 +218,17 @@ define([
t.preventDuplicates = function (cb) {
$t.on('tokenfield:createtoken', function (ev) {
+ // Close the suggest list when a token is added because we're going to wipe the input
+ var $input = $t.closest('.tokenfield').find('.token-input');
+ $input.autocomplete('close');
+
var val;
ev.attrs.value = ev.attrs.value.toLowerCase();
if (t.getTokens(true).some(function (t) {
- if (t === ev.attrs.value) { return ((val = t)); }
+ if (t === ev.attrs.value) {
+ ev.preventDefault();
+ return ((val = t));
+ }
})) {
ev.preventDefault();
if (typeof(cb) === 'function') { cb(val); }
@@ -240,7 +256,7 @@ define([
return t;
};
- dialog.tagPrompt = function (tags, cb) {
+ dialog.tagPrompt = function (tags, existing, cb) {
var input = dialog.textInput();
var tagger = dialog.frame([
@@ -254,7 +270,7 @@ define([
dialog.nav(),
]);
- var field = UI.tokenField(input).preventDuplicates(function (val) {
+ var field = UI.tokenField(input, existing).preventDuplicates(function (val) {
UI.warn(Messages._getKey('tags_duplicate', [val]));
});
@@ -395,7 +411,7 @@ define([
stopListening(listener);
cb();
});
- listener = listenForKeys(close, close, ok);
+ listener = listenForKeys(close, close);
var $ok = $(ok).click(close);
document.body.appendChild(frame);
@@ -512,6 +528,50 @@ define([
Alertify.error(Util.fixHTML(msg));
};
+ UI.passwordInput = function (opts, displayEye) {
+ opts = opts || {};
+ var attributes = merge({
+ type: 'password'
+ }, opts);
+
+ var input = h('input.cp-password-input', attributes);
+ var reveal = UI.createCheckbox('cp-password-reveal', Messages.password_show);
+ var eye = h('span.fa.fa-eye.cp-password-reveal');
+
+ $(reveal).find('input').on('change', function () {
+ if($(this).is(':checked')) {
+ $(input).prop('type', 'text');
+ $(input).focus();
+ return;
+ }
+ $(input).prop('type', 'password');
+ $(input).focus();
+ });
+
+ $(eye).mousedown(function () {
+ $(input).prop('type', 'text');
+ $(input).focus();
+ }).mouseup(function(){
+ $(input).prop('type', 'password');
+ $(input).focus();
+ }).mouseout(function(){
+ $(input).prop('type', 'password');
+ $(input).focus();
+ });
+ if (displayEye) {
+ $(reveal).hide();
+ } else {
+ $(eye).hide();
+ }
+
+ return h('span.cp-password-container', [
+ input,
+ reveal,
+ eye
+ ]);
+ };
+
+
/*
* spinner
*/
@@ -539,48 +599,99 @@ define([
var LOADING = 'cp-loading';
- var getRandomTip = function () {
+ /*var getRandomTip = function () {
if (!Messages.tips || !Object.keys(Messages.tips).length) { return ''; }
var keys = Object.keys(Messages.tips);
var rdm = Math.floor(Math.random() * keys.length);
return Messages.tips[keys[rdm]];
+ };*/
+ var loading = {
+ error: false,
+ driveState: 0,
+ padState: 0
};
UI.addLoadingScreen = function (config) {
config = config || {};
var loadingText = config.loadingText;
- var hideTips = config.hideTips || AppConfig.hideLoadingScreenTips;
- var hideLogo = config.hideLogo;
- var $loading, $container;
- if ($('#' + LOADING).length) {
- $loading = $('#' + LOADING); //.show();
+ var todo = function () {
+ var $loading = $('#' + LOADING); //.show();
$loading.css('display', '');
$loading.removeClass('cp-loading-hidden');
$('.cp-loading-spinner-container').show();
+ if (!config.noProgress && !$loading.find('.cp-loading-progress').length) {
+ var progress = h('div.cp-loading-progress', [
+ h('p.cp-loading-progress-drive'),
+ h('p.cp-loading-progress-pad')
+ ]);
+ $loading.find('.cp-loading-container').append(progress);
+ } else if (config.noProgress) {
+ $loading.find('.cp-loading-progress').remove();
+ }
if (loadingText) {
- $('#' + LOADING).find('p').text(loadingText);
+ $('#' + LOADING).find('#cp-loading-message').show().text(loadingText);
} else {
- $('#' + LOADING).find('p').text('');
+ $('#' + LOADING).find('#cp-loading-message').hide().text('');
}
- $container = $loading.find('.cp-loading-container');
+ loading.error = false;
+ };
+ if ($('#' + LOADING).length) {
+ todo();
} else {
- $loading = $(Pages.loadingScreen());
- $container = $loading.find('.cp-loading-container');
- if (hideLogo) {
- $loading.find('img').hide();
+ Loading();
+ todo();
+ }
+ };
+ UI.updateLoadingProgress = function (data, isDrive) {
+ var $loading = $('#' + LOADING);
+ if (!$loading.length || loading.error) { return; }
+ var $progress;
+ if (isDrive) {
+ console.log(data);
+ // Drive state
+ if (loading.driveState === -1) { return; } // Already loaded
+ $progress = $loading.find('.cp-loading-progress-drive');
+ if (!$progress.length) { return; } // Can't find the box to display data
+
+ // If state is -1, remove the box, drive is loaded
+ if (data.state === -1) {
+ loading.driveState = -1;
+ $progress.remove();
} else {
- $loading.find('img').show();
+ if (data.state < loading.driveState) { return; } // We should not display old data
+ // Update the current state
+ loading.driveState = data.state;
+ data.progress = data.progress || 100;
+ data.msg = Messages['loading_drive_'+data.state] || '';
+ $progress.html(data.msg);
+ if (data.progress) {
+ $progress.append(h('div.cp-loading-progress-bar', [
+ h('div.cp-loading-progress-bar-value', {style: 'width:'+data.progress+'%;'})
+ ]));
+ }
+ }
+ } else {
+ // Pad state
+ if (loading.padState === -1) { return; } // Already loaded
+ $progress = $loading.find('.cp-loading-progress-pad');
+ if (!$progress.length) { return; } // Can't find the box to display data
+
+ // If state is -1, remove the box, pad is loaded
+ if (data.state === -1) {
+ loading.padState = -1;
+ $progress.remove();
+ } else {
+ if (data.state < loading.padState) { return; } // We should not display old data
+ // Update the current state
+ loading.padState = data.state;
+ data.progress = data.progress || 100;
+ data.msg = Messages['loading_pad_'+data.state] || '';
+ $progress.html(data.msg);
+ if (data.progress) {
+ $progress.append(h('div.cp-loading-progress-bar', [
+ h('div.cp-loading-progress-bar-value', {style: 'width:'+data.progress+'%;'})
+ ]));
+ }
}
- var $spinner = $loading.find('.cp-loading-spinner-container');
- $spinner.show();
- $('body').append($loading);
- }
- if (Messages.tips && !hideTips) {
- var $loadingTip = $('
', {'id': 'cp-loading-tip'});
- $('', {'class': 'tips'}).text(getRandomTip()).appendTo($loadingTip);
- $loadingTip.css({
- 'bottom': $('body').height()/2 - $container.height()/2 + 20 + 'px'
- });
- $('body').append($loadingTip);
}
};
UI.removeLoadingScreen = function (cb) {
@@ -591,7 +702,7 @@ define([
$('#' + LOADING).addClass("cp-loading-hidden");
setTimeout(cb, 750);
- //$('#' + LOADING).fadeOut(750, cb);
+ loading.error = false;
var $tip = $('#cp-loading-tip').css('top', '')
// loading.less sets transition-delay: $wait-time
// and transition: opacity $fadeout-time
@@ -605,18 +716,27 @@ define([
// jquery.fadeout can get stuck
};
UI.errorLoadingScreen = function (error, transparent, exitable) {
- if (!$('#' + LOADING).is(':visible') || $('#' + LOADING).hasClass('cp-loading-hidden')) {
+ var $loading = $('#' + LOADING);
+ if (!$loading.is(':visible') || $loading.hasClass('cp-loading-hidden')) {
UI.addLoadingScreen({hideTips: true});
}
+ loading.error = true;
+ $loading.find('.cp-loading-progress').remove();
$('.cp-loading-spinner-container').hide();
$('#cp-loading-tip').remove();
- if (transparent) { $('#' + LOADING).css('opacity', 0.8); }
- $('#' + LOADING).find('p').html(error || Messages.error);
+ if (transparent) { $loading.css('opacity', 0.9); }
+ var $error = $loading.find('#cp-loading-message').show();
+ if (error instanceof Element) {
+ $error.html('').append(error);
+ } else {
+ $error.html(error || Messages.error);
+ }
if (exitable) {
$(window).focus();
$(window).keydown(function (e) {
if (e.which === 27) {
- $('#' + LOADING).hide();
+ $loading.hide();
+ loading.error = false;
if (typeof(exitable) === "function") { exitable(); }
}
});
@@ -660,18 +780,40 @@ define([
});
};
+ var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500;
+ $.extend(true, Tippy.defaults, {
+ placement: 'bottom',
+ performance: true,
+ delay: [delay, 0],
+ //sticky: true,
+ theme: 'cryptpad',
+ arrow: true,
+ maxWidth: '200px',
+ flip: true,
+ popperOptions: {
+ modifiers: {
+ preventOverflow: { boundariesElement: 'window' }
+ }
+ },
+ //arrowType: 'round',
+ dynamicTitle: true,
+ arrowTransform: 'scale(2)',
+ zIndex: 100000001
+ });
UI.addTooltips = function () {
var MutationObserver = window.MutationObserver;
- var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500;
var addTippy = function (i, el) {
+ if (el._tippy) { return; }
if (el.nodeName === 'IFRAME') { return; }
- Tippy(el, {
- position: 'bottom',
- distance: 0,
- performance: true,
- delay: [delay, 0],
- sticky: true
+ var opts = {
+ distance: 15
+ };
+ Array.prototype.slice.apply(el.attributes).filter(function (obj) {
+ return /^data-tippy-/.test(obj.name);
+ }).forEach(function (obj) {
+ opts[obj.name.slice(11)] = obj.value;
});
+ Tippy(el, opts);
};
// This is the robust solution to remove dangling tooltips
// The mutation observer does not always find removed nodes.
@@ -720,5 +862,9 @@ define([
});
};
+ UI.createCheckbox = Pages.createCheckbox;
+
+ UI.createRadio = Pages.createRadio;
+
return UI;
});
diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js
index 13d132219..b5a12da0b 100644
--- a/www/common/common-messaging.js
+++ b/www/common/common-messaging.js
@@ -99,7 +99,7 @@ define([
try {
var parsed = Hash.parsePadUrl(window.location.href);
if (!parsed.hashData) { return; }
- var chan = parsed.hashData.channel;
+ var chan = Hash.hrefToHexChannelId(window.location.href);
// Decrypt
var keyStr = parsed.hashData.key;
var cryptor = Crypto.createEditCryptor(keyStr);
@@ -113,7 +113,7 @@ define([
if (!decryptMsg) { return; }
// Parse
msg = JSON.parse(decryptMsg);
- if (msg[1] !== parsed.hashData.channel) { return; }
+ if (msg[1] !== chan) { return; }
var msgData = msg[2];
var msgStr;
if (msg[0] === "FRIEND_REQ") {
@@ -199,7 +199,7 @@ define([
var parsed = Hash.parsePadUrl(data.href);
if (!parsed.hashData) { return; }
// Message
- var chan = parsed.hashData.channel;
+ var chan = Hash.hrefToHexChannelId(data.href);
var myData = createData(cfg.proxy);
var msg = ["FRIEND_REQ", chan, myData];
// Encryption
diff --git a/www/common/common-thumbnail.js b/www/common/common-thumbnail.js
index 455b095c9..ab6d3e6d4 100644
--- a/www/common/common-thumbnail.js
+++ b/www/common/common-thumbnail.js
@@ -205,7 +205,7 @@ define([
if (content === oldThumbnailState) { return; }
oldThumbnailState = content;
Thumb.fromDOM(opts, function (err, b64) {
- Thumb.setPadThumbnail(common, opts.href, b64);
+ Thumb.setPadThumbnail(common, opts.href, null, b64);
});
};
var nafa = Util.notAgainForAnother(mkThumbnail, Thumb.UPDATE_INTERVAL);
@@ -240,20 +240,22 @@ define([
Thumb.addThumbnail = function(thumb, $span, cb) {
return addThumbnail(null, thumb, $span, cb);
};
- var getKey = function (href) {
- var parsed = Hash.parsePadUrl(href);
- return 'thumbnail-' + parsed.type + '-' + parsed.hashData.channel;
+ var getKey = function (type, channel) {
+ return 'thumbnail-' + type + '-' + channel;
};
- Thumb.setPadThumbnail = function (common, href, b64, cb) {
+ Thumb.setPadThumbnail = function (common, href, channel, b64, cb) {
cb = cb || function () {};
- var k = getKey(href);
+ var parsed = Hash.parsePadUrl(href);
+ channel = channel || common.getMetadataMgr().getPrivateData().channel;
+ var k = getKey(parsed.type, channel);
common.setThumbnail(k, b64, cb);
};
- Thumb.displayThumbnail = function (common, href, $container, cb) {
+ Thumb.displayThumbnail = function (common, href, channel, $container, cb) {
cb = cb || function () {};
var parsed = Hash.parsePadUrl(href);
- var k = getKey(href);
+ var k = getKey(parsed.type, channel);
var whenNewThumb = function () {
+ // PASSWORD_FILES
var secret = Hash.getSecrets('file', parsed.hash);
var hexFileName = Util.base64ToHex(secret.channel);
var src = Hash.getBlobPathFromHex(hexFileName);
@@ -270,7 +272,7 @@ define([
if (!v) {
v = 'EMPTY';
}
- Thumb.setPadThumbnail(common, href, v, function (err) {
+ Thumb.setPadThumbnail(common, href, hexFileName, v, function (err) {
if (!metadata.thumbnail) { return; }
addThumbnail(err, metadata.thumbnail, $container, cb);
});
diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index 8b282813c..4aaad5b9b 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -13,8 +13,6 @@ define([
'/customize/messages.js',
'/customize/application_config.js',
'/bower_components/nthen/index.js',
-
- 'css!/common/tippy.css',
], function ($, Config, Util, Hash, Language, UI, Constants, Feedback, h, MediaTag, Clipboard,
Messages, AppConfig, NThen) {
var UIElements = {};
@@ -25,20 +23,27 @@ define([
}
UIElements.updateTags = function (common, href) {
- var sframeChan = common.getSframeChannel();
- sframeChan.query('Q_TAGS_GET', href || null, function (err, res) {
- if (err || res.error) {
- if (res.error === 'NO_ENTRY') {
- UI.alert(Messages.tags_noentry);
+ var existing, tags;
+ NThen(function(waitFor) {
+ common.getSframeChannel().query("Q_GET_ALL_TAGS", null, waitFor(function(err, res) {
+ if (err || res.error) { return void console.error(err || res.error); }
+ existing = Object.keys(res.tags).sort();
+ }));
+ }).nThen(function (waitFor) {
+ common.getPadAttribute('tags', waitFor(function (err, res) {
+ if (err) {
+ if (err === 'NO_ENTRY') {
+ UI.alert(Messages.tags_noentry);
+ }
+ waitFor.abort();
+ return void console.error(err);
}
- return void console.error(err || res.error);
- }
- UI.dialog.tagPrompt(res.data, function (tags) {
- if (!Array.isArray(tags)) { return; }
- sframeChan.event('EV_TAGS_SET', {
- tags: tags,
- href: href,
- });
+ tags = res || [];
+ }), href);
+ }).nThen(function () {
+ UI.dialog.tagPrompt(tags, existing, function (newTags) {
+ if (!Array.isArray(newTags)) { return; }
+ common.setPadAttribute('tags', newTags, null, href);
});
});
};
@@ -62,6 +67,10 @@ define([
var getPropertiesData = function (common, cb) {
var data = {};
NThen(function (waitFor) {
+ common.getPadAttribute('password', waitFor(function (err, val) {
+ data.password = val;
+ }));
+ }).nThen(function (waitFor) {
common.getPadAttribute('href', waitFor(function (err, val) {
var base = common.getMetadataMgr().getPrivateData().origin;
@@ -73,15 +82,19 @@ define([
// We're not in a read-only pad
data.href = base + val;
+
// Get Read-only href
if (parsed.hashData.type !== "pad") { return; }
var i = data.href.indexOf('#') + 1;
var hBase = data.href.slice(0, i);
- var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash);
+ var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash, data.password);
if (!hrefsecret.keys) { return; }
- var viewHash = Hash.getViewHashFromKeys(hrefsecret.channel, hrefsecret.keys);
+ var viewHash = Hash.getViewHashFromKeys(hrefsecret);
data.roHref = hBase + viewHash;
}));
+ common.getPadAttribute('channel', waitFor(function (err, val) {
+ data.channel = val;
+ }));
common.getPadAttribute('atime', waitFor(function (err, val) {
data.atime = val;
}));
@@ -137,6 +150,22 @@ define([
$d.append(UI.dialog.selectable(expire, {
id: 'cp-app-prop-expire',
}));
+
+ if (typeof data.password !== "undefined") {
+ $('