Merge branch 'staging' into locks

pull/1/head
yflory 6 years ago
commit ed4f8016a1

@ -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

@ -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",

@ -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' ],

@ -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;

@ -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)")
]);
};

@ -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',[

@ -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)
])
])
]),

@ -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);

@ -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 {

@ -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

@ -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:

@ -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; }

@ -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",

@ -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);
});

@ -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());

@ -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 () {

@ -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;

@ -1324,21 +1324,27 @@ define([
var urls = common.getMetadataMgr().getPrivateData().accounts;
var makeDonateButton = function () {
$('<a>', {
var $a = $('<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 () {
$('<a>', {
var $a = $('<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) {

@ -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");

@ -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

@ -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",

@ -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

@ -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();

@ -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),

@ -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.");
}

@ -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) {

@ -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;
}

@ -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);

@ -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.<br>Du kannst <a href="/drive/">zum CryptDrive</a> 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 <em>{0}</em> 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 (<a href="https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/" target="_blank">mehr dazu lesen</a>). ' +
'Der Zugang zu den Dokumenten ist in deinem Browser gespeichert, daher wird das Löschen des Browserverlaufs auch die Dokumente verschwinden lassen.<br>' +
'<a href="/register/">Registriere dich</a> oder <a href="/login/">logge dich ein</a>, um sie dauerhaft zu machen.<br>';
out.fm_info_sharedFolder = 'Dieser Ordner ist verteilt. Da du aber nicht eingeloggt bist, hast du nur einen schreibgeschützen Zugang.<br>' +
'<a href="/register/">Registriere</a> oder <a href="/login/">logge ich ein</a>, 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.<br>" +
"Es ist <strong>hoch empfohlen</strong> diesen Link geheim zu halten.<br>" +
@ -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 <a href="https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/" target="_blank">lesen</a>, weshalb wir das machen und weshalb du wirklich ' +
'<a href="/register/">registrieren</a> oder <a href="/login/">einloggen</a> solltest.';
out.fm_info_sharedFolder = 'Dieser Ordner ist verteilt. Da du aber nicht eingeloggt bist, hast du nur einen schreibgeschützen Zugang.<br>' +
'<a href="/register/">Registriere</a> oder <a href="/login/">logge ich ein</a>, 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:<br><b>{0}</b>";
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?<br>" +
@ -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.<br>"+
"Bist du sicher, dass du das tun möchtest?<br>" +
"Gebe <em>I love CryptPad</em> ein, um zu bestätigen."; // TODO: I love CryptPad should be localized
"Gib <em>I love CryptPad</em> 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.";
@ -677,7 +678,17 @@ define(function () {
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 <em>{0}</em> 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,6 +727,7 @@ 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";
@ -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 <a href=\"https://github.com/xwiki-labs/cryptpad\" target=\"_blank\" rel=\"noreferrer noopener\">auf GitHub</a> 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 <a href='/settings/'>Deine Einstellungen</a>, dort findest du ein Haken, wo du es deaktivieren kannst.";
out.feedback_optout = "Wenn du das nicht möchtest, kannst du es in <a href='/settings/'>deinen Einstellungen</a> 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.<br>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 = "<h3>Wir brauchen deine Hilfe!</h3>" +
"Um sicherzustellen, dass CryptPad weiter aktiv entwickelt wird, unterstütze bitte das Projekt durch die " +
'<a href="https://opencollective.com/cryptpad">OpenCollective Seite</a>, wo du unser <b>Roadmap</b> und <b>Funding Ziele</b> lesen kannst.';
'<a href="https://opencollective.com/cryptpad">OpenCollective Seite</a>, wo du unsere <b>Roadmap</b> und <b>Funding-Ziele</b> lesen kannst.';
out.crowdfunding_popup_yes = "OpenCollective besuchen";
out.crowdfunding_popup_no = "Nicht jetzt";
out.crowdfunding_popup_never = "Nicht mehr darum bitten.";

@ -54,6 +54,9 @@ define(function () {
out.deleted = "Pad supprimé de votre CryptDrive";
out.deletedFromServer = "Pad supprimé du serveur";
out.mustLogin = "Vous devez être enregistré pour avoir accès à cette page.";
out.disabledApp = "Cette application a été désactivée. Pour plus d'information, veuillez contacter l'administrateur de ce CryptPad.";
out.realtime_unrecoverableError = "Une erreur critique est survenue. Cliquez sur OK pour recharger la page.";
out.disconnected = 'Déconnecté';
@ -547,6 +550,7 @@ define(function () {
// Settings
out.settings_cat_account = "Compte";
out.settings_cat_drive = "CryptDrive";
out.settings_cat_cursor = "Curseur";
out.settings_cat_code = "Code";
out.settings_cat_pad = "Documents texte";
out.settings_cat_creation = "Nouveau pad";
@ -637,6 +641,10 @@ define(function () {
out.settings_logoutEverywhere = "Se déconnecter de force de toutes les autres sessions.";
out.settings_logoutEverywhereConfirm = "Êtes-vous sûr ? Vous devrez vous reconnecter sur tous vos autres appareils.";
out.settings_driveDuplicateTitle = "Doublons des pads dont vous êtes propriétaire";
out.settings_driveDuplicateHint = "Quand vous déplacez un pad dont vous êtes le propriétaire dans un dossier partagé, une copie est créée dans votre CryptDrive pour s'assurer que vous puissiez garder le contrôle de ce pad. Vous pouvez choisir de cacher ces doublons. Seules les versions partagées seront affichées, jusqu'à leur suppression, dans quels cas la version dans votre CryptDrive redeviendra visible.";
out.settings_driveDuplicateLabel = "Cacher les doublons";
out.settings_codeIndentation = "Indentation dans l'éditeur de code (nombre d'espaces)";
out.settings_codeUseTabs = "Utiliser des tabulations au lieu d'espaces";
out.settings_codeFontSize = "Taille de la police dans l'éditeur de code (px)";
@ -644,6 +652,9 @@ define(function () {
out.settings_padWidth = "Largeur de l'éditeur de texte";
out.settings_padWidthHint = "L'éditeur de documents texte occupe toute la largeur de l'écran disponible par défaut, ce qui peut rendre le texte difficile à lire. Vous pouvez ici réduire la largeur de l'éditeur.";
out.settings_padWidthLabel = "Réduire la largeur de l'éditeur";
out.settings_padSpellcheckTitle = "Vérification orthographique";
out.settings_padSpellcheckHint = "Cette option vous permet d'activer la vérification orthographique dans l'éditeur de Texte. Les fautes seront soulignées et des propositions correctes seront disponibles en effectuant un clic-droit avec la touche Ctrl ou Meta enfoncée.";
out.settings_padSpellcheckLabel = "Activer la vérification orthographique";
out.settings_creationSkip = "Passer l'écran de création de pad";
out.settings_creationSkipHint = "L'écran de création de pad offre de nouvelles options pour créer un pad, permettant d'avoir plus de contrôle et de sécurité concernant vos données. Toutefois, il peut ralentir votre travail en ajoutant une étape supplémentaire et donc, ici, vous avez la possibilité de choisir de passer cet écran et d'utiliser les paramètres par défaut choisis au-dessus.";
@ -653,6 +664,12 @@ define(function () {
out.settings_templateSkip = "Passer la fenêtre de choix d'un modèle";
out.settings_templateSkipHint = "Quand vous créez un nouveau pad, et si vous possédez des modèles pour ce type de pad, une fenêtre peut apparaître pour demander si vous souhaitez importer un modèle. Ici vous pouvez choisir de ne jamais montrer cette fenêtre et donc de ne jamais utiliser de modèle.";
out.settings_ownDriveTitle = "Activer les dernières fonctionnalités du compte";
out.settings_ownDriveHint = "Pour des raisons techniques, les comptes utilisateurs les plus anciens n'ont pas accès à toutes les fonctionnalités. Une mise à niveau gratuite permet de préparer votre CryptDrive pour les nouveautés à venir sans perturber vos activités habituelles.";
out.settings_ownDriveButton = "Mettre à niveau votre compte";
out.settings_ownDriveConfirm = "La mise à niveau peut prendre du temps. Vous devrez vous reconnecter sur tous vos appareils. Voulez-vous continuer?";
out.settings_ownDrivePending = "Votre compte est en train d'être mis à jour. Veuillez ne pas fermer ou recharger cette page avant que le traitement soit terminé.";
out.settings_changePasswordTitle = "Changer de mot de passe";
out.settings_changePasswordHint = "Pour modifier le mot de passe de votre compte utilisateur, entrez votre mot de passe actuel et confirmez le nouveau mot de passe en la tapant deux fois.<br>" +
"<b>Nous ne pouvons pas réinitialiser votre mot de passe si vous le perdez, donc soyez très prudent !</b>";

@ -59,7 +59,7 @@ define(function () {
out.deletedFromServer = "Pad deleted from the server";
out.mustLogin = "You must be logged in to access this page";
out.disabledApp = "This application has been disabled. Contact the administrator of this CryptPad to have more information.";
out.disabledApp = "This application has been disabled. Contact the administrator of this CryptPad for more information.";
out.realtime_unrecoverableError = "An unrecoverable error has occured. Click OK to reload.";
@ -380,7 +380,6 @@ define(function () {
out.contacts_typeHere = "Type a message here...";
out.contacts_warning = "Everything you type here is persistent and available to all the existing and future users of this pad. Be careful with sensitive information!";
out.contacts_padTitle = "Chat";
out.contacts_mustLogin = "You must be logged in to add contacts";
out.contacts_info1 = "These are your contacts. From here, you can:";
out.contacts_info2 = "Click your contact's icon to chat with them";
@ -650,6 +649,10 @@ define(function () {
out.settings_logoutEverywhere = "Force log out of all other web sessions";
out.settings_logoutEverywhereConfirm = "Are you sure? You will need to log in with all your devices.";
out.settings_driveDuplicateTitle = "Duplicated owned pads";
out.settings_driveDuplicateHint = "When you move your owned pads to a shared folder, a copy is kept in your CryptDrive to ensure that you retain your control over it. You can hide duplicated files. Only the shared version will be visible, unless deleted, in which case the original will be displayed in its previous location.";
out.settings_driveDuplicateLabel = "Hide duplicates";
out.settings_codeIndentation = 'Code editor indentation (spaces)';
out.settings_codeUseTabs = "Indent using tabs (instead of spaces)";
out.settings_codeFontSize = "Font size in the code editor";
@ -657,6 +660,9 @@ define(function () {
out.settings_padWidth = "Editor's maximum width";
out.settings_padWidthHint = "Rich text pads use by default the maximum available width on your screen and it can be difficult to read. You can reduce the editor's width here.";
out.settings_padWidthLabel = "Reduce the editor's width";
out.settings_padSpellcheckTitle = "Spellcheck";
out.settings_padSpellcheckHint = "This option allows you to enable spellcheck in rich text pads. Spelling errors will be underlined in red and you'll have to hold your Ctrl or Meta key while right-clicking to see the correct options.";
out.settings_padSpellcheckLabel = "Enable spellcheck in rich text pads";
out.settings_creationSkip = "Skip the pad creation screen";
out.settings_creationSkipHint = "The pad creation screen offers new options to create a pad, providing you more control and security over your data. However, it may slow down your workflow by adding one additional step so, here, you have the option to skip this screen and use the default settings selected above.";
@ -666,10 +672,11 @@ define(function () {
out.settings_templateSkip = "Skip the template selection modal";
out.settings_templateSkipHint = "When you create a new empty pad, if you have stored templates for this type of pad, a modal appears to ask if you want to use a template. Here you can choose to never show this modal and so to never use a template.";
out.settings_ownDriveTitle = "Drive migration"; // XXX
out.settings_ownDriveHint = "Migrating your drive to the new version will give you access to new features..."; // XXX
out.settings_ownDriveButton = "Migrate"; // XXX
out.settings_ownDriveConfirm = "Are you sure?"; // XXX
out.settings_ownDriveTitle = "Enable latest account features";
out.settings_ownDriveHint = "For technical reasons, older accounts do not have access to all of our latest features. A free upgrade to a new account will prepare your CryptDrive for upcoming features without disrupting your usual activities.";
out.settings_ownDriveButton = "Upgrade your account";
out.settings_ownDriveConfirm = "Upgrading your account may take some time. You will need to log back in on all your devices. Are you sure?";
out.settings_ownDrivePending = "Your account is being upgraded. Please do not close or reload this page until the process has completed.";
out.settings_changePasswordTitle = "Change your password";
out.settings_changePasswordHint = "Change your account's password. Enter your current password, and confirm the new password by typing it twice.<br>" +

@ -54,16 +54,6 @@ define([
APP.toolbar = Toolbar.create(configTb);
APP.toolbar.$rightside.hide();
// we're in upload mode
if (!common.isLoggedIn()) {
UI.removeLoadingScreen();
return UI.alert(Messages.contacts_mustLogin, function () {
common.setLoginRedirect(function () {
common.gotoURL('/login/');
});
});
}
MessengerUI.create($(appElement), common);
UI.removeLoadingScreen();

@ -363,8 +363,9 @@ define([
APP.origin = priv.origin;
config.loggedIn = APP.loggedIn;
config.sframeChan = sframeChan;
APP.hideDuplicateOwned = Util.find(priv, ['settings', 'drive', 'hideDuplicate']);
var manager = ProxyManager.createInner(files, sframeChan, config);
var manager = ProxyManager.createInner(files, sframeChan, edPublic, config);
Object.keys(folders).forEach(function (id) {
var f = folders[id];
@ -1207,18 +1208,14 @@ define([
}
return manager.getTitle(file);
};
// manager.moveElements is able to move several paths to a new location, including
// the Trash or the "Unsorted files" folder
var moveElements = function (paths, newPath, force, cb) {
// moveElements is able to move several paths to a new location
var moveElements = function (paths, newPath, copy, cb) {
if (!APP.editable) { return; }
var andThenMove = function () {
manager.move(paths, newPath, cb);
};
// Cancel drag&drop from TRASH to TRASH
if (manager.isPathIn(newPath, [TRASH]) && paths.length && paths[0][0] === TRASH) {
return;
}
andThenMove();
manager.move(paths, newPath, cb, copy);
};
// Delete paths from the drive and/or shared folders (without moving them to the trash)
var deletePaths = function (paths, pathsList) {
@ -1227,6 +1224,10 @@ define([
paths.forEach(function (p) { pathsList.push(p.path); });
}
var hasOwned = pathsList.some(function (p) {
// NOTE: Owned pads in shared folders won't be removed from the server
// so we don't have to check, we can use the default message
if (manager.isInSharedFolder(p)) { return false; }
var el = manager.find(p);
var data = manager.isSharedFolder(el) ? manager.getSharedFolderData(el)
: manager.getFileData(el);
@ -1309,7 +1310,7 @@ define([
$('.cp-app-drive-element-droppable').removeClass('cp-app-drive-element-droppable');
var data = ev.dataTransfer.getData("text");
// Don't the the normal drop handler for file upload
// Don't use the normal drop handler for file upload
var fileDrop = ev.dataTransfer.files;
if (fileDrop.length) { return void onFileDrop(fileDrop, ev); }
@ -1332,6 +1333,7 @@ define([
return void deletePaths(null, movedPaths);
}
var copy = false;
if (manager.isPathIn(newPath, [TRASH])) {
// Filter the selection to remove shared folders.
// Shared folders can't be moved to the trash!
@ -1346,10 +1348,12 @@ define([
}
movedPaths = filteredPaths;
} else if (ev.ctrlKey || (ev.metaKey && APP.isMac)) {
copy = true;
}
if (movedPaths && movedPaths.length) {
moveElements(movedPaths, newPath, null, refresh);
moveElements(movedPaths, newPath, copy, refresh);
}
};
@ -2374,6 +2378,8 @@ define([
var filesList = manager.search(value);
filesList.forEach(function (r) {
r.paths.forEach(function (path) {
if (!r.inSharedFolder &&
APP.hideDuplicateOwned && manager.isDuplicateOwned(path)) { return; }
var href = r.data.href;
var parsed = Hash.parsePadUrl(href);
var $table = $('<table>');
@ -2481,7 +2487,7 @@ define([
// Owned pads category
var displayOwned = function ($container) {
var list = manager.getOwnedPads(edPublic);
var list = manager.getOwnedPads();
if (list.length === 0) { return; }
var $fileHeader = getFileListHeader(false);
$container.append($fileHeader);
@ -2743,6 +2749,9 @@ define([
// display files
sortedFiles.forEach(function (key) {
if (manager.isFolder(root[key])) { return; }
var p = path.slice();
p.push(key);
if (APP.hideDuplicateOwned && manager.isDuplicateOwned(p)) { return; }
var $element = createElement(path, key, root, false);
if (!$element) { return; }
$element.appendTo($list);

@ -71,9 +71,6 @@ define([
if ($uname.val()) {
sessionStorage.login_user = $uname.val();
}
if ($passwd.val()) {
sessionStorage.login_pass = $passwd.val();
}
}
window.location.href = '/register/';
});

@ -1,3 +1,3 @@
<!DOCTYPE html>
<html dir="ltr" lang="en"><head><title>Rich Text Editor, editor1</title><style data-cke-temp="1">html{cursor:text;*cursor:auto}
img,input,textarea{cursor:default}</style><link type="text/css" rel="stylesheet" href="/customize/ckeditor-contents.css"><link type="text/css" rel="stylesheet" href="/bower_components/ckeditor/plugins/tableselection/styles/tableselection.css"></head><body><p><br></p></body></html>
img,input,textarea{cursor:default}</style><link type="text/css" rel="stylesheet" href="/bower_components/ckeditor/plugins/tableselection/styles/tableselection.css"></head><body><p><br></p></body></html>

@ -23,6 +23,18 @@ define([
}
};
var removeNode = function (el) {
if (!el) { return; }
if (typeof el.remove === "function") {
return void el.remove();
}
if (el.parentNode) {
el.parentNode.removeChild(el);
return;
}
$(el).remove();
};
Cursor.create = function (inner, hjsonToDom, cursorModule) {
var exp = {};
@ -40,9 +52,9 @@ define([
var makeCursor = function (id, cursor) {
if (cursors[id]) {
cursors[id].el.remove();
cursors[id].elstart.remove();
cursors[id].elend.remove();
removeNode(cursors[id].el);
removeNode(cursors[id].elstart);
removeNode(cursors[id].elend);
}
cursors[id] = {
el: $('<span>', {
@ -68,9 +80,9 @@ define([
};
var deleteCursor = function (id) {
if (!cursors[id]) { return; }
cursors[id].el.remove();
cursors[id].elstart.remove();
cursors[id].elend.remove();
removeNode(cursors[id].el);
removeNode(cursors[id].elstart);
removeNode(cursors[id].elend);
delete cursors[id];
};
@ -136,6 +148,15 @@ define([
end: cursorObj.selectionEnd
}, ops);
var cursorEl = makeCursor(id, cursorObj);
['start', 'end'].forEach(function (t) {
// Prevent the cursor from creating a new line at the beginning
if (r[t].el.nodeName.toUpperCase() === 'BODY') {
if (!r[t].el.childNodes.length) { r[t] = null; return; }
r[t].el = r[t].el.childNodes[0];
r[t].offset = 0;
}
});
if (!r.start || !r.end) { return; }
if (r.start.el === r.end.el && r.start.offset === r.end.offset) {
// Cursor
addCursorAtRange(cursorEl, r, cursorObj, '');

@ -316,6 +316,11 @@ define([
}
}
// Do not change the spellcheck value in view mode
if (readOnly && info.node && info.node.tagName === 'BODY' &&
info.diff.action === 'modifyAttribute' && info.diff.name === 'spellcheck') {
return true;
}
// Do not change the contenteditable value in view mode
if (readOnly && info.node && info.node.tagName === 'BODY' &&
info.diff.action === 'modifyAttribute' && info.diff.name === 'contenteditable') {
@ -447,6 +452,9 @@ define([
var $iframe = $('html').find('iframe').contents();
var ifrWindow = $html.find('iframe')[0].contentWindow;
var customCss = '/customize/ckeditor-contents.css?' + window.CKEDITOR.CRYPTPAD_URLARGS;
$iframe.find('head').append('<link href="' + customCss + '" type="text/css" rel="stylesheet" _fcktemp="true"/>');
framework._.sfCommon.addShortcuts(ifrWindow);
var documentBody = ifrWindow.document.body;
@ -493,11 +501,6 @@ define([
var $link = $(link);
$inner.append(link);
console.log($inner[0].getBoundingClientRect());
console.log(rect);
console.log($link.width(), $link.outerWidth());
console.log($inner.width());
console.log(l, t);
if (rect.left + $link.outerWidth() - rect0.left > $inner.width()) {
$link.css('left', 'unset');
$link.css('right', 0);
@ -572,9 +575,16 @@ define([
var DD = new DiffDom(mkDiffOptions(cursor, framework.isReadOnly()));
var cursorStopped = false;
var cursorTo;
var updateCursor = function () {
if (cursorStopped) { return; }
framework.updateCursor();
if (cursorTo) { clearTimeout(cursorTo); }
// If we're receiving content
if (cursorStopped) { return void setTimeout(updateCursor, 100); }
cursorTo = setTimeout(function () {
framework.updateCursor();
}, 500); // 500ms to make sure it is sent after chainpad sync
};
// apply patches, and try not to lose the cursor in the process!
@ -609,8 +619,10 @@ define([
var ops = ChainPad.Diff.diff(oldText, newText);
cursor.restoreOffset(ops);
cursorStopped = false;
updateCursor();
setTimeout(function () {
cursorStopped = false;
updateCursor();
}, 200);
// MEDIATAG: Migrate old mediatags to the widget system
$inner.find('media-tag:not(.cke_widget_element)').each(function (i, el) {
@ -705,6 +717,12 @@ define([
};
window.APP.FM = framework._.sfCommon.createFileManager(fmConfig);
framework._.sfCommon.getAttribute(['pad', 'spellcheck'], function (err, data) {
if (framework.isReadOnly()) { return; }
if (data) {
$iframe.find('body').attr('spellcheck', true);
}
});
framework._.sfCommon.getAttribute(['pad', 'width'], function (err, data) {
if (data) {
$iframe.find('html').addClass('cke_body_width');
@ -785,10 +803,10 @@ define([
});
/* Display the cursor of other users and send our cursor */
//framework.setCursorGetter(cursors.cursorGetter);
//framework.onCursorUpdate(cursors.onCursorUpdate);
//inner.addEventListener('click', updateCursor);
//inner.addEventListener('keyup', updateCursor);
framework.setCursorGetter(cursors.cursorGetter);
framework.onCursorUpdate(cursors.onCursorUpdate);
inner.addEventListener('click', updateCursor);
inner.addEventListener('keyup', updateCursor);
/* hitting enter makes a new line, but places the cursor inside
@ -811,7 +829,10 @@ define([
The solution is the "input" event, triggered by the browser as soon as the
character is inserted.
*/
inner.addEventListener('input', framework.localChange);
inner.addEventListener('input', function () {
framework.localChange();
updateCursor();
});
editor.on('change', framework.localChange);
// export the typing tests to the window.

@ -38,11 +38,9 @@ define([
var $confirm = $('#password-confirm');
if (sessionStorage.login_user) {
delete sessionStorage.login_user;
$uname.val(sessionStorage.login_user);
}
if (sessionStorage.login_pass) {
$passwd.val(sessionStorage.login_pass);
}
[ $uname, $passwd, $confirm]
.some(function ($el) { if (!$el.val()) { $el.focus(); return true; } });

@ -154,7 +154,7 @@
}
}
.cp-settings-change-password {
.cp-settings-change-password, .cp-settings-migrate {
[type="password"], [type="text"] {
width: @sidebar_button-width;
flex: unset;

@ -13,6 +13,7 @@ define([
'/customize/application_config.js',
'/api/config',
'/settings/make-backup.js',
'/common/common-feedback.js',
'/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
@ -32,7 +33,8 @@ define([
Cred,
AppConfig,
ApiConfig,
Backup
Backup,
Feedback
)
{
var saveAs = window.saveAs;
@ -52,6 +54,7 @@ define([
'cp-settings-autostore',
'cp-settings-userfeedback',
'cp-settings-change-password',
'cp-settings-migrate',
'cp-settings-backup',
'cp-settings-delete'
],
@ -62,6 +65,7 @@ define([
'cp-settings-creation-template'
],
'drive': [
'cp-settings-drive-duplicate',
'cp-settings-resettips',
'cp-settings-thumbnails',
'cp-settings-drive-backup',
@ -75,6 +79,7 @@ define([
],
'pad': [
'cp-settings-pad-width',
'cp-settings-pad-spellcheck',
],
'code': [
'cp-settings-code-indent-unit',
@ -85,6 +90,7 @@ define([
onClick: function () {
var urls = common.getMetadataMgr().getPrivateData().accounts;
window.open(urls.upgradeURL);
Feedback.send('SUBSCRIPTION_BUTTON');
}
}
};
@ -206,6 +212,7 @@ define([
$spinner.show();
$ok.hide();
Feedback.send('LOGOUT_EVERYWHERE');
sframeChan.query('Q_SETTINGS_LOGOUT', null, function () {
$spinner.hide();
$ok.show();
@ -473,10 +480,7 @@ define([
};
create['migrate'] = function () {
if (true) { return; } // STUBBED until we have a reason to deploy this
// TODO
// if (!loginBlock) { return; }
// if (alreadyMigrated) { return; }
if (privateData.isDriveOwned) { return; }
if (!common.isLoggedIn()) { return; }
var $div = $('<div>', { 'class': 'cp-settings-migrate cp-sidebarlayout-element'});
@ -489,25 +493,49 @@ define([
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var $button = $('<button>', {'id': 'cp-settings-delete', 'class': 'btn btn-primary'})
.text(Messages.settings_ownDriveButton).appendTo($div);
var form = h('div', [
UI.passwordInput({
id: 'cp-settings-migrate-password',
placeholder: Messages.settings_changePasswordCurrent
}, true),
h('button.btn.btn-primary', Messages.settings_ownDriveButton)
]);
$button.click(function () {
$(form).appendTo($div);
var todo = function () {
var password = $(form).find('#cp-settings-migrate-password').val();
if (!password) { return; }
$spinner.show();
UI.confirm(Messages.settings_ownDriveConfirm, function (yes) {
if (!yes) { return; }
sframeChan.query("Q_OWN_USER_DRIVE", null, function (err, data) {
if (err || data.error) {
console.error(err || data.error);
// TODO
$spinner.hide();
return;
}
// TODO: drive is migrated, autoamtic redirect from outer?
var data = {
password: password,
newPassword: password
};
UI.addLoadingScreen({
hideTips: true,
loadingText: Messages.settings_ownDrivePending,
});
sframeChan.query('Q_CHANGE_USER_PASSWORD', data, function (err, obj) {
UI.removeLoadingScreen();
if (err || obj.error) { return UI.alert(Messages.settings_changePasswordError); }
$ok.show();
$spinner.hide();
});
});
};
$(form).find('button').click(function () {
todo();
});
$(form).find('input').keydown(function (e) {
// Save on Enter
if (e.which === 13) {
e.preventDefault();
e.stopPropagation();
todo();
}
});
$spinner.hide().appendTo($div);
@ -754,6 +782,45 @@ define([
// Drive settings
create['drive-duplicate'] = function () {
if (!common.isLoggedIn()) { return; }
var $div = $('<div>', {
'class': 'cp-settings-drive-duplicate cp-sidebarlayout-element'
});
$('<label>').text(Messages.settings_driveDuplicateTitle).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_driveDuplicateHint).appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var $cbox = $(UI.createCheckbox('cp-settings-drive-duplicate',
Messages.settings_driveDuplicateLabel,
false, { label: {class: 'noTitle'} }));
var $checkbox = $cbox.find('input').on('change', function () {
$spinner.show();
$ok.hide();
var val = $checkbox.is(':checked');
common.setAttribute(['drive', 'hideDuplicate'], val, function () {
$spinner.hide();
$ok.show();
});
});
$cbox.appendTo($div);
$ok.hide().appendTo($cbox);
$spinner.hide().appendTo($cbox);
common.getAttribute(['drive', 'hideDuplicate'], function (e, val) {
if (e) { return void console.error(e); }
if (val) {
$checkbox.attr('checked', 'checked');
}
});
return $div;
};
create['resettips'] = function () {
var $div = $('<div>', {'class': 'cp-settings-resettips cp-sidebarlayout-element'});
$('<label>').text(Messages.settings_resetTips).appendTo($div);
@ -863,6 +930,7 @@ define([
$(cancel).click(function () {
UI.confirm(Messages.settings_exportCancel, function (yes) {
if (!yes) { return; }
Feedback.send('FULL_DRIVE_EXPORT_CANCEL');
_onCancel.forEach(function (h) { h(); });
});
}).appendTo(actions);
@ -1011,6 +1079,7 @@ define([
// Backup all the pads
var exportDrive = function () {
Feedback.send('FULL_DRIVE_EXPORT_START');
var todo = function (data, filename) {
var getPad = function (data, cb) {
sframeChan.query("Q_CRYPTGET", data, function (err, obj) {
@ -1027,6 +1096,7 @@ define([
saveAs(blob, filename);
sframeChan.event('EV_CRYPTGET_DISCONNECT');
ui.complete(function () {
Feedback.send('FULL_DRIVE_EXPORT_COMPLETE');
saveAs(blob, filename);
}, errors);
}, ui.update);
@ -1261,6 +1331,43 @@ define([
return $div;
};
create['pad-spellcheck'] = function () {
var $div = $('<div>', {
'class': 'cp-settings-pad-spellcheck cp-sidebarlayout-element'
});
$('<label>').text(Messages.settings_padSpellcheckTitle).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages.settings_padSpellcheckHint).appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var $cbox = $(UI.createCheckbox('cp-settings-pad-spellcheck',
Messages.settings_padSpellcheckLabel,
false, { label: {class: 'noTitle'} }));
var $checkbox = $cbox.find('input').on('change', function () {
$spinner.show();
$ok.hide();
var val = $checkbox.is(':checked');
common.setAttribute(['pad', 'spellcheck'], val, function () {
$spinner.hide();
$ok.show();
});
});
$cbox.appendTo($div);
$ok.hide().appendTo($cbox);
$spinner.hide().appendTo($cbox);
common.getAttribute(['pad', 'spellcheck'], function (e, val) {
if (e) { return void console.error(e); }
if (val) {
$checkbox.attr('checked', 'checked');
}
});
return $div;
};
// Code settings
create['code-indent-unit'] = function () {

@ -43,28 +43,32 @@ define([
});
});
sframeChan.on('Q_SETTINGS_DRIVE_GET', function (d, cb) {
if (d === "full") {
// We want shared folders too
}
Cryptpad.getUserObject(function (obj) {
if (obj.error) { return void cb(obj); }
var result = {
uo: obj,
sf: {}
};
if (!obj.drive || !obj.drive.sharedFolders) { return void cb(result); }
Utils.nThen(function (waitFor) {
Object.keys(obj.drive.sharedFolders).forEach(function (id) {
Cryptpad.getSharedFolder(id, waitFor(function (obj) {
result.sf[id] = obj;
}));
if (d === "full") {
// We want shared folders too
var result = {
uo: obj,
sf: {}
};
if (!obj.drive || !obj.drive.sharedFolders) { return void cb(result); }
Utils.nThen(function (waitFor) {
Object.keys(obj.drive.sharedFolders).forEach(function (id) {
Cryptpad.getSharedFolder(id, waitFor(function (obj) {
result.sf[id] = obj;
}));
});
}).nThen(function () {
cb(result);
});
}).nThen(function () {
cb(result);
});
return;
}
// We want only the user object
cb(obj);
});
});
sframeChan.on('Q_SETTINGS_DRIVE_SET', function (data, cb) {
if (data && data.uo) { data = data.uo; }
var sjson = JSON.stringify(data);
require([
'/common/cryptget.js',

@ -466,17 +466,23 @@ define([
});
framework.setContentGetter(function () {
CodeMirror.removeCursors();
var content = CodeMirror.getContent();
Slide.update(content.content);
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());

Loading…
Cancel
Save