diff --git a/CHANGELOG.md b/CHANGELOG.md
index e70921605..aed467811 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,74 @@
+# Pademelon release (v2.15.0)
+
+## Goals
+
+For this release we planned to improve upon last release's introduction of the display of other users' cursors in our code and slide editors by adding the same functionality to our rich text editor.
+
+Beyond just producing software, the CryptPad team has also begun to produce peer-reviewed papers.
+We have previously published [Private Document Editing with Some Trust](https://dl.acm.org/citation.cfm?doid=3209280.3209535) as a part of the 2018 proceedings of the ACM Symposium on Document Engineering.
+We have recently been accepted for publication as a part of [HCI-CPT](http://2019.hci.international/hci-cpt): the first international conference on HCI (Human Computer Interaction) for cybersecurity, privacy and trust.
+In preparation for this publication we've begun to collect additional usage data in order to inform the wider community of our findings regarding usability of cryptography-based collaboration systems.
+
+## Update notes
+
+* Updating to version 2.15.0 from 2.14.0 should only require that update to the latest clientside code via git, and update any cache-busting parameters you've set.
+* Several of our third-party clientside dependencies have been updated, and you may optionally run `bower update` to receive their latest versions.
+* As explained above, we have added a number of new keys to our existing feedback system. The new keys are detailed below
+ * HOME_SUPPORT_CRYPTPAD informs us when users discover our opencollective campaign from the CryptPad home page
+ * UPGRADE_ACCOUNT informs us when someone clicks the upgrade account button from their CryptDrive or settings page
+ * SUPPORT_CRYPTPAD is not active on our CryptPad instance, since this key is only sent when clicking the _donate button_ which is shown when upgraded accounts are disabled
+ * DELETE_ACCOUNT_AUTOMATIC informs us when somebody deletes their account automatically from the settings page. Automatic account deletion is only available for accounts created since version 1.29.0
+ * DELETE_ACCOUNT_MANUAL informs us when a user generates the proof of their account ownership which is required for manual account deletion. This feature is available only for accounts predating version 1.29.0
+ * OWNED_DRIVE_MIGRATION informs us when a user migrates their CryptDrive from our legacy format (which does not support automatic deletion) to our newer format (which does) via the settings page
+ * PASSWORD_CHANGED informs us when a user changes their password from the settings page
+ * NO_WEBRTC informs us when a users browser does not support WebRTC at all via a crude test which never actually runs any WebRTC-based code
+ * SUBSCRIPTION_BUTTON informs us when a user navigates to our paid account administration panel from their settings page
+ * LOGOUT_EVERYWHERE informs us when a user executes the command to log out of their account on all remote devices from the settings page
+* We've implemented the ability to configure which applications are available on a particular CryptPad instance via `cryptpad/customize/application_config.js`. Two arrays (`config.availablePadTypes` and `config.registeredOnlyTypes`) define which applications are available to everyone, and which applications are available to registered users. Due to a bug which was discovered, this behaviour is incorrect for our encrypted file viewer, and as a result encrypted files cannot currently be disabled. This will be addressed in our next release.
+
+## Features
+
+* Our rich text editor now displays other users' cursors when editing with a group. Preferences for this behaviour can be defined via the settings page.
+* Links in our rich text editor can now be clicked more easily, as a small tooltip with a clickable link will be displayed above the editable link in the document.
+* Users who wish to be notified of spelling errors in their rich text pads can enable spellcheck via the settings page.
+* As noted above, various pad types can be disabled by instance administrators via `customize/application_config.js`.
+* We've enabled a feature in the settings page which will migrate users' CryptDrive from our legacy format to our latest format (which supports automatic deletion). Only users with accounts dating back to version 1.29.0 will notice any difference.
+* We've worked to improve some usability issues presented by the interaction of _owned files_ and _shared folders_. Since only the owner of an owned document can delete it the owner must keep a record of that document in their CryptDrive even if they place it in a shared folder (where someone else could delete it while they are offline). As such, owned documents were always copied to shared folders instead of being moved, and this proliferation of copies made it more difficult for users to organize their CryptDrives. Duplicated owned documents which are kept in your CryptDrive can now be hidden via the settings page. If those files are removed from a shared folder by another user, the hidden duplicate will be revealed in the root of your CryptDrive's tree.
+* Finally, we've implemented the ability to copy documents to multiple shared folders via an entry in the right-click menu for any such document.
+
+## Bugfixes
+
+* We've improved the styles for displaying other users' cursors in the code and slide editors to avoid moving your view of the text when someone else highlights it.
+* We've also changed some of the logic for how often other users' cursors are updated and displayed, so as to maximize the accuracy of their position and not show incorrect placements while you are typing.
+* We fixed a bug which caused errors while loading your CryptDrive after a shared folder had been deleted.
+
+# Opossum release (v2.14.0)
+
+## Goals
+
+For this release we chose to focus on our in-pad chat functionality and the ability to show your cursor's position to other users in the same pad.
+
+## Update notes
+
+* We've released an updated version of a serverside dependency: `chainpad-server`
+ * this addresses a recently introduced bug which is capable of sending more history than clients require under certain circumstances
+ * to use this updated dependency, run `npm update` and restart your server
+
+## Features
+
+* Our code editor is now capable of displaying other user's cursors within your view of the document.
+ * this is enabled by default, but you can choose not to share your own cursor, and to disable the display of other users' cursors in your document
+ * your initial color is chosen randomly, but you can choose any color you like within the settings page alongside the other configuration options for cursors
+* After some consideration, we have chosen to change the permissions around the chat functionality embedded within every pad.
+ * previously we had allowed viewers to participate in chat, even though they could not change the document.
+ * we decided that this was counter-intuitive
+ * in the event of an XSS vulnerability it could be used as a vector for privilege escalation
+ * as such, we have modified our embedded chat functionality to only allow editors to participate
+ * this change is not backwards-compatible, and so the embedded chat boxes will have dropped their older history
+ * our assumption is that this will be an improvement for the majority of our users, and that it's fairly safe to drop older history given that chat is a relatively new feature
+ * if this has affected you in an adverse way, the information is still accessible, and you can contact us if you need a way to recover that information
+* Finally, it is now possible to print the rendered markdown content in our code editor, thanks to a contribution from [@joldie](https://github.com/joldie)
+
# Numbat release (v2.13.0)
## Goals
diff --git a/bower.json b/bower.json
index 27b1ea1f4..94c6af3a7 100644
--- a/bower.json
+++ b/bower.json
@@ -36,7 +36,7 @@
"alertifyjs": "1.0.11",
"scrypt-async": "1.2.0",
"require-css": "0.1.10",
- "less": "^3.7.1",
+ "less": "3.7.1",
"bootstrap": "^v4.0.0",
"diff-dom": "2.1.1",
"nthen": "^0.1.5",
diff --git a/customize.dist/ckeditor-config.js b/customize.dist/ckeditor-config.js
index d8936c2c4..720b289bc 100644
--- a/customize.dist/ckeditor-config.js
+++ b/customize.dist/ckeditor-config.js
@@ -28,7 +28,6 @@ CKEDITOR.editorConfig = function( config ) {
config.font_defaultLabel = 'Arial';
config.fontSize_defaultLabel = '16';
- config.contentsCss = '/customize/ckeditor-contents.css?' + CKEDITOR.CRYPTPAD_URLARGS;
config.keystrokes = [
[ CKEDITOR.ALT + 121 /*F10*/, 'toolbarFocus' ],
diff --git a/customize.dist/ckeditor-contents.css b/customize.dist/ckeditor-contents.css
index fe4a3799f..4d2abae08 100644
--- a/customize.dist/ckeditor-contents.css
+++ b/customize.dist/ckeditor-contents.css
@@ -158,18 +158,20 @@ a > img {
border: 2px solid red;
border-right-color: transparent !important;
border-left-color: transparent !important;
- margin-left: -2px;
- margin-right: -2px;
+ margin-left: -3px;
+ margin-right: -3px;
}
.cp-cursor-position[data-type="start"] {
border-left: none;
border-right-width: 4px;
- margin-right: -4px;
+ margin-right: -5px;
+ margin-left: -1px;
}
.cp-cursor-position[data-type="end"] {
border-right: none;
border-left-width: 4px;
- margin-left: -4px;
+ margin-left: -5px;
+ margin-right: -1px;
}
.cp-cursor-avatar {
display: flex;
diff --git a/customize.dist/pages.js b/customize.dist/pages.js
index c8ae8988b..04a47e202 100644
--- a/customize.dist/pages.js
+++ b/customize.dist/pages.js
@@ -91,7 +91,7 @@ define([
])
])
]),
- h('div.cp-version-footer', "CryptPad v2.14.0 (Opossum)")
+ h('div.cp-version-footer', "CryptPad v2.15.0 (Pademelon)")
]);
};
diff --git a/customize.dist/pages/index.js b/customize.dist/pages/index.js
index ee3c7fa46..34c0b6db4 100644
--- a/customize.dist/pages/index.js
+++ b/customize.dist/pages/index.js
@@ -2,11 +2,12 @@ define([
'jquery',
'/api/config',
'/common/hyperscript.js',
+ '/common/common-feedback.js',
'/customize/messages.js',
'/customize/application_config.js',
'/common/outer/local-store.js',
'/customize/pages.js'
-], function ($, Config, h, Msg, AppConfig, LocalStore, Pages) {
+], function ($, Config, h, Feedback, Msg, AppConfig, LocalStore, Pages) {
var urlArgs = Config.requireConf.urlArgs;
var isAvailableType = function (x) {
@@ -84,6 +85,7 @@ define([
$(crowdFunding).click(function () {
_link.click();
+ Feedback.send('HOME_SUPPORT_CRYPTPAD');
});
var blocks = h('div.container',[
diff --git a/customize.dist/pages/login.js b/customize.dist/pages/login.js
index f7b7a43d8..8efdb1f56 100644
--- a/customize.dist/pages/login.js
+++ b/customize.dist/pages/login.js
@@ -30,7 +30,8 @@ define([
UI.createCheckbox('import-recent', Msg.register_importRecent),
]),
h('div.extra', [
- h('button.login.first.btn', Msg.login_login)
+ h('button.login.first.btn', Msg.login_login),
+ h('button#register.first.btn', Msg.login_register)
])
])
]),
diff --git a/customize.dist/src/less2/include/cursor.less b/customize.dist/src/less2/include/cursor.less
index e70e877a1..355cf4076 100644
--- a/customize.dist/src/less2/include/cursor.less
+++ b/customize.dist/src/less2/include/cursor.less
@@ -8,6 +8,8 @@
border: 2px solid red;
border-right-color: transparent !important;
border-left-color: transparent !important;
+ margin-left: -3px;
+ margin-right: -3px;
}
.cp-codemirror-selection {
background-color: rgba(255,0,0,0.3);
diff --git a/customize.dist/src/less2/pages/page-login.less b/customize.dist/src/less2/pages/page-login.less
index c92a08bde..e4b3e96e0 100644
--- a/customize.dist/src/less2/pages/page-login.less
+++ b/customize.dist/src/less2/pages/page-login.less
@@ -61,6 +61,16 @@
transform: scale(1.05);
}
}
+ #register {
+ border-color: @cryptpad_color_blue;
+ background: #fff;
+ color: @cryptpad_color_blue;
+ padding: 10px;
+ border-radius: 0;
+ &:hover {
+ transform: scale(1.05);
+ }
+ }
}
}
.cp-container {
diff --git a/docker-compose.yml b/docker-compose.yml
index 22cc3d59e..fab74870a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -21,3 +21,8 @@ services:
volumes:
- ./data/files:/cryptpad/datastore:rw
- ./data/customize:/cryptpad/customize:rw
+ - ./data/pins:/cryptpad/pins:rw
+ - ./data/blob:/cryptpad/blob:rw
+ - ./data/blobstage:/cryptpad/blobstage:rw
+ - ./data/tasks:/cryptpad/tasks:rw
+ - ./data/block:/cryptpad/block:rw
diff --git a/docs/cryptpad-docker.md b/docs/cryptpad-docker.md
index 027792c2e..a41e2ad75 100644
--- a/docs/cryptpad-docker.md
+++ b/docs/cryptpad-docker.md
@@ -1,5 +1,11 @@
# Cryptpad Docker Image
+Cryptpad includes support for building a Docker image and running it to provide a Cryptpad instance. You can manage the container manually, or let Docker Compose manage it for you.
+
+A full tutorial is available [on the Cryptpad Github wiki](https://github.com/xwiki-labs/cryptpad/wiki/Docker-(with-Nginx-and-Traefik)). This document provides a brief overview.
+
+## Features
+
- Configuration via .env file
- Ready for use with traffic
- Using github master for now, release 0.3.0 too old
@@ -9,14 +15,22 @@
## Run
-Run from the cryptpad source directory:
+Run from the cryptpad source directory, keeping instance state in `/var/cryptpad`:
```
docker build -t xwiki/cryptpad .
-docker run --restart=always -d --name cryptpad -p 3000:3000 -v /var/cryptpad:/cryptpad/datastore xwiki/cryptpad
+docker run --restart=always -d --name cryptpad -p 3000:3000 \
+-v /var/cryptpad/files:/cryptpad/datastore \
+-v /var/cryptpad/customize:/cryptpad/customize
+-v /var/cryptpad/blob:/cryptpad/blob \
+-v /var/cryptpad/blobstage:/cryptpad/blobstage \
+-v /var/cryptpad/pins:/cryptpad/pins \
+-v /var/cryptpad/tasks:/cryptpad/tasks \
+-v /var/cryptpad/block:/cryptpad/block \
+xwiki/cryptpad
```
-Or, using docker-compose
+Or, using docker-compose and the included `docker-compose.yml`, keeping instance state in the current directory under `./data`:
```
docker-compose up -d
@@ -39,10 +53,15 @@ On runtime, in `bin/container-start.sh` the settings are written to the `config.
The docker-compose file is preconfigured to persist folders
-- cryptpad/datastore --> ./data/customize
+- cryptpad/datastore --> ./data/files
- cryptpad/customize --> ./data/customize
+- cryptpad/pins --> ./data/pins
+- cryptpad/blob --> ./data/blob
+- cryptpad/blobstage --> ./data/blobstage
+- cryptpad/tasks --> ./data/tasks
+- cryptpad/block --> ./data/block
-In customize included find your configuration in `config.js`.
+Your configuration file will be in `./data/customize/config.js`.
The data folder is ignored by git, so if you want to add your customizations to git versioning change the volume:
diff --git a/docs/example.nginx.conf b/docs/example.nginx.conf
index e8454dc31..5c4ff2fbe 100644
--- a/docs/example.nginx.conf
+++ b/docs/example.nginx.conf
@@ -34,15 +34,15 @@ server {
# Will not set any header if it is emptystring
add_header Cache-Control $cacheControl;
- set $styleSrc "'unsafe-inline' 'self' your-main-domain.com";
- set $scriptSrc "'self' your-main-domain.com";
- set $connectSrc "'self' https://your-main-domain.com wss://your-main-domain.com https://api.your-main-domain.com wss://your-main-domain.com your-main-domain.com blob: your-main-domain.com";
- set $fontSrc "'self' data: your-main-domain.com";
- set $imgSrc "data: * blob:";
- set $frameSrc "'self' your-sandbox-domain.com blob:";
- set $mediaSrc "* blob:";
- set $childSrc "https://your-main-domain.com";
- set $workerSrc "https://your-main-domain.com";
+ set $styleSrc "'unsafe-inline' 'self' your-main-domain.com";
+ set $scriptSrc "'self' your-main-domain.com";
+ set $connectSrc "'self' https://your-main-domain.com wss://your-main-domain.com your-main-domain.com https://api.your-main-domain.com blob: your-main-domain.com";
+ set $fontSrc "'self' data: your-main-domain.com";
+ set $imgSrc "'self' data: * blob: your-main-domain.com";
+ set $frameSrc "'self' your-sandbox-domain.com blob: your-sandbox-domain.com";
+ set $mediaSrc "'self' data: * blob: your-main-domain.com";
+ set $childSrc "https://your-main-domain.com";
+ set $workerSrc "https://your-main-domain.com";
set $unsafe 0;
if ($uri = "/pad/inner.html") { set $unsafe 1; }
diff --git a/package.json b/package.json
index b6456cf4c..1768001b7 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server",
- "version": "2.14.0",
+ "version": "2.15.0",
"license": "AGPL-3.0+",
"repository": {
"type": "git",
diff --git a/rpc.js b/rpc.js
index f64fddd48..7a0deb343 100644
--- a/rpc.js
+++ b/rpc.js
@@ -457,10 +457,40 @@ var getHash = function (Env, publicKey, cb) {
});
};
+var applyCustomLimits = function (Env, config) {
+ var isLimit = function (o) {
+ var valid = o && typeof(o) === 'object' &&
+ typeof(o.limit) === 'number' &&
+ typeof(o.plan) === 'string' &&
+ typeof(o.note) === 'string';
+ return valid;
+ };
+
+ // read custom limits from the config
+ var customLimits = (function (custom) {
+ var limits = {};
+ Object.keys(custom).forEach(function (k) {
+ k.replace(/\/([^\/]+)$/, function (all, safeKey) {
+ var id = unescapeKeyCharacters(safeKey || '');
+ limits[id] = custom[k];
+ return '';
+ });
+ });
+ return limits;
+ }(config.customLimits || {}));
+
+ Object.keys(customLimits).forEach(function (k) {
+ if (!isLimit(customLimits[k])) { return; }
+ Env.limits[k] = customLimits[k];
+ });
+};
+
// The limits object contains storage limits for all the publicKey that have paid
// To each key is associated an object containing the 'limit' value and a 'note' explaining that limit
var updateLimits = function (Env, config, publicKey, cb /*:(?string, ?any[])=>void*/) {
+
if (config.adminEmail === false) {
+ applyCustomLimits(Env, config);
if (config.allowSubscriptions === false) { return; }
throw new Error("allowSubscriptions must be false if adminEmail is false");
}
@@ -490,27 +520,6 @@ var updateLimits = function (Env, config, publicKey, cb /*:(?string, ?any[])=>vo
}
};
- // read custom limits from the config
- var customLimits = (function (custom) {
- var limits = {};
- Object.keys(custom).forEach(function (k) {
- k.replace(/\/([^\/]+)$/, function (all, safeKey) {
- var id = unescapeKeyCharacters(safeKey || '');
- limits[id] = custom[k];
- return '';
- });
- });
- return limits;
- }(config.customLimits || {}));
-
- var isLimit = function (o) {
- var valid = o && typeof(o) === 'object' &&
- typeof(o.limit) === 'number' &&
- typeof(o.plan) === 'string' &&
- typeof(o.note) === 'string';
- return valid;
- };
-
var req = Https.request(options, function (response) {
if (!('' + response.statusCode).match(/^2\d\d$/)) {
return void cb('SERVER ERROR ' + response.statusCode);
@@ -525,10 +534,7 @@ var updateLimits = function (Env, config, publicKey, cb /*:(?string, ?any[])=>vo
try {
var json = JSON.parse(str);
Env.limits = json;
- Object.keys(customLimits).forEach(function (k) {
- if (!isLimit(customLimits[k])) { return; }
- Env.limits[k] = customLimits[k];
- });
+ applyCustomLimits(Env, config);
var l;
if (userId) {
@@ -544,6 +550,7 @@ var updateLimits = function (Env, config, publicKey, cb /*:(?string, ?any[])=>vo
});
req.on('error', function (e) {
+ applyCustomLimits(Env, config);
if (!config.domain) { return cb(); }
cb(e);
});
diff --git a/www/code/inner.js b/www/code/inner.js
index 36137a6f3..9e4d0a207 100644
--- a/www/code/inner.js
+++ b/www/code/inner.js
@@ -310,18 +310,24 @@ define([
});
framework.setContentGetter(function () {
+ CodeMirror.removeCursors();
var content = CodeMirror.getContent();
content.highlightMode = CodeMirror.highlightMode;
previewPane.draw();
return content;
});
+ var cursorTo;
+ var updateCursor = function () {
+ if (cursorTo) { clearTimeout(cursorTo); }
+ if (editor._noCursorUpdate) { return; }
+ cursorTo = setTimeout(function () {
+ framework.updateCursor();
+ }, 500); // 500ms to make sure it is sent after chainpad sync
+ };
framework.onCursorUpdate(CodeMirror.setRemoteCursor);
framework.setCursorGetter(CodeMirror.getCursor);
- editor.on('cursorActivity', function () {
- if (editor._noCursorUpdate) { return; }
- framework.updateCursor();
- });
+ editor.on('cursorActivity', updateCursor);
framework.onEditableChange(function () {
editor.setOption('readOnly', framework.isLocked() || framework.isReadOnly());
diff --git a/www/common/common-feedback.js b/www/common/common-feedback.js
index 6d2c62e30..d377bae5c 100644
--- a/www/common/common-feedback.js
+++ b/www/common/common-feedback.js
@@ -21,15 +21,16 @@ define([
};
http.send();
};
- Feedback.send = function (action, force) {
- if (AppConfig.disableFeedback) { return; }
- if (!action) { return; }
+ Feedback.send = function (action, force, cb) {
+ if (typeof(cb) !== 'function') { cb = function () {}; }
+ if (AppConfig.disableFeedback) { return void cb(); }
+ if (!action) { return void cb(); }
if (force !== true) {
- if (!Feedback.state) { return; }
+ if (!Feedback.state) { return void cb(); }
}
var href = '/common/feedback.html?' + action + '=' + randomToken();
- ajax(href);
+ ajax(href, cb);
};
Feedback.reportAppUsage = function () {
diff --git a/www/common/common-hash.js b/www/common/common-hash.js
index e089852cc..0f5341d9b 100644
--- a/www/common/common-hash.js
+++ b/www/common/common-hash.js
@@ -76,9 +76,10 @@ define([
return s.replace(/\/+/g, '/');
};
- Hash.createChannelId = function () {
- var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
- if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
+ Hash.ephemeralChannelLength = 34;
+ Hash.createChannelId = function (ephemeral) {
+ var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(ephemeral? 17: 16));
+ if ([32, 34].indexOf(id.length) === -1 || /[^a-f0-9]/.test(id)) {
throw new Error('channel ids must consist of 32 hex characters');
}
return id;
diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index da2eb6fe9..3e3660ca4 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -1324,21 +1324,27 @@ define([
var urls = common.getMetadataMgr().getPrivateData().accounts;
var makeDonateButton = function () {
- $('', {
+ var $a = $('', {
'class': 'cp-limit-upgrade btn btn-success',
href: urls.donateURL,
rel: "noreferrer noopener",
target: "_blank",
}).text(Messages.supportCryptpad).appendTo($container);
+ $a.click(function () {
+ Feedback.send('SUPPORT_CRYPTPAD');
+ });
};
var makeUpgradeButton = function () {
- $('', {
+ var $a = $('', {
'class': 'cp-limit-upgrade btn btn-success',
href: urls.upgradeURL,
rel: "noreferrer noopener",
target: "_blank",
}).text(Messages.upgradeAccount).appendTo($container);
+ $a.click(function () {
+ Feedback.send('UPGRADE_ACCOUNT');
+ });
};
if (!Config.removeDonateButton) {
diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js
index 296bf4a1b..b81b152f2 100644
--- a/www/common/cryptpad-common.js
+++ b/www/common/cryptpad-common.js
@@ -118,7 +118,14 @@ define([
};
// Settings
common.deleteAccount = function (cb) {
- postMessage("DELETE_ACCOUNT", null, cb);
+ postMessage("DELETE_ACCOUNT", null, function (obj) {
+ if (obj.state) {
+ Feedback.send('DELETE_ACCOUNT_AUTOMATIC');
+ } else {
+ Feedback.send('DELETE_ACCOUNT_MANUAL');
+ }
+ cb(obj);
+ });
};
// Drive
common.userObjectCommand = function (data, cb) {
@@ -933,7 +940,12 @@ define([
}
}).nThen(function () {
// We have the new drive, with the new login block
- window.location.reload();
+ var feedbackKey = (password === newPassword)?
+ 'OWNED_DRIVE_MIGRATION': 'PASSWORD_CHANGED';
+
+ Feedback.send(feedbackKey, undefined, function () {
+ window.location.reload();
+ });
});
};
@@ -1108,6 +1120,14 @@ define([
return doesSupport;
};
+ common.isWebRTCSupported = function () {
+ return Boolean(navigator.getUserMedia ||
+ navigator.webkitGetUserMedia ||
+ navigator.mozGetUserMedia ||
+ navigator.msGetUserMedia ||
+ window.RTCPeerConnection);
+ };
+
common.ready = (function () {
var env = {};
var initialized = false;
@@ -1123,6 +1143,10 @@ define([
Feedback.send("NO_PROXIES");
}
+ if (!common.isWebRTCSupported()) {
+ Feedback.send("NO_WEBRTC");
+ }
+
var shimPattern = /CRYPTPAD_SHIM/;
if (shimPattern.test(Array.isArray.toString())) {
Feedback.send("NO_ISARRAY");
diff --git a/www/common/cursor.js b/www/common/cursor.js
index ceabfd133..dd5577be0 100644
--- a/www/common/cursor.js
+++ b/www/common/cursor.js
@@ -81,7 +81,7 @@ define([
var path = [];
while (current !== element) {
path.unshift(current);
- current = current.parentElement;
+ current = current.parentNode;
}
if (current === element) { // Should always be the case
diff --git a/www/common/modes.js b/www/common/modes.js
index b10272d17..ffed08b17 100644
--- a/www/common/modes.js
+++ b/www/common/modes.js
@@ -10,6 +10,8 @@ define([
"ASN.1 asn.1",
"Asterisk asterisk",
"Brainfuck brainfuck .b",
+ "C text/x-csrc .c",
+ "C text/x-c++src .cpp",
"C-like clike",
"Clojure clojure",
"CMake cmake",
@@ -48,6 +50,7 @@ define([
"HTTP http",
"IDL idl",
"JADE jade",
+ "Java text/x-java .java",
"JavaScript javascript .js",
"Jinja2 jinja2",
"JSX jsx .jsx",
@@ -65,6 +68,7 @@ define([
"Nginx nginx",
"NSIS nsis",
"N-Triples ntriples",
+ "Objective-C text/x-objectivec .m",
"Octave octave",
"Org-mode orgmode .org",
"Oz oz",
diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js
index a05e92b3c..6a3d64f06 100644
--- a/www/common/outer/async-store.js
+++ b/www/common/outer/async-store.js
@@ -458,7 +458,8 @@ define([
edPublic: store.proxy.edPublic,
friends: store.proxy.friends || {},
settings: store.proxy.settings,
- thumbnails: disableThumbnails === false
+ thumbnails: disableThumbnails === false,
+ isDriveOwned: Boolean(Util.find(store, ['driveMetadata', 'owners']))
}
};
cb(JSON.parse(JSON.stringify(metadata)));
@@ -967,12 +968,20 @@ define([
history: [],
pushHistory: function (msg, isCp) {
if (isCp) {
+ // the current message is a checkpoint.
+ // push it to your worker's history, prepending it with cp|
+ // cp| and anything else related to checkpoints has already
+ // been stripped by chainpad-netflux-worker or within async store
+ // when the message was outgoing.
channel.history.push('cp|' + msg);
+ // since the latest message is a checkpoint, we are able to drop
+ // some of the older history, but we can't rely on checkpoints being
+ // correct, as they might be checkpoints from different forks
var i;
- for (i = channel.history.length - 2; i > 0; i--) {
+ for (i = channel.history.length - 101; i > 0; i--) {
if (/^cp\|/.test(channel.history[i])) { break; }
}
- channel.history = channel.history.slice(i);
+ channel.history = channel.history.slice(Math.max(i, 0));
return;
}
channel.history.push(msg);
@@ -1416,8 +1425,13 @@ define([
if (!store.loggedIn) { return void cb(); }
Store.pinPads(null, data, cb);
};
- var manager = store.manager = ProxyManager.create(proxy.drive, proxy.edPublic,
- pin, unpin, loadSharedFolder, {
+ var manager = store.manager = ProxyManager.create(proxy.drive, {
+ edPublic: proxy.edPublic,
+ pin: pin,
+ unpin: unpin,
+ loadSharedFolder: loadSharedFolder,
+ settings: proxy.settings
+ }, {
outer: true,
removeOwnedChannel: function (data, cb) { Store.removeOwnedChannel('', data, cb); },
edPublic: store.proxy.edPublic,
@@ -1547,8 +1561,9 @@ define([
if (!data.userHash) {
returned.anonHash = Hash.getEditHashFromKeys(secret);
}
- }).on('ready', function () {
+ }).on('ready', function (info) {
if (store.userObject) { return; } // the store is already ready, it is a reconnection
+ store.driveMetadata = info.metadata;
if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; }
var drive = rt.proxy.drive;
// Creating a new anon drive: import anon pads from localStorage
diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js
index bd081e2ae..ae8879e31 100644
--- a/www/common/outer/userObject.js
+++ b/www/common/outer/userObject.js
@@ -682,8 +682,20 @@ define([
var sf = files[SHARED_FOLDERS];
var rootFiles = exp.getFiles([ROOT]);
var root = exp.find([ROOT]);
+ var parsed, secret, el;
for (var id in sf) {
+ el = sf[id];
id = Number(id);
+
+ // Fix undefined hash
+ parsed = Hash.parsePadUrl(el.href || el.roHref);
+ secret = Hash.getSecrets('drive', parsed.hash, el.password);
+ if (!secret.keys) {
+ delete sf[id];
+ continue;
+ }
+
+ // Fix shared folder not displayed in root
if (rootFiles.indexOf(id) === -1) {
console.log('missing' + id);
var newName = Hash.createChannelId();
diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js
index 6cb7e1654..76d5f3ab8 100644
--- a/www/common/proxy-manager.js
+++ b/www/common/proxy-manager.js
@@ -212,6 +212,28 @@ define([
};
};
+ // Check if a given path is resolved to a shared folder or to the main drive
+ var _isInSharedFolder = function (Env, path) {
+ var resolved = _resolvePath(Env, path);
+ return typeof resolved.id === "number" ? resolved.id : false;
+ };
+
+ // Get the owned files in the main drive that are also duplicated in shared folders
+ var _isDuplicateOwned = function (Env, path, id) {
+ if (path && _isInSharedFolder(Env, path)) { return; }
+ var data = _getFileData(Env, id || Env.user.userObject.find(path));
+ if (!data) { return; }
+ if (!_ownedByMe(Env, data.owners)) { return; }
+ var channel = data.channel;
+ if (!channel) { return; }
+ var foldersUO = Object.keys(Env.folders).map(function (k) {
+ return Env.folders[k].userObject;
+ });
+ return foldersUO.some(function (uo) {
+ return uo.findChannels([channel]).length;
+ });
+ };
+
// Get a copy of the elements located in the given paths, with their files data
// Note: This function is only called to move files from a proxy to another
var _getCopyFromPaths = function (Env, paths, userObject) {
@@ -281,6 +303,10 @@ define([
var resolved = _resolvePaths(Env, data.paths);
var newResolved = _resolvePath(Env, data.newPath);
+ // NOTE: we can only copy when moving from one drive to another. We don't want
+ // duplicates in the same drive
+ var copy = data.copy;
+
if (!newResolved.userObject.isFolder(newResolved.path)) { return void cb(); }
nThen(function (waitFor) {
@@ -305,6 +331,8 @@ define([
Array.prototype.push.apply(ownedPads, _owned);
});
+ if (copy) { return; }
+
if (resolved.main.length) {
var rootPath = resolved.main[0].slice();
rootPath.pop();
@@ -338,6 +366,8 @@ define([
uoTo.copyFromOtherDrive(newResolved.path, obj.el, obj.data, obj.key);
});
+ if (copy) { return; }
+
// Remove the elements from the old location (without unpinning)
uoFrom.delete(paths, waitFor());
}
@@ -442,7 +472,24 @@ define([
// Delete paths from the main drive and get the list of pads to unpin
// We also get the list of owned pads that were removed
if (resolved.main.length) {
- Env.user.userObject.delete(resolved.main, waitFor(function (err, _toUnpin, _ownedRemoved) {
+ var uo = Env.user.userObject;
+ if (Util.find(Env.settings, ['drive', 'hideDuplicate'])) {
+ // If we hide duplicate owned pads in our drive, we have
+ // to make sure we're not deleting a hidden own file
+ // from inside a folder we're trying to delete
+ resolved.main.forEach(function (p) {
+ var el = uo.find(p);
+ if (uo.isFile(el) || uo.isSharedFolder(el)) { return; }
+ var arr = [];
+ uo.getFilesRecursively(el, arr);
+ arr.forEach(function (id) {
+ if (_isDuplicateOwned(Env, null, id)) {
+ Env.user.userObject.add(Number(id), [UserObject.ROOT]);
+ }
+ });
+ });
+ }
+ uo.delete(resolved.main, waitFor(function (err, _toUnpin, _ownedRemoved) {
if (!Env.unpinPads || !_toUnpin) { return; }
Array.prototype.push.apply(toUnpin, _toUnpin);
ownedRemoved = _ownedRemoved;
@@ -740,13 +787,14 @@ define([
});
};
- var create = function (proxy, edPublic, pinPads, unpinPads, loadSf, uoConfig) {
+ var create = function (proxy, data, uoConfig) {
var Env = {
- pinPads: pinPads,
- unpinPads: unpinPads,
- loadSharedFolder: loadSf,
+ pinPads: data.pin,
+ unpinPads: data.unpin,
+ loadSharedFolder: data.loadSharedFolder,
cfg: uoConfig,
- edPublic: edPublic,
+ edPublic: data.edPublic,
+ settings: data.settings,
user: {
proxy: proxy,
userObject: UserObject.init(proxy, uoConfig)
@@ -797,12 +845,13 @@ define([
}
}, cb);
};
- var moveInner = function (Env, paths, newPath, cb) {
+ var moveInner = function (Env, paths, newPath, cb, copy) {
return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "move",
data: {
paths: paths,
- newPath: newPath
+ newPath: newPath,
+ copy: copy
}
}, cb);
};
@@ -891,6 +940,7 @@ define([
if (fPath) {
// This is a shared folder, we have to fix the paths in the search results
results.forEach(function (r) {
+ r.inSharedFolder = true;
r.paths.forEach(function (p) {
Array.prototype.unshift.apply(p, fPath);
});
@@ -905,8 +955,8 @@ define([
var getRecentPads = function (Env) {
return Env.user.userObject.getRecentPads();
};
- var getOwnedPads = function (Env, edPublic) {
- return Env.user.userObject.getOwnedPads(edPublic);
+ var getOwnedPads = function (Env) {
+ return Env.user.userObject.getOwnedPads(Env.edPublic);
};
var getSharedFolderData = function (Env, id) {
@@ -918,10 +968,7 @@ define([
return obj;
};
- var isInSharedFolder = function (Env, path) {
- var resolved = _resolvePath(Env, path);
- return typeof resolved.id === "number" ? resolved.id : false;
- };
+ var isInSharedFolder = _isInSharedFolder;
/* Generic: doesn't need access to a proxy */
var isFile = function (Env, el, allowStr) {
@@ -967,10 +1014,13 @@ define([
return Env.user.userObject.hasFile(el, trashRoot);
};
- var createInner = function (proxy, sframeChan, uoConfig) {
+ var isDuplicateOwned = _isDuplicateOwned;
+
+ var createInner = function (proxy, sframeChan, edPublic, uoConfig) {
var Env = {
cfg: uoConfig,
sframeChan: sframeChan,
+ edPublic: edPublic,
user: {
proxy: proxy,
userObject: UserObject.init(proxy, uoConfig)
@@ -1012,6 +1062,7 @@ define([
getSharedFolderData: callWithEnv(getSharedFolderData),
isInSharedFolder: callWithEnv(isInSharedFolder),
getUserObjectPath: callWithEnv(getUserObjectPath),
+ isDuplicateOwned: callWithEnv(isDuplicateOwned),
// Generic
isFile: callWithEnv(isFile),
isFolder: callWithEnv(isFolder),
diff --git a/www/common/sframe-chainpad-netflux-outer.js b/www/common/sframe-chainpad-netflux-outer.js
index eaafe6656..a91f9f6d1 100644
--- a/www/common/sframe-chainpad-netflux-outer.js
+++ b/www/common/sframe-chainpad-netflux-outer.js
@@ -59,7 +59,7 @@ define([], function () {
var id = '';
if (window.nacl) {
var hash = window.nacl.hash(window.nacl.util.decodeUTF8(msg));
- id = window.nacl.util.encodeBase64(hash.slice(0, 8)) + '|';
+ id = window.nacl.util.encodeBase64(hash.subarray(0, 8)) + '|';
} else {
console.log("Checkpoint sent without an ID. Nacl is missing.");
}
diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js
index b7dfb531e..8ea515cc9 100644
--- a/www/common/sframe-common-codemirror.js
+++ b/www/common/sframe-common-codemirror.js
@@ -154,10 +154,15 @@ define([
var setMode = exp.setMode = function (mode, cb) {
exp.highlightMode = mode;
if (mode === 'markdown') { mode = 'gfm'; }
- if (mode !== "text") {
- CMeditor.autoLoadMode(editor, mode);
+ if (/text\/x/.test(mode)) {
+ CMeditor.autoLoadMode(editor, 'clike');
+ editor.setOption('mode', mode);
+ } else {
+ if (mode !== "text") {
+ CMeditor.autoLoadMode(editor, mode);
+ }
+ editor.setOption('mode', mode);
}
- editor.setOption('mode', mode);
if (exp.$language) {
var name = exp.$language.find('a[data-value="' + mode + '"]').text() || undefined;
name = name ? Messages.languageButton + ' ('+name+')' : Messages.languageButton;
@@ -297,6 +302,7 @@ define([
} else {
mode = mime && mime.mode || null;
}
+ if (mode === "markdown") { mode = "gfm"; }
if (mode && Modes.list.some(function (o) { return o.mode === mode; })) {
exp.setMode(mode);
$toolbarContainer.find('#language-mode').val(mode);
@@ -402,6 +408,12 @@ define([
return html;
};
var marks = {};
+ exp.removeCursors = function () {
+ for (var id in marks) {
+ marks[id].clear();
+ delete marks[id];
+ }
+ };
exp.setRemoteCursor = function (data) {
if (data.leave) {
$('.cp-codemirror-cursor[id^='+data.id+']').each(function (i, el) {
diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js
index 9512f1e61..37697ad2c 100644
--- a/www/common/sframe-common-outer.js
+++ b/www/common/sframe-common-outer.js
@@ -301,7 +301,7 @@ define([
forceCreationScreen: forceCreationScreen,
password: password,
channel: secret.channel,
- enableSF: localStorage.CryptPad_SF === "1" // TODO to remove when enabled by default
+ enableSF: localStorage.CryptPad_SF === "1", // TODO to remove when enabled by default
};
if (window.CryptPad_newSharedFolder) {
additionalPriv.newSharedFolder = window.CryptPad_newSharedFolder;
@@ -311,7 +311,8 @@ define([
additionalPriv.disabledApp = true;
}
if (!Utils.LocalStore.isLoggedIn() &&
- AppConfig.registeredOnlyTypes.indexOf(parsed.type) !== -1) {
+ AppConfig.registeredOnlyTypes.indexOf(parsed.type) !== -1 &&
+ parsed.type !== "file") {
additionalPriv.registeredOnly = true;
}
diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js
index 3170d974e..1236d4fcd 100644
--- a/www/common/sframe-common.js
+++ b/www/common/sframe-common.js
@@ -197,7 +197,10 @@ define([
};
funcs.openCursorChannel = function (saveChanges) {
var md = JSON.parse(JSON.stringify(ctx.metadataMgr.getMetadata()));
- var channel = md.cursor || Hash.createChannelId();
+ var channel = md.cursor;
+ if (typeof(channel) !== 'string' || channel.length !== Hash.ephemeralChannelLength) {
+ channel = Hash.createChannelId(true); // true indicates that it's an ephemeral channel
+ }
if (!md.cursor) {
md.cursor = channel;
ctx.metadataMgr.updateMetadata(md);
diff --git a/www/common/translations/messages.de.js b/www/common/translations/messages.de.js
index 33ffcc0c2..3a916cab3 100644
--- a/www/common/translations/messages.de.js
+++ b/www/common/translations/messages.de.js
@@ -11,7 +11,6 @@ define(function () {
out._languageName = 'German';
out.main_title = "Cryptpad: Echtzeitzusammenarbeit ohne Preisgabe von Informationen";
- out.main_slogan = "Einigkeit ist Stärke - Zusammenarbeit der Schlüssel";
out.type = {};
out.type.pad = 'Pad';
@@ -134,10 +133,9 @@ define(function () {
out.user_accountName = "Kontoname";
out.clickToEdit = "Zum Bearbeiten klicken";
- out.saveTitle = "Bitte gebe den Titel ein (Enter)";
+ out.saveTitle = "Bitte gib den Titel ein (Enter)";
out.forgetButton = "Entfernen";
- 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';
@@ -178,7 +176,7 @@ define(function () {
out.printText = "Drucken";
out.printButton = "Drucken (enter)";
- out.printButtonTitle = "Deine Präsentation ausdrucken oder als PDF Dateien exportieren";
+ out.printButtonTitle2 = "Deine Präsentation ausdrucken oder als PDF Dateien exportieren";
out.printOptions = "Druckeinstellungen";
out.printSlideNumber = "Foliennummer anzeigen";
out.printDate = "Datum anzeigen";
@@ -226,7 +224,7 @@ define(function () {
out.fileShare = "Link kopieren";
out.getEmbedCode = "Einbettungscode anzeigen";
out.viewEmbedTitle = "Das Dokument in eine externe Webseite einbetten";
- out.viewEmbedTag = "Um dieses Dokument einzubetten, platziere dieses iframe an der gewünschten Stelle Deiner HTML Seite. Du kannst es mit CSS oder HTML Attributen gestalten";
+ out.viewEmbedTag = "Um dieses Dokument einzubetten, platziere dieses iframe an der gewünschten Stelle Deiner HTML-Seite. Du kannst es mit CSS oder HTML Attributen gestalten";
out.fileEmbedTitle = "Die Datei in einer externen Seite einbetten";
out.fileEmbedScript = "Um diese Datei einzubetten, füge dieses Skript einmal in Deiner Webseite ein, damit das Media-Tag geladen wird:";
out.fileEmbedTag = "Dann platziere das Media-Tag an der gewünschten Stelle der Seite:";
@@ -306,8 +304,8 @@ define(function () {
out.poll_optionPlaceholder = "Option";
out.poll_userPlaceholder = "Dein Name";
- out.poll_removeOption = "Bist du sicher, dass du diese Option entfernen möchtest?";
- out.poll_removeUser = "Bist du sicher, dass du diese(n) Nutzer*in entfernen möchtest?";
+ out.poll_removeOption = "Bist du sicher, dass du diese Option entfernen möchtest?";
+ out.poll_removeUser = "Bist du sicher, dass du diese(n) Nutzer*in entfernen möchtest?";
out.poll_titleHint = "Titel";
out.poll_descriptionHint = "Beschreibe deine Abstimmung und publiziere sie mit dem 'Veröffentlichen'-Knopf wenn du fertig bist."+
@@ -376,7 +374,7 @@ define(function () {
out.contacts_send = 'Schicken';
out.contacts_remove = 'Diesen 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_typeHere = "Gib eine Nachricht ein...";
out.contacts_warning = "Alles, was du hier eingibst, wird bleiben und ersichtlich zu allen aktuellen und zukünftigen Benutzern. Sei sorgfältig mit sensible Information!";
out.contacts_padTitle = "Chat";
@@ -413,6 +411,7 @@ define(function () {
out.fm_newFolder = "Neuer Ordner";
out.fm_newFile = "Neues Dokument";
out.fm_folder = "Ordner";
+ out.fm_sharedFolder = "Verteiler Ordner";
out.fm_folderName = "Ordnername";
out.fm_numberOfFolders = "# von Ordnern";
out.fm_numberOfFiles = "# von Dateien";
@@ -448,6 +447,8 @@ define(function () {
out.fm_info_anonymous = 'Du bist nicht eingeloggt, daher laufen die Dokumente nach 3 Monaten aus (mehr dazu lesen). ' +
'Der Zugang zu den Dokumenten ist in deinem Browser gespeichert, daher wird das Löschen des Browserverlaufs auch die Dokumente verschwinden lassen.
' +
'Registriere dich oder logge dich ein, um sie dauerhaft zu machen.
';
+ out.fm_info_sharedFolder = 'Dieser Ordner ist verteilt. Da du aber nicht eingeloggt bist, hast du nur einen schreibgeschützen Zugang.
' +
+ 'Registriere oder logge ich ein, damit du dieses Ordner in dein CryptDrive importieren und bearbeiten kannst.';
out.fm_info_owned = "Diese Dokumente sind deine eigenen. 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 diesen Link geheim zu halten.
" +
@@ -457,14 +458,13 @@ define(function () {
"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 einloggen solltest.';
- out.fm_info_sharedFolder = 'Dieser Ordner ist verteilt. Da du aber nicht eingeloggt bist, hast du nur einen schreibgeschützen Zugang.
' +
- 'Registriere oder logge ich ein, damit du dieses Ordner in dein CryptDrive importieren und bearbeiten kannst.';
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 = "Listenansicht";
out.fm_viewGridButton = "Kachelansicht";
out.fm_renamedPad = "Du hast einen speziellen Name für dieses Dokument gesetzt. Seine geteilter Titel ist:
{0}";
+ out.fm_canBeShared = "Dieser Ordner can verteilt werden";
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 gespeichert hat, löschen möchtest?
" +
@@ -479,6 +479,7 @@ define(function () {
// File - Context menu
out.fc_newfolder = "Neuer Ordner";
+ out.fc_newsharedfolder = "Neuer verteilte Ordner";
out.fc_rename = "Umbenennen";
out.fc_open = "Öffnen";
out.fc_open_ro = "Öffnen (schreibgeschützt)";
@@ -589,7 +590,7 @@ define(function () {
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 das tun möchtest?
" +
- "Gebe I love CryptPad ein, um zu bestätigen."; // TODO: I love CryptPad should be localized
+ "Gib 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.";
@@ -626,7 +627,7 @@ define(function () {
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 niemand anderes diese bei sich gelagert hat.";
out.settings_deleteButton = "Dein Konto löschen";
- out.settings_deleteModal = "Gebe die folgende Information deinem CryptPad Adminstrator, damit er die Daten vom Server löschen kann.";
+ out.settings_deleteModal = "Gib die folgende Information deinem CryptPad Adminstrator, damit er die Daten vom Server löschen kann.";
out.settings_deleteConfirm = "Wenn du OK klickst, wird dein Konto dauerhaft löschen. Bist Du sicher?";
out.settings_deleted = "Dein Konto ist jetzt gelöscht. Drucke OK, um zum Homepage zu gelangen.";
@@ -676,8 +677,18 @@ define(function () {
out.settings_changePasswordError = "Ein Fehler ist aufgetreten. Wenn du nicht mehr einloggen oder dein Passwort ändern kannst, solltest du die Administratoren des CryptPad Servers kontaktieren.";
out.settings_changePasswordPending = "Dein Passwort wird geändert. Bitte schliesse nicht und lade diese Seite nicht neu, bis dieser Vorgang erledigt ist.";
out.settings_changePasswordNewPasswordSameAsOld = "Dein neues Passwort muss anders als dein aktuelles Passwort sein.";
+
+ out.settings_cursorColorTitle = "Kursorfarbe";
+ out.settings_cursorColorHint = "Die Farber deines Kursors in kollaborative Dokumente ändern.";
+ out.settings_cursorShareTitle = "Meine Kursorposition teilen";
+ out.settings_cursorShareHint = "Du kannst entscheide, ob andere Benutzer dein Kursors in kollaborative Dokumente sehen können.";
+ out.settings_cursorShareLabel = "Die Position teilen";
+ out.settings_cursorShowTitle = "Die Position des Kursors von anderen anzeigen";
+ out.settings_cursorShowHint = "Du kannst wählen, ob du es wünscht, dass die Kursore von anderen sichtbar in kollaborative Dokumente sind.";
+ out.settings_cursorShowLabel = "Kursore zeigen";
out.upload_title = "Datei hochladen";
+ out.upload_type = "Typ";
out.upload_modal_title = "Uploadeinstellungen";
out.upload_modal_filename = "Dateiname (die Dateierweiterung {0} wird automatisch hinzugefügt)";
out.upload_modal_owner = "Eigene Datei";
@@ -698,9 +709,13 @@ define(function () {
out.upload_size = "Grösse";
out.upload_progress = "Fortschritt";
out.upload_mustLogin = "Du muss eingeloggt sein, um Dateien hochzuladen";
+ out.upload_up = "Hochladen";
out.download_button = "Entschlüsseln und runterladen";
out.download_mt_button = "Runterladen";
out.download_resourceNotAvailable = "Diese Ressource war nicht verfügbar..";
+ out.download_dl = "Runteraden";
+ out.download_step1 = "Laden...";
+ out.download_step2 = "Entschlüsselung...";
out.todo_title = "CryptTodo";
out.todo_newTodoNamePlaceholder = "Die Aufgabe prüfen...";
@@ -712,7 +727,8 @@ define(function () {
// pad
out.pad_showToolbar = "Werkzeugsleiste anzeigen";
out.pad_hideToolbar = "Werkzeugsleiste verbergen";
-
+ out.pad_base64 = "Dieses Pad enthält Bilder die nicht ressourcenschonend gespeichert sind. Sie werden die Größe des Pads im CryptDrive belasten und wird den Ladevorgang verlangsamen. Du kannst diese Bilder zum neuen Format migrieren. Sie werden dann separat in deinem CryptDrive gespeichert. Willst du die Bilder jetzt migrieren?";
+
// markdown toolbar
out.mdToolbar_button = "Die Markdown-Werkzeugsleiste anzeigen oder verbergen";
out.mdToolbar_defaultText = "Dein Text hier";
@@ -733,6 +749,7 @@ define(function () {
out.home_product = "CryptPad ist eine alternative zu verbreiteten Office- und Clouddienste mit eingebauten Datenschutz. Mit CryptPad, der gesamten Inhalt ist verschlüsselt, bevor es geschickt wird. Das heisst, dass keiner hat Zugang zum Inhalt, ausser du gibst den Schlüssel aus. Selbst die Softwarehersteller haben diesen Zugang nicht.";
out.home_host = "Dieses CryptPad Server ist eine unabhängige Installation des Communitysoftwares. Das Quellcode ist auf GitHub verfügbar.";
out.home_host_agpl = "CryptPad kann durch die Lizenz AGPL3 verbreitet werden";
+ out.home_ngi = "Gewinner beim NGI Award";
//about.html
@@ -821,6 +838,7 @@ define(function () {
out.features_feature = "Funktion";
out.features_anon = "Anonymer Benutzer";
out.features_registered = "Angemeldete Benutzer";
+ out.features_premium = "Premium Benutzer";
out.features_notes = "Notizzen";
out.features_f_apps = "Zugang zu den wichtige Anwendungen";
out.features_f_core = "Gemeinsame Funktionen der Anwendungen";
@@ -1147,7 +1165,7 @@ define(function () {
out.codeInitialState = [
'# CryptPad\'s Zero Knowledge Kollaborativer Code Editor ohne Preisgabe deiner Daten\n',
'\n',
- '* Was du hier tippst, ist verschlüsselt. Nur Personen die das vollen Link haben können es zugreifen.\n',
+ '* Was du hier tippst, ist verschlüsselt. Nur wer den kompletten Link kennt, kann darauf zugreifen.\n',
'* Du kannst die Programmierungsprache für die Syntaxhervorhebung sowie das Farbschema oben rechts wählen.'
].join('');
@@ -1166,11 +1184,11 @@ define(function () {
out.readme_p1 = "Willkommen zu CryptPad, hier kannst du deine Notizen aufschreiben, allein oder mit Bekannten.";
out.readme_p2 = "Dieses Dokument gibt dir einen kurzen Überblick, wie du CryptPad verwenden kann, um Notizen zu schreiben und und mit anderen zusammen zu arbeiten.";
out.readme_cat1 = "Lerne CryptDrive kennen";
- out.readme_cat1_l1 = "Ein Dokument erstellen: Klicke in Deinem CryptDrive {0}, dann {1} und Du kannst ein Dokuemnt erstellen."; // 0: New, 1: Rich Text
+ out.readme_cat1_l1 = "Ein Dokument erstellen: Klicke in Deinem CryptDrive {0}, dann {1} und Du kannst ein Dokument erstellen."; // 0: New, 1: Rich Text
out.readme_cat1_l2 = "Ein Dokument Deines CryptDrives öffnen: Doppelklicke auf das Symbol eines Dokument, um es zu öffnen.";
out.readme_cat1_l3 = "Deine Dokumente organisieren: Wenn du eingeloggst bist, wird jedes Dokument, das du besuchst, im {0} Bereich deines CryptDrives angezeigt";
out.readme_cat1_l3_l1 = "Im Abschnitt {0} deines CryptDrives kannst du Dateien zwischen Ordnern ziehen und ablegen oder neue Ordner anlegen."; // 0: Documents
- out.readme_cat1_l3_l2 = "Ein Rechtklick auf Symbole kann zusätzliche Menüfunktionen anbieten.";
+ out.readme_cat1_l3_l2 = "Ein Rechtsklick auf Symbole zeigt zusätzliche Menüfunktionen.";
out.readme_cat1_l4 = "Verschiebe deine alten Dokumente in den 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";
@@ -1178,26 +1196,26 @@ define(function () {
out.readme_cat2_l1 = "Der Knopf {0} in deinem Dokument erlaubt dir, anderen einen Mitbearbeitungszugang zu geben (entweder zu {1} oder {2}).";
out.readme_cat2_l2 = "Der Titel eines Dokuments kann mit einem Klick auf den Stift geändert werden.";
out.readme_cat3 = "Entdecke CryptPad Apps";
- out.readme_cat3_l1 = "Mit dem CryptPad Codeeditor kannst du Code wie JavaScript, Markdown, oder HTML bearbeiten";
- out.readme_cat3_l2 = "Mit dem CryptPad Präsentationseditor kannst du schnell Vorträge mit Hilfe von Markdown gestalten";
- out.readme_cat3_l3 = "Mit der CryptPad Umfrage kannst du schnell Abstimmungen durchführen, insbesondere, um Meetings zu planen, die in den Kalender von allen passen.";
+ out.readme_cat3_l1 = "Mit dem CryptPad-Codeeditor kannst du Code wie JavaScript, Markdown, oder HTML bearbeiten";
+ out.readme_cat3_l2 = "Mit dem CryptPad-Präsentationseditor kannst du schnell Vorträge mit Hilfe von Markdown gestalten";
+ out.readme_cat3_l3 = "Mit der CryptPad-Umfrage kannst du schnell Abstimmungen durchführen, insbesondere, um Meetings zu planen, die in den 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 bezifferten oder einfache Listen kannst du TAB und SHIFT-TAB benutzen, um den Einzug zu erhöhen oder reduzieren.";
+ out.tips.shortcuts = "Mit den Tastenkürzeln `ctrl+b`, `ctrl+i` and `ctrl+u` formatierst du Text fett, kursiv, oder unterstrichen.";
+ out.tips.indent = "In bezifferten oder einfachen Listen kannst du mit TAB und SHIFT-TAB den Einzug erhöhen oder reduzieren.";
out.tips.store = "Jedes Mal, wenn du ein Dokument besuchst 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 Festplatte hochladen, indem sie sie einfach verschieben 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 das CryptPad Symbol oben links erreichbar, wenn man in einem Dokument ist.";
- out.tips.profile = "Registrierte Benutzer können ihr Profil mit dem Benutzer Menü oben rechts bearbeiten.";
+ out.tips.profile = "Registrierte Benutzer können ihr Profil im Benutzer-Menü oben rechts bearbeiten.";
out.tips.avatars = "Du kannst ein Benutzerbild in dein Profil hochladen. Andere sehen es, wenn sie in einem Dokument zusammenarbeiten.";
- out.tips.tags = "Bringe Tags auf deinen Dokumenten an und starte eine Suche-nach-Tags mit dem # Zeichen in dem CryptDrive Suche.";
+ out.tips.tags = "Bringe Tags auf deinen Dokumenten an und starte eine Suche-nach-Tags mit dem # Zeichen in der CryptDrive-Suche.";
out.feedback_about = "Wenn Du das liest, fragst du dich, weshalb dein Browser Anfragen an Webseiten schickt, wenn manche Aktionen ausgeführt werden.";
out.feedback_privacy = "Wir kümmern uns um deinen Datenschutz, aber gleichzeitig wollen wir, dass die Benutzung von CryptPad sehr leicht ist. Deshalb wollen wir erfahren, welche UI-Funktion am wichtigsten für unsere Benutzer ist, indem wir diese mit einer genauen Parameterbeschreibung anfordern.";
- out.feedback_optout = "Wenn du das aber nicht möchtest. besuche Deine Einstellungen, dort findest du ein Haken, wo du es deaktivieren kannst.";
+ out.feedback_optout = "Wenn du das nicht möchtest, kannst du es in deinen Einstellungen deaktivieren.";
// Creation page
out.creation_404 = "Dieses Dokument existiert nicht mehr. Benutze das folgende Formular, um ein neues Dokument zu gestalten.";
@@ -1246,6 +1264,7 @@ define(function () {
out.properties_addPassword = "Passwort hinzufügen";
out.properties_changePassword = "Passwort ändern";
out.properties_confirmNew = "Bist du sicher? Das Hinzufügen eines Passworts wird die URL dieses Pads ändern und die Chronik entfernen. Benutzer ohne Passwort werden den Zugang zu diesem Pad verlieren.";
+ out.properties_passwordSame = "Neue Passwörter müssen, anders als das aktuell sein.";
out.properties_confirmChange = "Bist du sicher? Das Ändern des Passworts wird die Chronik entfernen. Benutzer ohne das neue Passwort werden den Zugang zu diesem Pad verlieren.";
out.properties_passwordError = "Ein Fehler ist aufgetreten beim Versuch das Passwort zu ändern. Bitte versuche es nochmal.";
out.properties_passwordWarning = "Das Password wurde erfolgreich geändert, aber dein CryptDrive konnte nicht aktualisiert werden. Du mußt möglicherweise die alte Version des Pads manuell entfernen.
Bitte klicke OK um die Seite neu zu laden und die Zugeriffsrechte zu aktualisieren.";
@@ -1273,8 +1292,8 @@ define(function () {
out.loading_drive_3 = "Verifiziere Datenintegrität";
// Shared folders
- out.sharedFolders_forget = "Dieses pad wird nur in einem geteilten Ordner gespeichert, du kannst es nicht in den Papierkorb verschieben. Du kannst es in deinem CryptDrive löschen.";
- out.sharedFolders_duplicate = "Einige der pads, die du versucht hast zu verschieben, waren schon im Zielordner geteilt.";
+ out.sharedFolders_forget = "Dieses Pad wird nur in einem geteilten Ordner gespeichert, du kannst es nicht in den Papierkorb verschieben. Du kannst es in deinem CryptDrive löschen.";
+ out.sharedFolders_duplicate = "Einige der Pads, die du versucht hast zu verschieben, waren schon im Zielordner geteilt.";
out.sharedFolders_create = "Erstelle einen geteilten Ordner";
out.sharedFolders_create_name = "Neuer Ordner";
out.sharedFolders_create_owned = "Eigener Ordner";
@@ -1298,12 +1317,12 @@ define(function () {
// Crowdfunding messages
out.crowdfunding_home1 = "CryptPad braucht deine Hilfe!";
- out.crowdfunding_home2 = "Klicke auf dem Knopf, um über die Crowdfunding Campagne zu erfahren.";
+ out.crowdfunding_home2 = "Klicke auf dem Knopf, um über die Crowdfunding-Kampagne zu erfahren.";
out.crowdfunding_button = "Unterstütze CryptPad";
out.crowdfunding_popup_text = "