Merge branch 'staging' into oo2

pull/1/head
yflory 6 years ago
commit bf9cf95292

2
.gitignore vendored

@ -14,5 +14,7 @@ data
npm-debug.log
pins/
blob/
block/
blobstage/
block/
privileged.conf

@ -1,19 +1,23 @@
node_modules/
www/bower_components/
www/common/pdfjs/
www/common/tippy/
www/common/jquery-ui/
www/common/onlyoffice/*
server.js
www/common/media-tag.js
www/common/old-media-tag.js
www/scratch
www/common/toolbar.js
www/common/hyperscript.js
www/common/tippy.min.js
www/common/onlyoffice/*
www/pad/wysiwygarea-plugin.js
www/pad/mediatag-plugin.js
www/pad/mediatag-plugin-dialog.js
www/pad/disable-base64.js
www/kanban/jkanban.js
www/common/media-tag-nacl.min.js

@ -40,7 +40,7 @@
"depthLevel": {
"depth": 1 // TODO(cjd) This is obviously not triggering, even with 1
},
"duplicateProperty": { "enabled": true },
"duplicateProperty": { "enabled": false },
"emptyRule": { "enabled": true },
"hexValidation": { "enabled": true }, // disallow actual garbage color hex codes (e.g. #ab)
"propertyUnits": {

@ -0,0 +1,260 @@
# Fossa release (v2.5.0)
## Goals
This release took longer than usual - three weeks instead of two - due to our plans involving a complete redesign of how login and registration function.
Any time we rework a critical system within CryptPad we're very cautious about deploying it, however, this update should bring considerable value for users.
From now on, users will be able to change their passwords without losing access to their old data, however, this is very different from _password recovery_.
While we will still be unable to help you if you have forgotten your password, this update will address our inability up until this point to change your password in the event that it has been compromised in some way.
## Update notes
* v2.5.0 uses newly released features in a clientside dependency ([chainpad-netflux](https://github.com/xwiki-labs/chainpad-netflux/releases/tag/0.7.2)). Run `bower update` to make sure you have the latest version.
* Update your server config to serve /block/ with maxAge 0d, if you are using a reverse proxy, or docker. `cryptpad/docs/example.nginx.conf` has been updated to include an example.
* Restart your server after updating.
* We have added a new feedback key, `NO_CSS_VARIABLES`, in order to diagnose how many of our clients support the CSS3 functionality.
### Features
* v2.5.0 introduces support for what we have called _modern users_.
* New registrations will use the new APIs that we've built to facillitate the ability to change your account password.
* _Legacy registrations_ will continue to function as they always have.
* Changing your password (via the settings page) will migrate old user accounts to the new system.
* We'll publish a blog post in the coming weeks to explain in depth how this functionality is implemented.
* The _kanban_ application now features support for export and import of your project data.
* This release features minor improvements to the _Deutsch_ translation
### Bug fixes
* We noticed that if you entered credentials for registration, and cancelled the displayed prompt informing you that such a user was already registered, the registration interface would not unlock for further interaction. This has been fixed.
* We found that on very slow connections, or when users opened pads in Firefox without focusing the tab, requirejs would fail to load dependencies before timing out. We've increased the timeout period by a factor of ten to address such cases.
# Echidna release (v2.4.0)
## Goals
For version 2.4.0 we chose to use our time to address difficulties that some users had, and to release some features which have been in development for some time. With the recent release of the _password-protected-pads_ feature, some users desired to be able to change the passwords that they'd already set, or to add a password to a pad retroactively. Other users wanted to recover information that had accidentally been deleted from their pads, but found that the history feature was difficult to use on networks with poor connectivity. Others still found that loading pads in general was too slow.
## Update notes
* We have released new clientside dependencies, so server administrators will need to run `bower update`
* This release also depends on new serverside dependencies, so administrators will also need to run `npm update`
* This release (optionally) takes advantage of Webworker APIs, so administrators may need to update their Content Security Headers to include worker-src (and child-src for safari)
* see cryptpad/docs/example.nginx.conf for more details regarding configuration for nginx as a reverse proxy
* to enable webworkers as an experimental feature, add `AppConfig.disableWorkers = false;` to your `cryptpad/customize/application-config.js`
* Finally, administrators will need to restart their servers after updating, as clients will require new functionality
## What's new
### Features
* CryptPad now takes advantage of some very modern browser APIs
* Shared Workers allow common tasks for all CryptPad editors to be handled by a single background process which runs in the background. This results in better performance savings for anyone using multiple editors at once in different tabs
* Webworkers are used in situations where shared workers are not supported, for most of the same tasks. They are not shared amongst different tabs, but can allow for a more responsive user experience since some heavy commands will be run in the background
* Not all browsers feature complete support for webworkers. For cases where they are not supported at all, or where cryptographic APIs are not supported within their context (https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7607496/), we fall back to an asynchronous context in the same thread
* Pads with no password can now be updated to include a password, and pads with a password can have their passwords changed
* right-click on the pad in question, and see its properties. The following dialog will present the option to change its password
* changing a pad's password will remove its history
* Accessing a pad's history used to require that clients fetch the entire history of the pad before they could view any of it. History retrieval is now done on an on-demand basis, approximately 100 versions of the pad at a time
* this also features an updated UI with a slider
* We've refactored our whiteboard application to be compatible with our internal framework. As a result, it will be easier to maintain and will have all the same features as the other editors built with the same framework
* We've defined some new server-side features which will allow clients to change their user passwords in a coming release
* We've updated our messaging server implementation
* the aspect of the server which stores and distributes history has been untangled from the aspect which tracks user lists and broadcasts messages
* the server will now store the time when each message was received, so as to be able to allow users to view the time of edits in a later release
### Bug fixes
* When a user tries to register, but enters credentials which have already been used for that CryptPad instance, we prompt them to log in as that user. We discovered that the login had stopped working at some point. This has been fixed
* Server administrators may have seen warnings from npm when attempting to update. We have fixed invalid entries and added missing entries where appropriate such that there are no more warnings
* Static info pages have been restyled to be more responsive, thanks to @CatalinScr
* Support for friend requests in pads with version 0 hashes has been repaired
* We noticed a regression in how default titles for pads were suggested, and have implemented the intended behaviour
# Donkey release (v2.3.0)
## Goals
For this release we wanted to deploy some new features related to our encrypted file functionality.
## Update notes
* new clientside dependencies. run `bower update`
* new serverside APIs. Restart your server
## What's new
### Features
* When uploading files to your CryptDrive or a pad, users will now be prompted to protect the file with a password (in addition to some random data)
* this adds an additional layer of security in case a third party gains access to the file's link, but not the password.
* Users are also able to claim an encrypted file as their own, allowing them the option to delete it from the server at a later date.
* We've refactored the Media-Tag library to be much smaller and easier to use.
### Bug fixes
* When setting a title for a pad which was created from a template, titles were not correctly inferred from the content of a document. This has been fixed.
* We discovered that users who had installed _AdBlock Plus_ and configured it to **Block social media icons tracking** were unable to use the _share menu_ to construct alternative links to the same pad, but with different attributes. We have worked around the problem.
* Admins who had configured their CryptPad instance to use custom icons for applications in the CryptDrive may have noticed that the same icons were not used on the home page. We've fixed this such that the same icons will be used everywhere
* We have also updated the icon for the Kanban app to a more appropriate symbol
* We found that the download button in the _file_ app was downloading the user's avatar, instead of the correct encrypted file embedded in the page. We've since fixed this
# Coati release (v2.2.0)
## Goals
For this release we wanted to continue our efforts towards improving CryptPad usability. We've also added a new Kanban application which was in its final stage for quite some time.
## What's new
### Features
* We've added a new kanban application!
* You can create boards, add items to those boards and move items from one board to another.
* It includes almost all the features seen in the other apps: templates, password protection, history, read-only, etc.
* Kanban can be shared and used collaboratively.
* This new app was prototyped by @ldubost, and based on [jkanban](https://github.com/riktar/jkanban) by @riktar
* We've improved our tagging feature.
* When you want to add tags to a pad, you will see suggestions based on the tags you've already used
* There is a new *Tags* category in CryptDrive for logged in users. It shows all the tags you've used in your pads and their number of use.
* In the Poll application, the line where your cursor is located will be highlighted so that you can see easily which option you're looking at.
### Bug fixes
* We've fixed two interface bugs in the Share menu which made it difficult to change the access rights for the link (edit or read-only) in some cases.
* A bug introduced in the previous version prevented loading of the drive if it contained some content from an alpha version of CryptPad.
* Some parts of our UI were using CSS values not supported by all browsers.
* Some pads created more than one year ago were not loading properly.
# Badger release (v2.1.0)
## Goals
This is a small release due to a surplus of holidays in France during the Month of May.
We'd been planning to implement _Password-protected Pads_ for a long time, but we had not found a good opportunity to do so within our roadmap.
After a generous donation from one of our users who considered this a critical feature, we were able to dedicate some resources towards delivering it to all of our users.
## Update notes
This release depends on new APIs in our `chainpad-crypto` module. Additionally, we have fixed a critical bug in `chainpad-listmap`.
Admins will need to update their clientside dependencies with `bower update` when deploying.
## What's new
### For Users
* Users can now protect their new pads with a password.
* This makes it safer to share very sensitive links over email or messengers, as anyone who gains access to the link will still need the password to edit or view pads.
* This also protects your pads against browsers which share your history across devices via the cloud.
* We recommend that you share passwords using a different messenger tool.
* Passwords cannot be set or changed after creation time (yet), so we also recommend you consider how secure your pad will need to be when you create it.
* Password protection coincides with an update to our URL encoding scheme. URLs are generally quite a bit shorter than before, while offering more functionality.
* Existing users will have a short delay the first time that they load this version of CryptPad, as it contains a migration of their CryptDrive's data format.
* This migration is very tolerant of interuptions, so if you need to close your browser while it is in progress, you are free to do so.
### For Admins
* Admins can look forward to happier users!
### Bug fixes
* data loss when reconnecting in our poll app
* we've fixed a minor bug in our poll app which caused an increasing number of tooltips to be added to elements
# Alpaca release (v2.0.0)
This is the first release of our 2.0 cycle.
After careful consideration we've decided to name each release in this cycle after a cute animal, iterating through the letters of the Latin alphabet from A to Z.
## Goals
We wanted to update CryptPad's appearance once more, adopting the colors from our logo throughout more of its interface.
## Update notes
This release coincides with the introduction of new APIs in ChainPad, so we recommend that adminstrators update their clientside dependencies by running `bower update`.
As recent updates have updated serverside dependencies, we also recommend that you run `npm update` and _restart your server_.
## What's new
### For Users
* CryptPad 2.0.0 features a complete German-language translation, thanks to contributions from @polx, @kpcyrd, and @micealachman
* CryptPad has a new look!
* we've adopted the color scheme of our logo for more UI elements throughout CryptPad, on the loading screen and various dialogs
* we've customized our checkboxes and radio buttons to match
* we've updated the look of our pad creation screen to feature up to four templates per page, with tab and button navigation
* tooltips have been made to match the dialogs on our pad creation screen
* clients now store their usage of various templates in their CryptDrive, and rank templates by popularity in the pad creation screen
* we no longer show usage tips on the loading screen
* Users who visit pads which have been deleted or otherwise do not exist are now prompted to redirect to their home page
* Our poll and whiteboard apps now use an in-house CSS framework to help us maintain consistency with the other applications
### For Admins
* we've updated the example configuration file (`config.example.js`) to no longer require a leading space before the domain, as we found it to be a common source of confusion. This will only affect newly generated config files.
* our webserver has been configured to support HTTP access of the client datastore, to facilitate scripts which parse and decrypt history without having to go through our websocket infrastructure
* we no longer use a single image for our favicon and our loading screen icon, allowing admins to customize either feature of their instance independently
* We've also moved the rest of the styles for the loading screen from `/common/` into `/customize.dist/`,
* move loading screen implementation from `/common/` to `/customize.dist/`
## Bug fixes
* don't eat tab presses when focused on register button
* idempotent picker initialization
* CKEditor fixes
* drag and drop text
* media-tag movement integrated as CKEditor plugin
* avoid media-tag flicker on updates
* set content type for the 404 page
# 1.29.0
## Goals
For this release we wanted to direct our effort towards improving user experience issues surrounding user accounts.
## Update notes
This release features breaking changes to some clientside dependencies. Administrators must make sure to deploy the
latest server with npm update before updating your clientside dependencies with bower update.
## What's new
* newly registered users are now able to delete their accounts automatically, along with any personal
information which had been created:
* ToDo list data is automatically deleted, along with user profiles
* all of a user's owned pads are also removed immediately in their account deletion process
* users who predate account deletion will not benefit from automatic account deletion, since the server
does not have sufficient knowledge to guarantee that the information they could request to have deleted is strictly
their own. For this reason, we've started working on scripts for validating user requests, so as to enable manual
deletion by the server administrator.
* the script can be found in cryptpad/check-account-deletion.js, and it will be a part of an ongoing
effort to improve administrator tooling for situations like this
* users who have not logged in, but wish to use their drive now see a ghost icon which they can use to create pads.
We hope this makes it easier to get started as a new user.
* registered users who have saved templates in their drives can now use those templates at any time, rather than only
using them to create new pads
* we've updated our file encryption code such that it does not interfere with other scripts which may be running at
the same time (synchronous blocking, for those who are interested)
* we now validate message signatures clientside, except when they are coming from the history keeper because clients
trust that the server has already validated those signatures
## Bug fixes
* we've removed some dependencies from our home page that were introduced when we updated to use bootstrap4
* we now import fontawesome as css, and not less, which saves processing time and saves room in our localStorage cache
* templates which do not have a 'type' attribute set are migrated such that the pads which are created with their
content are valid
* thumbnail creation for pads is now disabled by default, due to poor performance
* users can enable thumbnail creation in their settings page
* we've fixed a significant bug in how our server handles checkpoints (special patches in history which contain the
entire pads content)
* it was possible for two users to independently create checkpoints in close proximity while the document was in a
forked state. New users joining while the session was in this state would get stuck on one side of the fork,
and could lose data if the users on the opposing fork overrode their changes
* we've updated our tests, which have been failing for some time because their success conditions were no longer valid
* while trying to register a previously registered user, users could cancel the prompt to login as that user.
If they did so, the registration form remained locked. This has been fixed.

@ -8,13 +8,13 @@ RUN apk add --no-cache git tini \
&& npm install -g bower \
&& bower install --allow-root
EXPOSE 3000
EXPOSE 3000 3001
VOLUME /cryptpad/datastore
VOLUME /cryptpad/customize
ENV USE_SSL=false
ENV STORAGE='./storage/file'
ENV STORAGE=\'./storage/file\'
ENV LOG_TO_STDOUT=true
CMD ["/sbin/tini", "--", "/cryptpad/container-start.sh"]

@ -0,0 +1,25 @@
FROM arm64v8/node:6
COPY . /cryptpad
WORKDIR /cryptpad
RUN npm config set unsafe-perm true
ADD https://github.com/krallin/tini/releases/download/v0.18.0/tini-static-arm64 /sbin/tini
RUN chmod a+x /sbin/tini
RUN apt install -y git \
&& npm install --production \
&& npm install -g bower \
&& bower install --allow-root
EXPOSE 3000 3001
VOLUME /cryptpad/datastore
VOLUME /cryptpad/customize
ENV USE_SSL=false
ENV STORAGE=\'./storage/file\'
ENV LOG_TO_STDOUT=true
CMD ["/sbin/tini", "--", "/cryptpad/container-start.sh"]

@ -29,15 +29,14 @@
"json.sortify": "~2.1.0",
"secure-fabric.js": "secure-v1.7.9",
"hyperjson": "~1.4.0",
"chainpad-crypto": "^0.1.3",
"chainpad-listmap": "^0.4.2",
"chainpad": "^5.0.0",
"chainpad-netflux": "^0.6.1",
"chainpad-crypto": "^0.2.0",
"chainpad-listmap": "^0.5.0",
"chainpad": "^5.1.0",
"file-saver": "1.3.1",
"alertifyjs": "1.0.11",
"scrypt-async": "1.2.0",
"require-css": "0.1.10",
"less": "^2.7.2",
"less": "^3.7.1",
"bootstrap": "^v4.0.0",
"diff-dom": "2.1.1",
"nthen": "^0.1.5",
@ -46,7 +45,8 @@
"localforage": "^1.5.2",
"html2canvas": "^0.4.1",
"croppie": "^2.5.0",
"sortablejs": "#^1.6.0"
"sortablejs": "#^1.6.0",
"saferphore": "^0.0.1"
},
"resolutions": {
"bootstrap": "^v4.0.0"

@ -0,0 +1,76 @@
/* jshint esversion: 6, node: true */
const Fs = require('fs');
const nThen = require('nthen');
const Pinned = require('./pinned');
const Nacl = require('tweetnacl');
const hashesFromPinFile = (pinFile, fileName) => {
var pins = {};
pinFile.split('\n').filter((x)=>(x)).map((l) => JSON.parse(l)).forEach((l) => {
switch (l[0]) {
case 'RESET': {
pins = {};
if (l[1] && l[1].length) { l[1].forEach((x) => { pins[x] = 1; }); }
//jshint -W086
// fallthrough
}
case 'PIN': {
l[1].forEach((x) => { pins[x] = 1; });
break;
}
case 'UNPIN': {
l[1].forEach((x) => { delete pins[x]; });
break;
}
default: throw new Error(JSON.stringify(l) + ' ' + fileName);
}
});
return Object.keys(pins);
};
var escapeKeyCharacters = function (key) {
return key && key.replace && key.replace(/\//g, '-');
};
const dataIdx = process.argv.indexOf('--data');
let edPublic;
if (dataIdx === -1) {
const hasEdPublic = process.argv.indexOf('--ed');
if (hasEdPublic === -1) { return void console.error("Missing ed argument"); }
edPublic = escapeKeyCharacters(process.argv[hasEdPublic+1]);
} else {
const deleteData = JSON.parse(process.argv[dataIdx+1]);
if (!deleteData.toSign || !deleteData.proof) { return void console.error("Invalid arguments"); }
// Check sig
const ed = Nacl.util.decodeBase64(deleteData.toSign.edPublic);
const signed = Nacl.util.decodeUTF8(JSON.stringify(deleteData.toSign));
const proof = Nacl.util.decodeBase64(deleteData.proof);
if (!Nacl.sign.detached.verify(signed, proof, ed)) { return void console.error("Invalid signature"); }
edPublic = escapeKeyCharacters(deleteData.toSign.edPublic);
}
let data = [];
let pinned = [];
nThen((waitFor) => {
let f = './pins/' + edPublic.slice(0, 2) + '/' + edPublic + '.ndjson';
Fs.readFile(f, waitFor((err, content) => {
if (err) { throw err; }
pinned = hashesFromPinFile(content.toString('utf8'), f);
}));
}).nThen((waitFor) => {
Pinned.load(waitFor((d) => {
data = Object.keys(d);
}), {
exclude: [edPublic + '.ndjson']
});
}).nThen(() => {
console.log('Pads pinned by this user and not pinned by anybody else:');
pinned.forEach((p) => {
if (data.indexOf(p) === -1) {
console.log(p);
}
});
});

@ -12,6 +12,10 @@ var domain = ' http://localhost:3000/';
// to enable this feature, uncomment the line below:
// require('heapdump');
// we prepend a space because every usage expects it
// requiring admins to preserve it is unnecessarily confusing
domain = ' ' + domain;
module.exports = {
// the address you want to bind to, :: means all ipv4 and ipv6 addresses
@ -207,6 +211,11 @@ module.exports = {
*/
taskPath: './tasks',
/* if you would like users' authenticated blocks to be stored in
a custom location, change the path below:
*/
blockPath: './block',
/*
* By default, CryptPad also contacts our accounts server once a day to check for changes in
* the people who have accounts. This check-in will also send the version of your CryptPad
@ -267,7 +276,7 @@ module.exports = {
*/
blobStagingPath: './blobstage',
/* CryptPad's file storage adaptor closes unused files after a configurale
/* CryptPad's file storage adaptor closes unused files after a configurable
* number of milliseconds (default 30000 (30 seconds))
*/
channelExpirationMs: 30000,

@ -24,5 +24,5 @@ sedeasy() {
[ -n "$LOG_TO_STDOUT" ] && echo "Logging to stdout: $LOG_TO_STDOUT" \
&& sedeasy "logToStdout: [^,]*," "logToStdout: ${LOG_TO_STDOUT}," customize/config.js
export FRESH=1
exec node ./server.js

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 393.94 476.2"><defs><style>.cls-1{opacity:0.04;}.cls-2{fill:#999;}.cls-3{fill:#4591c4;}</style></defs><title>CryptPadlogo</title><g id="Layer_2" data-name="Layer 2"><g id="svg2"><g id="g4764" class="cls-1"><path class="cls-2" d="M139.36,288.16l32.07-64.43a13.59,13.59,0,0,0-4.23-16.62,48.65,48.65,0,0,1-21.28-48.25l-23.47-15.31a75.74,75.74,0,0,0-3.36,21.87c0,22,9.52,41.45,24.35,55.54l-27.24,54.8c-.11,0-.2,0-.31,0a23.27,23.27,0,1,0,0,46.53,23,23,0,0,0,17.31-7.86h40.27V288.16Z"/><path class="cls-2" d="M278.28,275.73c-.57,0-1.11.13-1.68.17l-27.33-55.09c14.75-14.07,24.2-33.47,24.2-55.39a77.13,77.13,0,0,0-3.06-21.72l-23.62,15.45a51.69,51.69,0,0,1,.44,6.27A50.21,50.21,0,0,1,225.65,207a13.58,13.58,0,0,0-4.22,16.62l31.77,64.58h-34V314.4h41.63a23.23,23.23,0,1,0,17.41-38.67Z"/><polygon class="cls-2" points="270.41 143.7 270.41 143.7 270.41 143.7 270.41 143.7"/><circle class="cls-3" cx="196.06" cy="167.4" r="21.21"/><path class="cls-3" d="M362.25,21.36a31.14,31.14,0,0,0-18.1,5.8L197,0,50,27.16a31.62,31.62,0,1,0-33.68,53.4v212c0,19.95,8.93,41.51,26.62,64.08,15.66,20,37.82,40.46,65.89,60.83a603,603,0,0,0,57,36.21,31.54,31.54,0,0,0,60,1.25,606,606,0,0,0,59.26-37.46c28.09-20.37,50.23-40.86,65.9-60.83,17.7-22.6,26.61-44.13,26.61-64.08V80.38a31.46,31.46,0,0,0-15.39-59ZM62.82,55.94,196.61,31.32l134.33,24.8a29.58,29.58,0,0,0,.9,5.61L253.09,113a76.78,76.78,0,0,0-113.69.36L61.55,62.82A31.8,31.8,0,0,0,62.82,55.94ZM350.49,288.21a51.41,51.41,0,0,1-.73,8.51c-3.16,12.6-10.11,25.8-20.82,39.47-13.41,17.09-32.43,34.52-56.48,51.95a532.75,532.75,0,0,1-54.19,34.09,31.5,31.5,0,0,0-43.73-.62,534,534,0,0,1-53.06-33.47c-24-17.43-42.9-34.86-56.31-51.95-12.77-16.31-20.25-31.94-22.26-46.71,0-.41,0-.86,0-1.27V82.37a31.51,31.51,0,0,0,8.69-5.25l88.88,57.75.37-.36,10.5,6.88a51,51,0,0,1,45.07-27,50.37,50.37,0,0,1,45.08,27.15l22.09-14.3L341.44,76.4A32.55,32.55,0,0,0,350.49,82Z"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -5,12 +5,12 @@ CKEDITOR.editorConfig = function( config ) {
config.needsBrFiller= fixThings;
config.needsNbspFiller= fixThings;
config.removeButtons= 'Source,Maximize,Anchor';
config.removeButtons= 'Source,Maximize';
// magicline plugin inserts html crap into the document which is not part of the
// document itself and causes problems when it's sent across the wire and reflected back
config.removePlugins= 'resize,elementspath';
config.resize_enabled= false; //bottom-bar
config.extraPlugins= 'autolink,colorbutton,colordialog,font,indentblock,justify,mediatag,print';
config.extraPlugins= 'autolink,colorbutton,colordialog,font,indentblock,justify,mediatag,print,blockbase64';
config.toolbarGroups= [
// {"name":"clipboard","groups":["clipboard","undo"]},
//{"name":"editing","groups":["find","selection"]},

@ -0,0 +1,12 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="cptools" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" glyph-name="template" horiz-adv-x="876" d="M804.244 960h-732.699c-39.513 0-71.545-32.032-71.545-71.545v0-880.909c0-39.513 32.032-71.545 71.545-71.545v0h732.699c39.513 0 71.545 32.032 71.545 71.545v0 880.909c0 39.513-32.032 71.545-71.545 71.545v0zM808.421 7.545c0-2.307-1.87-4.177-4.177-4.177v0h-732.699c-2.307 0-4.177 1.87-4.177 4.177v0 880.909c0 2.307 1.87 4.177 4.177 4.177v0h732.699c2.307 0 4.177-1.87 4.177-4.177v0zM107.789 852.211h660.211v-256h-660.211v256zM107.789 555.789h269.474v-256h-269.474v256zM417.684 70.737h350.316v-26.947h-350.316v26.947zM417.684 133.659h350.316v-26.947h-350.316v26.947zM417.684 196.446h350.316v-26.947h-350.316v26.947zM417.684 259.368h350.316v-26.947h-350.316v26.947zM417.684 555.789h350.316v-26.947h-350.316v26.947zM417.684 498.526h350.316v-26.947h-350.316v26.947zM417.684 441.263h350.316v-26.947h-350.316v26.947zM417.684 384h350.316v-26.947h-350.316v26.947zM417.684 326.737h350.316v-26.947h-350.316v26.947zM107.789 259.368h269.474v-215.579h-269.474v215.579z" />
<glyph unicode="&#xe901;" glyph-name="new-template" horiz-adv-x="920" d="M103.696 856.304h635.139v-246.278h-635.139v246.278zM103.696 571.139h259.241v-246.278h-259.241v246.278zM401.823 165.039h337.013v-25.924h-337.013v25.924zM401.823 225.442h337.013v-25.924h-337.013v25.924zM401.823 285.975h337.013v-25.924h-337.013v25.924zM401.823 571.139h337.013v-25.924h-337.013v25.924zM401.823 516.051h337.013v-25.924h-337.013v25.924zM401.823 460.962h337.013v-25.924h-337.013v25.924zM401.823 405.873h337.013v-25.924h-337.013v25.924zM401.823 350.785h337.013v-25.924h-337.013v25.924zM103.696 285.975h259.241v-207.392h-259.241v207.392zM842.532 140.152v751.020c0 38.013-30.816 68.828-68.828 68.828v0h-704.875c-38.013 0-68.828-30.816-68.828-68.828v0-847.457c0-38.013 30.816-68.828 68.828-68.828v0h666.896c19.163-23.765 48.277-38.841 80.912-38.841 57.27 0 103.696 46.426 103.696 103.696 0 48.068-32.706 88.497-77.078 100.248l-0.724 0.163zM68.828 39.696c-2.219 0-4.018 1.799-4.018 4.018v0 847.457c0 2.219 1.799 4.018 4.018 4.018v0h704.875c2.219 0 4.018-1.799 4.018-4.018v0-755.297c-16.886-6.977-31.013-17.69-41.84-31.166l-0.157-0.202h-333.902v-25.924h318.607c-4.743-11.511-7.504-24.874-7.518-38.881v-0.006zM816.608-51.038c-50.111 0-90.734 40.623-90.734 90.734v0c0 0.068 0 0.148 0 0.228 0 14.058 3.253 27.357 9.047 39.184l-0.233-0.526c1.324 3.018 2.711 5.571 4.28 7.995l-0.132-0.218c9.3 15.303 22.547 27.384 38.344 35.021l0.542 0.236c11.301 5.562 24.6 8.814 38.658 8.814 0.080 0 0.16 0 0.241 0h-0.012c50.111 0 90.734-40.623 90.734-90.734s-40.623-90.734-90.734-90.734v0zM861.975 52.658h-32.405v32.405c0 3.579-2.902 6.481-6.481 6.481v0h-12.962c-3.579 0-6.481-2.902-6.481-6.481v0-32.405h-32.405c-3.579 0-6.481-2.902-6.481-6.481v0-12.962c0-3.579 2.902-6.481 6.481-6.481v0h32.405v-32.405c0-3.579 2.902-6.481 6.481-6.481v0h12.962c3.579 0 6.481 2.902 6.481 6.481v0 32.405h32.405c3.579 0 6.481 2.902 6.481 6.481v0 12.962c0 3.579-2.902 6.481-6.481 6.481v0z" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

@ -0,0 +1,31 @@
@font-face {
font-family: 'cptools';
src:
url('fonts/cptools.ttf?dysqmo') format('truetype'),
url('fonts/cptools.woff?dysqmo') format('woff'),
url('fonts/cptools.svg?dysqmo#cptools') format('svg');
font-weight: normal;
font-style: normal;
}
.cptools {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'cptools' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.cptools-template:before {
content: "\e900";
}
.cptools-new-template:before {
content: "\e901";
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@ -0,0 +1,200 @@
// dark #326599
// light #4591c4
define([], function () {
var loadingStyle = (function(){/*
#cp-loading {
transition: opacity 0.75s, visibility 0s 0.75s;
visibility: visible;
position: fixed;
z-index: 10000000;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
background: linear-gradient(to right, #326599 0%, #326599 50%, #4591c4 50%, #4591c4 100%);
color: #fafafa;
font-size: 1.5em;
opacity: 1;
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
}
#cp-loading.cp-loading-hidden {
opacity: 0;
visibility: hidden;
}
#cp-loading .cp-loading-logo {
height: 300px;
width: 300px;
margin-top: 50px;
flex: 0 1 auto;
min-height: 0;
text-align: center;
}
#cp-loading .cp-loading-logo img {
max-width: 100%;
max-height: 100%;
}
#cp-loading .cp-loading-container {
width: 700px;
max-width: 90vw;
height: 500px;
max-height: calc(100vh - 20px);
margin: 50px;
flex-shrink: 0;
display: flex;
flex-flow: column;
justify-content: space-around;
align-items: center;
}
@media screen and (max-height: 800px) {
#cp-loading .cp-loading-container {
height: auto;
}
}
@media screen and (max-width: 600px) {
#cp-loading .cp-loading-container {
height: auto;
}
}
#cp-loading .cp-loading-cryptofist {
margin-left: auto;
margin-right: auto;
//height: 300px;
max-width: 90vw;
max-height: 300px;
width: auto;
height: auto;
margin-bottom: 2em;
}
@media screen and (max-height: 500px) {
#cp-loading .cp-loading-logo {
display: none;
}
}
#cp-loading-message {
background: #FFF;
padding: 20px;
width: 100%;
color: #000;
text-align: center;
display: none;
}
#cp-loading-password-prompt {
font-size: 18px;
}
#cp-loading-password-prompt .cp-password-error {
color: white;
background: #9e0000;
padding: 5px;
margin-bottom: 15px;
}
#cp-loading-password-prompt .cp-password-info {
text-align: left;
margin-bottom: 15px;
}
#cp-loading-password-prompt .cp-password-form {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
}
#cp-loading-password-prompt .cp-password-form button,
#cp-loading-password-prompt .cp-password-form .cp-password-input {
background-color: #4591c4;
color: white;
border: 1px solid #4591c4;
}
#cp-loading-password-prompt .cp-password-form .cp-password-container {
flex-shrink: 1;
min-width: 0;
}
#cp-loading-password-prompt .cp-password-form input {
flex: 1;
padding: 0 5px;
min-width: 0;
text-overflow: ellipsis;
}
#cp-loading-password-prompt .cp-password-form button:hover {
background-color: #326599;
}
#cp-loading .cp-loading-spinner-container {
position: relative;
height: 100px;
}
#cp-loading .cp-loading-spinner-container > div {
height: 100px;
}
#cp-loading-tip {
position: fixed;
z-index: 10000000;
top: 80%;
left: 0;
right: 0;
text-align: center;
transition: opacity 750ms;
transition-delay: 3000ms;
}
@media screen and (max-height: 600px) {
#cp-loading-tip {
display: none;
}
}
#cp-loading-tip span {
background: #222;
color: #fafafa;
text-align: center;
font-size: 1.5em;
opacity: 0.7;
font-family: 'Open Sans', 'Helvetica Neue', sans-serif;
padding: 15px;
max-width: 60%;
display: inline-block;
}
.cp-loading-progress {
width: 100%;
margin: 20px;
}
.cp-loading-progress p {
margin: 5px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.cp-loading-progress-bar {
height: 24px;
background: white;
}
.cp-loading-progress-bar-value {
height: 100%;
background: #5cb85c;
}
*/}).toString().slice(14, -3);
var urlArgs = window.location.href.replace(/^.*\?([^\?]*)$/, function (all, x) { return x; });
var elem = document.createElement('div');
elem.setAttribute('id', 'cp-loading');
elem.innerHTML = [
'<style>',
loadingStyle,
'</style>',
'<div class="cp-loading-logo">',
'<img class="cp-loading-cryptofist" src="/customize/loading-logo.png?' + urlArgs + '">',
'</div>',
'<div class="cp-loading-container">',
'<div class="cp-loading-spinner-container">',
'<span class="fa fa-spinner fa-pulse fa-4x fa-fw"></span>',
'</div>',
'<p id="cp-loading-message"></p>',
'</div>'
].join('');
return function () {
var intr;
var append = function () {
if (!document.body) { return; }
clearInterval(intr);
document.body.appendChild(elem);
};
intr = setInterval(append, 100);
append();
};
});

@ -12,17 +12,23 @@ define([
'/common/common-feedback.js',
'/common/outer/local-store.js',
'/customize/messages.js',
'/bower_components/nthen/index.js',
'/common/outer/login-block.js',
'/common/common-hash.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'/bower_components/scrypt-async/scrypt-async.min.js', // better load speed
], function ($, Listmap, Crypto, Util, NetConfig, Cred, ChainPad, Realtime, Constants, UI,
Feedback, LocalStore, Messages) {
Feedback, LocalStore, Messages, nThen, Block, Hash) {
var Exports = {
Cred: Cred,
// this is depended on by non-customizable files
// be careful when modifying login.js
requiredBytes: 192,
};
var Nacl = window.nacl;
var allocateBytes = function (bytes) {
var allocateBytes = Exports.allocateBytes = function (bytes) {
var dispense = Cred.dispenser(bytes);
var opt = {};
@ -41,6 +47,10 @@ define([
// 32 more for a signing key
var edSeed = opt.edSeed = dispense(32);
// 64 more bytes to seed an additional signing key
var blockKeys = opt.blockKeys = Block.genkeys(new Uint8Array(dispense(64)));
opt.blockHash = Block.getBlockHash(blockKeys);
// derive a private key from the ed seed
var signingKeypair = Nacl.sign.keyPair.fromSeed(new Uint8Array(edSeed));
@ -58,10 +68,24 @@ define([
// should never happen
if (channelHex.length !== 32) { throw new Error('invalid channel id'); }
opt.channel64 = Util.hexToBase64(channelHex);
var channel64 = Util.hexToBase64(channelHex);
// we still generate a v1 hash because this function needs to deterministically
// derive the same values as it always has. New accounts will generate their own
// userHash values
opt.userHash = '/1/edit/' + [channel64, opt.keys.editKeyStr].join('/') + '/';
return opt;
};
opt.userHash = '/1/edit/' + [opt.channel64, opt.keys.editKeyStr].join('/');
var loginOptionsFromBlock = function (blockInfo) {
var opt = {};
var parsed = Hash.getSecrets('pad', blockInfo.User_hash);
opt.channelHex = parsed.channel;
opt.keys = parsed.keys;
opt.edPublic = blockInfo.edPublic;
opt.User_name = blockInfo.User_name;
return opt;
};
@ -92,6 +116,14 @@ define([
return Object.keys(proxy).length === 0;
};
var setMergeAnonDrive = function () {
sessionStorage.migrateAnonDrive = 1;
};
var setCreateReadme = function () {
sessionStorage.createReadme = 1;
};
Exports.loginOrRegister = function (uname, passwd, isRegister, shouldImport, cb) {
if (typeof(cb) !== 'function') { return; }
@ -105,22 +137,101 @@ define([
return void cb('PASS_TOO_SHORT');
}
Cred.deriveFromPassphrase(uname, passwd, 128, function (bytes) {
// results...
var res = {
register: isRegister,
};
// run scrypt to derive the user's keys
var opt = res.opt = allocateBytes(bytes);
var RT, blockKeys, blockHash, Pinpad, rpc, userHash;
nThen(function (waitFor) {
// derive a predefined number of bytes from the user's inputs,
// and allocate them in a deterministic fashion
Cred.deriveFromPassphrase(uname, passwd, Exports.requiredBytes, waitFor(function (bytes) {
res.opt = allocateBytes(bytes);
blockHash = res.opt.blockHash;
blockKeys = res.opt.blockKeys;
}));
}).nThen(function (waitFor) {
// the allocated bytes can be used either in a legacy fashion,
// or in such a way that a previously unused byte range determines
// the location of a layer of indirection which points users to
// an encrypted block, from which they can recover the location of
// the rest of their data
// determine where a block for your set of keys would be stored
var blockUrl = Block.getBlockUrl(res.opt.blockKeys);
// Check whether there is a block at that location
Util.fetch(blockUrl, waitFor(function (err, block) {
// if users try to log in or register, we must check
// whether there is a block.
// the block is only useful if it can be decrypted, though
if (err) {
console.log("no block found");
return;
}
// use the derived key to generate an object
loadUserObject(opt, function (err, rt) {
var decryptedBlock = Block.decrypt(block, blockKeys);
if (!decryptedBlock) {
console.error("Found a login block but failed to decrypt");
return;
}
console.error(decryptedBlock);
res.blockInfo = decryptedBlock;
}));
}).nThen(function (waitFor) {
// we assume that if there is a block, it was created in a valid manner
// so, just proceed to the next block which handles that stuff
if (res.blockInfo) { return; }
var opt = res.opt;
// load the user's object using the legacy credentials
loadUserObject(opt, waitFor(function (err, rt) {
if (err) { return void cb(err); }
// if a proxy is marked as deprecated, it is because someone had a non-owned drive
// but changed their password, and couldn't delete their old data.
// if they are here, they have entered their old credentials, so we should not
// allow them to proceed. In time, their old drive should get deleted, since
// it will should be pinned by anyone's drive.
if (rt.proxy[Constants.deprecatedKey]) {
return void cb('NO_SUCH_USER', res);
}
if (isRegister && isProxyEmpty(rt.proxy)) {
// If they are trying to register,
// and the proxy is empty, then there is no 'legacy user' either
// so we should just shut down this session and disconnect.
rt.network.disconnect();
return; // proceed to the next async block
}
// they tried to just log in but there's no such user
// and since we're here at all there is no modern-block
if (!isRegister && isProxyEmpty(rt.proxy)) {
rt.network.disconnect(); // clean up after yourself
waitFor.abort();
return void cb('NO_SUCH_USER', res);
}
// they tried to register, but those exact credentials exist
if (isRegister && !isProxyEmpty(rt.proxy)) {
rt.network.disconnect();
waitFor.abort();
Feedback.send('LOGIN', true);
return void cb('ALREADY_REGISTERED', res);
}
// if you are here, then there is no block, the user is trying
// to log in. The proxy is **not** empty. All values assigned here
// should have been deterministically created using their credentials
// so setting them is just a precaution to keep things in good shape
res.proxy = rt.proxy;
res.realtime = rt.realtime;
res.network = rt.network;
// they're registering...
res.userHash = opt.userHash;
@ -130,52 +241,168 @@ define([
res.edPrivate = opt.edPrivate;
res.edPublic = opt.edPublic;
// export their encryption key
res.curvePrivate = opt.curvePrivate;
res.curvePublic = opt.curvePublic;
// they tried to just log in but there's no such user
if (shouldImport) { setMergeAnonDrive(); }
// don't proceed past this async block.
waitFor.abort();
// We have to call whenRealtimeSyncs asynchronously here because in the current
// version of listmap, onLocal calls `chainpad.contentUpdate(newValue)`
// asynchronously.
// The following setTimeout is here to make sure whenRealtimeSyncs is called after
// `contentUpdate` so that we have an update userDoc in chainpad.
setTimeout(function () {
Realtime.whenRealtimeSyncs(rt.realtime, function () {
// the following stages are there to initialize a new drive
// if you are registering
LocalStore.login(res.userHash, res.userName, function () {
setTimeout(function () { cb(void 0, res); });
});
});
});
}));
}).nThen(function (waitFor) { // MODERN REGISTRATION / LOGIN
var opt;
if (res.blockInfo) {
opt = loginOptionsFromBlock(res.blockInfo);
userHash = res.blockInfo.User_hash;
console.error(opt, userHash);
} else {
console.log("allocating random bytes for a new user object");
opt = allocateBytes(Nacl.randomBytes(Exports.requiredBytes));
// create a random v2 hash, since we don't need backwards compatibility
userHash = opt.userHash = Hash.createRandomHash('drive');
var secret = Hash.getSecrets('drive', userHash);
opt.keys = secret.keys;
opt.channelHex = secret.channel;
}
// according to the location derived from the credentials which you entered
loadUserObject(opt, waitFor(function (err, rt) {
if (err) {
waitFor.abort();
return void cb('MODERN_REGISTRATION_INIT');
}
console.error(JSON.stringify(rt.proxy));
// export the realtime object you checked
RT = rt;
var proxy = rt.proxy;
if (isRegister && !isProxyEmpty(proxy) && (!proxy.edPublic || !proxy.edPrivate)) {
console.error("INVALID KEYS");
console.log(JSON.stringify(proxy));
return;
}
res.proxy = rt.proxy;
res.realtime = rt.realtime;
// they're registering...
res.userHash = userHash;
res.userName = uname;
// somehow they have a block present, but nothing in the user object it specifies
// this shouldn't happen, but let's send feedback if it does
if (!isRegister && isProxyEmpty(rt.proxy)) {
// this really shouldn't happen, but let's handle it anyway
Feedback.send('EMPTY_LOGIN_WITH_BLOCK');
rt.network.disconnect(); // clean up after yourself
waitFor.abort();
return void cb('NO_SUCH_USER', res);
}
// they tried to register, but those exact credentials exist
if (isRegister && !isProxyEmpty(rt.proxy)) {
rt.network.disconnect();
waitFor.abort();
res.blockHash = blockHash;
if (shouldImport) {
setMergeAnonDrive();
}
return void cb('ALREADY_REGISTERED', res);
}
if (isRegister) {
var proxy = rt.proxy;
proxy.edPublic = res.edPublic;
proxy.edPrivate = res.edPrivate;
proxy.curvePublic = res.curvePublic;
proxy.curvePrivate = res.curvePrivate;
if (!isRegister && !isProxyEmpty(rt.proxy)) {
LocalStore.setBlockHash(blockHash);
waitFor.abort();
if (shouldImport) {
setMergeAnonDrive();
}
return void LocalStore.login(userHash, uname, function () {
cb(void 0, res);
});
}
if (isRegister && isProxyEmpty(rt.proxy)) {
proxy.edPublic = opt.edPublic;
proxy.edPrivate = opt.edPrivate;
proxy.curvePublic = opt.curvePublic;
proxy.curvePrivate = opt.curvePrivate;
proxy.login_name = uname;
proxy[Constants.displayNameKey] = uname;
sessionStorage.createReadme = 1;
setCreateReadme();
if (shouldImport) {
setMergeAnonDrive();
} else {
proxy.version = 6;
}
Feedback.send('REGISTRATION', true);
} else {
Feedback.send('LOGIN', true);
}
if (shouldImport) {
sessionStorage.migrateAnonDrive = 1;
setTimeout(waitFor(function () {
Realtime.whenRealtimeSyncs(rt.realtime, waitFor());
}));
}));
}).nThen(function (waitFor) {
require(['/common/pinpad.js'], waitFor(function (_Pinpad) {
console.log("loaded rpc module");
Pinpad = _Pinpad;
}));
}).nThen(function (waitFor) {
// send an RPC to store the block which you created.
console.log("initializing rpc interface");
Pinpad.create(RT.network, RT.proxy, waitFor(function (e, _rpc) {
if (e) {
waitFor.abort();
console.error(e); // INVALID_KEYS
return void cb('RPC_CREATION_ERROR');
}
rpc = _rpc;
console.log("rpc initialized");
}));
}).nThen(function (waitFor) {
console.log("creating request to publish a login block");
// We have to call whenRealtimeSyncs asynchronously here because in the current
// version of listmap, onLocal calls `chainpad.contentUpdate(newValue)`
// asynchronously.
// The following setTimeout is here to make sure whenRealtimeSyncs is called after
// `contentUpdate` so that we have an update userDoc in chainpad.
setTimeout(function () {
Realtime.whenRealtimeSyncs(rt.realtime, function () {
LocalStore.login(res.userHash, res.userName, function () {
setTimeout(function () { cb(void 0, res); });
});
});
});
// Finally, create the login block for the object you just created.
var toPublish = {};
toPublish[Constants.userNameKey] = uname;
toPublish[Constants.userHashKey] = userHash;
toPublish.edPublic = RT.proxy.edPublic;
var blockRequest = Block.serialize(JSON.stringify(toPublish), res.opt.blockKeys);
rpc.writeLoginBlock(blockRequest, waitFor(function (e) {
if (e) { return void console.error(e); }
console.log("blockInfo available at:", blockHash);
LocalStore.setBlockHash(blockHash);
LocalStore.login(userHash, uname, function () {
cb(void 0, res);
});
}));
});
};
Exports.redirect = function () {
@ -212,6 +439,7 @@ define([
loadingText: Messages.login_hashing,
hideTips: true,
});
// We need a setTimeout(cb, 0) otherwise the loading screen is only displayed
// after hashing the password
window.setTimeout(function () {
@ -253,17 +481,27 @@ define([
});
break;
case 'ALREADY_REGISTERED':
// logMeIn should reset registering = false
UI.removeLoadingScreen(function () {
UI.confirm(Messages.register_alreadyRegistered, function (yes) {
if (!yes) { return; }
if (!yes) {
hashing = false;
return;
}
proxy.login_name = uname;
if (!proxy[Constants.displayNameKey]) {
proxy[Constants.displayNameKey] = uname;
}
LocalStore.eraseTempSessionValues();
proceed(result);
if (result.blockHash) {
LocalStore.setBlockHash(result.blockHash);
}
LocalStore.login(result.userHash, result.userName, function () {
setTimeout(function () { proceed(result); });
});
});
});
break;

@ -1,18 +1,38 @@
define([
'/api/config',
'/common/hyperscript.js',
'/common/common-language.js',
'/customize/messages.js',
'jquery',
'/customize/application_config.js',
], function (Config, h, Msg, $, AppConfig) {
], function (Config, h, Language, Msg, $, AppConfig) {
var Pages = {};
var urlArgs = Config.requireConf.urlArgs;
var setHTML = function (e, html) {
var setHTML = Pages.setHTML = function (e, html) {
e.innerHTML = html;
return e;
};
var languageSelector = function () {
var options = [];
var languages = Msg._languages;
var selected = Msg._languageUsed;
var keys = Object.keys(languages).sort();
keys.forEach(function (l) {
var attr = { value: l };
if (selected === l) { attr.selected = 'selected'; }
options.push(h('option', attr, languages[l]));
});
var select = h('select', {}, options);
$(select).change(function () {
Language.setLanguage($(select).val() || '', null, function () {
window.location.reload();
});
});
return select;
};
var footerCol = function (title, L, literal) {
return h('div.col-6.col-sm-3', [
h('ul.list-unstyled', [
@ -47,7 +67,8 @@ define([
h('div.row', [
footerCol(null, [
h('div.cp-bio-foot', [
h('p', Msg.main_footerText)
h('p', Msg.main_footerText),
languageSelector()
])
], ''),
footerCol('footer_applications', [
@ -56,6 +77,7 @@ define([
footLink('/code/', 'main_code'),
footLink('/slide/', 'main_slide'),
footLink('/poll/', 'main_poll'),
footLink('/kanban/', 'main_kanban'),
footLink('/whiteboard/', null, Msg.type.whiteboard)
]),
footerCol('footer_aboutUs', [
@ -72,7 +94,7 @@ define([
])
])
]),
h('div.cp-version-footer', "CryptPad v1.28.0 (toString)")
h('div.cp-version-footer', "CryptPad v2.6.0 (Gibbon)")
]);
};
@ -92,16 +114,25 @@ define([
]);
}
return h('nav.navbar.navbar-expand-lg',
h('a.navbar-brand', { href: '/index.html'}),
h('button.navbar-toggler', {
var button = h('button.navbar-toggler', {
'type':'button',
'data-toggle':'collapse',
/*'data-toggle':'collapse',
'data-target':'#menuCollapse',
'aria-controls': 'menuCollapse',
'aria-expanded':'false',
'aria-label':'Toggle navigation'
}, h('i.fa.fa-bars ')),
'aria-label':'Toggle navigation'*/
}, h('i.fa.fa-bars '));
$(button).click(function () {
if ($('#menuCollapse').is(':visible')) {
return void $('#menuCollapse').slideUp();
}
$('#menuCollapse').slideDown();
});
return h('nav.navbar.navbar-expand-lg',
h('a.navbar-brand', { href: '/index.html'}),
button,
h('div.collapse.navbar-collapse.justify-content-end#menuCollapse', [
//h('a.nav-item.nav-link', { href: '/what-is-cryptpad.html'}, Msg.topbar_whatIsCryptpad), // Moved the FAQ
h('a.nav-item.nav-link', { href: '/faq.html'}, Msg.faq_link),
@ -121,7 +152,7 @@ define([
h('div.container', [
h('center', [
h('h1', Msg.about),
setHTML(h('p'), 'CryptPad is created inside of the Research Team at <a href="http://xwiki.com">XWiki SAS</a>, a small business located in Paris France and Iasi Romania. There are 3 core team members working on CryptPad plus a number of contributors both inside and outside of XWiki SAS.'),
setHTML(h('p'), Msg.about_intro),
]),
]),
]),
@ -129,7 +160,7 @@ define([
h('div.row', [
h('div.cp-develop-about.col-12',[
h('div.cp-icon-cent'),
h('h2.text-center', 'Core Developers')
h('h2.text-center', Msg.about_core)
]),
]),
h('div.row.align-items-center', [
@ -149,10 +180,10 @@ define([
]),
]),
h('div.row.align-items-center',[
h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-2.cp-bio-avatar.cp-bio-avatar-right', [
h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-lg-2.cp-bio-avatar.cp-bio-avatar-right', [
h('img.img-fluid', {'src': '/customize/images/AaronMacSween.jpg'})
]),
h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-1.cp-profile-det',[
h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-lg-1.cp-profile-det',[
h('h3', "Aaron MacSween"),
h('hr'),
setHTML(h('div#bioAaron'), '<p>Aaron transitioned into distributed systems development from a background in jazz and live stage performance. <br/> He appreciates the elegance of biological systems and functional programming, and focused on both as a student at the University of Toronto, where he studied cognitive and computer sciences.<br/>He moved to Paris in 2015 to work as a research engineer at XWiki SAS, after having dedicated significant time to various cryptography-related software projects.<br/>He spends his spare time experimenting with guitars, photography, science fiction, and spicy food.</p>'),
@ -180,7 +211,7 @@ define([
h('div.row', [
h('div.cp-develop-about.col-12.cp-contrib',[
h('div.cp-icon-cent'),
h('h2.text-center', 'Key Contributors')
h('h2.text-center', Msg.about_contributors)
]),
]),
h('div.row.align-items-center', [
@ -200,10 +231,10 @@ define([
]),
]),
h('div.row.align-items-center',[
h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-2.cp-bio-avatar.cp-bio-avatar-right', [
h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-lg-2.cp-bio-avatar.cp-bio-avatar-right', [
h('img.img-fluid', {'src': '/customize/images/Catalin.jpg'})
]),
h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-1.cp-profile-det',[
h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-lg-1.cp-profile-det',[
h('h3', "Catalin Scripcariu"),
h('hr'),
setHTML(h('div#bioCatalin'), '<p> Catalin is a Maths majour and has worked in B2B sales for 12 years. Design was always his passion and 3 years ago he started to dedicate himself to web design and front-end.<br/>At the beginning of 2017 he joined the XWiki, where he worked both on the business and the community side of XWiki, including the research team and CryptPad. </p>'),
@ -239,113 +270,83 @@ define([
Pages['/features.html'] = function () {
return h('div#cp-main', [
infopageTopbar(),
h('div.container.cp-container', [
h('div.container-fluid.cp_cont_features',[
h('div.container',[
h('center', h('h1', Msg.features_title)),
h('table#cp-features-table', [
h('thead', h('tr', [
h('th', Msg.features_feature),
h('th', Msg.features_anon),
h('th', Msg.features_registered),
h('th', Msg.features_notes)
])),
h('tbody', [
h('tr', [
h('td', Msg.features_f_pad),
h('td.yes', '✔'),// u2714, u2715
h('td.yes', '✔'),
h('td', Msg.features_f_pad_notes)
]),
h('tr', [
h('td', Msg.features_f_history),
h('td.yes', '✔'),
h('td.yes', '✔'),
h('td', Msg.features_f_history_notes)
]),
h('tr', [
h('td', Msg.features_f_export),
h('td.yes', '✔'),
h('td.yes', '✔'),
h('td', Msg.features_f_export_notes)
]),
h('tr', [
h('td', Msg.features_f_todo),
h('td.yes', '✔'),
h('td.yes', '✔'),
h('td')
]),
h('tr', [
h('td', Msg.features_f_viewFiles),
h('td.yes', '✔'),
h('td.yes', '✔'),
h('td')
]),
h('tr', [
h('td', Msg.features_f_drive),
h('td.part', '~'),
h('td.yes', '✔'),
h('td', Msg.features_f_drive_notes)
]),
h('tr', [
h('td', Msg.features_f_uploadFiles),
h('td.no', '✕'),
h('td.yes', '✔'),
h('td')
]),
h('tr', [
h('td', Msg.features_f_embedFiles),
h('td.no', '✕'),
h('td.yes', '✔'),
h('td')
]),
h('tr', [
h('td', Msg.features_f_multiple),
h('td.no', '✕'),
h('td.yes', '✔'),
h('td', Msg.features_f_multiple_notes)
]),
h('tr', [
h('td', Msg.features_f_logoutEverywhere),
h('td.no', '✕'),
h('td.yes', '✔'),
h('td', Msg.features_f_logoutEverywhere_notes || '')
]),
h('tr', [
h('td', Msg.features_f_templates),
h('td.no', '✕'),
h('td.yes', '✔'),
h('td', Msg.features_f_templates_notes)
]),
h('tr', [
h('td', Msg.features_f_profile),
h('td.no', '✕'),
h('td.yes', '✔'),
h('td', Msg.features_f_profile_notes)
]),
h('tr', [
h('td', Msg.features_f_tags),
h('td.no', '✕'),
h('td.yes', '✔'),
h('td', Msg.features_f_tags_notes)
]),
h('tr', [
h('td', Msg.features_f_contacts),
h('td.no', '✕'),
h('td.yes', '✔'),
h('td', Msg.features_f_contacts_notes)
]),
h('tr', [
h('td', Msg.features_f_storage),
h('td.no', Msg.features_f_storage_anon),
setHTML(h('td.yes.left'), Msg.features_f_storage_registered),
h('td')
]),
])
]),
h('div.container',[
h('div.row.cp-container.cp-features-web.justify-content-sm-center',[
h('div.col-12.col-sm-6.cp-anon-user',[
h('div.card',[
h('div.card-body',[
h('h3.text-center',Msg.features_anon)
]),
h('ul.list-group.list-group-flush', [
h('li.list-group-item.text-center', Msg.features_f_pad , [
h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_pad_notes}, h('i.fa.fa-question'))
]),
h('li.list-group-item.text-center', Msg.features_f_history, [
h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_history_notes }, h('i.fa.fa-question') )
]),
h('li.list-group-item.text-center', Msg.features_f_export, [
h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_export_notes }, h('i.fa.fa-question'))
]),
h('li.list-group-item.text-center', Msg.features_f_todo),
h('li.list-group-item.text-center', Msg.features_f_viewFiles),
h('li.list-group-item.text-center', Msg.features_f_drive),
h('li.list-group-item.text-center', Msg.features_f_storage_anon),
]),
]),
]),
h('div.col-12.col-sm-6.cp-regis-user',[
h('div.card',[
h('div.card-body',[
h('h3.text-center',Msg.features_registered)
]),
h('ul.list-group.list-group-flush', [
h('li.list-group-item.text-center', Msg.features_f_pad, [
h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_pad_notes}, h('i.fa.fa-question'))
]),
h('li.list-group-item.text-center', Msg.features_f_history, [
h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_history_notes }, h('i.fa.fa-question'))
]),
h('li.list-group-item.text-center', Msg.features_f_export, [
h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_export_notes }, h('i.fa.fa-question'))
]),
h('li.list-group-item.text-center', Msg.features_f_todo),
h('li.list-group-item.text-center', Msg.features_f_viewFiles),
h('li.list-group-item.text-center', Msg.features_f_drive_full),
h('li.list-group-item.text-center', Msg.features_f_uploadFiles),
h('li.list-group-item.text-center', Msg.features_f_embedFiles),
h('li.list-group-item.text-center', Msg.features_f_multiple, [
h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_multiple_notes }, h('i.fa.fa-question'))
]),
h('li.list-group-item.text-center', Msg.features_f_logoutEverywhere),
h('li.list-group-item.text-center', Msg.features_f_templates, [
h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_templates_notes }, h('i.fa.fa-question'))
]),
h('li.list-group-item.text-center', Msg.features_f_profile, [
h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_profile_notes }, h('i.fa.fa-question'))
]),
h('li.list-group-item.text-center', Msg.features_f_tags, [
h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_tags_notes }, h('i.fa.fa-question'))
]),
h('li.list-group-item.text-center', Msg.features_f_contacts, [
h('a.voted', {href: '#', 'data-toggle' : 'tooltip', 'data-placement': 'bottom', title : Msg.features_f_contacts_notes }, h('i.fa.fa-question'))
]),
h('li.list-group-item.text-center', setHTML(h('div'), Msg.features_f_storage_registered)),
]),
h('div.card-body',[
h('div#cp-features-register', [
h('a', {
href: '/register/'
}, h('button.cp-features-register-button', 'Register for free'))
])
}, h('button.cp-features-register-button', Msg.features_f_register))
]),
]),
]),
]),
]),
]),
infopageFooter()
]);
@ -354,25 +355,35 @@ define([
Pages['/privacy.html'] = function () {
return h('div#cp-main', [
infopageTopbar(),
h('div.container.cp-container', [
h('.container-fluid.cp-privacy-top', [
h('div.container',[
h('center', h('h1', Msg.policy_title)),
h('h2', Msg.policy_whatweknow),
]),
]),
h('div.container.cp-container.cp-privacy',[
h('h3', Msg.policy_whatweknow),
h('hr'),
setHTML(h('p'), Msg.policy_whatweknow_p1),
h('h2', Msg.policy_howweuse),
h('h3', Msg.policy_howweuse),
h('hr'),
h('p', Msg.policy_howweuse_p1),
h('p', Msg.policy_howweuse_p2),
h('h2', Msg.policy_whatwetell),
h('h3', Msg.policy_whatwetell),
h('hr'),
h('p', Msg.policy_whatwetell_p1),
h('h2', Msg.policy_links),
h('h3', Msg.policy_links),
h('hr'),
h('p', Msg.policy_links_p1),
h('h2', Msg.policy_ads),
h('h3', Msg.policy_ads),
h('hr'),
h('p', Msg.policy_ads_p1),
h('h2', Msg.policy_choices),
h('h3', Msg.policy_choices),
h('hr'),
h('p', Msg.policy_choices_open),
setHTML(h('p'), Msg.policy_choices_vpn),
]),
@ -393,8 +404,10 @@ define([
var question = h('p.cp-faq-questions-q#' + hash);
$(question).click(function () {
if ($(answer).is(':visible')) {
$(question).toggleClass('cp-active-faq');
return void $(answer).slideUp();
}
$(question).toggleClass('cp-active-faq');
$(answer).slideDown();
});
questions.push(h('div.cp-faq-questions-items', [
@ -413,11 +426,15 @@ define([
}
return h('div#cp-main', [
infopageTopbar(),
h('div.container.cp-container', [
h('div.container-fluid.cp-faq', [
h('div.container',[
h('center', h('h1', Msg.faq_title)),
h('p.cp-faq-header', h('a.nav-item.nav-link', {
]),
]),
h('div.container.cp-faq-ques-det',[
h('div.cp-faq-header.text-center', h('a.nav-item.nav-link', {
href: '/what-is-cryptpad.html'
}, Msg.faq_whatis)),
}, setHTML(h('h4'),Msg.faq_whatis))),
h('div.cp-faq-container', categories)
]),
infopageFooter()
@ -452,28 +469,28 @@ define([
h('div.col-12',
setHTML(h('h4.text-center'), Msg.main_about_p26)
),
h('div.col-6.col-sm-3.col-md-3.col-lg-3',
h('div.col-12.col-sm-6.col-md-3.col-lg-3',
h('a.card', {href : "https://twitter.com/cryptpad"},
h('div.card-body',
setHTML(h('p'), Msg.main_about_p22)
)
)
),
h('div.col-6.col-sm-3.col-md-3.col-lg-3',
h('div.col-12.col-sm-6.col-md-3.col-lg-3',
h('a.card', {href : "https://github.com/xwiki-labs/cryptpad/issues/"},
h('div.card-body',
setHTML(h('p'), Msg.main_about_p23)
)
)
),
h('div.col-6.col-sm-3.col-md-3.col-lg-3',
h('div.col-12.col-sm-6.col-md-3.col-lg-3',
h('a.card', {href : "https://riot.im/app/#/room/#cryptpad:matrix.org"},
h('div.card-body',
setHTML(h('p'), Msg.main_about_p24)
)
)
),
h('div.col-6.col-sm-3.col-md-3.col-lg-3',
h('div.col-12.col-sm-6.col-md-3.col-lg-3',
h('a.card', {href : "mailto:research@xwiki.com"},
h('div.card-body',
setHTML(h('p'), Msg.main_about_p25)
@ -553,12 +570,13 @@ define([
var showingMore = false;
var icons = [
[ 'pad', '/pad/', Msg.main_richTextPad, 'fa-file-word-o' ],
[ 'code', '/code/', Msg.main_codePad, 'fa-file-code-o' ],
[ 'slide', '/slide/', Msg.main_slidePad, 'fa-file-powerpoint-o' ],
[ 'poll', '/poll/', Msg.main_pollPad, 'fa-calendar' ],
[ 'whiteboard', '/whiteboard/', Msg.main_whiteboardPad, 'fa-paint-brush' ],
[ 'recent', '/drive/', Msg.main_localPads, 'fa-hdd-o' ]
[ 'pad', '/pad/', Msg.main_richTextPad, 'pad' ],
[ 'code', '/code/', Msg.main_codePad, 'code' ],
[ 'slide', '/slide/', Msg.main_slidePad, 'slide' ],
[ 'poll', '/poll/', Msg.main_pollPad, 'poll' ],
[ 'kanban', '/kanban/', Msg.main_kanbanPad, 'kanban' ],
[ 'whiteboard', '/whiteboard/', Msg.main_whiteboardPad, 'whiteboard' ],
[ 'recent', '/drive/', Msg.main_localPads, 'drive' ]
].filter(function (x) {
return isAvailableType(x[1]);
})
@ -568,7 +586,7 @@ define([
return h('a', [
{ href: x[1] },
h(s, [
h('i.fa.' + x[3]),
h('i.fa.' + AppConfig.applicationsIcon[x[3]]),
h('div.pad-button-text', [ h('h4', x[2]) ])
])
]);
@ -614,26 +632,95 @@ define([
])
]),
]),
infopageFooter(),
];
};
var loadingScreen = Pages.loadingScreen = function () {
return h('div#cp-loading',
h('div.cp-loading-container', [
h('img.cp-loading-cryptofist', {
src: '/customize/cryptpad-new-logo-colors-logoonly.png?' + urlArgs
}),
h('div.cp-loading-spinner-container',
h('span.fa.fa-circle-o-notch.fa-spin.fa-4x.fa-fw')),
h('p'),
])
);
Pages.createCheckbox = function (id, labelTxt, checked, opts) {
opts = opts|| {};
// Input properties
var inputOpts = {
type: 'checkbox',
id: id
};
if (checked) { inputOpts.checked = 'checked'; }
$.extend(inputOpts, opts.input || {});
// Label properties
var labelOpts = {};
$.extend(labelOpts, opts.label || {});
if (labelOpts.class) { labelOpts.class += ' cp-checkmark'; }
// Mark properties
var markOpts = { tabindex: 0 };
$.extend(markOpts, opts.mark || {});
var input = h('input', inputOpts);
var mark = h('span.cp-checkmark-mark', markOpts);
var label = h('span.cp-checkmark-label', labelTxt);
$(mark).keydown(function (e) {
if (e.which === 32) {
e.stopPropagation();
e.preventDefault();
$(input).prop('checked', !$(input).is(':checked'));
$(input).change();
}
});
$(input).change(function () { $(mark).focus(); });
return h('label.cp-checkmark', labelOpts, [
input,
mark,
label
]);
};
var hiddenLoader = function () {
var loader = loadingScreen();
loader.style.display = 'none';
return loader;
Pages.createRadio = function (name, id, labelTxt, checked, opts) {
opts = opts|| {};
// Input properties
var inputOpts = {
type: 'radio',
id: id,
name: name
};
if (checked) { inputOpts.checked = 'checked'; }
$.extend(inputOpts, opts.input || {});
// Label properties
var labelOpts = {};
$.extend(labelOpts, opts.label || {});
if (labelOpts.class) { labelOpts.class += ' cp-checkmark'; }
// Mark properties
var markOpts = { tabindex: 0 };
$.extend(markOpts, opts.mark || {});
var input = h('input', inputOpts);
var mark = h('span.cp-radio-mark', markOpts);
var label = h('span.cp-checkmark-label', labelTxt);
$(mark).keydown(function (e) {
if (e.which === 32) {
e.stopPropagation();
e.preventDefault();
$(input).prop('checked', !$(input).is(':checked'));
$(input).change();
}
});
$(input).change(function () { $(mark).focus(); });
var radio = h('label', labelOpts, [
input,
mark,
label
]);
$(radio).addClass('cp-radio');
return radio;
};
Pages['/user/'] = Pages['/user/index.html'] = function () {
@ -679,27 +766,10 @@ define([
placeholder: Msg.login_confirm,
}),
h('div.checkbox-container', [
h('input#import-recent', {
name: 'import-recent',
type: 'checkbox',
checked: true
}),
// hscript doesn't generate for on label for some
// reason... use jquery as a temporary fallback
setHTML($('<label for="import-recent"></label>')[0], Msg.register_importRecent)
/*h('label', {
'for': 'import-recent',
}, Msg.register_importRecent),*/
Pages.createCheckbox('import-recent', Msg.register_importRecent, true)
]),
h('div.checkbox-container', [
h('input#accept-terms', {
name: 'accept-terms',
type: 'checkbox'
}),
setHTML($('<label for="accept-terms"></label>')[0], Msg.register_acceptTerms)
/*setHTML(h('label', {
'for': 'accept-terms',
}), Msg.register_acceptTerms),*/
$(Pages.createCheckbox('accept-terms')).find('.cp-checkmark-label').append(Msg.register_acceptTerms).parent()[0]
]),
h('button#register.btn.cp-login-register', Msg.login_register)
])
@ -714,7 +784,6 @@ define([
]),
infopageFooter(),
hiddenLoader(),
])];
};
@ -741,17 +810,7 @@ define([
placeholder: Msg.login_password,
}),
h('div.checkbox-container', [
h('input#import-recent', {
name: 'import-recent',
type: 'checkbox',
checked: true
}),
// hscript doesn't generate for on label for some
// reason... use jquery as a temporary fallback
setHTML($('<label for="import-recent"></label>')[0], Msg.register_importRecent)
/*h('label', {
'for': 'import-recent',
}, Msg.register_importRecent),*/
Pages.createCheckbox('import-recent', Msg.register_importRecent, true),
]),
h('div.extra', [
h('button.login.first.btn', Msg.login_login)
@ -760,7 +819,6 @@ define([
]),
]),
infopageFooter(),
hiddenLoader(),
])];
};

@ -1,40 +0,0 @@
@import (once) './include/font.less';
.font_neuropolitical();
.font_open-sans();
body.cp-page-index { @import "./pages/page-index.less"; }
body.cp-page-contact { @import "./pages/page-contact.less"; }
body.cp-page-login { @import "./pages/page-login.less"; }
body.cp-page-register { @import "./pages/page-register.less"; }
body.cp-page-what-is-cryptpad { @import "./pages/page-what-is-cryptpad.less"; }
body.cp-page-about { @import "./pages/page-about.less"; }
body.cp-page-privacy { @import "./pages/page-privacy.less"; }
body.cp-page-terms { @import "./pages/page-terms.less"; }
// Set the HTML style for the apps which shouldn't have a body scrollbar
html.cp-app-noscroll {
@import "./include/app-noscroll.less";
.app-noscroll_main();
}
// Set the HTML style for printing slides
html.cp-app-print {
@import "./include/app-print.less";
.app-print_main();
}
body.cp-readonly .cp-hidden-if-readonly { display: none !important; }
body.cp-app-drive { @import "../../../drive/app-drive.less"; }
body.cp-app-pad { @import "../../../pad/app-pad.less"; }
body.cp-app-code { @import "../../../code/app-code.less"; }
body.cp-app-slide { @import "../../../slide/app-slide.less"; }
body.cp-app-file { @import "../../../file/app-file.less"; }
body.cp-app-filepicker { @import "../../../filepicker/app-filepicker.less"; }
body.cp-app-contacts { @import "../../../contacts/app-contacts.less"; }
body.cp-app-poll { @import "../../../poll/app-poll.less"; }
body.cp-app-whiteboard { @import "../../../whiteboard/app-whiteboard.less"; }
body.cp-app-todo { @import "../../../todo/app-todo.less"; }
body.cp-app-profile { @import "../../../profile/app-profile.less"; }
body.cp-app-settings { @import "../../../settings/app-settings.less"; }
body.cp-app-debug { @import "../../../debug/app-debug.less"; }

@ -1,8 +1,11 @@
@import (once) "./colortheme-all.less";
@import (once) "./browser.less";
@import (once) "./variables.less";
@import (reference) "./colortheme-all.less";
@import (reference) "./browser.less";
@import (reference) "./variables.less";
.alertify_main() {
--LessLoader_require: LessLoader_currentFile();
};
& {
@max-z-index: 2147483647;
@alertify-fore: @colortheme_modal-fg;
@alertify-base: @colortheme_modal-bg;
@ -12,14 +15,11 @@
@alertify-btn-fg: @alertify-fore;
@alertify-btn-bg: rgba(200, 200, 200, 0.1);
@alertify-btn-bg-hover: rgba(200, 200, 200, .3);
@alertify-bg: @colortheme_modal-dim;
@alertify-fg: @alertify-fore;
@alertify-input-bg: @colortheme_modal-input;
@alertify-input-fg: @colortheme_modal-fg;
@alertify-input-fg: @colortheme_modal-input-fg;
@alertify_padding-base: @variables_padding;
@alertify_box-shadow: @variables_shadow;
@ -34,7 +34,7 @@
}
> * {
padding: @alertify_padding-base @alertify_padding-base * 4;
color: @alertify-fore;
color: @colortheme_notification-color;
font-family: @colortheme_font;
font-size: large;
@ -65,6 +65,8 @@
width: 100%;
height: 100%;
z-index: 100000; // alertify container
font: @colortheme_app-font;
&.forefront {
z-index: @max-z-index; // alertify max forefront
}
@ -112,10 +114,6 @@
}
.dialog, .alert {
.bright {
color: @colortheme_light-base;
}
& > div {
background-color: @alertify-dialog-bg;
&.half {
@ -208,6 +206,19 @@
padding: @alertify_padding-base;
}
span.cp-password-container {
display: flex;
align-items: center;
margin-bottom: 15px;
justify-content: space-between;
& > * {
margin-bottom: 0 !important;
}
button {
margin: 0 !important;
}
}
input[type="checkbox"], input[type="radio"] {
width: auto;
padding: 0;
@ -221,13 +232,9 @@
}
}
nav {
text-align: right;
button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button) {
background-color: @alertify-btn-bg;
background-color: @colortheme_alertify-cancel;
box-sizing: border-box;
position: relative;
outline: 0;
@ -247,7 +254,7 @@
border-radius: 0;
color: @alertify-btn-fg;
border: 1px solid transparent;
border: 1px solid @colortheme_alertify-cancel-border;
&.safe, &.danger {
color: @colortheme_old-base;
@ -256,32 +263,40 @@
}
&.danger {
background-color: @colortheme_alertify-red;
border-color: @colortheme_alertify-red-border;
color: @colortheme_alertify-red-color;
&:hover, &:active {
background-color: lighten(@colortheme_alertify-red, 5%);
background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-red, 10%), lighten(@colortheme_alertify-red, 10%));
}
}
&.safe {
background-color: @colortheme_alertify-green;
border-color: @colortheme_alertify-green-border;
color: @colortheme_alertify-green-color;
&:hover, &:active {
background-color: lighten(@colortheme_alertify-green, 10%);
background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-green, 10%), lighten(@colortheme_alertify-green, 10%));
}
}
&.primary {
background-color: @colortheme_alertify-primary;
color: @colortheme_alertify-primary-text;
border-color: @colortheme_alertify-primary-border;
font-weight: bold;
&:hover, &:active {
background-color: darken(@colortheme_alertify-primary, 10%);
background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-primary, 10%), lighten(@colortheme_alertify-primary, 10%));
}
}
&:hover, &:active {
background-color: @alertify-btn-bg-hover;
background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-cancel, 10%), lighten(@colortheme_alertify-cancel, 10%));
}
&:focus {
border: 1px dotted @alertify-base;
//border: 1px dotted @alertify-base;
box-shadow: 0px 0px 5px @colortheme_alertify-primary;
outline: none;
}
&::-moz-focus-inner {
border: 0;
@ -291,6 +306,9 @@
button.btn {
margin: 6px 4px;
}
nav {
text-align: right;
}
}
}
@ -392,3 +410,4 @@
}
}
}

@ -1,5 +1,8 @@
// html
.app-noscroll_main() {
--LessLoader_require: LessLoader_currentFile();
}
& {
.cp-app-noscroll {
height: 100%;
width: 100%;
padding: 0px;
@ -7,6 +10,7 @@
overflow: hidden;
box-sizing: border-box;
position: relative;
border: 0;
body {
height: 100%;
width: 100%;
@ -15,6 +19,8 @@
overflow: hidden;
box-sizing: border-box;
position: relative;
border: 0;
}
}
}

@ -1,4 +1,8 @@
.app-print_main() {
--LessLoader_require: LessLoader_currentFile();
}
& {
.cp-app-print {
// Current scope is <html>
@media print {
height: auto;
@ -44,3 +48,5 @@
}
}
}
}

@ -1,6 +1,18 @@
@import (once) "./tools.less";
.avatar_main (@width) {
@import (reference) "./tools.less";
.avatar_vars(
@width: 30px
) {
@avatar-width: @width;
@avatar-font-size: @width / 1.2;
}
.avatar_main(@width: 30px) {
--LessLoader_require: LessLoader_currentFile();
.avatar_vars(@width);
--avatar-width: @avatar-width;
--avatar-font-size: @avatar-font-size;
}
& {
.avatar_vars();
&.cp-avatar {
overflow: hidden;
text-overflow: ellipsis;
@ -9,8 +21,13 @@
align-items: center;
.cp-avatar-default, media-tag {
display: inline-flex;
width: @width;
height: @width;
width: @avatar-width;
width: var(--avatar-width);
height: @avatar-width;
height: var(--avatar-width);
justify-content: center;
align-items: center;
border-radius: 4px;
@ -21,13 +38,21 @@
.tools_unselectable();
background: white;
color: black;
font-size: @width/1.2;
font-size: @avatar-font-size;
font-size: var(--avatar-font-size);
}
media-tag {
min-height: @width;
min-width: @width;
max-height: @width;
max-width: @width;
min-height: @avatar-width;
min-height: var(--avatar-width);
min-width: @avatar-width;
min-width: var(--avatar-width);
max-height: @avatar-width;
max-height: var(--avatar-width);
max-width: @avatar-width;
max-width: var(--avatar-width);
img {
min-width: 100%;
min-height: 100%;

@ -1,11 +1,28 @@
@import (once) "./colortheme-all.less";
@import (reference) "./colortheme-all.less";
.checkmark_main(@size) {
.checkmark_vars(
@size: 20px
) {
@checkmark-size: @size;
@checkmark-width: round(@size / 8);
@checkmark-dim1: round(@size / 3);
@checkmark-dim2: round(2 * @size / 3);
@checkmark-top: round(@size / 12) - 1;
@checkmark-radio-size: @checkmark-dim1 * 3;
}
@width: round(@size / 8);
@dim1: round(@size / 3);
@dim2: round(2 * @size / 3);
@top: round(@size / 12);
.checkmark_main(@size: 20px) {
--LessLoader_require: LessLoader_currentFile();
.checkmark_vars(@size);
--checkmark-size: @checkmark-size;
--checkmark-width: @checkmark-width;
--checkmark-dim1: @checkmark-dim1;
--checkmark-dim2: @checkmark-dim2;
--checkmark-top: @checkmark-top;
--checkmark-radio-size: @checkmark-radio-size;
}
& {
.checkmark_vars();
// <label.cp-checkmark><input><span.cp-checkmark-mark></span>Text</label>
.cp-checkmark {
margin: 0;
@ -17,6 +34,10 @@
-ms-user-select: none;
user-select: none;
& > a {
margin-left: 0.25em;
}
&.cp-checkmark-secondary {
.cp-checkmark-mark {
&:after {
@ -26,6 +47,13 @@
input {
&:checked ~ .cp-checkmark-mark {
background-color: @colortheme_checkmark-back2;
border-color: @colortheme_checkmark-back2;
}
&:disabled ~ .cp-checkmark-mark {
background-color: @colortheme_checkmark-disabled;
}
&:disabled ~ .cp-checkmark-label {
color: @colortheme_checkmark-disabled;
}
}
}
@ -37,29 +65,149 @@
display: none;
&:checked ~ .cp-checkmark-mark {
background-color: @colortheme_checkmark-back1;
border-color: @colortheme_checkmark-back1;
&:after {
display: block;
}
}
&:disabled ~ .cp-checkmark-mark {
background-color: @colortheme_checkmark-disabled;
}
&:disabled ~ .cp-checkmark-label {
color: @colortheme_checkmark-disabled;
}
}
.cp-checkmark-label {
cursor: default;
&:empty {
display: none;
}
}
.cp-checkmark-mark {
margin-right: 10px;
position: relative;
height: @size;
width: @size;
height: @checkmark-size;
height: var(--checkmark-size);
width: @checkmark-size;
width: var(--checkmark-size);
background-color: @colortheme_checkmark-back0;
display: flex;
justify-content: center;
border: 1px solid @colortheme_form-border;
flex-shrink: 0;
&:after {
content: "";
display: none;
margin-top: @top;
width: @dim1;
height: @dim2;
margin-top: @checkmark-top;
margin-top: var(--checkmark-top);
width: @checkmark-dim1;
width: var(--checkmark-dim1);
height: @checkmark-dim2;
height: var(--checkmark-dim2);
transform: rotate(45deg);
border: solid @colortheme_checkmark-col1;
border-width: 0 @width @width 0;
border-width: 0 @checkmark-width @checkmark-width 0;
border-width: 0 var(--checkmark-width) var(--checkmark-width) 0;
position: absolute;
}
&:focus {
//border-color: #FF007C !important;
box-shadow: 0px 0px 5px @colortheme_checkmark-back1;
outline: none;
}
}
}
.cp-radio {
margin: 0;
display: flex;
align-items: center;
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
&.cp-radio-secondary {
.cp-radio-mark {
&:after {
border-color: @colortheme_checkmark-col2;
}
}
input {
&:checked ~ .cp-radio-mark {
background-color: @colortheme_checkmark-back2;
}
&:disabled ~ .cp-checkmark-mark {
background-color: @colortheme_checkmark-disabled;
}
&:disabled ~ .cp-checkmark-label {
color: @colortheme_checkmark-disabled;
}
}
}
&:hover .cp-radio-mark {
background-color: @colortheme_checkmark-back0-active;
}
input {
display: none;
&:checked ~ .cp-radio-mark {
background-color: @colortheme_checkmark-back1;
border-color: @colortheme_checkmark-back1;
&:after {
display: block;
}
}
&:disabled ~ .cp-checkmark-mark {
background-color: @colortheme_checkmark-disabled;
}
&:disabled ~ .cp-checkmark-label {
color: @colortheme_checkmark-disabled;
}
}
.cp-checkmark-label {
cursor: default;
&:empty {
display: none;
}
}
.cp-radio-mark {
margin-right: 10px;
position: relative;
height: @checkmark-radio-size;
height: var(--checkmark-radio-size);
width: @checkmark-radio-size;
width: var(--checkmark-radio-size);
background-color: @colortheme_checkmark-back0;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid @colortheme_form-border;
flex-shrink: 0;
&:after {
display: none;
content: "";
border-radius: 50%;
background: white;
width: @checkmark-dim1;
width: var(--checkmark-dim1);
height: @checkmark-dim1;
height: var(--checkmark-dim1);
//transform: rotate(45deg);
//border: solid @colortheme_checkmark-col1;
//border-width: 0 var(--checkmark-width) var(--checkmark-width) 0;
}
&:focus {
//border-color: #FF007C !important;
box-shadow: 0px 0px 5px @colortheme_checkmark-back1;
outline: none;
}
}

@ -2,5 +2,5 @@
// create a file: customize/src/less2/include/colortheme.less
// override whatever colors you want. When you update, the new colors will be
// added ok because the original file is pulled in first.
@import (once) "/customize.dist/src/less2/include/colortheme.less";
@import (once) "/customize/src/less2/include/colortheme.less";
@import (reference) "/customize.dist/src/less2/include/colortheme.less";
@import (reference) "/customize/src/less2/include/colortheme.less";

@ -2,6 +2,9 @@
@colortheme_app-font-size: 16px;
@colortheme_app-font: @colortheme_app-font-size @colortheme_font;
@colortheme_logo-1: #326599;
@colortheme_logo-2: #4591c4;
@colortheme_link-color: #0275D8;
@colortheme_link-color-visited: #005999;
@colortheme_info-background: #fafafa;
@ -15,23 +18,42 @@
@colortheme_cp-red: #FA5858; // remove red
@colortheme_cp-green: #46E981;
@colortheme_modal-bg: #222;
@colortheme_modal-fg: #fff;
@colortheme_modal-link: #eee;
@colortheme_form-border: #bbbbbb;
@colortheme_form-bg: @colortheme_logo-2;
@colortheme_form-color: #ffffff;
@colortheme_form-bg-alt: #ffffff;
@colortheme_form-color-alt: @colortheme_logo-1;
@colortheme_form-warning: #f49842;
@colortheme_form-warning-hov: darken(@colortheme_form-warning, 5%);
@colortheme_modal-bg: @colortheme_form-bg-alt; // TODO Modals bg
@colortheme_modal-fg: @colortheme_form-color-alt;
@colortheme_modal-link: @colortheme_link-color;
@colortheme_modal-link-visited: lighten(@colortheme_modal-link, 10%);
@colortheme_modal-dim: rgba(0, 0, 0, 0.4);
@colortheme_modal-dim: fade(@colortheme_logo-2, 50%); // TODO transparent background behind modals
@colortheme_modal-input: @colortheme_form-bg;
@colortheme_modal-input-fg: @colortheme_form-color;
@colortheme_loading-bg: #222;
@colortheme_loading-bg: @colortheme_logo-1;
@colortheme_loading-bg-alt: @colortheme_logo-2;
@colortheme_loading-color: @colortheme_old-fore;
@colortheme_modal-input: #111;
// TODO modals buttons
@colortheme_alertify-red: #E55236;
@colortheme_alertify-red-color: #FFF;
@colortheme_alertify-red-border: transparent;
@colortheme_alertify-green: #77C825;
@colortheme_alertify-primary: #fff;
@colortheme_alertify-primary-text: #000;
@colortheme_notification-log: rgba(0, 0, 0, 0.8);
@colortheme_alertify-green-color: #FFF;
@colortheme_alertify-green-border: transparent;
@colortheme_alertify-primary: @colortheme_form-bg;
@colortheme_alertify-primary-text: @colortheme_form-color;
@colortheme_alertify-primary-border: transparent;
@colortheme_alertify-cancel: @colortheme_modal-bg;
@colortheme_alertify-cancel-border: #ccc;
@colortheme_notification-log: fade(@colortheme_logo-1, 90%);
@colortheme_notification-color: #fff;;
@colortheme_notification-warn: rgba(205, 37, 50, 0.8);
@colortheme_dropdown-bg: #f9f9f9;
@ -77,8 +99,8 @@
@colortheme_friends-color: #fff;
@colortheme_friends-warn: #cd2532;
@colortheme_default-bg: #ddd;
@colortheme_default-color: #000;
@colortheme_default-bg: #326599;
@colortheme_default-color: #FFF;
@colortheme_default-warn: #cd2532;
@colortheme_settings-bg: #0087ff;
@ -105,6 +127,10 @@
@colortheme_oocell-color: #FFF;
@colortheme_oocell-warn: #cd2532;
@colortheme_kanban-bg: #8C4;
@colortheme_kanban-color: #000;
@colortheme_kanban-warn: #e6385d;
// Sidebar layout (profile / settings)
@colortheme_sidebar-active: #fff;
@colortheme_sidebar-left-bg: #eee;
@ -121,10 +147,12 @@
@cryptpad_color_grey: #999999;
@cryptpad_header_col: #1E1F1F;
@cryptpad_text_col: #3F4141;
@colortheme_checkmark-back0: #ffffff;
@colortheme_checkmark-back0-active: #bbbbbb;
@colortheme_checkmark-back1: #FF0073;
@colortheme_checkmark-col1: #ffffff;
@colortheme_checkmark-back2: #FFFFFF;
@colortheme_checkmark-col2: #000000;
@cryptpad_color_light_blue: #00b7d8;
@colortheme_checkmark-back0: @colortheme_form-bg-alt;
@colortheme_checkmark-back0-active: @colortheme_form-border;
@colortheme_checkmark-back1: @colortheme_form-bg;
@colortheme_checkmark-col1: @colortheme_form-color;
@colortheme_checkmark-back2: @colortheme_form-bg-alt;
@colortheme_checkmark-col2: @colortheme_form-color-alt;
@colortheme_checkmark-disabled: #AAA;

@ -0,0 +1,19 @@
@import (reference) "./colortheme-all.less";
.contextmenu_main() {
--LessLoader_require: LessLoader_currentFile();
};
& {
.cp-contextmenu {
display: none;
position: absolute;
z-index: 50000;
li {
padding: 0;
font-size: @colortheme_app-font-size;
a {
cursor: pointer;
}
}
}
}

@ -0,0 +1,127 @@
@import (reference) "./colortheme-all.less";
.corner_main() {
--LessLoader_require: LessLoader_currentFile();
};
& {
@corner-button-ok: #2c9b00;
@corner-button-cancel: #990000;
@corner-link: #ffff7a;
@keyframes appear {
0% {
transform: scale(0.1);
}
100% {
transform: scale(1.0);
}
}
@keyframes minimize {
0% {
transform: scale(1.0);
}
100% {
transform: scale(0.1);
}
}
.cp-corner-container {
position: absolute;
right: 0;
bottom: 0;
width: 300px;
height: 200px;
border-top-left-radius: 200px;
padding: 15px;
text-align: right;
background-color: @colortheme_logo-1;
color: @colortheme_base;
z-index: 999;
transform-origin: bottom right;
animation: appear 0.8s ease-in-out;
box-shadow: 0 0 10px 0 @colortheme_logo-1;
//transform: scale(0.1);
//transform: scale(1);
.cp-corner-filler {
float: left;
clear: left;
height: 21px;
}
.cp-corner-minimize, .cp-corner-maximize {
position: absolute;
height: 15px;
width: 20px;
top: 0;
right: 0;
font-size: 12px;
text-align: left;
cursor: pointer;
line-height: 15px;
display: none;
&:hover {
color: darken(@colortheme_base, 15%);
}
}
.cp-corner-minimize {
display: inline;
}
&.cp-minimized {
transition: transform 0.8s ease-in-out;
transform: scale(0.1);
animation: none;
.cp-corner-text, .cp-corner-actions, .cp-corner-footer {
display: none;
}
.cp-corner-maximize {
display: inline;
font-size: 130px;
width: 180px;
height: 200px;
line-height: 200px;
}
}
&.cp-corner-big {
width: 400px;
height: 250px;
}
.cp-corner-actions {
min-height: 30px;
margin: 15px auto;
display: inline-block;
}
.cp-corner-footer {
font-style: italic;
font-size: 0.8em;
a {
color: @corner-link;
&:hover {
color: darken(@corner-link, 20%);
}
}
}
button {
color: white;
border: 0px;
padding: 5px;
color: @colortheme_base;
&.cp-corner-primary {
background-color: @corner-button-ok;
font-weight: bold;
&:hover {
background-color: lighten(@corner-button-ok, 10%);
}
}
&.cp-corner-cancel {
background-color: @corner-button-cancel;
margin-left: 10px;
&:hover {
background-color: lighten(@corner-button-cancel, 10%);
}
}
}
}
}

@ -1,47 +1,93 @@
@import (once) "./colortheme-all.less";
@import (once) "./tools.less";
@import (once) "./checkmark.less";
@import (once) './icon-colors.less';
@import (reference) "./browser.less";
@import (reference) "./colortheme-all.less";
@import (reference) "./tools.less";
@import (reference) './icon-colors.less';
.creation_vars(
@color: @colortheme_default-color,
@bg-color: @colortheme_default-bg
) {
@creation-color: @color;
@creation-bg-color: @bg-color;
};
.creation_main(
@color: @colortheme_default-color,
@bg-color: @colortheme_default-bg
) {
--LessLoader_require: LessLoader_currentFile();
.creation_vars(@color, @bg-color);
--creation-color: @color;
--creation-bg-color: @bg-color;
}
& {
.creation_vars();
@colortheme_creation-modal-bg: #fff;
@colortheme_creation-modal: #666;
@colortheme_creation-modal-title: @colortheme_loading-bg;
.creation_main() {
.tippy-popper {
z-index: 100000001 !important;
}
#cp-creation-container {
position: absolute;
z-index: 100000000; // #loading * 10
top: 0px;
background: @colortheme_loading-bg;
//background: @colortheme_loading-bg;
background: linear-gradient(to right, @colortheme_loading-bg 0%, @colortheme_loading-bg 50%, @colortheme_loading-bg-alt 50%, @colortheme_loading-bg-alt 100%);
color: @colortheme_loading-color;
display: flex;
flex-flow: column; /* we need column so that the child can shrink vertically */
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
overflow: auto;
}
#cp-creation {
.cp-creation-logo {
height: 300px;
width: 300px;
margin-top: 50px;
flex: 0 1 auto; /* allows shrink */
min-height: 0;
text-align: center;
img {
max-width: 100%;
max-height: 100%;
}
}
}
#cp-creation {
overflow: auto;
text-align: center;
background: @colortheme_creation-modal-bg;
color: @colortheme_creation-modal;
font: @colortheme_app-font;
width: 100%;
outline: none;
width: 700px;
max-width: 90vw;
height: 500px;
max-height: calc(~"100vh - 20px");
margin: 50px;
flex-shrink: 0;
display: flex;
flex-flow: column;
& > div {
width: 60vw;
width: 100%;
max-width: 100%;
margin: 40px auto;
margin: auto;
text-align: left;
}
.cp-creation-create, .cp-creation-settings {
.cp-creation-title {
color: @colortheme_creation-modal-title;
font-weight: bold;
margin: 15px;
}
.cp-creation-create {
margin-top: 0px;
@creation-button: #30B239;
button {
.tools_unselectable();
padding: 15px;
background: @creation-button;
background: linear-gradient(to right, @colortheme_logo-2, @colortheme_logo-1);
color: #FFF;
font-weight: bold;
margin: 3px 10px;
@ -50,15 +96,16 @@
outline: none;
width: 100%;
&:hover {
background: linear-gradient(to right, lighten(@colortheme_logo-2, 5%), lighten(@colortheme_logo-1, 5%));
//background: darken(@creation-button, 5%);
background: lighten(@creation-button, 5%);
//background: lighten(@creation-button, 5%);
}
}
}
.cp-creation-create {
text-align: center;
margin: auto;
margin-top: 20px;
//margin: auto;
//margin-top: 20px;
width: 400px;
max-width: 100%;
button {
@ -70,6 +117,8 @@
display: flex;
flex-flow: column;
align-items: center;
flex: 1 0 auto;
justify-content: space-around;
& > div {
width: 400px;
max-width: 100%;
@ -77,9 +126,13 @@
align-items: center;
flex-wrap: wrap;
font-size: 16px;
margin: 10px 0;
//margin: 10px 0;
min-height: 28px;
line-height: 28px;
label {
flex: 1;
// Force wrap when the other element in the line is 100% (IE bug):
min-width: 1px;
}
input[type="checkbox"] {
&+ label {
@ -88,31 +141,72 @@
padding: 0 10px;
}
}
.cp-creation-help {
}
.cp-creation-help, .cp-creation-warning {
font-size: 18px;
color: white;
color: @colortheme_form-warning;
&:hover {
color: #AAA;
color: @colortheme_form-warning-hov;
text-decoration: none;
}
}
}
.cp-creation-slider {
display: block;
overflow: hidden;
max-height: 0px;
transition: max-height 0.5s ease-in-out;
width: 100%;
margin-top: 10px;
max-width: 0px;
//margin-top: 10px;
&.active {
max-height: 40px;
transition: max-height 0.5s ease-in-out;
max-width: none;
max-height: 100px;
}
input, select {
vertical-align: middle;
}
}
input, select {
font-size: 14px;
border: 1px solid @colortheme_form-border;
height: 26px;
line-height: 26px;
background-color: @colortheme_form-bg;
color: @colortheme_form-color;
}
.cp-creation-expire {
.cp-creation-expire-picker {
text-align: center;
input {
width: 100px;
width: 50px;
margin: 0 5px;
}
select {
margin-right: 5px;
}
}
&.active {
label {
flex: none;
}
.cp-creation-slider {
flex: 1;
}
}
}
.cp-creation-password {
.cp-creation-password-picker {
text-align: center;
width: 100%;
.cp-password-container {
input {
width: 150px;
padding: 0 5px;
}
label {
flex: none;
}
}
}
}
@ -125,31 +219,51 @@
}
div.cp-creation-remember {
margin-top: 30px;
.cp-creation-remember-help {
font-style: italic;
width: 100%;
//font-style: italic;
font-size: 12px;
font-weight: bold;
color: @colortheme_form-bg;
line-height: 20px;
.fa {
margin-right: 10px;
}
}
}
div.cp-creation-template {
width: 100%;
background-color: darken(@colortheme_modal-bg, 3%);
padding: 20px;
margin: 30px 0;
.cp-creation-title {
padding: 0 0 10px 10px;
margin: auto;
//flex: 1 0 auto;
flex-wrap: nowrap;
.cp-creation-template-more {
font-size: 30px;
cursor: pointer;
margin: 0 5px;
text-align: center;
&:first-child {
left: 5px;
}
&:last-child {
right: 5px;
}
&:hover {
color: #888;
}
&.hidden {
visibility: hidden;
}
}
}
.cp-creation-template-container {
width: 100%;
flex: 1;
display: flex;
flex-wrap: wrap;
justify-content: center;
overflow-y: auto;
//overflow-y: auto;
align-items: center;
.cp-creation-template-element {
@darker: darken(@colortheme_modal-fg, 30%);
box-shadow: 2px 2px 7px @colortheme_form-border;
width: 135px;
padding: 5px;
margin: 5px;
@ -162,21 +276,22 @@
line-height: 1em;
cursor: pointer;
background-color: #111;
color: @darker;
color: black;
border: 1px solid transparent;
&.cp-creation-template-selected {
border: 1px solid white;
background-color: #222;
&.cp-creation-template-selected, &:hover {
color: @creation-color !important;
color: var(--creation-color) !important;
background-color: @creation-bg-color !important;
background-color: var(--creation-bg-color) !important;
.fa, .cptools {
color: @creation-color;
color: var(--creation-color);
}
}
transition: all 0.1s;
&:hover {
color: @colortheme_modal-fg;
}
align-items: center;
img {
@ -190,12 +305,15 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-height: 20px;
height: 20px;
line-height: 20px;
margin-top: 5px;
max-width: 100%;
}
.fa {
.fa, .cptools {
color: @creation-bg-color;
color: var(--creation-bg-color);
cursor: pointer;
width: 100px;
height: 100px;
@ -210,23 +328,50 @@
.cp-creation-deleted-container {
text-align: center;
.cp-creation-deleted {
background: #111;
margin: 0 10px;
background: @colortheme_loading-bg;
color: @colortheme_loading-color;
padding: 10px;
text-align: center;
font-weight: bold;
display: inline-block;
}
}
}
.checkmark_main(30px);
@media screen and (max-width: @browser_media-narrow-screen) {
@media screen and (max-height: 700px) {
#cp-creation-container {
.cp-creation-logo {
//flex-shrink: 0;
display: none;
}
}
}
@media screen and (max-width: 500px) {
#cp-creation {
#cp-creation-form {
& > div {
width: 95%;
margin: 10px auto;
}
.cp-creation-expire {
&.active {
label {
flex: 1;
}
.cp-creation-slider {
flex: none;
order: 10;
width: 100%;
}
}
}
}
}
}
@media screen and (max-width: @browser_media-medium-screen) {
#cp-creation {
height: auto;
#cp-creation-form {
div.cp-creation-template {
margin: 0;
@ -256,6 +401,5 @@
}
}
}
}
}

@ -1,8 +1,11 @@
@import (once) "./colortheme-all.less";
@import (once) "./tools.less";
@import (reference) "./colortheme-all.less";
@import (reference) "./tools.less";
/* The container <div> - needed to position the dropdown content */
.dropdown_main () {
--LessLoader_require: LessLoader_currentFile();
}
& {
.cp-dropdown-container {
@dropdown_font: @colortheme_app-font-size @colortheme_font;
position: relative;
@ -55,9 +58,23 @@
user-select: none;
float: none;
text-align: left;
font: @dropdown_font;
line-height: 1em;
align-items: center;
&:not(.fa) {
font: @dropdown_font;
}
&.fa {
font-size: 18px;
&::before {
width: 40px;
margin-left: -10px;
text-align: center;
}
* {
font: @dropdown_font;
}
}
.fa {
width: 20px;

@ -1,7 +1,11 @@
@import (once) './colortheme-all.less';
@import (once) './modal.less';
@import (reference) './colortheme-all.less';
@import (reference) './modal.less';
.fileupload_main () {
--LessLoader_require: LessLoader_currentFile();
.modal_main();
}
& {
/* Upload status table */
#cp-fileupload {
.modal_base();
@ -54,4 +58,3 @@
}
}
}

@ -1,9 +1,18 @@
.font_neuropolitical () {
.font_main () {
--LessLoader_require: LessLoader_currentFile();
}
// Fonts need to go on the global scope
@font-face {
font-family: Neuropolitical;
src: url("/customize/fonts/neuropolitical.ttf");
}
// Fonts need to go on the global scope
@font-tools {
font-family: CryptPadTools;
src: url("/customize/fonts/cryptpadtools.ttf");
}
.font_open-sans () {
@import (once) '/customize/fonts/open-sans.less';
}

@ -1,18 +1,78 @@
@import (once) "./toolbar.less";
@import (once) './fileupload.less';
@import (once) './alertify.less';
@import (once) './tokenfield.less';
@import (once) './creation.less';
@import (reference) "./colortheme-all.less";
@import (reference) "./toolbar.less";
@import (reference) './fileupload.less';
@import (reference) './alertify.less';
@import (reference) './corner.less';
@import (reference) './contextmenu.less';
@import (reference) './tokenfield.less';
@import (reference) './creation.less';
@import (reference) './tippy.less';
@import (reference) "./checkmark.less";
@import (reference) "./password-input.less";
@import (reference) './font.less';
@import (reference) "./app-print.less";
@import (reference) "./app-noscroll.less";
.framework_main(@bg-color, @warn-color, @color) {
--LessLoader_require: LessLoader_currentFile();
// Set the HTML style for the apps which shouldn't have a body scrollbar
.app-noscroll_main();
// Set the HTML style for printing slides
.app-print_main();
.font_main();
.toolbar_main(
@bg-color: @bg-color,
@warn-color: @warn-color,
@color: @color
);
.fileupload_main();
.alertify_main();
.corner_main();
.contextmenu_main();
.fileupload_main();
.tokenfield_main();
.creation_main();
.tippy_main();
.checkmark_main(20px);
.password_main();
.creation_main(
@bg-color: @bg-color,
@color: @color
);
font: @colortheme_app-font;
};
.framework_min_main(
@color: @colortheme_default-color, // Color of the text for the toolbar
@bg-color: @colortheme_default-bg, // color of the toolbar background
@warn-color: @colortheme_default-warn, // color of the warning text in the toolbar
) {
--LessLoader_require: LessLoader_currentFile();
// Set the HTML style for the apps which shouldn't have a body scrollbar
.app-noscroll_main();
// Set the HTML style for printing slides
.app-print_main();
.font_main();
.toolbar_main(
@bg-color: @bg-color,
@warn-color: @warn-color,
@color: @color
);
.fileupload_main();
.alertify_main();
.corner_main();
.contextmenu_main();
.tippy_main();
.checkmark_main(20px);
.password_main();
font: @colortheme_app-font;
}
& {
body.cp-readonly .cp-hidden-if-readonly { display: none !important; }
}

@ -1,9 +1,30 @@
@import (once) "./colortheme-all.less";
@import (reference) "./colortheme-all.less";
.help_main (@color, @bg-color) {
.help_vars (
@color: @colortheme_default-color,
@bg-color: @colortheme_default-bg
) {
@help-bg-color-l15: lighten(@bg-color, 15%);
@help-text-color: contrast(@help-bg-color-l15, #fff, #000); //@color;
@help-link-color: contrast(@help-bg-color-l15, lighten(spin(@bg-color, 180), 10%), darken(spin(@bg-color, 180), 10%));
}
.help_main (
@color: @colortheme_default-color,
@bg-color: @colortheme_default-bg
) {
--LessLoader_require: LessLoader_currentFile();
.help_vars(@color, @bg-color);
--help-bg-color-l15: @help-bg-color-l15;
--help-text-color: @help-text-color;
--help-link-color: @help-link-color;
};
& {
.help_vars();
.cp-help-container {
position: relative;
background-color: lighten(@bg-color, 15%);
background-color: @help-bg-color-l15;
background-color: var(--help-bg-color-l15);
&.cp-help-hidden {
display: none;
}
@ -14,14 +35,13 @@
right: 5px;
}
.cp-help-text {
color: @color;
color: @help-text-color;
color: var(--help-text-color);
margin: 0;
padding: 15px;
a {
//color: darken(@colortheme_link-color, 30%);
@spin: spin(lighten(@bg-color, 15%), 180);
color: contrast(lighten(@bg-color, 15%), lighten(@spin, 10%), darken(@spin, 10%));
//color: darken(spin(lighten(@bg-color, 15%), 180), 10%);
color: @help-link-color;
color: var(--help-link-color);
}
h1 {
font-size: 20px;
@ -36,3 +56,4 @@
}
}
}

@ -1,5 +1,8 @@
@import (once) "./colortheme-all.less";
@import (reference) "./colortheme-all.less";
.iconColors_main () {
--LessLoader_require: LessLoader_currentFile();
}
& {
// Classes used in common-interface.js
.cp-icon-color-pad { color: @colortheme_pad-bg; }
.cp-icon-color-code { color: @colortheme_code-bg; }
@ -16,6 +19,7 @@
.cp-icon-color-oodoc { color: @colortheme_oodoc-bg; }
.cp-icon-color-ooslide { color: @colortheme_ooslide-bg; }
.cp-icon-color-oocell { color: @colortheme_oocell-bg; }
.cp-icon-color-kanban { color: @colortheme_kanban-bg; }
.cp-border-color-pad { border-color: @colortheme_pad-bg !important; }
.cp-border-color-code { border-color: @colortheme_code-bg !important; }
@ -32,5 +36,6 @@
.cp-border-color-oodoc { border-color: @colortheme_oodoc-bg !important; }
.cp-border-color-ooslide { border-color: @colortheme_ooslide-bg !important; }
.cp-border-color-oocell { border-color: @colortheme_oocell-bg !important; }
.cp-border-color-kanban { border-color: @colortheme_kanban-bg !important; }
}

@ -10,7 +10,6 @@
text-overflow: ellipsis;
padding-top: 5px;
padding-bottom: 5px;
border: 1px solid white;
.cp-icons-name {
width: 100%;
@ -26,7 +25,7 @@
word-wrap: break-word;
}
&.cp-icons-element-selected {
background-color: white;
background-color: rgba(0,0,0,0.2);
color: #666;
}
.fa {

@ -1,10 +1,25 @@
@import (once) "./colortheme-all.less";
@import (reference) "./colortheme-all.less";
@import (reference) "./font.less";
.infopages_link () {
text-decoration: none;
color: #0275D8;
cursor: pointer;
display: inline-flex;
&:hover {
transform: scale(1.05);
}
}
.infopages_main () {
--LessLoader_require: LessLoader_currentFile();
}
body {
.font_main();
@infopages_infobar-height: 64px;
@infopages_padding: 32px;
// Basic setup for info pages, this should be used at the global level
.infopages_main () {
background-color: @colortheme_info-background;
a {
color: @cryptpad_color_blue;
@ -100,20 +115,8 @@
border-top: 2px solid #fff;
}
}
};
.infopages_link () {
text-decoration: none;
color: #0275D8;
cursor: pointer;
display: inline-flex;
&:hover {
transform: scale(1.05);
}
}
// Apply this to the top bar div
.infopages_topbar () {
.cp-topbar {
background: #fff;
z-index: 10000; //Z infopage toolbar
@ -149,7 +152,6 @@
margin-right: 0.5em;
}
}
}
// navigation top bar
.navbar {
@ -161,6 +163,10 @@
background-size: contain;
height: 50px;
width: 250px;
@media (max-width: 326px) {
width: 180px;
}
margin-right: 0;
}
a {
border: 2px solid transparent;
@ -169,8 +175,8 @@
.nav-link {
padding: 0.5em 0.7em;
&:hover {
transform: scale(1.05);
};
color: @cryptpad_color_light_blue;
}
}
.cp-register-btn {
border: 2px solid #4591C4;
@ -184,9 +190,18 @@
color: #4591C4;
}
}
@media (max-width: 991px) {
@media (max-width: 1000px) {
#menuCollapse {
text-align: right;
/* @media (min-width: 576px) {
top: 100%;
background: rgba(255,255,255,0.8);
position: absolute;
right: 0px;
padding: 0 20px;
z-index: 1;
}
*/
}
.navbar-nav a {
text-align: right !important;
@ -204,3 +219,5 @@
font-size: 1.2em;
color: #1E1F1F;
}
}

@ -1,6 +1,6 @@
@import (once) "./unselectable.less";
@import (once) "./variables.less";
@import (once) "./colortheme-all.less";
@import (reference) "./unselectable.less";
@import (reference) "./variables.less";
@import (reference) "./colortheme-all.less";
.leftside-menu_main() {
}

@ -1,6 +1,9 @@
@import (once) "./colortheme-all.less";
@import (reference) "./colortheme-all.less";
.limit-bar_main () {
--LessLoader_require: LessLoader_currentFile();
}
& {
.cp-limit-container {
@colortheme_green: #5cb85c;
display: inline-flex;

@ -1,20 +0,0 @@
@import (once) "./colortheme-all.less";
.markdownToolbar_main (@color, @bg-color) {
.cp-markdown-toolbar {
height: @toolbar_line-height;
background-color: lighten(@bg-color, 20%);
display: none;
button {
height: @toolbar_line-height !important;
outline: 0;
color: @color;
.toolbar_button;
font: normal normal normal 14px/1 FontAwesome;
&:hover {
background-color: lighten(@bg-color, 8%);
}
&.cp-markdown-help { float: right; }
}
}
}

@ -1,3 +1,15 @@
.markdown_main() {
blockquote {
background: #e5e5e5;
padding: 10px;
border-left: 3px solid #999;
padding-right: 0;
p { margin: 0; }
blockquote { margin: 0; }
}
// todo ul, ol
}
.markdown_preformatted-code (@color: #333) {
pre > code {
display: block;
@ -23,15 +35,3 @@
}
}
.markdown_main() {
blockquote {
background: #e5e5e5;
padding: 10px;
border-left: 3px solid #999;
padding-right: 0;
p { margin: 0; }
blockquote { margin: 0; }
}
}
// todo ul, ol

@ -1,5 +1,5 @@
@import (once) "./colortheme-all.less";
@import (once) "./variables.less";
@import (reference) "./colortheme-all.less";
@import (reference) "./variables.less";
.modal_base() {
font-family: @colortheme_font;
@ -17,6 +17,10 @@
}
}
.modal_main() {
--LessLoader_require: LessLoader_currentFile();
}
& {
.cp-modal-container {
display: none;
@ -57,7 +61,7 @@
input {
background-color: @colortheme_modal-input;
color: @colortheme_modal-fg;
color: @colortheme_modal-input-fg;
border: 0;
padding: 8px 12px;
margin: 1em;
@ -76,3 +80,5 @@
}
}
}
}

@ -0,0 +1,16 @@
.password_main() {
--LessLoader_require: LessLoader_currentFile();
}
& {
.cp-password-container {
display: flex;
align-items: center;
input {
flex: 1;
min-width: 0;
}
label, .fa {
margin-left: 10px;
}
}
}

@ -1,18 +1,22 @@
@import (once) "/customize/src/less2/include/colortheme-all.less";
@import (once) "/customize/src/less2/include/leftside-menu.less";
@leftside-bg: @colortheme_sidebar-left-bg;
@leftside-color: @colortheme_sidebar-left-fg;
@rightside-color: @colortheme_sidebar-right-fg;
@description-color: @colortheme_sidebar-description;
@import (reference) "/customize/src/less2/include/colortheme-all.less";
@import (reference) "/customize/src/less2/include/leftside-menu.less";
@sidebar_button-width: 400px;
.sidebar-layout_main() {
input[type="text"] {
--LessLoader_require: LessLoader_currentFile();
// This is way too broad to put in the global scope
input[type="text"], input[type="password"] {
padding-left: 10px;
}
}
& {
@leftside-bg: @colortheme_sidebar-left-bg;
@leftside-color: @colortheme_sidebar-left-fg;
@rightside-color: @colortheme_sidebar-right-fg;
@description-color: @colortheme_sidebar-description;
#cp-sidebarlayout-container {
font-size: 16px;
display: flex;
@ -52,9 +56,15 @@
margin-bottom: 0;
}
}
label.noTitle {
display: inline-flex;
.fa {
margin-left: 10px;
}
}
margin-bottom: 20px;
}
[type="text"], button {
[type="text"], [type="password"], button {
vertical-align: middle;
height: 40px;
box-sizing: border-box;

@ -0,0 +1,18 @@
@import (reference) './colortheme-all.less';
.tippy_main() {
--LessLoader_require: LessLoader_currentFile();
}
& {
.tippy-tooltip.cryptpad-theme {
/* Your styling here. Example: */
background-color: white;
box-shadow: 2px 2px 10px #000;
font-weight: bold;
color: #333;
overflow-wrap: break-word;
[x-circle] {
background-color: unset;
}
}
}

@ -1,25 +1,34 @@
@import (once) "./tools.less";
@import (reference) "./tools.less";
.tokenfield_main () {
--LessLoader_require: LessLoader_currentFile();
}
& {
.ui-autocomplete {
z-index: 100001; // alertify + 1
}
.tokenfield {
.tools_unselectable();
display: flex;
flex-wrap: wrap;
justify-content: space-around;
height: auto;
min-height: 34px;
padding-bottom: 0px;
background-color: unset;
border: none;
display: flex;
flex-wrap: wrap;
align-items: center;
padding: 0 10px;
margin: 0 10px;
padding: 0;
width: ~"calc(100% - 20px)";
.token {
box-sizing: border-box;
border-radius: 3px;
display: inline-block;
display: inline-flex;
align-items: center;
border: 1px solid #d9d9d9;
background-color: #ededed;
white-space: nowrap;
margin: 10px 5px;
margin: 2px 0;
height: 24px;
vertical-align: middle;
cursor: default;
@ -50,7 +59,7 @@
.close {
font-family: Arial;
display: inline-block;
line-height: 24px;
line-height: 1.49em;
font-size: 1.1em;
margin-left: 5px;
float: none;
@ -73,6 +82,8 @@
margin: 0 !important; // Override alertify
box-shadow: none;
max-width: 100%;
width: 100%;
min-width: 100% !important;
&:focus {
border-color: transparent;
outline: 0;

@ -1,27 +1,55 @@
@import (once) "./colortheme-all.less";
@import (reference) "./colortheme-all.less";
.history_main () {
--LessLoader_require: LessLoader_currentFile();
}
& {
.cp-toolbar-history {
display: none;
text-align: center;
width: 100%;
padding: 10px 0;
align-items: center;
justify-content: center;
* {
font: @colortheme_app-font;
}
.cp-toolbar-history-next {
display: inline-block;
vertical-align: middle;
margin: 20px;
.cp-history-filler {
flex: 1;
}
.cp-toolbar-history-previous {
display: inline-block;
vertical-align: middle;
margin: 20px;
.cp-toolbar-history-close,
.cp-toolbar-history-revert {
background: white;
color: black;
//margin-top: 5px;
&:hover {
background-color: #e6e6e6;
}
}
.cp-toolbar-history-loadmore {
height: 100%;
color: black;
width: 25px;
position: absolute;
left: 0;
padding: 0;
}
.cp-toolbar-history-version {
position: absolute;
height: 25px;
line-height: 25px;
width: 100%;
text-align: center;
color: black;
}
.cp-toolbar-history-goto {
display: inline-block;
vertical-align: middle;
text-align: center;
flex: 1;
flex-basis: 80%;
min-width: 0;
max-width: 600px;
input { width: 75px; }
}
.cp-toolbar-history-goto-input {
@ -29,6 +57,30 @@
margin-left: 5px;
vertical-align: middle;
}
.cp-toolbar-history-bar {
width: 100%;
background: white;
height: 25px;
margin: auto;
position: relative;
}
.cp-toolbar-history-pos-container {
width: ~"calc(100% - 2px)";
height: 25px;
position: relative;
}
@pos-color: #55FF55;
.cp-toolbar-history-pos {
width: 2px;
height: 25px;
background: @pos-color;
&:after {
content: '';
border: 6px solid transparent;
border-top-color: @pos-color;
margin-left: -5px;
}
}
button {
color: inherit;
background-color: rgba(0,0,0,0.2);
@ -36,14 +88,6 @@
background-color: rgba(0,0,0,0.4);
}
}
.cp-toolbar-history-close {
background: white;
color: black;
margin-top: 5px;
&:hover {
background-color: #e6e6e6;
}
}
.fa-spinner {
font-size: 66px;
}

@ -1,15 +1,36 @@
@import (once) "./dropdown.less";
@import (once) "./colortheme-all.less";
@import (once) "./browser.less";
@import (once) "./ckeditor-fix.less";
@import (once) "./avatar.less";
@import (once) "./toolbar-history.less";
@import (once) "./icon-colors.less";
@import (once) "./tools.less";
@import (once) "./icons.less";
@import (once) "./modal.less";
@import (once) "./markdown-toolbar.less";
@import (once) "./help.less";
@import (reference) "./dropdown.less";
@import (reference) "./colortheme-all.less";
@import (reference) "./browser.less";
@import (reference) "./ckeditor-fix.less";
@import (reference) "./avatar.less";
@import (reference) "./toolbar-history.less";
@import (reference) "./icon-colors.less";
@import (reference) "./tools.less";
@import (reference) "./icons.less";
@import (reference) "./modal.less";
@import (reference) "./help.less";
.toolbar_vars (
@color: @colortheme_default-color, // Color of the text for the toolbar
@bg-color: @colortheme_default-bg, // color of the toolbar background
@warn-color: @colortheme_default-warn // color of the warning text in the toolbar
) {
@toolbar-color: @color;
@toolbar-color-l20: lighten(@color, 20%);
@toolbar-color-d20: darken(@color, 20%);
@toolbar-color-d15: darken(@color, 15%);
@toolbar-bg-color: @bg-color;
@toolbar-bg-color-l8: lighten(@bg-color, 8%);
@toolbar-bg-color-l20: lighten(@bg-color, 20%);
@toolbar-bg-color-d5: darken(@bg-color, 5%);
@toolbar-bg-color-d10: darken(@bg-color, 10%);
@toolbar-bg-color-d15: darken(@bg-color, 15%);
@toolbar-warn-color: @warn-color;
@toolbar-userlist-name-edit: contrast(@toolbar-color, @toolbar-color-l20, @toolbar-color-d20);
};
.toolbar_main (
@color: @colortheme_default-color, // Color of the text for the toolbar
@ -17,17 +38,70 @@
@warn-color: @colortheme_default-warn, // color of the warning text in the toolbar
@barWidth: 600px // width of the toolbar
) {
--LessLoader_require: LessLoader_currentFile();
.toolbar_vars(@color, @bg-color, @warn-color);
--toolbar-color: @toolbar-color;
--toolbar-color-l20: @toolbar-color-l20;
--toolbar-color-d20: @toolbar-color-d20;
--toolbar-color-d15: @toolbar-color-d15;
--toolbar-bg-color: @toolbar-bg-color;
--toolbar-bg-color-l8: @toolbar-bg-color-l8;
--toolbar-bg-color-l20: @toolbar-bg-color-l20;
--toolbar-bg-color-d5: @toolbar-bg-color-d5;
--toolbar-bg-color-d10: @toolbar-bg-color-d10;
--toolbar-bg-color-d15: @toolbar-bg-color-d15;
--toolbar-warn-color: @toolbar-warn-color;
--toolbar-userlist-name-edit: @toolbar-userlist-name-edit;
@media screen and (max-width: @barWidth) {
.cp-toolbar-rightside {
flex-wrap: wrap;
height: auto;
width: 100%;
}
}
.help_main(@color, @bg-color);
.dropdown_main();
.history_main();
.iconColors_main();
.modal_main();
};
& {
.toolbar_vars();
@toolbar_line-height: 32px;
@toolbar_top-height: 64px;
@toolbar_button-font: @colortheme_app-font;
.dropdown_main();
// if we spell 'share' correctly, then adblock plus hides the share button
// this is a workaround
.fa-shhare-alt:before { content: "\f1e0"; }
.ckeditor_fix();
.history_main();
.iconColors_main();
.markdownToolbar_main(@color, @bg-color);
.help_main(@color, @bg-color);
.cp-markdown-toolbar {
height: @toolbar_line-height;
background-color: @toolbar-bg-color-l20;
background-color: var(--toolbar-bg-color-l20);
display: none;
button {
height: @toolbar_line-height !important;
outline: 0;
color: @toolbar-color;
color: var(--toolbar-color);
.toolbar_button;
font: normal normal normal 14px/1 FontAwesome;
&:hover {
background-color: @toolbar-bg-color-l8;
background-color: var(--toolbar-bg-color-l8);
}
&.cp-markdown-help { float: right; }
}
}
.cp-toolbar-container {
display: flex;
@ -61,7 +135,8 @@
}
.cp-toolbar-userlist-drawer {
background-color: @bg-color;
background-color: @toolbar-bg-color;
background-color: var(--toolbar-bg-color);
font: @colortheme_app-font-size @colortheme_font;
min-width: 175px;
width: 175px;
@ -183,8 +258,13 @@
#cp-app-toolbar-creation-dialog.cp-modal-container {
.icons_main();
li:hover {
border: 1px solid white;
li {
border: 1px solid @colortheme_modal-fg;
&:hover {
//border: 1px solid @colortheme_modal-fg;
background: @colortheme_modal-fg;
color: @colortheme_modal-bg;
}
}
.cp-modal {
display: flex;
@ -244,31 +324,39 @@
}
.cp-toolbar-userlist-drawer {
background-color: @bg-color;
color: @color;
background-color: @toolbar-bg-color;
background-color: var(--toolbar-bg-color);
color: @toolbar-color;
color: var(--toolbar-color);
.cp-toolbar-userlist-drawer-close {
color: @color;
color: @toolbar-color;
color: var(--toolbar-color);
}
h2 {
background-color: darken(@bg-color, 10%);
color: @color;
background-color: @toolbar-bg-color-d10;
background-color: var(--toolbar-bg-color-d10);
color: @toolbar-color;
color: var(--toolbar-color);
}
.cp-toolbar-userlist-name-input {
background-color: darken(@bg-color, 10%);
color: @color;
background-color: @toolbar-bg-color-d10;
background-color: var(--toolbar-bg-color-d10);
color: @toolbar-color;
color: var(--toolbar-color);
}
.cp-toolbar-userlist-name-edit {
color: contrast(@color,
lighten(@color, 20%),
darken(@color, 20%));
color: @toolbar-userlist-name-edit;
color: var(--toolbar-userlist-name-edit);
background: transparent;
&:hover {
color: @color;
color: @toolbar-color;
color: var(--toolbar-color);
}
}
.cp-toolbar-userlist-friend {
&:hover {
color: darken(@color, 15%);
color: @toolbar-color-d15;
color: var(--toolbar-color-d15);
}
}
}
@ -288,8 +376,10 @@
display: flex;
flex-wrap: wrap;
justify-content: space-between;
background-color: @bg-color;
color: @color;
background-color: @toolbar-bg-color;
background-color: var(--toolbar-bg-color);
color: @toolbar-color;
color: var(--toolbar-color);
.fa {
font: normal normal normal 14px/1 FontAwesome;
@ -507,42 +597,57 @@
.cp-toolbar-spinner {
font-size: @colortheme_app-font-size;
color: @color;
color: @toolbar-color;
color: var(--toolbar-color);
}
.cp-toolbar-limit {
text-shadow: -1px 0 @color, 0 1px @color, 1px 0 @color, 0 -1px @color;
color: @warn-color;
text-shadow: -1px 0 @toolbar-color, 0 1px @toolbar-color, 1px 0 @toolbar-color, 0 -1px @toolbar-color;
text-shadow: -1px 0 var(--toolbar-color), 0 1px var(--toolbar-color), 1px 0 var(--toolbar-color), 0 -1px var(--toolbar-color);
color: @toolbar-warn-color;
color: var(--toolbar-warn-color);
}
.cp-toolbar-leftside, .cp-toolbar-rightside {
background-color: lighten(@bg-color, 8%);
background-color: @toolbar-bg-color-l8;
background-color: var(--toolbar-bg-color-l8);
button:hover, button.cp-toolbar-button-active {
background-color: @bg-color;
background-color: @toolbar-bg-color;
background-color: var(--toolbar-bg-color);
}
}
.cp-toolbar-title-hoverable:hover {
.cp-toolbar-title-editable, .cp-toolbar-title-edit {
cursor: text;
border: 1px solid darken(@bg-color, 15%);
background: darken(@bg-color, 10%);
border: 1px solid @toolbar-bg-color-d15;
border: 1px solid var(--toolbar-bg-color-d15);
background: @toolbar-bg-color-d10;
background: var(--toolbar-bg-color-d10);
transition: all 0.15s;
color: @color;
color: @toolbar-color;
color: var(--toolbar-color);
}
.cp-toolbar-title-editable {
cursor: text;
}
}
.cp-toolbar-title-save {
border: 1px solid darken(@bg-color, 15%);
background: darken(@bg-color, 10%);
color: @color;
border: 1px solid @toolbar-bg-color-d15;
border: 1px solid var(--toolbar-bg-color-d15);
background: @toolbar-bg-color-d10;
background: var(--toolbar-bg-color-d10);
color: @toolbar-color;
color: var(--toolbar-color);
&:hover {
background: darken(@bg-color, 5%);
background: @toolbar-bg-color-d5;
background: var(--toolbar-bg-color-d5);
}
}
input {
border: 1px solid darken(@bg-color, 15%);
background: darken(@bg-color, 10%);
color: @color;
border: 1px solid @toolbar-bg-color-d15;
border: 1px solid var(--toolbar-bg-color-d15);
background: @toolbar-bg-color-d10;
background: var(--toolbar-bg-color-d10);
color: @toolbar-color;
color: var(--toolbar-color);
}
.cp-dropdown-content.cp-dropdown-left a {
color: black;
@ -568,7 +673,8 @@
padding: 0;
margin: 0 5px;
font-size: @colortheme_app-font-size;
color: @warn-color;
color: @toolbar-warn-color;
color: var(--toolbar-warn-color);
.cp-pnp-msg {
padding-left: 5px;
font-family: @colortheme_font;
@ -577,7 +683,8 @@
font-size: @colortheme_app-font-size;
font-family: @colortheme_font;
font-weight: bold;
color: @warn-color;
color: @toolbar-warn-color;
color: var(--toolbar-warn-color);
&:hover {
text-decoration: underline;
}
@ -871,11 +978,6 @@
display: flex;
min-height: @toolbar_line-height;
overflow: hidden;
@media screen and (max-width: @barWidth) { // 450px
flex-wrap: wrap;
height: auto;
width: 100%;
}
&:empty {
min-height: 0;
height: 0;
@ -994,6 +1096,5 @@
}
}
}
}

@ -19,6 +19,7 @@
}
.tools_unselectable () {
user-select: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;

@ -1,73 +0,0 @@
/*
WARNING: THIS FILE DOES NOTHING
It exists only as a proposal of what CSS you should use in loading.js
The CSS inside of loading.js is precompiled in order to save 200ish milliseconds to the loading screen.
*/
@import (once) "./include/colortheme-all.less";
@import (once) "./include/browser.less";
#cp-loading {
transition: opacity 0.75s, visibility 0s 0.75s;
visibility: visible;
opacity: 1;
position: fixed;
z-index: 10000000; // #loading
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
background: @colortheme_loading-bg;
color: @colortheme_loading-color;
text-align: center;
font-size: 1.5em;
.cp-loading-container {
margin-top: 50vh;
transform: translateY(-50%);
}
.cp-loading-cryptofist {
margin-left: auto;
margin-right: auto;
height: 300px;
margin-bottom: 2em;
@media screen and (max-height: @browser_media-short-screen) {
display: none;
}
}
.cp-loading-spinner-container {
position: relative;
height: 100px;
> div {
height: 100px;
}
}
&.cp-loading-hidden {
opacity: 0;
visibility: hidden;
}
}
#cp-loading-tip {
position: fixed;
z-index: 10000000; // loading tip
top: 80%;
left: 0;
right: 0;
text-align: center;
transition: opacity 750ms;
transition-delay: 3000ms;
@media screen and (max-height: @browser_media-medium-screen) {
display: none;
}
span {
background: @colortheme_loading-bg;
color: @colortheme_loading-color;
text-align: center;
font-size: 1.5em;
opacity: 0.7;
font-family: @colortheme_font;
padding: 15px;
max-width: 60%;
display: inline-block;
}
}

@ -1,46 +0,0 @@
@import (once) './include/font.less';
.font_neuropolitical();
.font_open-sans();
body.cp-page-index { @import "./pages/page-index.less"; }
body.cp-page-contact { @import "./pages/page-contact.less"; }
body.cp-page-login { @import "./pages/page-login.less"; }
body.cp-page-register { @import "./pages/page-register.less"; }
body.cp-page-what-is-cryptpad { @import "./pages/page-what-is-cryptpad.less"; }
body.cp-page-about { @import "./pages/page-about.less"; }
body.cp-page-privacy { @import "./pages/page-privacy.less"; }
body.cp-page-features { @import "./pages/page-features.less"; }
body.cp-page-faq { @import "./pages/page-faq.less"; }
body.cp-page-terms { @import "./pages/page-terms.less"; }
// Set the HTML style for the apps which shouldn't have a body scrollbar
html.cp-app-noscroll {
@import "./include/app-noscroll.less";
.app-noscroll_main();
}
// Set the HTML style for printing slides
html.cp-app-print {
@import "./include/app-print.less";
.app-print_main();
}
body.cp-readonly .cp-hidden-if-readonly { display: none !important; }
body.cp-app-drive { @import "../../../drive/app-drive.less"; }
body.cp-app-pad { @import "../../../pad/app-pad.less"; }
body.cp-app-code { @import "../../../code/app-code.less"; }
body.cp-app-slide { @import "../../../slide/app-slide.less"; }
body.cp-app-file { @import "../../../file/app-file.less"; }
body.cp-app-filepicker { @import "../../../filepicker/app-filepicker.less"; }
body.cp-app-contacts { @import "../../../contacts/app-contacts.less"; }
body.cp-app-poll { @import "../../../poll/app-poll.less"; }
body.cp-app-whiteboard { @import "../../../whiteboard/app-whiteboard.less"; }
body.cp-app-todo { @import "../../../todo/app-todo.less"; }
body.cp-app-profile { @import "../../../profile/app-profile.less"; }
body.cp-app-settings { @import "../../../settings/app-settings.less"; }
body.cp-app-debug { @import "../../../debug/app-debug.less"; }
body.cp-app-worker { @import "../../../worker/app-worker.less"; }
body.cp-app-oodoc { @import "../../../oodoc/app-oodoc.less"; }
body.cp-app-ooslide { @import "../../../ooslide/app-ooslide.less"; }
body.cp-app-oocell { @import "../../../oocell/app-oocell.less"; }

@ -1,9 +1,8 @@
@import (once) "../include/colortheme-all.less";
@import (once) "../include/font.less";
.font_neuropolitical();
.font_open-sans();
@import (reference) "../include/colortheme-all.less";
@import (reference) "../include/font.less";
html, body {
.font_main();
margin: 0px;
padding: 0px;
#cp-main {

@ -1,8 +1,9 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme-all.less";
@import (reference) "../include/infopages.less";
@import (reference) "../include/colortheme-all.less";
&.cp-page-about {
.infopages_main();
.infopages_topbar();
#cp-main {
background: #fff;
}
@ -113,3 +114,5 @@
.cp-margin-bot {
margin-bottom: 1.5em;
}
}

@ -1,8 +1,8 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme-all.less";
@import (reference) "../include/infopages.less";
@import (reference) "../include/colortheme-all.less";
&.cp-page-contact {
.infopages_main();
.infopages_topbar();
.fa {
padding-right: 0.25em;
@ -88,3 +88,5 @@
color: #fff;
}
}
}

@ -1,22 +1,52 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme-all.less";
@import (reference) "../include/infopages.less";
@import (reference) "../include/colortheme-all.less";
&.cp-page-faq {
.infopages_main();
.infopages_topbar();
#cp-main {
background: #fff;
}
.cp-faq {
padding-top: 3em;
padding-bottom: 3em;
background-image: url(/customize/images/cover-faq.jpg);
background-size: cover;
background-repeat: no-repeat;
background-position: center;
.container {
color: #fff;
font-family: "Open Sans";
}
h1 {
font-weight: 700;
}
}
.cp-faq-ques-det {
.cp-faq-header {
padding: 0;
font-size: 1.2em;
a {
padding: 0;
h4 {
margin-top: 1.5rem;
margin-bottom: 1.5rem;
.cp-brand-font {
font-family: "Neuropolitical";
}
}
}
}
}
.cp-faq-container {
.cp-faq-questions-items {
background: #3a84b6;
color: #fff;
padding: 1rem 1rem 0.5rem 1rem;
margin-bottom: 1rem;
}
.cp-faq-questions-q {
color: #3a84b6;
padding: 0;
margin-bottom: 0;
margin-top: 5px;
margin-bottom: 0.5rem;
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
@ -24,14 +54,33 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
&:hover {
text-decoration: none;
}
&:after {
content: '\f067';
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
float: right;
text-decoration: none;
font-size: 13px;
line-height: 1.5;
}
}
.cp-faq-questions-q.active-faq {
&:after {
content: '\f068';
}
.cp-faq-questions-q:hover {
color: #2e688f;
text-decoration: underline;
}
.cp-faq-questions-a {
display: none;
padding: 0;
padding: 0.5rem;
margin-bottom: 0.5rem;
background-color: #fff;
color: #212529;
}
margin-bottom: 1.5rem;
}
}

@ -1,55 +1,24 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme-all.less";
@import (reference) "../include/infopages.less";
@import (reference) "../include/colortheme-all.less";
&.cp-page-features {
.infopages_main();
.infopages_topbar();
@features_th-bg: #555;
@features_th-fg: #fff;
@features_tr-bg-alt: #ddd;
@features_notes: #333;
@features_yes: #c4ffca;
@features_no: #ffc4bc;
@features_part: #faffd3;
table#cp-features-table {
width: 100%;
th {
background-color: @features_th-bg;
color: @features_th-fg;
padding: 10px;
border: 1px solid @features_th-bg;
}
tbody {
td {
height: 32px;
line-height: 32px;
padding: 5px;
border: 1px solid @features_th-bg;
}
tr:nth-child(odd) {
background-color: @features_tr-bg-alt;
}
}
td:nth-child(4) {
color: @features_notes;
font-size: 14px;
font-style: italic;
#cp-main {
background-color: #fff;
}
td:first-child {
font-weight: bold;
}
.yes, .no, .part {
text-align: center;
}
.yes { background-color: @features_yes; }
.no { background-color: @features_no; }
.part { background-color: @features_part; }
.left {
text-align: left;
.cp_cont_features {
padding-top: 3em;
padding-bottom: 3em;
background-image: url('/customize/images/cover-features.jpg');
background-size: cover;
background-repeat: no-repeat;
background-position: center;
h1 {
font-weight: 700;
color: #fff;
}
}
#cp-features-register {
text-align: center;
padding: 20px;
@ -62,6 +31,59 @@ table#cp-features-table {
border-radius: 0;
&:hover {
transform: scale(1.05);
cursor: pointer;
}
padding: 0.5em 1em;
}
.cp-features-web {
.card {
box-shadow: 0 5px 15px rgba(69, 145, 196, 0.3);
border: none;
}
h3 {
color: #fff;
}
.list-group {
li {
&:before {
content: "\f00c";
font-family: "FontAwesome";
font-size: 14px;
color: @cryptpad_color_blue;
padding-right: 10px;
}
}
div {
display: inline;
}
}
a.voted {
opacity: 0.6;
margin-left: 15px;
&:hover {
opacity: 1;
}
}
.list-group-item {
border-color: rgba(69, 145, 196, 0.125);
}
}
.cp-anon-user {
.card-body {
background-color: @cryptpad_color_blue;
}
}
.cp-regis-user {
@media (max-width:575px) {
margin-top: 3em;
}
.card-body {
&:first-of-type {
background: #4591C4;
background: -webkit-linear-gradient(to right, #FF7C4F, #4592C4); // lesshint duplicateProperty: false
background: linear-gradient(to right, #FF7C4F, #4592C4); // lesshint duplicateProperty: false
}
}
}
}

@ -1,8 +1,8 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme-all.less";
@import (reference) "../include/infopages.less";
@import (reference) "../include/colortheme-all.less";
&.cp-page-index {
.infopages_main();
.infopages_topbar();
@background_lighter: rgba(0,0,0,0.1);
@background_darker: rgba(0,0,0,0.4);
@ -80,6 +80,7 @@ body {
}
.nav-link {
&:hover {
color: inherit;
transform: scale(1.05);
};
}
@ -154,6 +155,7 @@ h4 {
.cp-callout-code .fa { background-color: @colortheme_code-bg; }
.cp-callout-slide .fa { background-color: @colortheme_slide-bg; }
.cp-callout-poll .fa { background-color: @colortheme_poll-bg; }
.cp-callout-kanban .fa { background-color: @colortheme_kanban-bg; }
.cp-callout-whiteboard .fa { background-color: @colortheme_whiteboard-bg; }
.cp-callout-recent .fa { background-color: @colortheme_drive-bg; }
.cp-hidden { display: none !important; }
@ -187,4 +189,5 @@ h4 {
left: 5px;
}
}
}

@ -1,11 +1,12 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme-all.less";
@import (once) "../include/alertify.less";
@import (once) "../loading.less";
@import (reference) "../include/infopages.less";
@import (reference) "../include/colortheme-all.less";
@import (reference) "../include/alertify.less";
@import (reference) "../include/checkmark.less";
&.cp-page-login {
.infopages_main();
.infopages_topbar();
.alertify_main();
.checkmark_main(20px);
.form-group {
.extra {
@ -66,3 +67,5 @@
padding-top: 3em;
min-height: 66vh;
}
}

@ -1,5 +1,51 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme-all.less";
@import (reference) "../include/infopages.less";
@import (reference) "../include/colortheme-all.less";
&.cp-page-privacy {
.infopages_main();
.infopages_topbar();
#cp-main {
background: #fff;
}
.cp-privacy-top {
padding-top: 3em;
padding-bottom: 3em;
background-image: url(/customize/images/cover-privacy.jpg);
background-size: cover;
background-repeat: no-repeat;
background-position: center;
.container {
color: #fff;
font-family: "Open Sans";
h1 {
font-weight: 700;
}
a {
color: #fff;
text-decoration: underline;
}
p {
padding-top: 1em;
}
}
}
.cp-privacy {
background-image: url(/customize/CryptPadlogo_op5.svg);
background-size: cover;
background-repeat: no-repeat;
background-position: center;
hr {
margin-left: 0;
width: 15rem;
border-top: 2px solid #4591C4;
}
h3 {
color: #1E1F1F;
font-weight: 700;
}
p {
color: #3F4141;
}
}
}

@ -1,11 +1,13 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme-all.less";
@import (once) "../include/alertify.less";
@import (once) "../loading.less";
@import (reference) "../include/infopages.less";
@import (reference) "../include/colortheme-all.less";
@import (reference) "../include/alertify.less";
@import (reference) "../include/checkmark.less";
&.cp-page-register {
.infopages_main();
.infopages_topbar();
.alertify_main();
.checkmark_main(20px);
.cp-container {
.form-group {
@ -23,11 +25,7 @@
}
margin-top: 16px;
font-size: 1.25em;
min-width: 30%; // conflict?
width: 30%;
@media (max-width: 500px) {
width: 45%;
}
min-width: 30%;
}
}
padding-bottom: 3em;
@ -140,3 +138,5 @@
#cp-main {
background: #fff;
}
}

@ -1,5 +1,7 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme-all.less";
@import (reference) "../include/infopages.less";
@import (reference) "../include/colortheme-all.less";
&.cp-page-terms {
.infopages_main();
.infopages_topbar();
}

@ -1,8 +1,8 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme-all.less";
@import (reference) "../include/infopages.less";
@import (reference) "../include/colortheme-all.less";
&.cp-page-what-is-cryptpad {
.infopages_main();
.infopages_topbar();
.cp-what-is {
padding-top: 3em;
@ -41,3 +41,5 @@
margin: 0 auto;
}
}
}

@ -1,19 +1,45 @@
# CryptPad Styling
How it works:
* In this example, we use the index page, for each page we will have a corresponding class name and a corresponding less file.
* The index page has a main div containing everything `<div id="cp-main" class="cp-page-index">`
* There is a corresponding less file called `less2/pages/page-index.less`
* Finally there is a corresponding line in main.less which imports that less file: `div#main.cp-page-index { @import "./pages/page-index.less"; }`
* cp-page-index class means:
* cp -> cryptpad
* page -> this is a style for accessing a page's less file
* index -> the name of the page and of the less file (page-index.less)
* And everything which is standardized across pages is included from `page-index.less` as variables and mixins.
## Linking Less/CSS
In order to keep the amount of CSS generated under control, we use "linking", via the LessLoader.
This makes use of CSS variables in order to work. The old solution was to put all of the content into less mixins
which would be used inside of the scope where they should be, but this caused a state explosion because each app needed
essentially the same mixins. However, these mixins had arguments such as colors which were different per-app.
The new solution is to set CSS variables for the arguments (like color) and then put the bulk of the less at the global
scope. When you include a dependency, the following happens:
1. You `@include (reference) './include/dependency.less`. The (reference) argument which means it will not emit CSS,
this is important because otherwise all of the dependencies of your app's less file would end up bundled with it, the
state explosion problem.
2. You invoke `.dependency_main(@arg1 @arg2)` inside of the scope you want it in, the name `dependency_main` is a
convention, all less variables, mixins, or CSS variables which a file creates should be prefixed with the name of the
file (in this case, "dependency").
3. The mixin `.dependency_main` does a couple of things:
* First, it sets a CSS variable called `--LessLoader_require`, this is a special variable which the browser does not
use, the only objective of this variable is to inform LessLoader that another file is needed. To do this, there is a
helper function (also specified in LessLoader.js) called `LessLoader_currentFile()`. The syntax is:
`--LessLoader_require: LessLoader_currentFile();` and in the CSS, this outputs something like:
`--LessLoader_require: "/customize/src/less2/include/dependency.less?ver=2.4.0-1531572157592";`
* Secondly, it sets browser variables for it's arguments, making sure to avoid namespace collisions:
`--dependency-arg1: @arg1;`, `--dependency-arg2: @arg2;`. Sometimes a less transformation needs to be done on a
variable, unfortunately in this case the transformation must be done here and the transformed variable must be output.
`--dependency-arg1-l10: lighten(@arg1, 10%);`.
4. After less processing is completed, the LessLoader caches the result of parsing, then scans the it for instances of
`--LessLoader_require` variable and then processes them, but it does this separately. So even if dependency.less is
required many times, it will only be processed by the less interpreter once.
## Other convensions
Rules:
* All of our new classes and ids should start with `cp-`.
* You may make as many files as you need, for different purposes, but they can only contain mixins and variables.
* The document body has a class on it depending on the app/page, app classes begin with `cp-app-` and page classes begin
with `cp-page-`.
* Custom classes ought to begin with `cp-` and the name of the file where the rules are written for them (see help.less as
an example of doing the right thing).
* Since the include files generate CSS and the app cannot control the scope which it's run at, be considerate avoid
making an include file which changes something significant (like making a rule for `li`). help.less is an excellent example
of doing this well, infopages.less is the worst example (fortunately it doesn't get included in any of the apps).
* All mixins and variables must be prefixed with the name of the file where they're defined and and underscore.
* e.g. `@colortheme_toolbar-poll-bg: #006304;` defined in `colortheme.less`
* All mixin / variable files go in an `/include/` directory.

@ -3,7 +3,7 @@ define([
'/common/hyperscript.js',
'/customize/pages.js',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
], function ($, h, Pages) {
$(function () {
var $body = $('body');
@ -26,9 +26,8 @@ $(function () {
window.Tether = function () {};
require([
'less!/customize/src/less2/main.less',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'/bower_components/bootstrap/dist/js/bootstrap.bundle.min.js'
'less!/customize/src/less2/pages/page-' + css + '.less',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css'
], function () {
$body.append($main);

File diff suppressed because it is too large Load Diff

@ -31,7 +31,7 @@ define(function () {
out.websocketError = 'Αδυναμία σύνδεσης στον διακομιστή...';
out.typeError = "Αυτό το pad δεν είναι συμβατό με την επιλεγμένη εφαρμογή";
out.onLogout = 'Έχετε αποσυνδεθεί, <a href="/" target="_blank">κάντε "κλικ" εδώ</a> για να συνδεθείτε<br>ή πατήστε <em>Escape</em> για να προσπελάσετε το έγγραφο σε λειτουργία ανάγνωσης μόνο.';
out.onLogout = 'Έχετε αποσυνδεθεί, {0}κάντε "κλικ" εδώ{1} για να συνδεθείτε<br>ή πατήστε <em>Escape</em> για να προσπελάσετε το έγγραφο σε λειτουργία ανάγνωσης μόνο.';
out.wrongApp = "Αδυναμία προβολής του περιεχομένου αυτής της συνεδρίας στον περιηγητή σας. Παρακαλώ δοκιμάστε επαναφόρτωση της σελίδας.";
out.loading = "Φόρτωση...";

@ -152,7 +152,7 @@ define(function () {
out.websocketError = "Error al conectarse al servidor WebSocket";
out.typeError = "Este documento no es compatible con la aplicación seleccionada";
out.onLogout = "Tu sesión está cerrada, <a href=\"/\" target=\"_blank\">haz clic aquí</a> para iniciar sesión<br>o pulsa <em>Escape</em> para acceder al documento en modo sólo lectura.";
out.onLogout = "Tu sesión está cerrada, {0}haz clic aquí{1} para iniciar sesión<br>o pulsa <em>Escape</em> para acceder al documento en modo sólo lectura.";
out.loading = "Cargando...";
out.error = "Error";
out.language = "Idioma";

@ -2,12 +2,12 @@ define(function () {
var out = {};
out.main_title = "CryptPad : Éditeur collaboratif en temps réel, zero knowledge";
out.main_slogan = "L'unité est la force, la collaboration est la clé";
out.type = {};
out.type.pad = 'Texte';
out.type.code = 'Code';
out.type.poll = 'Sondage';
out.type.kanban = 'Kanban';
out.type.slide = 'Présentation';
out.type.drive = 'CryptDrive';
out.type.whiteboard = "Tableau Blanc";
@ -27,13 +27,14 @@ define(function () {
out.button_newoodoc = 'Nouveau texte OnlyOffice';
out.button_newooslide = 'Nouvelle présentation OnlyOffice';
out.button_newoocell = 'Nouveau tableur OnlyOffice';
out.button_newkanban = 'Nouveau kanban';
out.updated_0_common_connectionLost = "<b>Connexion au serveur perdue</b><br>Vous êtes désormais en mode lecture seule jusqu'au retour de la connexion.";
out.common_connectionLost = out.updated_0_common_connectionLost;
out.websocketError = 'Impossible de se connecter au serveur WebSocket...';
out.typeError = "Ce pad n'est pas compatible avec l'application sélectionnée";
out.onLogout = 'Vous êtes déconnecté de votre compte utilisateur, <a href="/" target="_blank">cliquez ici</a> pour vous authentifier<br>ou appuyez sur <em>Échap</em> pour accéder au pad en mode lecture seule.';
out.onLogout = 'Vous êtes déconnecté de votre compte utilisateur, {0}cliquez ici{1} pour vous authentifier<br>ou appuyez sur <em>Échap</em> pour accéder au pad en mode lecture seule.';
out.wrongApp = "Impossible d'afficher le contenu de ce document temps-réel dans votre navigateur. Vous pouvez essayer de recharger la page.";
out.padNotPinned = 'Ce pad va expirer après 3 mois d\'inactivité, {0}connectez-vous{1} ou {2}enregistrez-vous{3} pour le préserver.';
out.anonymousStoreDisabled = "L'administrateur de cette instance de CryptPad a désactivé le drive pour les utilisateurs non enregistrés. Vous devez vous connecter pour pouvoir utiliser CryptDrive.";
@ -43,6 +44,9 @@ define(function () {
out.chainpadError = 'Une erreur critique est survenue lors de la mise à jour du contenu. Le pad est désormais en mode lecture seule afin de s\'assurer que vous ne perdiez pas davantage de données.<br>' +
'Appuyez sur <em>Échap</em> pour voir le pad ou rechargez la page pour pouvoir le modifier à nouveau.';
out.errorCopy = ' Vous pouvez toujours copier son contenu ailleurs en appuyant sur <em>Échap</em>.<br> Dés que vous aurez quitté la page, il sera impossible de le récupérer.';
out.errorRedirectToHome = 'Appuyez sur <em>Échap</em> pour retourner vers votre CryptDrive.';
out.newVersionError = "Une nouvelle version de CryptPad est disponible.<br>" +
"<a href='#'>Rechargez la page</a> pour utiliser la nouvelle version, ou appuyez sur Échap pour accéder au contenu actuel en <b>mode hors-ligne</b>.";
out.loading = "Chargement...";
out.error = "Erreur";
@ -51,7 +55,7 @@ define(function () {
out.deleted = "Pad supprimé de votre CryptDrive";
out.deletedFromServer = "Pad supprimé du serveur";
out.realtime_unrecoverableError = "Le moteur temps-réel a rencontré une erreur critique. Cliquez sur OK pour recharger la page.";
out.realtime_unrecoverableError = "Une erreur critique est survenue. Cliquez sur OK pour recharger la page.";
out.disconnected = 'Déconnecté';
out.synchronizing = 'Synchronisation';
@ -151,6 +155,8 @@ define(function () {
out.useTemplate = "Commencer avec un modèle?";
out.useTemplateOK = 'Choisir un modèle (Entrée)';
out.useTemplateCancel = 'Document vierge (Échap)';
out.template_import = "Importer un modèle";
out.template_empty = "Aucun modèle disponible";
out.previewButtonTitle = "Afficher ou cacher la prévisualisation de Markdown";
@ -232,12 +238,10 @@ define(function () {
out.historyText = "Historique";
out.historyButton = "Afficher l'historique du document";
out.history_next = "Voir la version suivante";
out.history_prev = "Voir la version précédente";
out.history_goTo = "Voir la version sélectionnée";
out.history_close = "Retour";
out.history_next = "Version plus récente";
out.history_prev = "Version plus ancienne";
out.history_loadMore = "Charger davantage d'historique";
out.history_closeTitle = "Fermer l'historique";
out.history_restore = "Restaurer";
out.history_restoreTitle = "Restaurer la version du document sélectionnée";
out.history_restorePrompt = "Êtes-vous sûr de vouloir remplacer la version actuelle du document par la version affichée ?";
out.history_restoreDone = "Document restauré";
@ -249,6 +253,17 @@ define(function () {
out.pad_mediatagWidth = "Largeur (px)";
out.pad_mediatagHeight = "Hauteur (px)";
// Kanban
out.kanban_newBoard = "Nouveau tableau";
out.kanban_item = "Élément {0}"; // Item number for initial content
out.kanban_todo = "À faire";
out.kanban_done = "Terminé";
out.kanban_working = "En cours";
out.kanban_deleteBoard = "Êtes-vous sûr de vouloir supprimer ce tableau ?";
out.kanban_addBoard = "Ajouter un tableau";
out.kanban_removeItem = "Supprimer cet élément";
out.kanban_removeItemConfirm = "Êtes-vous sûr de vouloir supprimer cet élément ?";
// Polls
out.poll_title = "Sélecteur de date Zero Knowledge";
@ -377,12 +392,14 @@ define(function () {
out.fm_searchName = "Recherche";
out.fm_recentPadsName = "Pads récents";
out.fm_ownedPadsName = "Pads en votre possession";
out.fm_tagsName = "Mots-clés";
out.fm_searchPlaceholder = "Rechercher...";
out.fm_newButton = "Nouveau";
out.fm_newButtonTitle = "Créer un nouveau pad ou un dossier, importer un fichier dans le dossier courant";
out.fm_newFolder = "Nouveau dossier";
out.fm_newFile = "Nouveau pad";
out.fm_folder = "Dossier";
out.fm_sharedFolder = "Dossier partagé";
out.fm_folderName = "Nom du dossier";
out.fm_numberOfFolders = "# de dossiers";
out.fm_numberOfFiles = "# de fichiers";
@ -432,6 +449,7 @@ define(function () {
out.fm_viewListButton = "Liste";
out.fm_viewGridButton = "Grille";
out.fm_renamedPad = "Vous avez renommé ce pad dans votre Drive. Son titre est:<br><b>{0}</b>";
out.fm_canBeShared = "Ce dossier peut être partagé";
out.fm_prop_tagsList = "Mots-clés";
out.fm_burnThisDriveButton = "Effacer toutes les informations stockées par CryptPad dans votre navigateur";
out.fm_burnThisDrive = "Êtes-vous sûr de vouloir supprimmer tout ce qui est stocké par CryptPad dans votre navigateur ?<br>" +
@ -439,8 +457,13 @@ define(function () {
out.fm_padIsOwned = "Vous êtes le propriétaire de ce pad";
out.fm_padIsOwnedOther = "Ce pad est la propriété d'un autre utilisateur";
out.fm_deletedPads = "Ces pads n'existent plus sur le serveur, ils ont été supprimés de votre CryptDrive: {0}";
out.fm_tags_name = "Mot-clé";
out.fm_tags_used = "Nombre d'utilisations";
out.fm_restoreDrive = "Restauration de votre CryptDrive à une version antérieure. Pour de meilleurs résultats, veuillez éviter de modifier votre CryptDrive avant que cette restauration ne soit terminée.";
out.fm_moveNestedSF = "Vous ne pouvez pas placer un dossier partagé dans un autre. Le dossier {0} n'a pas été déplacé.";
// File - Context menu
out.fc_newfolder = "Nouveau dossier";
out.fc_newsharedfolder = "Nouveau dossier partagé";
out.fc_rename = "Renommer";
out.fc_open = "Ouvrir";
out.fc_open_ro = "Ouvrir (lecture seule)";
@ -448,12 +471,13 @@ define(function () {
out.fc_delete_owned = "Supprimer du serveur";
out.fc_restore = "Restaurer";
out.fc_remove = "Supprimer de votre CryptDrive";
out.fc_remove_sharedfolder = "Supprimer";
out.fc_empty = "Vider la corbeille";
out.fc_prop = "Propriétés";
out.fc_hashtag = "Mots-clés";
out.fc_sizeInKilobytes = "Taille en kilo-octets";
// fileObject.js (logs)
out.fo_moveUnsortedError = "La liste des éléments non triés ne peut pas contenir de dossiers.";
out.fo_moveUnsortedError = "La liste des modèles ne peut pas contenir de dossiers.";
out.fo_existingNameError = "Ce nom est déjà utilisé dans ce répertoire. Veuillez en choisir un autre.";
out.fo_moveFolderToChildError = "Vous ne pouvez pas déplacer un dossier dans un de ses descendants";
out.fo_unableToRestore = "Impossible de restaurer ce fichier à son emplacement d'origine. Vous pouvez essayer de le déplacer à un nouvel emplacement.";
@ -559,6 +583,8 @@ define(function () {
out.settings_deleteHint = "La suppression de votre compte utilisateur est permanente. Votre CryptDrive et votre liste de pads seront supprimés du serveur. Le reste de vos pads sera supprimé après 90 jours d'inactivité si personne ne les a stockés dans leur CryptDrive.";
out.settings_deleteButton = "Supprimer votre compte";
out.settings_deleteModal = "Veuillez envoyer les informations suivantes à votre administrateur CryptPad afin que vos données soient supprimées du serveur.";
out.settings_deleteConfirm = "Êtes-vous sûr de vouloir supprimer votre compte utilisateur ? Cette action est irréversible.";
out.settings_deleted = "Votre compte utilisateur a été supprimé. Appuyez sur OK pour être rédirigé(e) vers la page d'accueil.";
out.settings_anonymous = "Vous n'êtes pas connecté. Ces préférences seront utilisées pour ce navigateur.";
out.settings_publicSigningKey = "Clé publique de signature";
@ -589,10 +615,22 @@ 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_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>";
out.settings_changePasswordButton = "Changer le mot de passe";
out.settings_changePasswordCurrent = "Mot de passe actuel";
out.settings_changePasswordNew = "Nouveau mot de passe";
out.settings_changePasswordNewConfirm = "Confirmer le nouveau mot de passe";
out.settings_changePasswordConfirm = "Êtes-vous sûr de vouloir changer votre mot de passe ? Vous devrez vous reconnecter sur tous vos appareils.";
out.settings_changePasswordError = "Une erreur est survenue. Si vous n'êtes plus en mesure de vous connecter à votre compte utilisateur ou de changer votre mot de passe, veuillez contacter l'administrateur de votre CryptPad.";
out.settings_changePasswordPending = "Votre mot de passe est en train d'être modifié. Veuillez ne pas fermer ou recharger cette page avant que le traitement soit terminé.";
out.settings_changePasswordNewPasswordSameAsOld = "Votre nouveau mot de passe doit être différent de votre mot de passe actuel.";
out.upload_title = "Hébergement de fichiers";
out.upload_rename = "Souhaitez-vous renommer <b>{0}</b> avant son stockage en ligne ?<br>" +
"<em>L'extension du fichier ({1}) sera ajoutée automatiquement. "+
"Ce nom sera permanent et visible par les autres utilisateurs</em>.";
out.upload_modal_title = "Options d'importation du fichier";
out.upload_modal_filename = "Nom (extension <em>{0}</em> ajoutée automatiquement)";
out.upload_modal_owner = "Être propriétaire du fichier";
out.upload_serverError = "Erreur interne: impossible d'importer le fichier pour l'instant.";
out.upload_uploadPending = "Vous avez déjà un fichier en cours d'importation. Souhaitez-vous l'annuler et importer ce nouveau fichier ?";
out.upload_success = "Votre fichier ({0}) a été importé avec succès et ajouté à votre CryptDrive.";
@ -609,7 +647,7 @@ define(function () {
out.upload_mustLogin = "Vous devez vous connecter pour importer un fichier";
out.download_button = "Déchiffrer et télécharger";
out.download_mt_button = "Télécharger";
out.download_resourceNotAvailable = "Le fichier demandé n'est pas disponible...";
out.download_resourceNotAvailable = "Le fichier demandé n'est pas disponible... Appuyez sur Échap pour continuer.";
out.todo_title = "CryptTodo";
out.todo_newTodoNamePlaceholder = "Décrivez votre tâche...";
@ -622,9 +660,6 @@ define(function () {
out.pad_showToolbar = "Afficher la barre d'outils";
out.pad_hideToolbar = "Cacher la barre d'outils";
// general warnings
out.warn_notPinned = "Ce pad n'est stocké dans aucun CryptDrive. Il va expirer après 3 mois d'inactivité. <a href='/about.html#pinning'>En savoir plus...</a>";
// markdown toolbar
out.mdToolbar_button = "Afficher ou cacher la barre d'outils Markdown";
out.mdToolbar_defaultText = "Votre texte ici";
@ -644,38 +679,24 @@ define(function () {
// index.html
//about.html
out.main_p2 = 'Ce projet utilise l\'éditeur visuel (WYSIWYG) <a href="http://ckeditor.com/">CKEditor</a>, l\'éditeur de code source <a href="https://codemirror.net/">CodeMirror</a>, et le moteur temps-réel <a href="https://github.com/xwiki-contrib/chainpad">ChainPad</a>.';
out.main_howitworks_p1 = 'CryptPad utilise une variante de l\'algorithme d\'<a href="https://en.wikipedia.org/wiki/Operational_transformation">Operational transformation</a> qui est capable de trouver un consensus distribué en utilisant <a href="https://bitcoin.org/bitcoin.pdf">une chaîne de bloc Nakamoto</a>, un outil popularisé par le <a href="https://fr.wikipedia.org/wiki/Bitcoin">Bitcoin</a>. De cette manière, l\'algorithme évite la nécessité d\'utiliser un serveur central pour résoudre les conflits d\'édition de l\'Operational Transformation, et sans ce besoin de résolution des conflits le serveur peut rester ignorant du contenu qui est édité dans le pad.';
out.about_intro = 'CryptPad est développé au sein de l\'équipe Recherche d\'<a href="http://xwiki.com">XWiki SAS</a>, une petite entreprise située à Paris en France et à Iasi en Roumanie. Il y a 3 développeurs principaux qui travaillent sur CryptPad, ainsi que quelques contributeurs à la fois dans et en dehors d\'XWiki SAS';
out.about_core = 'Développeurs principaux';
out.about_contributors = 'Contributeurs clés';
//contact.html
out.main_about_p2 = 'Si vous avez des questions ou commentaires, vous pouvez <a href="https://twitter.com/cryptpad"><i class="fa fa-twitter"></i>nous tweeter</a>, ouvrir une issue sur <a href="https://github.com/xwiki-labs/cryptpad/issues/" title="our issue tracker"><i class="fa fa-github"></i>GitHub</a>, venir dire bonjour sur <a href="https://riot.im/app/#/room/#cryptpad:matrix.org" title="Matrix">notre <i class="fa fa-comment"></i>salle Matrix</a> ou IRC (#cryptpad sur irc.freenode.net), ou bien encore <a href="mailto:research@xwiki.com"><i class="fa fa-envelope"></i>nous envoyer un email</a>.';
out.main_about_p22 = 'Tweetez nous';
out.main_about_p23 = 'Ouvrez un ticket (GitHub)';
out.main_about_p24 = 'Dites Bonjour (Matrix)';
out.main_about_p25 = 'Envoyez-nous un email';
out.main_about_p26 = 'Si vous avez une question ou des remarques, n\'hésitez pas à nous contacter !';
out.main_info = "<h2>Collaborez avec confiance</h2><br>Développez vos idées en groupe avec des documents partagés; la technologie <strong>Zero Knowledge</strong> sécurise vos données.";
out.main_catch_phrase = "Le Cloud Zero Knowledge";
out.main_howitworks = 'Comment ça fonctionne';
out.main_zeroKnowledge = 'Zero Knowledge';
out.main_zeroKnowledge_p = "Vous n'avez pas besoin de croire que nous n'<em>allons</em> pas regarder vos pads. Avec la technologie Zero Knowledge de CryptPad, nous ne <em>pouvons</em> pas le faire. Apprenez-en plus sur notre manière de <a href=\"privacy.html\" title='Protection des données'>protéger vos données</a>.";
out.main_writeItDown = 'Prenez-en note';
out.main_writeItDown_p = "Les plus grands projets naissent des plus petites idées. Prenez note de vos moments d'inspiration et de vos idées inattendues car vous ne savez pas lesquels seront des découvertes capitales.";
out.main_share = 'Partagez le lien, partagez le pad';
out.main_share_p = "Faites croître vos idées à plusieurs : réalisez des réunions efficaces, collaborez sur vos listes de tâches et réalisez des présentations rapides avec tous vos amis sur tous vos appareils.";
out.main_organize = 'Soyez organisé';
out.main_organize_p = "Avec CryptDrive, vous pouvez garder vos vues sur ce qui est important. Les dossiers vous permettent de garder la trace de vos projets et d'avoir une vision globale du travail effectué.";
out.tryIt = 'Essayez-le !';
out.main_richText = 'Éditeur de texte';
out.main_richText_p = 'Éditez des documents texte collaborativement avec notre application <a href="http://ckeditor.com" target="_blank">CkEditor</a> temps-réel et Zero Knowledge.';
out.main_code = 'Éditeur de code';
out.main_code_p = 'Modifiez votre code collaborativement grâce à notre application <a href="https://www.codemirror.net" target="_blank">CodeMirror</a> temps-réel et Zero Knowledge.';
out.main_slide = 'Présentations';
out.main_slide_p = 'Créez vos présentations en syntaxe Markdown collaborativement de manière sécurisée et affichez les dans votre navigateur.';
out.main_poll = 'Sondages';
out.main_poll_p = 'Planifiez vos réunions ou évènements, ou votez pour la meilleure solution concernant votre problème.';
out.main_drive = 'CryptDrive';
out.main_richTextPad = 'Pad de Texte Riche';
@ -683,6 +704,7 @@ define(function () {
out.main_slidePad = 'Présentation Markdown';
out.main_pollPad = 'Sondage ou Planning';
out.main_whiteboardPad = 'Tableau blanc';
out.main_kanbanPad = 'Kanban';
out.main_localPads = 'Pads Locaux';
out.main_yourCryptDrive = 'Votre CryptDrive';
out.main_footerText = "Avec CryptPad, vous pouvez créer des documents collaboratifs rapidement pour prendre des notes à plusieurs.";
@ -715,7 +737,7 @@ define(function () {
out.whatis_drive_p2 = "Avec le glisser-déposer intuitif, vous pouvez déplacer vos pads dans votre drive tout en conservant les liens vers ces pads pour que vos collaborateurs n'en perdent pas l'accès";
out.whatis_drive_p3 = "Vous pouvez également importer des fichiers dans votre CryptDrive et les partager avec des collègues. Les fichiers importés peuvent être rangés de la même manière que vos pads collaboratifs.";
out.whatis_business = 'CryptPad for Business';
out.whatis_business_p1 = "Le chiffrement Zero Knowledge de CryptPad excelle pour multiplier l'efficacité des protocoles de sécurité existants en recréant les contrôles d'accès organisationnels de manière cryptographique. Puisque les données sensibles ne peuvent être déchiffrées qu'en utilisant les identifiants d'un employé, CryptPad empêche d'éventuels hackers ayant réussi à s'introduire dans le serveur d'avoir accès en clair à ces données. Découvrez-en plus sur la manière dont CryptPad peut aider votre entreprise en lisant le <a href=\"https://blog.cryptpad.fr/images/CryptPad-Whitepaper-v1.0.pdf\">CryptPad Whitepaper</a>.";
out.whatis_business_p1 = "Le chiffrement Zero Knowledge de CryptPad excelle pour accroître l'efficacité des protocoles de sécurité existants en les recréant de manière cryptographique. Puisque les données sensibles ne peuvent être déchiffrées qu'en utilisant les identifiants d'un utilisateur, CryptPad empêche d'éventuels hackers ayant réussi à s'introduire dans le serveur d'avoir accès en clair à ces données. Découvrez-en plus sur la manière dont CryptPad peut aider votre entreprise en lisant le <a href=\"https://blog.cryptpad.fr/images/CryptPad-Whitepaper-v1.0.pdf\">CryptPad Whitepaper</a>.";
out.whatis_business_p2 = "CryptPad est déployable sur site et les <a href=\"https://cryptpad.fr/about.html\">développeurs CryptPad</a> chez XWiki SAS peuvent effectuer du développement, des personnalisations et du support commercial. Contactez-nous à <a href=\"mailto:sales@cryptpad.fr\">sales@cryptpad.fr</a> pour plus d'informations.";
// privacy.html
@ -730,7 +752,7 @@ define(function () {
out.policy_whatwetell = 'Ce que nous dévoilons à d\'autres à propos de vous';
out.policy_whatwetell_p1 = 'Nous ne fournissons aucune information que nous récoltons ou que vous nous fournissez à des tierces parties à moins d\'y être contraints par la loi.';
out.policy_links = 'Liens vers d\'autres sites';
out.policy_links_p1 = 'Ce site contient des liens vers d\'autres sites, certains étant produits par d\'autres organisations. Nous ne sommes responsables des pratiques de confidentialité ou du contenu d\'aucun site externe. De manière générale, les liens vers des sites externes sont lancés dans une nouvelle fenêtre (ou onglet) du navigateur, pour rendre clair le fait que vous quittez CryptpPad.fr.';
out.policy_links_p1 = 'Ce site contient des liens vers d\'autres sites, certains étant produits par d\'autres organisations. Nous ne sommes responsables des pratiques de confidentialité ou du contenu d\'aucun site externe. De manière générale, les liens vers des sites externes sont lancés dans une nouvelle fenêtre (ou onglet) du navigateur, pour rendre clair le fait que vous quittez CryptPad.fr.';
out.policy_ads = 'Publicité';
out.policy_ads_p1 = 'Nous n\'affichons pas de publicité en ligne, bien que nous puissions afficher des liens vers les sites des organisations qui financent nos recherches.';
out.policy_choices = 'Vos choix';
@ -751,8 +773,8 @@ define(function () {
out.features_f_history = "Historique";
out.features_f_history_notes = "Voir et restaurer n'importe quelle version d'un pad";
out.features_f_todo = "Créer une TODO-list";
out.features_f_drive = "CryptDrive";
out.features_f_drive_notes = "Fonctionnalités basiques pour les utilisateurs anonymes";
out.features_f_drive = "Fonctionnalités CryptDrive limitées";
out.features_f_drive_full = "Fonctionnalités CryptDrive limitées";
out.features_f_export = "Export/Import";
out.features_f_export_notes = "Pour les pads et CryptDrive";
out.features_f_viewFiles = "Voir des fichiers";
@ -763,7 +785,7 @@ define(function () {
out.features_f_multiple_notes = "Moyen facile de voir vos pads depuis n'importe quel appareil";
out.features_f_logoutEverywhere = "Se déconnecter partout";
out.features_f_logoutEverywhere_notes = "Se déconnecter des autres appareils utilisés";
out.features_f_templates = "Modèles";
out.features_f_templates = "Utiliser les modèles";
out.features_f_templates_notes = "Créer des modèles et créer des pads basés sur ces modèles";
out.features_f_profile = "Créer un profil";
out.features_f_profile_notes = "Page personnelle contenant un avatar et une description";
@ -774,12 +796,13 @@ define(function () {
out.features_f_storage = "Stockage";
out.features_f_storage_anon = "Pads supprimés après 3 mois";
out.features_f_storage_registered = "Gratuit: 50Mo<br>Premium: 5Go/20Go/50Go";
out.features_f_register = "S'inscrire gratuitement";
// faq.html
out.faq_link = "FAQ";
out.faq_title = "Foire Aux Questions";
out.faq_whatis = "Qu'est-ce que CryptPad ?";
out.faq_whatis = "Qu'est-ce que <span class='cp-brand-font'>CryptPad</span> ?";
out.faq = {};
out.faq.keywords = {
title: 'Termes spéciaux',
@ -809,6 +832,10 @@ define(function () {
"Les pads existant dans votre CryptDrive peuvent être transformés en tant que modèle en les déplaçant dans la catégorie <em>Modèles</em> du CryptDrive.<br>" +
"Il est également possible de créer une copie d'un pad en tant que modèle en cliquant sur le bouton <span class=\"fa fa-bookmark\"></span> (<em>Sauver en tant que modèle</em>) dans la barre d'outils des éditeurs."
},
abandoned: {
q: "Qu'est-ce qu'un pad abandonné?",
a: "Un <em>pad abandonné</em> est un pad qui n'est stocké dans le CryptDrive d'aucun utilisateur enregistré et qui n'a pas été modifié depuis 6 mois. Les documents abandonnées sont automatiquement supprimés du serveur."
},
};
out.faq.privacy = {
title: 'Confidentialité',
@ -831,7 +858,7 @@ define(function () {
"Les formulaires d'inscription et de connexion génèrent à la place un ensemble de clés uniques, créées à partir de vos identifiants, et le serveur ne connaît donc que votre signature cryptographique.<br>" +
"Nous utilisons cette information principalement pour mesurer combien de données vous avez stocké sur nos serveurs, afin de pouvoir limiter chaque utilisateur à son quota.<br><br>" +
"Nous utilisons également notre fonctionnalité de <em>retour d'expérience</em> pour indiquer au serveur que quelqu'un avec votre adresse IP a créé un compte utilisateur, bien que nous ne sachions pas lequel. Cela nous permet de mesurer le nombre d'inscriptions sur CryptPad mais aussi de voir dans quelles régions du monde se trouvent les utilisateurs, afin de déterminer les langues dans lesquelles traduire CryptPad.<br><br>" +
"Enfin, les clés générées à l'inscription permettent d'indiquer au serveur que les pads dans votre CryptDrive ne doivent pas être supprimés, même s'ils sont inactifs. Ce système a l'inconvénient de nous fournir davantage d'informations sur la façon dont vous utilisez CryptPad, mais il est nécessaire pour que nous puissions supprimer du serveur les pads inactifs dont personne n'a besoin."
"Enfin, les utilisateurs enregistrés indiquent au serveur quels pads sont dans leur CryptDrive, afin que ces pads ne soient pas considérés comme abandonnés et ne soient donc pas supprimés pour inactivité."
},
other: {
q: "Que peuvent apprendre les autres collaborateurs à mon sujet ?",
@ -963,8 +990,6 @@ define(function () {
// Header.html
out.header_france = '<a href="http://www.xwiki.com/fr" target="_blank" rel="noopener noreferrer">Fait avec <img class="bottom-bar-heart" src="/customize/heart.png" alt="amour" /> en <img class="bottom-bar-fr" title="France" alt="France" src="/customize/fr.png" /> par <img src="/customize/logo-xwiki.png" alt="XWiki SAS" class="bottom-bar-xwiki"/></a>';
out.header_support = '<a href="http://ng.open-paas.org/" title="OpenPaaS::ng" target="_blank" rel="noopener noreferrer"> <img src="/customize/openpaasng.png" alt="OpenPaaS-ng" class="bottom-bar-openpaas" /></a>';
out.updated_0_header_logoTitle = 'Retourner vers votre CryptDrive';
out.header_logoTitle = out.updated_0_header_logoTitle;
out.header_homeTitle = "Aller sur la page d'accueil";
@ -1014,6 +1039,11 @@ define(function () {
embed: 'Intégrez des images de votre disque <span class="fa fa-file-image-o"></span> ou de votre CryptDrive <span class="fa fa-image"></span> et exporter le contenu en tant que PNG sur votre disque <span class="fa fa-download"></span> ou votre CryptDrive <span class="fa fa-cloud-upload"></span>'
};
out.help.kanban = {
add: 'Ajoutez un tableau en utilisant le bouton <span class="fa fa-plus"></span> dans le coin supérieur-droit',
task: 'Déplacez les éléments en les faisant glisser d\'un tableau à l\'autre',
color: 'Modifiez les couleurs en cliquant sur les parties colorées à côté du titre de chaque tableau'
};
out.initialState = [
'<p>',
@ -1064,7 +1094,7 @@ define(function () {
out.tips = {};
out.tips.shortcuts = "`ctrl+b`, `ctrl+i` et `ctrl+u` sont des raccourcis rapides pour mettre en gras, en italique ou souligner.";
out.tips.indent = "Dans les listes à puces ou numérotées, vous pouvez utiliser `Tab` ou `Maj+Tab` pour augmenter ou réduire rapidement l'indentation.";
out.tips.store = "Dès que vous ouvrez un nouveau pad, il est automatiquement stocké dans votre CryptDrive si vous êtes connectés.";
out.tips.store = "Dès que vous ouvrez un nouveau pad, il est automatiquement stocké dans votre CryptDrive si vous êtes connecté.";
out.tips.marker = "Vous pouvez surligner du texte dans un pad en utilisant l'option \"marker\" dans le menu déroulant des styles.";
out.tips.driveUpload = "Les utilisateurs enregistrés peuvent importer des fichiers en les faisant glisser et en les déposant dans leur CryptDrive.";
out.tips.filenames = "Vous pouvez renommer les fichiers de votre CryptDrive, ce nom ne sera visible que par vous.";
@ -1094,6 +1124,7 @@ define(function () {
out.creation_expireMonths = "Mois";
out.creation_expire1 = "Un pad <b>illimité</b> ne sera pas supprimé du serveur à moins que son propriétaire ne le décide.";
out.creation_expire2 = "Un pad <b>à durée de vie</b> sera supprimé automatiquement du serveur et du CryptDrive des utilisateurs lorsque cette durée sera dépassée.";
out.creation_password = "Ajouter un mot de passe";
out.creation_noTemplate = "Pas de modèle";
out.creation_newTemplate = "Nouveau modèle";
out.creation_create = "Créer";
@ -1105,12 +1136,31 @@ define(function () {
out.creation_ownedByOther = "Appartient à un autre utilisateur";
out.creation_noOwner = "Pas de propriétaire";
out.creation_expiration = "Date d'expiration";
out.creation_passwordValue = "Mot de passe";
out.creation_propertiesTitle = "Disponibilité";
out.creation_appMenuName = "Mode avancé (Ctrl + E)";
out.creation_newPadModalDescription = "Cliquez sur un type de pad pour le créer. Vous pouvez aussi appuyer sur <b>Tab</b> pour sélectionner un type et appuyer sur <b>Entrée</b> pour valider.";
out.creation_newPadModalDescriptionAdvanced = "Cochez la case si vous souhaitez voir l'écran de création de pads (pour les pads avec propriétaire ou à durée de vie). Vous pouvez appuyer sur <b>Espace</b> pour changer sa valeur.";
out.creation_newPadModalAdvanced = "Afficher l'écran de création de pads";
// Password prompt on the loadind screen
out.password_info = "Le pad auquel vous essayez d'accéder est protégé par un mot de passe. Entrez le bon mot de passe pour accéder à son contenu.";
out.password_error = "Pad introuvable !<br>Cette erreur peut provenir de deux facteurs. Soit le mot de passe est faux, soit le pad a été supprimé du serveur.";
out.password_placeholder = "Tapez le mot de passe ici...";
out.password_submit = "Valider";
out.password_show = "Afficher";
// Change password in pad properties
out.properties_addPassword = "Ajouter un mot de passe";
out.properties_changePassword = "Modifier le mot de passe";
out.properties_confirmNew = "Êtes-vous sûr ? Ajouter un mot de passe changera l'URL de ce pad et supprimera son historique. Les utilisateurs ne connaissant pas le nouveau mot de passe perdront l'accès au pad.";
out.properties_confirmChange = "Êtes-vous sûr ? Changer le mot de passe supprimera l'historique de ce pad. Les utilisateurs ne connaissant pas le nouveau mot de passe perdront l'accès au pad.";
out.properties_passwordSame = "Le nouveau mot de passe doit être différent de celui existant.";
out.properties_passwordError = "Une erreur est survenue lors de la modification du mot de passe. Veuillez réessayer.";
out.properties_passwordWarning = "Le mot de passe a été modifié avec succès mais nous n'avons pas réussi à mettre à jour votre CryptDrive avec les nouvelles informations. Vous devrez peut-être supprimer manuellement l'ancienne version de ce pad.<br>Appuyez sur OK pour recharger le pad et mettre à jour vos droits d'accès.";
out.properties_passwordSuccess = "Le mot de passe a été modifié avec succès.<br>Appuyez sur OK pour mettre à jour vos droits d'accès.";
out.properties_changePasswordButton = "Valider";
// New share modal
out.share_linkCategory = "Partage";
out.share_linkAccess = "Droits d'accès";
@ -1124,5 +1174,21 @@ define(function () {
out.share_embedCategory = "Intégration";
out.share_mediatagCopy = "Copier le mediatag";
// Loading info
out.loading_pad_1 = "Initialisation du pad";
out.loading_pad_2 = "Chargement du contenu du pad";
out.loading_drive_1 = "Chargement des données";
out.loading_drive_2 = "Mise à jour du format des données";
out.loading_drive_3 = "Vérification de l'intégrité des données";
// Shared folders
out.sharedFolders_forget = "Ce pad est stocké uniquement dans un dossier partagé. Vous ne pouvez pas le déplacer dans votre corbeille. Si vous souhaitez le supprimer, vous pouvez utiliser l'application CryptDrive.";
out.sharedFolders_duplicate = "Certains pads que vous essayez de déplacer sont déjà partagés dans le dossier de destination.";
out.sharedFolders_create = "Créer un dossier partagé";
out.sharedFolders_create_name = "Nom du dossier";
out.sharedFolders_create_owned = "Être propriétaire du dossier";
out.sharedFolders_create_password = "Mot de passe du dossier";
out.sharedFolders_share = "Partager cette URL avec d'autres utilisateurs enregistrés leur donne accès au dossier partagé. Une fois l'URL ouverte, le dossier partagé sera ajouté au répertoire racine de leur CryptDrive.";
return out;
});

@ -2,12 +2,12 @@ define(function () {
var out = {};
out.main_title = "CryptPad: Zero Knowledge, Collaborative Real Time Editing";
out.main_slogan = "Unity is Strength - Collaboration is Key"; // TODO remove?
out.type = {};
out.type.pad = 'Rich text';
out.type.code = 'Code';
out.type.poll = 'Poll';
out.type.kanban = 'Kanban';
out.type.slide = 'Presentation';
out.type.drive = 'CryptDrive';
out.type.whiteboard = 'Whiteboard';
@ -27,6 +27,7 @@ define(function () {
out.button_newoodoc = 'New OnlyOffice document';
out.button_newooslide = 'New OnlyOffice presentation';
out.button_newoocell = 'New OnlyOffice spreadsheet';
out.button_newkanban = 'New Kanban';
// NOTE: Remove updated_0_ if we need an updated_1_
out.updated_0_common_connectionLost = "<b>Server Connection Lost</b><br>You're now in read-only mode until the connection is back.";
@ -34,7 +35,7 @@ define(function () {
out.websocketError = 'Unable to connect to the websocket server...';
out.typeError = "This pad is not compatible with the selected application";
out.onLogout = 'You are logged out, <a href="/" target="_blank">click here</a> to log in<br>or press <em>Escape</em> to access your pad in read-only mode.';
out.onLogout = 'You are logged out, {0}click here{1} to log in<br>or press <em>Escape</em> to access your pad in read-only mode.';
out.wrongApp = "Unable to display the content of that realtime session in your browser. Please try to reload that page.";
out.padNotPinned = 'This pad will expire after 3 months of inactivity, {0}login{1} or {2}register{3} to preserve it.';
out.anonymousStoreDisabled = "The webmaster of this CryptPad instance has disabled the store for anonymous users. You have to log in to be able to use CryptDrive.";
@ -44,6 +45,9 @@ define(function () {
out.chainpadError = 'A critical error occurred when updating your content. This page is in read-only mode to make sure you won\'t lose your work.<br>' +
'Hit <em>Esc</em> to continue to view this pad, or reload to try editing again.';
out.errorCopy = ' You can still copy the content to another location by pressing <em>Esc</em>.<br>Once you leave this page, it will disappear forever!';
out.errorRedirectToHome = 'Press <em>Esc</em> to be redirected to your CryptDrive.';
out.newVersionError = "A new version of CryptPad is available.<br>" +
"<a href='#'>Reload</a> to use the new version, or press escape to access your content in <b>offline mode</b>.";
out.loading = "Loading...";
out.error = "Error";
@ -52,7 +56,7 @@ define(function () {
out.deleted = "Pad deleted from your CryptDrive";
out.deletedFromServer = "Pad deleted from the server";
out.realtime_unrecoverableError = "The realtime engine has encountered an unrecoverable error. Click OK to reload.";
out.realtime_unrecoverableError = "An unrecoverable error has occured. Click OK to reload.";
out.disconnected = 'Disconnected';
out.synchronizing = 'Synchronizing';
@ -152,6 +156,8 @@ define(function () {
out.useTemplate = "Start with a template?"; //Would you like to "You have available templates for this type of pad. Do you want to use one?";
out.useTemplateOK = 'Pick a template (Enter)';
out.useTemplateCancel = 'Start fresh (Esc)';
out.template_import = "Import a template";
out.template_empty = "No template available";
out.previewButtonTitle = "Display or hide the Markdown preview mode";
@ -222,6 +228,7 @@ define(function () {
out.notifyRenamed = "{0} is now known as {1}";
out.notifyLeft = "{0} has left the collaborative session";
out.ok = 'OK';
out.okButton = 'OK (enter)';
out.cancel = "Cancel";
@ -234,12 +241,10 @@ define(function () {
out.historyText = "History";
out.historyButton = "Display the document history";
out.history_next = "Go to the next version";
out.history_prev = "Go to the previous version";
out.history_goTo = "Go to the selected version";
out.history_close = "Back";
out.history_next = "Newer version";
out.history_prev = "Older version";
out.history_loadMore = "Load more history";
out.history_closeTitle = "Close the history";
out.history_restore = "Restore";
out.history_restoreTitle = "Restore the selected version of the document";
out.history_restorePrompt = "Are you sure you want to replace the current version of the document by the displayed one?";
out.history_restoreDone = "Document restored";
@ -250,6 +255,22 @@ define(function () {
out.pad_mediatagTitle = "Media-Tag settings";
out.pad_mediatagWidth = "Width (px)";
out.pad_mediatagHeight = "Height (px)";
out.pad_mediatagRatio = "Keep ratio";
out.pad_mediatagBorder = "Border width (px)";
out.pad_mediatagPreview = "Preview";
out.pad_mediatagImport = 'Save in CryptDrive';
out.pad_mediatagOptions = 'Image properties';
// Kanban
out.kanban_newBoard = "New board";
out.kanban_item = "Item {0}"; // Item number for initial content
out.kanban_todo = "To Do";
out.kanban_done = "Done";
out.kanban_working = "In progress";
out.kanban_deleteBoard = "Are you sure you want to delete this board?";
out.kanban_addBoard = "Add a board";
out.kanban_removeItem = "Remove this item";
out.kanban_removeItemConfirm = "Are you sure you want to delete this item?";
// Polls
@ -378,12 +399,14 @@ define(function () {
out.fm_searchName = "Search";
out.fm_recentPadsName = "Recent pads";
out.fm_ownedPadsName = "Owned";
out.fm_tagsName = "Tags";
out.fm_searchPlaceholder = "Search...";
out.fm_newButton = "New";
out.fm_newButtonTitle = "Create a new pad or folder, import a file in the current folder";
out.fm_newFolder = "New folder";
out.fm_newFile = "New pad";
out.fm_folder = "Folder";
out.fm_sharedFolder = "Shared folder";
out.fm_folderName = "Folder name";
out.fm_numberOfFolders = "# of folders";
out.fm_numberOfFiles = "# of files";
@ -433,6 +456,7 @@ define(function () {
out.fm_viewListButton = "List view";
out.fm_viewGridButton = "Grid view";
out.fm_renamedPad = "You've set a custom name for this pad. Its shared title is:<br><b>{0}</b>";
out.fm_canBeShared = "This folder can be shared";
out.fm_prop_tagsList = "Tags";
out.fm_burnThisDriveButton = "Erase all information stored by CryptPad in your browser";
out.fm_burnThisDrive = "Are you sure you want to remove everything stored by CryptPad in your browser?<br>" +
@ -440,8 +464,13 @@ define(function () {
out.fm_padIsOwned = "You are the owner of this pad";
out.fm_padIsOwnedOther = "This pad is owned by another user";
out.fm_deletedPads = "These pads no longer exist on the server, they've been removed from your CryptDrive: {0}";
out.fm_tags_name = "Tag name";
out.fm_tags_used = "Number of uses";
out.fm_restoreDrive = "Resetting your drive to an earlier state. For best results, avoid making changes to your drive until this process is complete.";
out.fm_moveNestedSF = "You can't place one shared folder within another. The folder {0} was not moved.";
// File - Context menu
out.fc_newfolder = "New folder";
out.fc_newsharedfolder = "New shared folder";
out.fc_rename = "Rename";
out.fc_open = "Open";
out.fc_open_ro = "Open (read-only)";
@ -449,12 +478,13 @@ define(function () {
out.fc_delete_owned = "Delete from the server";
out.fc_restore = "Restore";
out.fc_remove = "Remove from your CryptDrive";
out.fc_remove_sharedfolder = "Remove";
out.fc_empty = "Empty the trash";
out.fc_prop = "Properties";
out.fc_hashtag = "Tags";
out.fc_sizeInKilobytes = "Size in Kilobytes";
// fileObject.js (logs)
out.fo_moveUnsortedError = "You can't move a folder to the list of unsorted pads";
out.fo_moveUnsortedError = "You can't move a folder to the list of templates";
out.fo_existingNameError = "Name already used in that directory. Please choose another one.";
out.fo_moveFolderToChildError = "You can't move a folder into one of its descendants";
out.fo_unableToRestore = "Unable to restore that file to its original location. You can try to move it to a new location.";
@ -554,6 +584,14 @@ define(function () {
out.settings_importConfirm = "Are you sure you want to import recent pads from this browser to your user account's CryptDrive?";
out.settings_importDone = "Import completed";
out.settings_autostoreTitle = "Pad storage in CryptDrive";
out.settings_autostoreHint = "<b>Automatic</b> pad storage results in all the pads you visit being stored in your CryptDrive.<br>" +
"<b>Manual (always ask)</b> results in the pads not being stored but a reminder will appear to ask you if you want to store them in CryptDrive.<br>" +
"<b>Manual (never ask)</b> results in the pads not being stored and option to store them will be available but in a hidden way.";
out.settings_autostoreYes = "Automatic";
out.settings_autostoreNo = "Manual (never ask)";
out.settings_autostoreMaybe = "Manual (always ask)";
out.settings_userFeedbackTitle = "Feedback";
out.settings_userFeedbackHint1 = "CryptPad provides some very basic feedback to the server, to let us know how to improve your experience. ";
out.settings_userFeedbackHint2 = "Your pad's content will never be shared with the server.";
@ -563,6 +601,8 @@ define(function () {
out.settings_deleteHint = "Account deletion is permanent. Your CryptDrive and your list of pads will be deleted from the server. The rest of your pads will be deleted in 90 days if nobody else has stored them in their CryptDrive.";
out.settings_deleteButton = "Delete your account";
out.settings_deleteModal = "Share the following information with your CryptPad administrator in order to have your data removed from their server.";
out.settings_deleteConfirm = "Clicking OK will delete your account permanently. Are you sure?";
out.settings_deleted = "Your user account is now deleted. Press OK to go to the home page.";
out.settings_anonymous = "You are not logged in. Settings here are specific to this browser.";
out.settings_publicSigningKey = "Public Signing Key";
@ -593,10 +633,27 @@ 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_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>" +
"<b>We can't reset your password if you forget it, so be very careful!</b>";
out.settings_changePasswordButton = "Change password";
out.settings_changePasswordCurrent = "Current password";
out.settings_changePasswordNew = "New password";
out.settings_changePasswordNewConfirm = "Confirm new password";
out.settings_changePasswordConfirm = "Are you sure you want to change your password? You will need to log back in on all your devices.";
out.settings_changePasswordError = "An unexpected error occurred. If you are unable to login or change your password, contact your CryptPad administrators.";
out.settings_changePasswordPending = "Your password is being updated. Please do not close or reload this page until the process has completed.";
out.settings_changePasswordNewPasswordSameAsOld = "Your new password must be different than your current password.";
out.upload_title = "File upload";
out.upload_rename = "Do you want to rename <b>{0}</b> before uploading it to the server?<br>" +
"<em>The file extension ({1}) will be added automatically. "+
"This name will be permanent and visible to other users.</em>";
out.upload_modal_title = "File upload options";
out.upload_modal_filename = "File name (extension <em>{0}</em> added automatically)";
out.upload_modal_owner = "Owned file";
out.upload_serverError = "Server Error: unable to upload your file at this time.";
out.upload_uploadPending = "You already have an upload in progress. Cancel it and upload your new file?";
out.upload_success = "Your file ({0}) has been successfully uploaded and added to your drive.";
@ -613,7 +670,7 @@ define(function () {
out.upload_mustLogin = "You must be logged in to upload files";
out.download_button = "Decrypt & Download";
out.download_mt_button = "Download";
out.download_resourceNotAvailable = "The requested resource was not available...";
out.download_resourceNotAvailable = "The requested resource was not available... Press Esc to continue.";
out.todo_title = "CryptTodo";
out.todo_newTodoNamePlaceholder = "Describe your task...";
@ -625,9 +682,7 @@ define(function () {
// pad
out.pad_showToolbar = "Show toolbar";
out.pad_hideToolbar = "Hide toolbar";
// general warnings
out.warn_notPinned = "This pad is not in anyone's CryptDrive. It will expire after 3 months. <a href='/about.html#pinning'>Learn more...</a>";
out.pad_base64 = "This pad contains images stored in an inefficient way. These images will increase significantly the size of the pad in your CryptDrive, and they will make it slower to load. Do you want to migrate these images to a better format (they will be stored separately in your drive)?"; // XXX
// markdown toolbar
out.mdToolbar_button = "Show or hide the Markdown toolbar";
@ -649,11 +704,11 @@ define(function () {
//about.html
out.main_p2 = 'This project uses the <a href="http://ckeditor.com/">CKEditor</a> Visual Editor, <a href="https://codemirror.net/">CodeMirror</a>, and the <a href="https://github.com/xwiki-contrib/chainpad">ChainPad</a> realtime engine.';
out.main_howitworks_p1 = 'CryptPad uses a variant of the <a href="https://en.wikipedia.org/wiki/Operational_transformation">Operational transformation</a> algorithm which is able to find distributed consensus using a <a href="https://bitcoin.org/bitcoin.pdf">Nakamoto Blockchain</a>, a construct popularized by <a href="https://en.wikipedia.org/wiki/Bitcoin">Bitcoin</a>. This way the algorithm can avoid the need for a central server to resolve Operational Transform Edit Conflicts and without the need for resolving conflicts, the server can be kept unaware of the content which is being edited on the pad.';
out.about_intro = 'CryptPad is created inside of the Research Team at <a href="http://xwiki.com">XWiki SAS</a>, a small business located in Paris France and Iasi Romania. There are 3 core team members working on CryptPad plus a number of contributors both inside and outside of XWiki SAS.';
out.about_core = 'Core Developers';
out.about_contributors = 'Key Contributors';
// contact.html
out.main_about_p2 = 'If you have any questions or comments, feel free to reach out!<br/>You can <a href="https://twitter.com/cryptpad"><i class="fa fa-twitter"></i>tweet us</a>, open an issue <a href="https://github.com/xwiki-labs/cryptpad/issues/" title="our issue tracker">on <i class="fa fa-github"></i>GitHub</a>. Come say hi on <a href="https://riot.im/app/#/room/#cryptpad:matrix.org" title="Matrix">our <i class="fa fa-comment"></i>Matrix channel</a> or IRC (#cryptpad on irc.freenode.net), or <a href="mailto:research@xwiki.com"><i class="fa fa-envelope"></i>send us an email</a>.';
out.main_about_p22 = 'Tweet us';
out.main_about_p23 = 'open an issue on GitHub';
out.main_about_p24 = 'say Hello (Matrix)';
@ -663,25 +718,10 @@ define(function () {
out.main_info = "<h2>Collaborate in Confidence</h2> Grow your ideas together with shared documents while <strong>Zero Knowledge</strong> technology secures your privacy; <strong>even from us</strong>.";
out.main_catch_phrase = "The Zero Knowledge Cloud";
out.main_howitworks = 'How It Works';
out.main_zeroKnowledge = 'Zero Knowledge';
out.main_zeroKnowledge_p = "You don't have to trust that we <em>won't</em> look at your pads, with CryptPad's revolutionary Zero Knowledge Technology we <em>can't</em>. Learn more about how we protect your <a href=\"/privacy.html\" title='Privacy'>Privacy and Security</a>.";
out.main_writeItDown = 'Write it down';
out.main_writeItDown_p = "The greatest projects come from the smallest ideas. Take down the moments of inspiration and unexpected ideas because you never know which one might be a breakthrough.";
out.main_share = 'Share the link, share the pad';
out.main_share_p = "Grow your ideas together: conduct efficient meetings, collaborate on TODO lists and make quick presentations with all your friends and all your devices.";
out.main_organize = 'Get organized';
out.main_organize_p = "With CryptPad Drive, you can keep your sights on what's important. Folders allow you to keep track of your projects and have a global vision of where things are going.";
out.tryIt = 'Try it out!';
out.main_richText = 'Rich Text editor';
out.main_richText_p = 'Edit rich text pads collaboratively with our realtime Zero Knowledge <a href="http://ckeditor.com" target="_blank">CkEditor</a> application.';
out.main_code = 'Code editor';
out.main_code_p = 'Edit code from your software collaboratively with our realtime Zero Knowledge <a href="https://www.codemirror.net" target="_blank">CodeMirror</a> application.';
out.main_slide = 'Slide editor';
out.main_slide_p = 'Create your presentations using the Markdown syntax, and display them in your browser.';
out.main_poll = 'Polls';
out.main_poll_p = 'Plan your meeting or your event, or vote for the best solution regarding your problem.';
out.main_drive = 'CryptDrive';
out.main_richTextPad = 'Rich Text Pad';
@ -689,6 +729,7 @@ define(function () {
out.main_slidePad = 'Markdown Presentation';
out.main_pollPad = 'Poll or Schedule';
out.main_whiteboardPad = 'Whiteboard';
out.main_kanbanPad = 'Kanban';
out.main_localPads = 'Local Pads';
out.main_yourCryptDrive = 'Your CryptDrive';
out.main_footerText = "With CryptPad, you can make quick collaborative documents for taking notes and writing down ideas together.";
@ -721,7 +762,7 @@ define(function () {
out.whatis_drive_p2 = 'With intuitive drag-and-drop, you can move pads around in your drive and the link to these pads will stay the same so your collaborators will never lose access.';
out.whatis_drive_p3 = 'You can also upload files in your CryptDrive and share them with colleagues. Uploaded files can be organized just like collaborative pads.';
out.whatis_business = 'CryptPad for Business';
out.whatis_business_p1 = 'CryptPad\'s Zero Knowledge encryption is excellent for multiplying the effectiveness of existing security protocols by mirroring organizational access controls in cryptography. Because sensitive assets can only be decrypted using employee access credentials, CryptPad removes the hacker jackpot which exists in traditional IT servers. Read the <a href="https://blog.cryptpad.fr/images/CryptPad-Whitepaper-v1.0.pdf">CryptPad Whitepaper</a> to learn more about how it can help your business.';
out.whatis_business_p1 = "CryptPad\'s Zero Knowledge encryption multiplies the effectiveness of existing security protocols by mirroring organizational access controls in cryptography. Because sensitive assets can only be decrypted using user access credentials, CryptPad is less valuable as a target when compared to traditional cloud services. Read the <a href='https://blog.cryptpad.fr/images/CryptPad-Whitepaper-v1.0.pdf'>CryptPad Whitepaper</a> to learn more about how it can help your business.";
out.whatis_business_p2 = 'CryptPad is deployable on premises and the <a href="https://cryptpad.fr/about.html">CryptPad developers</a> at XWiki SAS are able to offer commercial support, customization and development. Reach out to <a href="mailto:sales@cryptpad.fr">sales@cryptpad.fr</a> for more information.';
// privacy.html
@ -757,8 +798,8 @@ define(function () {
out.features_f_history = "History";
out.features_f_history_notes = "View and restore any version of your pads";
out.features_f_todo = "Create a TODO-list";
out.features_f_drive = "CryptDrive";
out.features_f_drive_notes = "Basic features for anonymous users";
out.features_f_drive = "Limited CryptDrive functionality";
out.features_f_drive_full = "Complete CryptDrive functionality";
out.features_f_export = "Export/Import";
out.features_f_export_notes = "For pads and CryptDrive";
out.features_f_viewFiles = "View files";
@ -778,14 +819,15 @@ define(function () {
out.features_f_contacts = "Contacts application";
out.features_f_contacts_notes = "Add contacts and chat with them in an encrypted session";
out.features_f_storage = "Storage";
out.features_f_storage_anon = "Pads deleted after 3 months";
out.features_f_storage_anon = "Pads are deleted after 3 months";
out.features_f_storage_registered = "Free: 50MB<br>Premium: 5GB/20GB/50GB";
out.features_f_register = "Register for free";
// faq.html
out.faq_link = "FAQ";
out.faq_title = "Frequently Asked Questions";
out.faq_whatis = "What is CryptPad?";
out.faq_whatis = "What is <span class='cp-brand-font'>CryptPad</span>?";
out.faq = {};
out.faq.keywords = {
title: 'Keywords',
@ -817,6 +859,10 @@ define(function () {
" Any existing pad can be turned into a template by moving it into the <em>Templates</em> section in your CryptDrive." +
" You can also create a copy of a pad to be used as a template by clicking the template button (<span class='fa fa-bookmark'></span>) in the editor's toolbar."
},
abandoned: {
q: "What is an abandoned pad?",
a: "An <em>abandoned pad</em> is a pad that is not pinned in any registered user's CryptDrive and that hasn't been changed for six months. Abandoned documents will be automatically removed from the server."
},
};
out.faq.privacy = {
title: 'Privacy',
@ -847,8 +893,7 @@ define(function () {
"We use our <em>feedback</em> functionality to inform the server that someone with your IP has registered an account." +
" We use this to measure how many people register for CryptPad accounts, and to see what regions they are in so that we can guess which languages may need better support.<br><br>" +
"When you register, you generate a public key which is used to tell the server that the pads in your CryptDrive should not be deleted even if they are not actively being used." +
" This information does reveal more about how you are using CryptPad, but the system allows us to remove pads from the server once nobody cares enough to keep them."
"Registered users inform the server which pads are in their CryptDrive so that such pads are not considered abandoned, and are removed from the server due to inactivity."
},
other: {
q: "What can other collaborators learn about me?",
@ -1007,9 +1052,6 @@ define(function () {
// Header.html
out.header_france = '<a href="http://www.xwiki.com/" target="_blank" rel="noopener noreferrer">With <img class="bottom-bar-heart" src="/customize/heart.png" alt="love" /> from <img class="bottom-bar-fr" src="/customize/fr.png" title="France" alt="France"/> by <img src="/customize/logo-xwiki.png" alt="XWiki SAS" class="bottom-bar-xwiki"/></a>';
out.header_support = '<a href="http://ng.open-paas.org/" title="OpenPaaS::ng" target="_blank" rel="noopener noreferrer"> <img src="/customize/openpaasng.png" alt="OpenPaaS-ng" class="bottom-bar-openpaas" /></a>';
out.updated_0_header_logoTitle = 'Go to your CryptDrive';
out.header_logoTitle = out.updated_0_header_logoTitle;
out.header_homeTitle = 'Go to CryptPad homepage';
@ -1059,6 +1101,11 @@ define(function () {
embed: 'Embed images from your disk <span class="fa fa-file-image-o"></span> or your CryptDrive <span class="fa fa-image"></span> and export them as PNG to your disk <span class="fa fa-download"></span> or your CryptDrive <span class="fa fa-cloud-upload"></span>'
};
out.help.kanban = {
add: 'Add new boards using the <span class="fa fa-plus"></span> button in the top-right corner',
task: 'Move items by dragging and dropping them from one board to another',
color: 'Change the colors by clicking on the colored part next to the board titles',
};
out.initialState = [
'<p>',
@ -1141,6 +1188,7 @@ define(function () {
out.creation_expireMonths = "Month(s)";
out.creation_expire1 = "An <b>unlimited</b> pad will not be removed from the server until its owner deletes it.";
out.creation_expire2 = "An <b>expiring</b> pad has a set lifetime, after which it will be automatically removed from the server and other users' CryptDrives.";
out.creation_password = "Add a password";
out.creation_noTemplate = "No template";
out.creation_newTemplate = "New template";
out.creation_create = "Create";
@ -1152,12 +1200,31 @@ define(function () {
out.creation_ownedByOther = "Owned by another user";
out.creation_noOwner = "No owner";
out.creation_expiration = "Expiration time";
out.creation_passwordValue = "Password";
out.creation_propertiesTitle = "Availability";
out.creation_appMenuName = "Advanced mode (Ctrl + E)";
out.creation_newPadModalDescription = "Click on a pad type to create it. You can also press <b>Tab</b> to select the type and press <b>Enter</b> to confirm.";
out.creation_newPadModalDescriptionAdvanced = "You can check the box (or press <b>Space</b> to change its value) if you want to display the pad creation screen (for owned pads, expiring pads, etc.).";
out.creation_newPadModalAdvanced = "Display the pad creation screen";
// Password prompt on the loading screen
out.password_info = "The pad you're trying to open is protected with a password. Enter the correct password to access its content.";
out.password_error = "Pad not found!<br>This error can be caused by two factors: either the password in invalid, or the pad has been deleted from the server.";
out.password_placeholder = "Type the password here...";
out.password_submit = "Submit";
out.password_show = "Show";
// Change password in pad properties
out.properties_addPassword = "Add a password";
out.properties_changePassword = "Change the password";
out.properties_confirmNew = "Are you sure? Adding a password will change this pad's URL and remove its history. Users without the password will lose access to this pad";
out.properties_confirmChange = "Are you sure? Changing the password will remove its history. Users without the new password will lose access to this pad";
out.properties_passwordSame = "New passwords must differ from the current one.";
out.properties_passwordError = "An error occured while trying to change the password. Please try again.";
out.properties_passwordWarning = "The password was successfully changed but we were unable to update your CryptDrive with the new data. You may have to remove the old version of the pad manually.<br>Press OK to reload and update your acces rights.";
out.properties_passwordSuccess = "The password was successfully changed.<br>Press OK to reload and update your access rights.";
out.properties_changePasswordButton = "Submit";
// New share modal
out.share_linkCategory = "Share link";
out.share_linkAccess = "Access rights";
@ -1171,6 +1238,33 @@ define(function () {
out.share_embedCategory = "Embed";
out.share_mediatagCopy = "Copy mediatag to clipboard";
// Loading info
out.loading_pad_1 = "Initializing pad";
out.loading_pad_2 = "Loading pad content";
out.loading_drive_1 = "Loading data";
out.loading_drive_2 = "Updating data format";
out.loading_drive_3 = "Verifying data integrity";
// Shared folders
out.sharedFolders_forget = "This pad is only stored in a shared folder, you can't move it to the trash. You can use your CryptDrive if you want to delete it.";
out.sharedFolders_duplicate = "Some of the pads you were trying to move were already shared in the destination folder.";
out.sharedFolders_create = "Create a shared folder";
out.sharedFolders_create_name = "Folder name";
out.sharedFolders_create_owned = "Owned folder";
out.sharedFolders_create_password = "Folder password";
out.sharedFolders_share = "Share this URL with other registered users to give them access to the shared folder. Once they open this URL, the shared folder will be added to the root directory of their CryptDrive.";
out.chrome68 = "It seems that you're using the browser Chrome or Chromium version 68. It contains a bug resulting in the page turning completely white after a few seconds or the page being unresponsive to clicks. To fix this issue, you can switch to another tab and come back, or try to scroll in the page. This bug should be fixed in the next version of your browser.";
// Manual pad storage popup
out.autostore_notstored = "This pad is not in your CryptDrive. Do you want to store it now?"; // XXX
out.autostore_settings = "You can enable automatic pad storage in your <a href=\"/settings/\">Settings</a> page!"; // XXX
out.autostore_store = "Store";
out.autostore_hide = "Don't store";
out.autostore_error = "Unexpected error: we were unable to store this pad, please try again.";
out.autostore_saved = "The pad was successfully stored in your CryptDrive!";
out.autostore_forceSave = "Store the file in CryptDrive"; // File upload modal
out.autostore_notAvailable = "You must store this pad in your CryptDrive before being able to use this feature."; // Properties/tags/move to trash
return out;
});

@ -38,7 +38,7 @@ define(function () {
out.websocketError = 'Incapaz de se conectar com o servidor websocket...';
out.typeError = "Este bloco não é compatível com a aplicação selecionada";
out.onLogout = 'você foi desconectado, <a href="/" target="_blank">clique aqui</a> para se conectar, <br>ou pressione <em>ESC</em> para acessar seu bloco em modo somente leitura.';
out.onLogout = 'você foi desconectado, {0}clique aqui{1} para se conectar, <br>ou pressione <em>ESC</em> para acessar seu bloco em modo somente leitura.';
out.wrongApp = "Incapaz de mostrar o conteúdo em tempo real no seu navegador. Por favor tente recarregar a página.";
out.loading = "Carregando...";

@ -13,7 +13,7 @@ define(function () {
out.common_connectionLost = out.updated_0_common_connectionLost;
out.websocketError = "Conexiune inexistentă către serverul websocket...";
out.typeError = "Această filă nu este compatibilă cu aplicația aleasă";
out.onLogout = "Nu mai ești autentificat, <a href=\"/\" target=\"_blank\">apasă aici</a> să te autentifici<br>sau apasă <em>Escape</em>să accesezi fila în modul citire.";
out.onLogout = "Nu mai ești autentificat, {0}apasă aici{1} să te autentifici<br>sau apasă <em>Escape</em>să accesezi fila în modul citire.";
out.wrongApp = "Momentan nu putem arăta conținutul sesiunii în timp real în fereastra ta. Te rugăm reîncarcă pagina.";
out.loading = "Încarcă...";
out.error = "Eroare";

@ -31,7 +31,7 @@ define(function () {
out.websocketError = '無法連結上 websocket 伺服器...';
out.typeError = "這個編輯檔與所選的應用程式並不相容";
out.onLogout = '你已登出, <a href="/" target="_blank">點擊這裏</a> 來登入<br>或按<em>Escape</em> 來以唯讀模型使用你的編輯檔案';
out.onLogout = '你已登出, {0}點擊這裏{1} 來登入<br>或按<em>Escape</em> 來以唯讀模型使用你的編輯檔案';
out.wrongApp = "無法在瀏覽器顯示即時期間的內容,請試著再重新載入本頁。";
out.loading = "載入中...";

@ -33,7 +33,7 @@ nThen(function (waitFor) {
sem.take(function (give) {
Fs.unlink(f.filename, give(function (err) {
if (err) { return void console.error(err + " " + f.filename); }
console.log(f.filename + " " + f.size + " " + (+f.atime) + " " + (+new Date()));
console.log(f.filename + " " + f.size + " " + (+f.mtime) + " " + (+new Date()));
}));
});
});

@ -6,12 +6,11 @@
server {
listen 443 ssl http2;
server_name your-main-domain.com your-sandbox-domain.com;
server_name cryptpad.fr www.cryptpad.fr beta.cryptpad.fr;
ssl_certificate /home/cryptpad/.acme.sh/alpha.cryptpad.fr/fullchain.cer;
ssl_certificate_key /home/cryptpad/.acme.sh/alpha.cryptpad.fr/alpha.cryptpad.fr.key;
ssl_trusted_certificate /home/cryptpad/.acme.sh/alpha.cryptpad.fr/ca.cer;
ssl_certificate /home/cryptpad/.acme.sh/your-main-domain.com/fullchain.cer;
ssl_certificate_key /home/cryptpad/.acme.sh/your-main-domain.com/your-main-domain.com.key;
ssl_trusted_certificate /home/cryptpad/.acme.sh/your-main-domain.com/ca.cer;
ssl_dhparam /etc/nginx/dhparam.pem;
ssl_session_timeout 5m;
@ -27,6 +26,7 @@ server {
root /home/cryptpad/cryptpad;
index index.html;
error_page 404 /customize.dist/404.html;
if ($args ~ ver=) {
set $cacheControl max-age=31536000;
@ -34,25 +34,31 @@ server {
# Will not set any header if it is emptystring
add_header Cache-Control $cacheControl;
set $styleSrc "'unsafe-inline' 'self'";
set $scriptSrc "'self'";
set $connectSrc "'self' wss://cryptpad.fr wss://api.cryptpad.fr";
set $fontSrc "'self'";
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' beta.cryptpad.fr";
if ($uri = /pad/inner.html) {
set $scriptSrc "'self' 'unsafe-eval' 'unsafe-inline'";
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 $unsafe 0;
if ($uri = "/pad/inner.html") { set $unsafe 1; }
if ($host != sandbox.cryptpad.info) { set $unsafe 0; }
if ($unsafe) {
set $scriptSrc "'self' 'unsafe-eval' 'unsafe-inline' new2.cryptpad.fr cryptpad.fr";
}
add_header Content-Security-Policy "default-src 'none'; style-src $styleSrc; script-src $scriptSrc; connect-src $connectSrc; font-src $fontSrc; img-src $imgSrc; frame-src $frameSrc;";
add_header Content-Security-Policy "default-src 'none'; child-src $childSrc; worker-src $workerSrc; media-src $mediaSrc; style-src $styleSrc; script-src $scriptSrc; connect-src $connectSrc; font-src $fontSrc; img-src $imgSrc; frame-src $frameSrc;";
location = /cryptpad_websocket {
location ^~ /cryptpad_websocket {
proxy_pass http://localhost:3000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# WebSocket support (nginx 1.4)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
@ -75,14 +81,23 @@ server {
}
location ^~ /blob/ {
add_header Cache-Control max-age=31536000;
try_files $uri =404;
}
location ^~ /block/ {
add_header Cache-Control max-age=0;
try_files $uri =404;
}
## TODO fix in the code so that we don't need this
location ~ ^/(register|login|settings|user|pad|drive|poll|slide|code|whiteboard|file|media)$ {
location ^~ /datastore/ {
add_header Cache-Control max-age=0;
try_files $uri =404;
}
location ~ ^/(register|login|settings|user|pad|drive|poll|slide|code|whiteboard|file|media|profile|contacts|todo|filepicker|debug|kanban)$ {
rewrite ^(.*)$ $1/ redirect;
}
try_files /www/$uri /www/$uri/index.html /customize/$uri;
}

@ -1,12 +1,16 @@
{
"name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server",
"version": "1.28.0",
"license": "AGPL-3.0-or-later",
"version": "2.6.0",
"license": "AGPL-3.0+",
"repository": {
"type": "git",
"url": "git://github.com/xwiki-labs/cryptpad.git"
},
"dependencies": {
"chainpad-server": "^2.0.0",
"chainpad-server": "~2.1.0",
"express": "~4.16.0",
"mkdirp": "^0.5.1",
"fs-extra": "^7.0.0",
"nthen": "~0.1.0",
"pull-stream": "^3.6.1",
"replify": "^1.2.0",

@ -15,6 +15,7 @@ const hashesFromPinFile = (pinFile, fileName) => {
switch (l[0]) {
case 'RESET': {
pins = {};
if (l[1] && l[1].length) { l[1].forEach((x) => { pins[x] = 1; }); }
//jshint -W086
// fallthrough
}
@ -32,7 +33,7 @@ const hashesFromPinFile = (pinFile, fileName) => {
return Object.keys(pins);
};
module.exports.load = function (cb) {
module.exports.load = function (cb, config) {
nThen((waitFor) => {
Fs.readdir('./pins', waitFor((err, list) => {
if (err) {
@ -49,7 +50,10 @@ module.exports.load = function (cb) {
sema.take((returnAfter) => {
Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => {
if (err) { throw err; }
list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); });
list2.forEach((ff) => {
if (config && config.exclude && config.exclude.indexOf(ff) > -1) { return; }
fileList.push('./pins/' + f + '/' + ff);
});
})));
});
});

@ -3,14 +3,21 @@ const Fs = require('fs');
const Semaphore = require('saferphore');
const nThen = require('nthen');
/*
takes contents of a pinFile (UTF8 string)
and the pin file's name
returns an array of of channel ids which are pinned
throw errors on pin logs with invalid pin data
*/
const hashesFromPinFile = (pinFile, fileName) => {
var pins = {};
pinFile.split('\n').filter((x)=>(x)).map((l) => JSON.parse(l)).forEach((l) => {
switch (l[0]) {
case 'RESET': {
pins = {};
//jshint -W086
// fallthrough
if (l[1] && l[1].length) { l[1].forEach((x) => { pins[x] = 1; }); }
break;
}
case 'PIN': {
l[1].forEach((x) => { pins[x] = 1; });
@ -26,6 +33,11 @@ const hashesFromPinFile = (pinFile, fileName) => {
return Object.keys(pins);
};
/*
takes an array of pinned file names
and a global map of stats indexed by public keys
returns the sum of the size of those pinned files
*/
const sizeForHashes = (hashes, dsFileStats) => {
let sum = 0;
hashes.forEach((h) => {
@ -39,23 +51,31 @@ const sizeForHashes = (hashes, dsFileStats) => {
return sum;
};
// do twenty things at a time
const sema = Semaphore.create(20);
let dirList;
const fileList = [];
const dsFileStats = {};
const out = [];
const pinned = {};
const fileList = []; // array which we reuse for a lot of things
const dsFileStats = {}; // map of stats
const out = []; // what we return at the end
const pinned = {}; // map of pinned files
// define a function: 'load' which takes a config
// and a callback
module.exports.load = function (config, cb) {
nThen((waitFor) => {
// read the subdirectories in the datastore
Fs.readdir('./datastore', waitFor((err, list) => {
if (err) { throw err; }
dirList = list;
}));
}).nThen((waitFor) => {
// iterate over all subdirectories
dirList.forEach((f) => {
// process twenty subdirectories simultaneously
sema.take((returnAfter) => {
// get the list of files in every subdirectory
// and push them to 'fileList'
Fs.readdir('./datastore/' + f, waitFor(returnAfter((err, list2) => {
if (err) { throw err; }
list2.forEach((ff) => { fileList.push('./datastore/' + f + '/' + ff); });
@ -63,14 +83,19 @@ module.exports.load = function (config, cb) {
});
});
}).nThen((waitFor) => {
// read the subdirectories in 'blob'
Fs.readdir('./blob', waitFor((err, list) => {
if (err) { throw err; }
// overwrite dirList
dirList = list;
}));
}).nThen((waitFor) => {
// iterate over all subdirectories
dirList.forEach((f) => {
// process twenty subdirectories simultaneously
sema.take((returnAfter) => {
// get the list of files in every subdirectory
// and push them to 'fileList'
Fs.readdir('./blob/' + f, waitFor(returnAfter((err, list2) => {
if (err) { throw err; }
list2.forEach((ff) => { fileList.push('./blob/' + f + '/' + ff); });
@ -78,24 +103,34 @@ module.exports.load = function (config, cb) {
});
});
}).nThen((waitFor) => {
// iterate over the fileList
fileList.forEach((f) => {
// process twenty files simultaneously
sema.take((returnAfter) => {
// get the stats of each files
Fs.stat(f, waitFor(returnAfter((err, st) => {
if (err) { throw err; }
st.filename = f;
// push them to a big map of stats
dsFileStats[f.replace(/^.*\/([^\/\.]*)(\.ndjson)?$/, (all, a) => (a))] = st;
})));
});
});
}).nThen((waitFor) => {
// read the subdirectories in the pinstore
Fs.readdir('./pins', waitFor((err, list) => {
if (err) { throw err; }
dirList = list;
}));
}).nThen((waitFor) => {
// set file list to an empty array
// fileList = [] ??
fileList.splice(0, fileList.length);
dirList.forEach((f) => {
// process twenty directories at a time
sema.take((returnAfter) => {
// get the list of files in every subdirectory
// and push them to 'fileList' (which is empty because we keep reusing it)
Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => {
if (err) { throw err; }
list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); });
@ -103,70 +138,147 @@ module.exports.load = function (config, cb) {
});
});
}).nThen((waitFor) => {
// iterate over the list of pin logs
fileList.forEach((f) => {
// twenty at a time
sema.take((returnAfter) => {
// read the full content
Fs.readFile(f, waitFor(returnAfter((err, content) => {
if (err) { throw err; }
// get the list of channels pinned by this log
const hashes = hashesFromPinFile(content.toString('utf8'), f);
const size = sizeForHashes(hashes, dsFileStats);
if (config.unpinned) {
hashes.forEach((x) => { pinned[x] = 1; });
} else {
// get the size of files pinned by this log
// but only if we're gonna use it
let size = sizeForHashes(hashes, dsFileStats);
// we will return a list of values
// [user_public_key, size_of_files_they_have_pinned]
out.push([f, Math.floor(size / (1024 * 1024))]);
}
})));
});
});
}).nThen(() => {
// handle all the information you've processed so far
if (config.unpinned) {
// the user wants data about what has not been pinned
// by default we concern ourselves with pads and files older than infinity (everything)
let before = Infinity;
// but you can override this with config
if (config.olderthan) {
before = config.olderthan;
if (isNaN(before)) {
// FIXME validate inputs before doing the heavy lifting
if (isNaN(before)) { // make sure the supplied value is a number
return void cb('--olderthan error [' + config.olderthan + '] not a valid date');
}
}
// you can specify a different time for blobs...
let blobsbefore = before;
if (config.blobsolderthan) {
// use the supplied date if it exists
blobsbefore = config.blobsolderthan;
if (isNaN(blobsbefore)) {
return void cb('--blobsolderthan error [' + config.blobsolderthan + '] not a valid date');
}
}
let files = [];
// iterate over all the stats that you've saved
Object.keys(dsFileStats).forEach((f) => {
// we only care about files which are not in the pin map
if (!(f in pinned)) {
// check if it's a blob or a 'pad'
const isBlob = dsFileStats[f].filename.indexOf('.ndjson') === -1;
if ((+dsFileStats[f].atime) >= ((isBlob) ? blobsbefore : before)) { return; }
// if the mtime is newer than the specified value for its file type, ignore this file
if ((+dsFileStats[f].mtime) >= ((isBlob) ? blobsbefore : before)) { return; }
// otherwise push it to the list of files, with its filename, size, and mtime
files.push({
filename: dsFileStats[f].filename,
size: dsFileStats[f].size,
atime: dsFileStats[f].atime
mtime: dsFileStats[f].mtime
});
}
});
// return the list of files
cb(null, files);
} else {
// if you're not in 'unpinned' mode, sort by size (ascending)
out.sort((a,b) => (a[1] - b[1]));
// and return the sorted data
cb(null, out.slice());
}
});
};
// This script can be called directly on its own
// or required as part of another script
if (!module.parent) {
let config = {};
// if no parent, it is being invoked directly
let config = {}; // build the config from command line arguments...
// --unpinned gets the list of unpinned files
// if you don't pass this, it will list the size of pinned data per user
if (process.argv.indexOf('--unpinned') > -1) { config.unpinned = true; }
// '--olderthan' must be used in conjunction with '--unpinned'
// if you pass '--olderthan' with a string date or number, it will limit
// results only to pads older than the supplied time
// it defaults to 'infinity', or no filter at all
const ot = process.argv.indexOf('--olderthan');
config.olderthan = ot > -1 && new Date(process.argv[ot+1]);
if (ot > -1) {
config.olderthan = Number(process.argv[ot+1]) ? new Date(Number(process.argv[ot+1]))
: new Date(process.argv[ot+1]);
}
// '--blobsolderthan' must be used in conjunction with '--unpinned'
// if you pass '--blobsolderthan with a string date or number, it will limit
// results only to blobs older than the supplied time
// it defaults to using the same value passed '--olderthan'
const bot = process.argv.indexOf('--blobsolderthan');
config.blobsolderthan = bot > -1 && new Date(process.argv[bot+1]);
if (bot > -1) {
config.blobsolderthan = Number(process.argv[bot+1]) ? new Date(Number(process.argv[bot+1]))
: new Date(process.argv[bot+1]);
}
// call our big function directly
// pass our constructed configuration and a callback
module.exports.load(config, function (err, data) {
if (err) { throw new Error(err); }
if (!Array.isArray(data)) { return; }
if (err) { throw new Error(err); } // throw errors
if (!Array.isArray(data)) { return; } // if the returned value is not an array, you're done
if (config.unpinned) {
data.forEach((f) => { console.log(f.filename + " " + f.size + " " + (+f.atime)); });
// display the list of unpinned files with their size and mtime
data.forEach((f) => { console.log(f.filename + " " + f.size + " " + (+f.mtime)); });
} else {
// display the list of public keys and the size of the data they have pinned in megabytes
data.forEach((x) => { console.log(x[0] + ' ' + x[1] + ' MB'); });
}
});
}
/* Example usage of this script...
# display the list of public keys and the size of the data the have pinned in megabytes
node pinneddata.js
# display the list of unpinned pads and blobs with their size and mtime
node pinneddata.js --unpinned
# display the list of unpinned pads and blobs older than 12345 with their size and mtime
node pinneddata.js --unpinned --olderthan 12345
# display the list of unpinned pads older than 12345 and unpinned blobs older than 123
# each with their size and mtime
node pinneddata.js --unpinned --olderthan 12345 --blobsolderthan 123
*/

@ -35,7 +35,7 @@ As such, it is possible for a collaborator on the pad to include some silly/ugly
in a CryptPad such as an image which reveals your IP address when your browser automatically
loads it or a script which plays Rick Astleys's greatest hits. It is possible for anyone
who does not have the key to be able to change anything in the pad or add anything, even the
server, however the clients will notice this because the content hashes in ChainPad will fail to
server, however the clients will notice this because the content hashes in CryptPad will fail to
validate.
The server does have a certain power, it can send you evil javascript which does the wrong
@ -82,4 +82,4 @@ any later version. If you wish to use this technology in a proprietary product,
sales@xwiki.com.
[ChainPad]: https://github.com/xwiki-contrib/chainpad
[active attack]: https://en.wikipedia.org/wiki/Attack_(computing)#Types_of_attacks
[active attack]: https://en.wikipedia.org/wiki/Attack_(computing)#Types_of_attack

474
rpc.js

@ -7,13 +7,14 @@ var Nacl = require("tweetnacl");
/* globals process */
var Fs = require("fs");
var Fse = require("fs-extra");
var Path = require("path");
var Https = require("https");
const Package = require('./package.json');
const Pinned = require('./pinned');
const Saferphore = require("saferphore");
const nThen = require("nthen");
const Mkdirp = require("mkdirp");
var RPC = module.exports;
@ -25,7 +26,7 @@ var SUPPRESS_RPC_ERRORS = false;
var WARN = function (e, output) {
if (!SUPPRESS_RPC_ERRORS && e && output) {
console.error(new Date().toISOString() + ' [' + e + ']', output);
console.error(new Date().toISOString() + ' [' + String(e) + ']', output);
console.error(new Error(e).stack);
console.error();
}
@ -36,6 +37,7 @@ var isValidId = function (chan) {
[32, 48].indexOf(chan.length) > -1;
};
/*
var uint8ArrayToHex = function (a) {
// call slice so Uint8Arrays work as expected
return Array.prototype.slice.call(a).map(function (e) {
@ -52,14 +54,24 @@ var uint8ArrayToHex = function (a) {
}
}).join('');
};
*/
var testFileId = function (id) {
if (id.length !== 48 || /[^a-f0-9]/.test(id)) {
return false;
}
return true;
};
/*
var createFileId = function () {
var id = uint8ArrayToHex(Nacl.randomBytes(24));
if (id.length !== 48 || /[^a-f0-9]/.test(id)) {
if (!testFileId(id)) {
throw new Error('file ids must consist of 48 hex characters');
}
return id;
};
*/
var makeToken = function () {
return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
@ -326,6 +338,24 @@ var getFileSize = function (Env, channel, cb) {
});
};
var getMetadata = function (Env, channel, cb) {
if (!isValidId(channel)) { return void cb('INVALID_CHAN'); }
if (channel.length === 32) {
if (typeof(Env.msgStore.getChannelMetadata) !== 'function') {
return cb('GET_CHANNEL_METADATA_UNSUPPORTED');
}
return void Env.msgStore.getChannelMetadata(channel, function (e, data) {
if (e) {
if (e.code === 'INVALID_METADATA') { return void cb(void 0, {}); }
return void cb(e.code);
}
cb(void 0, data);
});
}
};
var getMultipleFileSize = function (Env, channels, cb) {
if (!Array.isArray(channels)) { return cb('INVALID_PIN_LIST'); }
if (typeof(Env.msgStore.getChannelSize) !== 'function') {
@ -793,6 +823,17 @@ var makeFileStream = function (root, id, cb) {
});
};
var isFile = function (filePath, cb) {
/*:: if (typeof(filePath) !== 'string') { throw new Error('should never happen'); } */
Fs.stat(filePath, function (e, stats) {
if (e) {
if (e.code === 'ENOENT') { return void cb(void 0, false); }
return void cb(e.message);
}
return void cb(void 0, stats.isFile());
});
};
var clearOwnedChannel = function (Env, channelId, unsafeKey, cb) {
if (typeof(channelId) !== 'string' || channelId.length !== 32) {
return cb('INVALID_ARGUMENTS');
@ -816,11 +857,66 @@ var clearOwnedChannel = function (Env, channelId, unsafeKey, cb) {
});
};
var removeOwnedBlob = function (Env, blobId, unsafeKey, cb) {
var safeKey = escapeKeyCharacters(unsafeKey);
var safeKeyPrefix = safeKey.slice(0,3);
var blobPrefix = blobId.slice(0,2);
var blobPath = makeFilePath(Env.paths.blob, blobId);
var ownPath = Path.join(Env.paths.blob, safeKeyPrefix, safeKey, blobPrefix, blobId);
nThen(function (w) {
// Check if the blob exists
isFile(blobPath, w(function (e, isFile) {
if (e) {
w.abort();
return void cb(e);
}
if (!isFile) {
WARN('removeOwnedBlob', 'The provided blob ID is not a file!');
w.abort();
return void cb('EINVAL_BLOBID');
}
}));
}).nThen(function (w) {
// Check if you're the owner
isFile(ownPath, w(function (e, isFile) {
if (e) {
w.abort();
return void cb(e);
}
if (!isFile) {
WARN('removeOwnedBlob', 'Incorrect owner');
w.abort();
return void cb('INSUFFICIENT_PERMISSIONS');
}
}));
}).nThen(function (w) {
// Delete the blob
/*:: if (typeof(blobPath) !== 'string') { throw new Error('should never happen'); } */
Fs.unlink(blobPath, w(function (e) {
if (e) {
w.abort();
return void cb(e.code);
}
}));
}).nThen(function () {
// Delete the proof of ownership
Fs.unlink(ownPath, function (e) {
cb(e && e.code);
});
});
};
var removeOwnedChannel = function (Env, channelId, unsafeKey, cb) {
if (typeof(channelId) !== 'string' || channelId.length !== 32) {
if (typeof(channelId) !== 'string' || !isValidId(channelId)) {
return cb('INVALID_ARGUMENTS');
}
if (testFileId(channelId)) {
return void removeOwnedBlob(Env, channelId, unsafeKey, cb);
}
if (!(Env.msgStore && Env.msgStore.removeChannel && Env.msgStore.getChannelMetadata)) {
return cb("E_NOT_IMPLEMENTED");
}
@ -858,7 +954,7 @@ var upload = function (Env, publicKey, content, cb) {
var session = getSession(Env.Sessions, publicKey);
if (typeof(session.currentUploadSize) !== 'number' ||
typeof(session.currentUploadSize) !== 'number') {
typeof(session.pendingUploadSize) !== 'number') {
// improperly initialized... maybe they didn't check before uploading?
// reject it, just in case
return cb('NOT_READY');
@ -884,12 +980,12 @@ var upload = function (Env, publicKey, content, cb) {
}
};
var upload_cancel = function (Env, publicKey, cb) {
var upload_cancel = function (Env, publicKey, fileSize, cb) {
var paths = Env.paths;
var session = getSession(Env.Sessions, publicKey);
delete session.currentUploadSize;
delete session.pendingUploadSize;
session.pendingUploadSize = fileSize;
session.currentUploadSize = 0;
if (session.blobstage) { session.blobstage.close(); }
var path = makeFilePath(paths.staging, publicKey);
@ -905,17 +1001,7 @@ var upload_cancel = function (Env, publicKey, cb) {
});
};
var isFile = function (filePath, cb) {
Fs.stat(filePath, function (e, stats) {
if (e) {
if (e.code === 'ENOENT') { return void cb(void 0, false); }
return void cb(e.message);
}
return void cb(void 0, stats.isFile());
});
};
var upload_complete = function (Env, publicKey, cb) {
var upload_complete = function (Env, publicKey, id, cb) {
var paths = Env.paths;
var session = getSession(Env.Sessions, publicKey);
@ -924,14 +1010,18 @@ var upload_complete = function (Env, publicKey, cb) {
delete session.blobstage;
}
if (!testFileId(id)) {
WARN('uploadComplete', "id is invalid");
return void cb('EINVAL_ID');
}
var oldPath = makeFilePath(paths.staging, publicKey);
if (!oldPath) {
WARN('safeMkdir', "oldPath is null");
return void cb('RENAME_ERR');
}
var tryRandomLocation = function (cb) {
var id = createFileId();
var tryLocation = function (cb) {
var prefix = id.slice(0, 2);
var newPath = makeFilePath(paths.blob, id);
if (typeof(newPath) !== 'string') {
@ -950,7 +1040,8 @@ var upload_complete = function (Env, publicKey, cb) {
return void cb(e);
}
if (yes) {
return void tryRandomLocation(cb);
WARN('isFile', 'FILE EXISTS!');
return void cb('RENAME_ERR');
}
cb(void 0, newPath, id);
@ -958,40 +1049,25 @@ var upload_complete = function (Env, publicKey, cb) {
});
};
var retries = 3;
var handleMove = function (e, newPath, id) {
if (e || !oldPath || !newPath) {
if (retries--) {
setTimeout(function () {
return tryRandomLocation(handleMove);
}, 750);
} else {
cb(e);
}
return;
return void cb(e || 'PATH_ERR');
}
// lol wut handle ur errors
Fs.rename(oldPath, newPath, function (e) {
Fse.move(oldPath, newPath, function (e) {
if (e) {
WARN('rename', e);
if (retries--) {
return void setTimeout(function () {
tryRandomLocation(handleMove);
}, 750);
}
return void cb('RENAME_ERR');
}
cb(void 0, id);
});
};
tryRandomLocation(handleMove);
tryLocation(handleMove);
};
/*
var owned_upload_complete = function (Env, safeKey, cb) {
var session = getSession(Env.Sessions, safeKey);
@ -1051,7 +1127,7 @@ var owned_upload_complete = function (Env, safeKey, cb) {
var finalPath;
nThen(function (w) {
// make the requisite directory structure using Mkdirp
Mkdirp(plannedPath, w(function (e /*, path */) {
Mkdirp(plannedPath, w(function (e) {
if (e) { // does not throw error if the directory already existed
w.abort();
return void cb(e);
@ -1070,8 +1146,8 @@ var owned_upload_complete = function (Env, safeKey, cb) {
// move the existing file to its new path
// flow is dumb and I need to guard against this which will never happen
/*:: if (typeof(oldPath) === 'object') { throw new Error('should never happen'); } */
Fs.rename(oldPath /* XXX */, finalPath, w(function (e) {
// / *:: if (typeof(oldPath) === 'object') { throw new Error('should never happen'); } * /
Fs.move(oldPath, finalPath, w(function (e) {
if (e) {
w.abort();
return void cb(e.code);
@ -1084,6 +1160,118 @@ var owned_upload_complete = function (Env, safeKey, cb) {
cb(void 0, blobId);
});
};
*/
var owned_upload_complete = function (Env, safeKey, id, cb) {
var session = getSession(Env.Sessions, safeKey);
// the file has already been uploaded to the staging area
// close the pending writestream
if (session.blobstage && session.blobstage.close) {
session.blobstage.close();
delete session.blobstage;
}
if (!testFileId(id)) {
WARN('ownedUploadComplete', "id is invalid");
return void cb('EINVAL_ID');
}
var oldPath = makeFilePath(Env.paths.staging, safeKey);
if (typeof(oldPath) !== 'string') {
return void cb('EINVAL_CONFIG');
}
// construct relevant paths
var root = Env.paths.blob;
//var safeKey = escapeKeyCharacters(safeKey);
var safeKeyPrefix = safeKey.slice(0, 3);
//var blobId = createFileId();
var blobIdPrefix = id.slice(0, 2);
var ownPath = Path.join(root, safeKeyPrefix, safeKey, blobIdPrefix);
var filePath = Path.join(root, blobIdPrefix);
var tryId = function (path, cb) {
Fs.access(path, Fs.constants.R_OK | Fs.constants.W_OK, function (e) {
if (!e) {
// generate a new id (with the same prefix) and recurse
WARN('ownedUploadComplete', 'id is already used '+ id);
return void cb('EEXISTS');
} else if (e.code === 'ENOENT') {
// no entry, so it's safe for us to proceed
return void cb();
} else {
// it failed in an unexpected way. log it
WARN('ownedUploadComplete', e);
return void cb(e.code);
}
});
};
// the user wants to move it into blob and create a empty file with the same id
// in their own space:
// /blob/safeKeyPrefix/safeKey/blobPrefix/blobID
var finalPath;
var finalOwnPath;
nThen(function (w) {
// make the requisite directory structure using Mkdirp
Fse.mkdirp(filePath, w(function (e /*, path */) {
if (e) { // does not throw error if the directory already existed
w.abort();
return void cb(e.code);
}
}));
Fse.mkdirp(ownPath, w(function (e /*, path */) {
if (e) { // does not throw error if the directory already existed
w.abort();
return void cb(e.code);
}
}));
}).nThen(function (w) {
// make sure the id does not collide with another
finalPath = Path.join(filePath, id);
finalOwnPath = Path.join(ownPath, id);
tryId(finalPath, w(function (e) {
if (e) {
w.abort();
return void cb(e);
}
}));
}).nThen(function (w) {
// Create the empty file proving ownership
Fs.writeFile(finalOwnPath, '', w(function (e) {
if (e) {
w.abort();
return void cb(e.code);
}
// otherwise it worked...
}));
}).nThen(function (w) {
// move the existing file to its new path
// flow is dumb and I need to guard against this which will never happen
/*:: if (typeof(oldPath) === 'object') { throw new Error('should never happen'); } */
Fse.move(oldPath, finalPath, w(function (e) {
if (e) {
// Remove the ownership file
Fs.unlink(finalOwnPath, function (e) {
WARN('E_UNLINK_OWN_FILE', e);
});
w.abort();
return void cb(e.code);
}
// otherwise it worked...
}));
}).nThen(function () {
// clean up their session when you're done
// call back with the blob id...
cb(void 0, id);
});
};
var upload_status = function (Env, publicKey, filesize, cb) {
var paths = Env.paths;
@ -1110,6 +1298,159 @@ var upload_status = function (Env, publicKey, filesize, cb) {
});
};
/*
We assume that the server is secured against MitM attacks
via HTTPS, and that malicious actors do not have code execution
capabilities. If they do, we have much more serious problems.
The capability to replay a block write or remove results in either
a denial of service for the user whose block was removed, or in the
case of a write, a rollback to an earlier password.
Since block modification is destructive, this can result in loss
of access to the user's drive.
So long as the detached signature is never observed by a malicious
party, and the server discards it after proof of knowledge, replays
are not possible. However, this precludes verification of the signature
at a later time.
Despite this, an integrity check is still possible by the original
author of the block, since we assume that the block will have been
encrypted with xsalsa20-poly1305 which is authenticated.
*/
var validateLoginBlock = function (Env, publicKey, signature, block, cb) {
// convert the public key to a Uint8Array and validate it
if (typeof(publicKey) !== 'string') { return void cb('E_INVALID_KEY'); }
var u8_public_key;
try {
u8_public_key = Nacl.util.decodeBase64(publicKey);
} catch (e) {
return void cb('E_INVALID_KEY');
}
var u8_signature;
try {
u8_signature = Nacl.util.decodeBase64(signature);
} catch (e) {
console.error(e);
return void cb('E_INVALID_SIGNATURE');
}
// convert the block to a Uint8Array
var u8_block;
try {
u8_block = Nacl.util.decodeBase64(block);
} catch (e) {
return void cb('E_INVALID_BLOCK');
}
// take its hash
var hash = Nacl.hash(u8_block);
// validate the signature against the hash of the content
var verified = Nacl.sign.detached.verify(hash, u8_signature, u8_public_key);
// existing authentication ensures that users cannot replay old blocks
// call back with (err) if unsuccessful
if (!verified) { return void cb("E_COULD_NOT_VERIFY"); }
return void cb(null, u8_block);
};
var createLoginBlockPath = function (Env, publicKey) {
// prepare publicKey to be used as a file name
var safeKey = escapeKeyCharacters(publicKey);
// validate safeKey
if (typeof(safeKey) !== 'string') {
return;
}
// derive the full path
// /home/cryptpad/cryptpad/block/fg/fg32kefksjdgjkewrjksdfksjdfsdfskdjfsfd
return Path.join(Env.paths.block, safeKey.slice(0, 2), safeKey);
};
var writeLoginBlock = function (Env, msg, cb) {
//console.log(msg);
var publicKey = msg[0];
var signature = msg[1];
var block = msg[2];
validateLoginBlock(Env, publicKey, signature, block, function (e, validatedBlock) {
if (e) { return void cb(e); }
if (!(validatedBlock instanceof Uint8Array)) { return void cb('E_INVALID_BLOCK'); }
// derive the filepath
var path = createLoginBlockPath(Env, publicKey);
// make sure the path is valid
if (typeof(path) !== 'string') {
return void cb('E_INVALID_BLOCK_PATH');
}
var parsed = Path.parse(path);
if (!parsed || typeof(parsed.dir) !== 'string') {
return void cb("E_INVALID_BLOCK_PATH_2");
}
nThen(function (w) {
// make sure the path to the file exists
Fse.mkdirp(parsed.dir, w(function (e) {
if (e) {
w.abort();
cb(e);
}
}));
}).nThen(function () {
// actually write the block
// flow is dumb and I need to guard against this which will never happen
/*:: if (typeof(validatedBlock) === 'undefined') { throw new Error('should never happen'); } */
/*:: if (typeof(path) === 'undefined') { throw new Error('should never happen'); } */
Fs.writeFile(path, new Buffer(validatedBlock), { encoding: "binary", }, function (err) {
if (err) { return void cb(err); }
cb();
});
});
});
};
/*
When users write a block, they upload the block, and provide
a signature proving that they deserve to be able to write to
the location determined by the public key.
When removing a block, there is nothing to upload, but we need
to sign something. Since the signature is considered sensitive
information, we can just sign some constant and use that as proof.
*/
var removeLoginBlock = function (Env, msg, cb) {
var publicKey = msg[0];
var signature = msg[1];
var block = Nacl.util.decodeUTF8('DELETE_BLOCK'); // clients and the server will have to agree on this constant
validateLoginBlock(Env, publicKey, signature, block, function (e /*::, validatedBlock */) {
if (e) { return void cb(e); }
// derive the filepath
var path = createLoginBlockPath(Env, publicKey);
// make sure the path is valid
if (typeof(path) !== 'string') {
return void cb('E_INVALID_BLOCK_PATH');
}
Fs.unlink(path, function (err) {
if (err) { return void cb(err); }
cb();
});
});
};
var isNewChannel = function (Env, channel, cb) {
if (!isValidId(channel)) { return void cb('INVALID_CHAN'); }
if (channel.length !== 32) { return void cb('INVALID_CHAN'); }
@ -1139,6 +1480,7 @@ var isNewChannel = function (Env, channel, cb) {
var isUnauthenticatedCall = function (call) {
return [
'GET_FILE_SIZE',
'GET_METADATA',
'GET_MULTIPLE_FILE_SIZE',
'IS_CHANNEL_PINNED',
'IS_NEW_CHANNEL',
@ -1165,6 +1507,8 @@ var isAuthenticatedCall = function (call) {
'CLEAR_OWNED_CHANNEL',
'REMOVE_OWNED_CHANNEL',
'REMOVE_PINS',
'WRITE_LOGIN_BLOCK',
'REMOVE_LOGIN_BLOCK',
].indexOf(call) !== -1;
};
@ -1235,6 +1579,7 @@ RPC.create = function (
var pinPath = paths.pin = keyOrDefaultString('pinPath', './pins');
var blobPath = paths.blob = keyOrDefaultString('blobPath', './blob');
var blobStagingPath = paths.staging = keyOrDefaultString('blobStagingPath', './blobstage');
paths.block = keyOrDefaultString('blockPath', './block');
var isUnauthenticateMessage = function (msg) {
return msg && msg.length === 2 && isUnauthenticatedCall(msg[0]);
@ -1260,12 +1605,14 @@ RPC.create = function (
}
case 'GET_FILE_SIZE':
return void getFileSize(Env, msg[1], function (e, size) {
if (e) {
console.error(e);
}
WARN(e, msg[1]);
respond(e, [null, size, null]);
});
case 'GET_METADATA':
return void getMetadata(Env, msg[1], function (e, data) {
WARN(e, msg[1]);
respond(e, [null, data, null]);
});
case 'GET_MULTIPLE_FILE_SIZE':
return void getMultipleFileSize(Env, msg[1], function (e, dict) {
if (e) {
@ -1369,7 +1716,7 @@ RPC.create = function (
var session = Sessions[safeKey];
var token = session? session.tokens.slice(-1)[0]: '';
var cookie = makeCookie(token).join('|');
respond(e, [cookie].concat(typeof(msg) !== 'undefined' ?msg: []));
respond(e ? String(e): e, [cookie].concat(typeof(msg) !== 'undefined' ?msg: []));
};
if (typeof(msg) !== 'object' || !msg.length) {
@ -1458,9 +1805,9 @@ RPC.create = function (
Respond(void 0, "OK");
});
case 'REMOVE_PINS':
return void removePins(Env, safeKey, function (e, response) {
return void removePins(Env, safeKey, function (e) {
if (e) { return void Respond(e); }
Respond(void 0, response);
Respond(void 0, "OK");
});
// restricted to privileged users...
case 'UPLOAD':
@ -1483,20 +1830,39 @@ RPC.create = function (
});
case 'UPLOAD_COMPLETE':
if (!privileged) { return deny(); }
return void upload_complete(Env, safeKey, function (e, hash) {
return void upload_complete(Env, safeKey, msg[1], function (e, hash) {
WARN(e, hash);
Respond(e, hash);
});
case 'OWNED_UPLOAD_COMPLETE':
if (!privileged) { return deny(); }
return void owned_upload_complete(Env, safeKey, function (e, blobId) {
return void owned_upload_complete(Env, safeKey, msg[1], function (e, blobId) {
WARN(e, blobId);
Respond(e, blobId);
});
case 'UPLOAD_CANCEL':
if (!privileged) { return deny(); }
return void upload_cancel(Env, safeKey, function (e) {
WARN(e);
// msg[1] is fileSize
// if we pass it here, we can start an upload right away without calling
// UPLOAD_STATUS again
return void upload_cancel(Env, safeKey, msg[1], function (e) {
WARN(e, 'UPLOAD_CANCEL');
Respond(e);
});
case 'WRITE_LOGIN_BLOCK':
return void writeLoginBlock(Env, msg[1], function (e) {
if (e) {
WARN(e, 'WRITE_LOGIN_BLOCK');
return void Respond(e);
}
Respond(e);
});
case 'REMOVE_LOGIN_BLOCK':
return void removeLoginBlock(Env, msg[1], function (e) {
if (e) {
WARN(e, 'REMOVE_LOGIN_BLOCK');
return void Respond(e);
}
Respond(e);
});
default:

@ -124,6 +124,12 @@ app.get(mainPagePattern, Express.static(__dirname + '/customize.dist'));
app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob')), {
maxAge: DEV_MODE? "0d": "365d"
}));
app.use("/datastore", Express.static(Path.join(__dirname, (config.filePath || './datastore')), {
maxAge: "0d"
}));
app.use("/block", Express.static(Path.join(__dirname, (config.blockPath || '/block')), {
maxAge: "0d",
}));
app.use("/customize", Express.static(__dirname + '/customize'));
app.use("/customize", Express.static(__dirname + '/customize.dist'));
@ -160,7 +166,7 @@ app.get('/api/config', function(req, res){
res.send('define(function(){\n' + [
'var obj = ' + JSON.stringify({
requireConf: {
waitSeconds: 60,
waitSeconds: 600,
urlArgs: 'ver=' + Package.version + (FRESH_KEY? '-' + FRESH_KEY: '') + (DEV_MODE? '-' + (+new Date()): ''),
},
removeDonateButton: (config.removeDonateButton === true),
@ -189,6 +195,7 @@ var custom_four04_path = Path.resolve(__dirname + '/customize/404.html');
var send404 = function (res, path) {
if (!path && path !== four04_path) { path = four04_path; }
Fs.exists(path, function (exists) {
res.setHeader('Content-Type', 'text/html; charset=utf-8');
if (exists) { return Fs.createReadStream(path).pipe(res); }
send404(res);
});

@ -9,9 +9,14 @@ define([
'/common/common-thumbnail.js',
'/common/wire.js',
'/common/flat-dom.js',
], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat) {
'/common/media-tag.js',
'/common/outer/login-block.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat, MediaTag, Block) {
window.Hyperjson = Hyperjson;
window.Sortify = Sortify;
var Nacl = window.nacl;
var assertions = 0;
var failed = false;
@ -132,6 +137,40 @@ define([
strungJSON(orig);
});
HTML_list.forEach(function (sel) {
var el = $(sel)[0];
var pred = function (el) {
if (el.nodeName === 'DIV') {
return true;
}
};
var filter = function (x) {
console.log(x);
if (x[1]['class']) {
x[1]['class'] = x[1]['class'].replace(/cke/g, '');
}
return x;
};
assert(function (cb) {
// FlatDOM output
var map = Flat.fromDOM(el, pred, filter);
// Hyperjson output
var hj = Hyperjson.fromDOM(el, pred, filter);
var x = Flat.toDOM(map);
var y = Hyperjson.toDOM(hj);
console.error(x.outerHTML);
console.error(y.outerHTML);
cb(x.outerHTML === y.outerHTML);
}, "Test equality of FlatDOM and HyperJSON");
});
// check that old hashes parse correctly
assert(function (cb) {
//if (1) { return cb(true); } // TODO(cjd): This is a test failure which is a known bug
@ -223,6 +262,33 @@ define([
hd.type === 'invite');
}, "test support for invite urls");
// test support for V2
assert(function (cb) {
var parsed = Hash.parsePadUrl('/pad/#/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/');
var secret = Hash.getSecrets('pad', '/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/');
return cb(parsed.hashData.version === 2 &&
parsed.hashData.mode === "edit" &&
parsed.hashData.type === "pad" &&
parsed.hashData.key === "oRE0oLCtEXusRDyin7GyLGcS" &&
secret.channel === "d8d51b4aea863f3f050f47f8ad261753" &&
window.nacl.util.encodeBase64(secret.keys.cryptKey) === "0Ts1M6VVEozErV2Nx/LTv6Im5SCD7io2LlhasyyBPQo=" &&
secret.keys.validateKey === "f5A1FM9Gp55tnOcM75RyHD1oxBG9ZPh9WDA7qe2Fvps=" &&
!parsed.hashData.present);
}, "test support for version 2 hash failed to parse");
assert(function (cb) {
var parsed = Hash.parsePadUrl('/pad/#/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed');
var secret = Hash.getSecrets('pad', '/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed', 'pewpew');
return cb(parsed.hashData.version === 2 &&
parsed.hashData.mode === "edit" &&
parsed.hashData.type === "pad" &&
parsed.hashData.key === "HGu0tK2od-2BBnwAz2ZNS-t4" &&
secret.channel === "3fb6dc93807d903aff390b5f798c92c9" &&
window.nacl.util.encodeBase64(secret.keys.cryptKey) === "EeCkGJra8eJgVu7v4Yl2Hc3yUjrgpKpxr0Lcc3bSWVs=" &&
secret.keys.validateKey === "WGkBczJf2V6vQZfAScz8V1KY6jKdoxUCckrD+E75gGE=" &&
parsed.hashData.embed &&
parsed.hashData.password);
}, "test support for password in version 2 hash failed to parse");
assert(function (cb) {
var url = '/pad/?utm_campaign=new_comment&utm_medium=email&utm_source=thread_mailer#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/';
var secret = Hash.parsePadUrl(url);
@ -234,6 +300,35 @@ define([
!secret.hashData.present);
}, "test support for ugly tracking query paramaters in url");
assert(function (cb) {
var keys = Block.genkeys(Nacl.randomBytes(64));
var hash = Block.getBlockHash(keys);
var parsed = Block.parseBlockHash(hash);
cb(parsed &&
parsed.keys.symmetric.length === keys.symmetric.length);
}, 'parse a block hash');
assert(function (cb) {
try {
MediaTag(void 0).on('progress').on('decryption');
return void cb(true);
} catch (e) {
console.error(e);
return void cb(false);
}
}, 'check that MediaTag does the right thing when passed no value');
assert(function (cb) {
try {
MediaTag(document.createElement('div')).on('progress').on('decryption');
return void cb(true);
} catch (e) {
console.error(e);
return void cb(false);
}
}, 'check that MediaTag does the right thing when passed no value');
assert(function (cb) {
// TODO
return cb(true);

@ -1,16 +1,14 @@
@import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/markdown.less";
@import (once) "../../customize/src/less2/include/framework.less";
@import (reference) "../../customize/src/less2/include/browser.less";
@import (reference) "../../customize/src/less2/include/markdown.less";
@import (reference) "../../customize/src/less2/include/framework.less";
&.cp-app-code {
.framework_main(
@bg-color: @colortheme_code-bg,
@warn-color: @colortheme_code-warn,
@color: @colortheme_code-color
);
// body
&.cp-app-code {
display: flex;
flex-flow: column;
max-height: 100%;

@ -37,6 +37,8 @@ define([
'cm/addon/fold/comment-fold',
'cm/addon/display/placeholder',
'less!/code/app-code.less'
], function (
$,
DiffMd,
@ -108,7 +110,7 @@ define([
return;
}
$previewContainer.removeClass('cp-app-code-preview-isempty');
DiffMd.apply(DiffMd.render(editor.getValue()), $preview);
DiffMd.apply(DiffMd.render(editor.getValue()), $preview, framework._.sfCommon);
} catch (e) { console.error(e); }
};
var drawPreview = Util.throttle(function () {
@ -331,13 +333,11 @@ define([
dropArea: $('.CodeMirror'),
body: $('body'),
onUploaded: function (ev, data) {
//var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')';
var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
var secret = Hash.getSecrets('file', parsed.hash, data.password);
var src = Hash.getBlobPathFromHex(secret.channel);
var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
editor.replaceSelection(mt);
}
};

@ -4,9 +4,11 @@ const define = (x:any, y:any) => {};
const require = define;
*/
define([
'/api/config'
], function (Config) { /*::});module.exports = (function() {
'/api/config',
'/bower_components/nthen/index.js'
], function (Config, nThen) { /*::});module.exports = (function() {
const Config = (undefined:any);
const nThen = (undefined:any);
*/
var module = { exports: {} };
@ -49,6 +51,7 @@ define([
if (ua[0].indexOf(':') === -1 && ua[0].indexOf('/') && parent) {
ua[0] = parent.replace(/\/[^\/]*$/, '/') + ua[0];
}
ua[0] = ua[0].replace(/^\/\.\.\//, '/');
var out = ua.join('#');
//console.log(url + " --> " + out);
return out;
@ -91,17 +94,36 @@ define([
};
var lessEngine;
var tempCache = { key: Math.random() };
var getLessEngine = function (cb) {
if (lessEngine) {
cb(lessEngine);
} else {
require(['/bower_components/less/dist/less.min.js'], function (Less) {
if (lessEngine) { return void cb(lessEngine); }
lessEngine = Less;
Less.functions.functionRegistry.add('LessLoader_currentFile', function () {
return new Less.tree.UnicodeDescriptor('"' +
fixURL(this.currentFileInfo.filename) + '"');
});
var doXHR = lessEngine.FileManager.prototype.doXHR;
lessEngine.FileManager.prototype.doXHR = function (url, type, callback, errback) {
url = fixURL(url);
//console.log("xhr: " + url);
return doXHR(url, type, callback, errback);
var cached = tempCache[url];
if (cached && cached.res) {
var res = cached.res;
return void setTimeout(function () { callback(res[0], res[1]); });
}
if (cached) { return void cached.queue.push(callback); }
cached = tempCache[url] = { queue: [ callback ], res: undefined };
return doXHR(url, type, function (text, lastModified) {
cached.res = [ text, lastModified ];
var queue = cached.queue;
cached.queue = [];
queue.forEach(function (f) {
setTimeout(function () { f(text, lastModified); });
});
}, errback);
};
cb(lessEngine);
});
@ -117,19 +139,38 @@ define([
});
};
module.exports.load = function (url /*:string*/, cb /*:()=>void*/) {
cacheGet(url, function (css) {
if (css) {
var loadSubmodulesAndInject = function (css, url, cb, stack) {
inject(css, url);
return void cb();
nThen(function (w) {
css.replace(/\-\-LessLoader_require\:\s*"([^"]*)"\s*;/g, function (all, u) {
u = u.replace(/\?.*$/, '');
module.exports.load(u, w(), stack);
return '';
});
}).nThen(function () { cb(); });
};
module.exports.load = function (url /*:string*/, cb /*:()=>void*/, stack /*:?Array<string>*/) {
var btime = stack ? null : +new Date();
stack = stack || [];
if (stack.indexOf(url) > -1) { return void cb(); }
var timeout = setTimeout(function () { console.log('failed', url); }, 10000);
var done = function () {
clearTimeout(timeout);
if (btime) {
console.log("Compiling [" + url + "] took " + (+new Date() - btime) + "ms");
}
cb();
};
stack.push(url);
cacheGet(url, function (css) {
if (css) { return void loadSubmodulesAndInject(css, url, done, stack); }
console.log('CACHE MISS ' + url);
((/\.less([\?\#].*)?$/.test(url)) ? loadLess : loadCSS)(url, function (err, css) {
if (!css) { return void console.error(err); }
var output = fixAllURLs(css, url);
cachePut(url, output);
inject(output, url);
cb();
loadSubmodulesAndInject(output, url, done, stack);
});
});
};

@ -9,9 +9,9 @@ define(function() {
/* Select the buttons displayed on the main page to create new collaborative sessions
* Existing types : pad, code, poll, slide
*/
config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'whiteboard',
config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'kanban', 'whiteboard',
'oodoc', 'ooslide', 'oocell', 'file', 'todo', 'contacts'];
config.registeredOnlyTypes = ['file', 'contacts'];
config.registeredOnlyTypes = ['file', 'contacts', 'oodoc', 'ooslide', 'oocell'];
/* Cryptpad apps use a common API to display notifications to users
* by default, notifications are hidden after 5 seconds
@ -85,6 +85,8 @@ define(function() {
oodoc: 'fa-file-word-o',
ooslide: 'fa-file-powerpoint-o',
oocell: 'fa-file-excel-o',
kanban: 'fa-columns',
drive: 'fa-hdd-o',
};
// Ability to create owned pads and expiring pads through a new pad creation screen.
@ -121,5 +123,18 @@ define(function() {
// You can use config.afterLogin to import these values in the users' drive.
//config.disableProfile = true;
// Disable the use of webworkers and sharedworkers in CryptPad.
// Workers allow us to run the websockets connection and open the user drive in a separate thread.
// SharedWorkers allow us to load only one websocket and one user drive for all the browser tabs,
// making it much faster to open new tabs.
// Warning: This is an experimental feature. It will be enabled by default once we're sure it's stable.
config.disableWorkers = true;
// Shared folder are in a beta-test state. They are likely to disappear from a user's drive
// spontaneously, resulting in the deletion of the entire folder's content.
// We highly recommend to keep them disabled until they are stable enough to be enabled
// by default by the CryptPad developers.
config.disableSharedFolders = true;
return config;
});

@ -34,7 +34,7 @@ define([
url: '/common/feedback.html?NO_LOCALSTORAGE=' + (+new Date()),
});
});
window.alert("CryptPad needs localStorage to work, try a different browser");
window.alert("CryptPad needs localStorage to work. Try changing your cookie permissions, or using a different browser");
};
window.onerror = function (e) {

@ -3,6 +3,7 @@ define(function () {
// localStorage
userHashKey: 'User_hash',
userNameKey: 'User_name',
blockHashKey: 'Block_hash',
fileHashKey: 'FS_hash',
// sessionStorage
newPadPathKey: "newPadPath",
@ -11,6 +12,7 @@ define(function () {
oldStorageKey: 'CryptPad_RECENTPADS',
storageKey: 'filesData',
tokenKey: 'loginToken',
displayPadCreationScreen: 'displayPadCreationScreen'
displayPadCreationScreen: 'displayPadCreationScreen',
deprecatedKey: 'deprecated'
};
});

@ -11,6 +11,7 @@ define([
var uint8ArrayToHex = Util.uint8ArrayToHex;
var hexToBase64 = Util.hexToBase64;
var base64ToHex = Util.base64ToHex;
Hash.encodeBase64 = Nacl.util.encodeBase64;
// This implementation must match that on the server
// it's used for a checksum
@ -19,22 +20,53 @@ define([
.decodeUTF8(JSON.stringify(list))));
};
var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) {
if (typeof keys === 'string') {
return chanKey + keys;
var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) {
var version = secret.version;
var data = secret.keys;
if (version === 0) {
return secret.channel + secret.key;
}
if (version === 1) {
if (!data.editKeyStr) { return; }
return '/1/edit/' + hexToBase64(secret.channel) +
'/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/';
}
if (version === 2) {
if (!data.editKeyStr) { return; }
var pass = secret.password ? 'p/' : '';
return '/2/' + secret.type + '/edit/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/' + pass;
}
if (!keys.editKeyStr) { return; }
return '/1/edit/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.editKeyStr)+'/';
};
var getViewHashFromKeys = Hash.getViewHashFromKeys = function (chanKey, keys) {
if (typeof keys === 'string') {
return;
var getViewHashFromKeys = Hash.getViewHashFromKeys = function (secret) {
var version = secret.version;
var data = secret.keys;
if (version === 0) { return; }
if (version === 1) {
if (!data.viewKeyStr) { return; }
return '/1/view/' + hexToBase64(secret.channel) +
'/'+Crypto.b64RemoveSlashes(data.viewKeyStr)+'/';
}
if (version === 2) {
if (!data.viewKeyStr) { return; }
var pass = secret.password ? 'p/' : '';
return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass;
}
return '/1/view/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.viewKeyStr)+'/';
};
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) {
return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/';
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (secret) {
var version = secret.version;
var data = secret.keys;
if (version === 0) { return; }
if (version === 1) {
return '/1/' + hexToBase64(secret.channel) + '/' +
Crypto.b64RemoveSlashes(data.fileKeyStr) + '/';
}
if (version === 2) {
if (!data.fileKeyStr) { return; }
var pass = secret.password ? 'p/' : '';
return '/2/' + secret.type + '/' + Crypto.b64RemoveSlashes(data.fileKeyStr) + '/' + pass;
}
};
Hash.getUserHrefFromKeys = function (origin, username, pubkey) {
return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
};
@ -43,6 +75,34 @@ define([
return s.replace(/\/+/g, '/');
};
Hash.createChannelId = function () {
var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
throw new Error('channel ids must consist of 32 hex characters');
}
return id;
};
Hash.createRandomHash = function (type, password) {
var cryptor;
if (type === 'file') {
cryptor = Crypto.createFileCryptor2(void 0, password);
return getFileHashFromKeys({
password: Boolean(password),
version: 2,
type: type,
keys: cryptor
});
}
cryptor = Crypto.createEditCryptor2(void 0, void 0, password);
return getEditHashFromKeys({
password: Boolean(password),
version: 2,
type: type,
keys: cryptor
});
};
/*
Version 0
/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy
@ -52,29 +112,60 @@ Version 1
var parseTypeHash = Hash.parseTypeHash = function (type, hash) {
if (!hash) { return; }
var options;
var parsed = {};
var hashArr = fixDuplicateSlashes(hash).split('/');
if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) {
parsed.type = 'pad';
if (hash.slice(0,1) !== '/' && hash.length >= 56) {
parsed.getHash = function () { return hash; };
if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0
// Old hash
parsed.channel = hash.slice(0, 32);
parsed.key = hash.slice(32, 56);
parsed.version = 0;
return parsed;
}
if (hashArr[1] && hashArr[1] === '1') {
if (hashArr[1] && hashArr[1] === '1') { // Version 1
parsed.version = 1;
parsed.mode = hashArr[2];
parsed.channel = hashArr[3];
parsed.key = hashArr[4].replace(/-/g, '/');
var options = hashArr.slice(5);
parsed.key = Crypto.b64AddSlashes(hashArr[4]);
options = hashArr.slice(5);
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 5).join('/') + '/';
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
return hash;
};
return parsed;
}
if (hashArr[1] && hashArr[1] === '2') { // Version 2
parsed.version = 2;
parsed.app = hashArr[2];
parsed.mode = hashArr[3];
parsed.key = hashArr[4];
options = hashArr.slice(5);
parsed.password = options.indexOf('p') !== -1;
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 5).join('/') + '/';
if (parsed.password) { hash += 'p/'; }
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
return hash;
};
return parsed;
}
return parsed;
}
parsed.getHash = function () { return hashArr.join('/'); };
if (['media', 'file'].indexOf(type) !== -1) {
parsed.type = 'file';
if (hashArr[1] && hashArr[1] === '1') {
@ -83,6 +174,25 @@ Version 1
parsed.key = hashArr[3].replace(/-/g, '/');
return parsed;
}
if (hashArr[1] && hashArr[1] === '2') { // Version 2
parsed.version = 2;
parsed.app = hashArr[2];
parsed.key = hashArr[3];
options = hashArr.slice(4);
parsed.password = options.indexOf('p') !== -1;
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 4).join('/') + '/';
if (parsed.password) { hash += 'p/'; }
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
return hash;
};
return parsed;
}
return parsed;
}
if (['user'].indexOf(type) !== -1) {
@ -125,17 +235,9 @@ Version 1
url += ret.type + '/';
if (!ret.hashData) { return url; }
if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; }
if (ret.hashData.version !== 1) { return url + '#' + ret.hash; }
url += '#/' + ret.hashData.version +
'/' + ret.hashData.mode +
'/' + ret.hashData.channel.replace(/\//g, '-') +
'/' + ret.hashData.key.replace(/\//g, '-') +'/';
if (options.embed) {
url += 'embed/';
}
if (options.present) {
url += 'present/';
}
if (ret.hashData.version === 0) { return url + '#' + ret.hash; }
var hash = ret.hashData.getHash(options);
url += '#' + hash;
return url;
};
@ -153,12 +255,13 @@ Version 1
return '';
});
idx = href.indexOf('/#');
if (idx === -1) { return ret; }
ret.hash = href.slice(idx + 2);
ret.hashData = parseTypeHash(ret.type, ret.hash);
return ret;
};
var getRelativeHref = Hash.getRelativeHref = function (href) {
Hash.getRelativeHref = function (href) {
if (!href) { return; }
if (href.indexOf('#') === -1) { return; }
var parsed = parsePadUrl(href);
@ -170,11 +273,13 @@ Version 1
* - no argument: use the URL hash or create one if it doesn't exist
* - secretHash provided: use secretHash to find the keys
*/
Hash.getSecrets = function (type, secretHash) {
Hash.getSecrets = function (type, secretHash, password) {
var secret = {};
var generate = function () {
secret.keys = Crypto.createEditCryptor();
secret.key = Crypto.createEditCryptor().editKeyStr;
secret.keys = Crypto.createEditCryptor2(void 0, void 0, password);
secret.channel = base64ToHex(secret.keys.chanId);
secret.version = 2;
secret.type = type;
};
if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) {
generate();
@ -191,7 +296,6 @@ Version 1
parsed = pHref.hashData;
hash = pHref.hash;
}
//var parsed = parsePadUrl(window.location.href);
//var hash = secretHash || window.location.hash.slice(1);
if (hash.length === 0) {
generate();
@ -203,9 +307,10 @@ Version 1
// Old hash
secret.channel = parsed.channel;
secret.key = parsed.key;
}
else if (parsed.version === 1) {
secret.version = 0;
} else if (parsed.version === 1) {
// New hash
secret.version = 1;
if (parsed.type === "pad") {
secret.channel = base64ToHex(parsed.channel);
if (parsed.mode === 'edit') {
@ -222,11 +327,43 @@ Version 1
}
}
} else if (parsed.type === "file") {
// version 2 hashes are to be used for encrypted blobs
secret.channel = parsed.channel;
secret.keys = { fileKeyStr: parsed.key };
secret.channel = base64ToHex(parsed.channel);
secret.keys = {
fileKeyStr: parsed.key,
cryptKey: Nacl.util.decodeBase64(parsed.key)
};
} else if (parsed.type === "user") {
throw new Error("User hashes can't be opened (yet)");
}
} else if (parsed.version === 2) {
// New hash
secret.version = 2;
secret.type = type;
secret.password = password;
if (parsed.type === "pad") {
if (parsed.mode === 'edit') {
secret.keys = Crypto.createEditCryptor2(parsed.key, void 0, password);
secret.channel = base64ToHex(secret.keys.chanId);
secret.key = secret.keys.editKeyStr;
if (secret.channel.length !== 32 || secret.key.length !== 24) {
throw new Error("The channel key and/or the encryption key is invalid");
}
}
else if (parsed.mode === 'view') {
secret.keys = Crypto.createViewCryptor2(parsed.key, password);
secret.channel = base64ToHex(secret.keys.chanId);
if (secret.channel.length !== 32) {
throw new Error("The channel key is invalid");
}
}
} else if (parsed.type === "file") {
secret.keys = Crypto.createFileCryptor2(parsed.key, password);
secret.channel = base64ToHex(secret.keys.chanId);
secret.key = secret.keys.fileKeyStr;
if (secret.channel.length !== 48 || secret.key.length !== 24) {
throw new Error("The channel key and/or the encryption key is invalid");
}
} else if (parsed.type === "user") {
// version 2 hashes are to be used for encrypted blobs
throw new Error("User hashes can't be opened (yet)");
}
}
@ -234,51 +371,47 @@ Version 1
return secret;
};
Hash.getHashes = function (channel, secret) {
Hash.getHashes = function (secret) {
var hashes = {};
if (!secret.keys) {
secret = JSON.parse(JSON.stringify(secret));
if (!secret.keys && !secret.key) {
console.error('e');
return hashes;
} else if (!secret.keys) {
secret.keys = {};
}
if (secret.keys.editKeyStr) {
hashes.editHash = getEditHashFromKeys(channel, secret.keys);
if (secret.keys.editKeyStr || (secret.version === 0 && secret.key)) {
hashes.editHash = getEditHashFromKeys(secret);
}
if (secret.keys.viewKeyStr) {
hashes.viewHash = getViewHashFromKeys(channel, secret.keys);
hashes.viewHash = getViewHashFromKeys(secret);
}
if (secret.keys.fileKeyStr) {
hashes.fileHash = getFileHashFromKeys(channel, secret.keys.fileKeyStr);
hashes.fileHash = getFileHashFromKeys(secret);
}
return hashes;
};
var createChannelId = Hash.createChannelId = function () {
var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
throw new Error('channel ids must consist of 32 hex characters');
}
return id;
};
Hash.createRandomHash = function () {
// 16 byte channel Id
var channelId = Util.hexToBase64(createChannelId());
// 18 byte encryption key
var key = Crypto.b64RemoveSlashes(Crypto.rand64(18));
return '/1/edit/' + [channelId, key].join('/') + '/';
};
// STORAGE
Hash.findWeaker = function (href, recents) {
var rHref = href || getRelativeHref(window.location.href);
var parsed = parsePadUrl(rHref);
Hash.findWeaker = function (href, channel, recents) {
var parsed = parsePadUrl(href);
if (!parsed.hash) { return false; }
// We can't have a weaker hash if we're already in view mode
if (parsed.hashData && parsed.hashData.mode === 'view') { return; }
var weaker;
Object.keys(recents).some(function (id) {
var pad = recents[id];
var p = parsePadUrl(pad.href);
if (pad.href || !pad.roHref) {
// This pad has an edit link, so it can't be weaker
return;
}
var p = parsePadUrl(pad.roHref);
if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
if (channel !== pad.channel) { return; } // Not the same channel
var pHash = p.hashData;
var parsedHash = parsed.hashData;
if (!parsedHash || !pHash) { return; }
@ -287,27 +420,31 @@ Version 1
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
if (pHash.version !== parsedHash.version) { return; }
if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'view' && parsedHash.mode === 'edit') {
weaker = pad.href;
weaker = pad;
return true;
}
return;
});
return weaker;
};
var findStronger = Hash.findStronger = function (href, recents) {
var rHref = href || getRelativeHref(window.location.href);
var parsed = parsePadUrl(rHref);
Hash.findStronger = function (href, channel, recents) {
var parsed = parsePadUrl(href);
if (!parsed.hash) { return false; }
// We can't have a stronger hash if we're already in edit mode
if (parsed.hashData && parsed.hashData.mode === 'edit') { return; }
var stronger;
Object.keys(recents).some(function (id) {
var pad = recents[id];
if (!pad.href) {
// This pad doesn't have an edit link, so it can't be stronger
return;
}
var p = parsePadUrl(pad.href);
if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
if (channel !== pad.channel) { return; } // Not the same channel
var pHash = p.hashData;
var parsedHash = parsed.hashData;
if (!parsedHash || !pHash) { return; }
@ -316,37 +453,20 @@ Version 1
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
if (pHash.version !== parsedHash.version) { return; }
if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'edit' && parsedHash.mode === 'view') {
stronger = pad.href;
stronger = pad;
return true;
}
return;
});
return stronger;
};
Hash.isNotStrongestStored = function (href, recents) {
return findStronger(href, recents);
};
Hash.hrefToHexChannelId = function (href) {
Hash.hrefToHexChannelId = function (href, password) {
var parsed = Hash.parsePadUrl(href);
if (!parsed || !parsed.hash) { return; }
parsed = parsed.hashData;
if (parsed.version === 0) {
return parsed.channel;
} else if (parsed.version !== 1 && parsed.version !== 2) {
console.error("parsed href had no version");
console.error(parsed);
return;
}
var channel = parsed.channel;
if (!channel) { return; }
var hex = base64ToHex(channel);
return hex;
var secret = Hash.getSecrets(parsed.type, parsed.hash, password);
return secret.channel;
};
Hash.getBlobPathFromHex = function (id) {

@ -1,3 +1,12 @@
if (!document.querySelector("#alertifyCSS")) {
// Prevent alertify from injecting CSS, we create our own in alertify.less.
// see: https://github.com/alertifyjs/alertify.js/blob/v1.0.11/src/js/alertify.js#L414
var head = document.getElementsByTagName("head")[0];
var css = document.createElement("span");
css.id = "alertifyCSS";
css.setAttribute('data-but-why', 'see: common-interface.js');
head.insertBefore(css, head.firstChild);
}
define([
'jquery',
'/customize/messages.js',
@ -6,15 +15,18 @@ define([
'/common/common-notifier.js',
'/customize/application_config.js',
'/bower_components/alertifyjs/dist/js/alertify.js',
'/common/tippy.min.js',
'/common/tippy/tippy.min.js',
'/customize/pages.js',
'/common/hyperscript.js',
'/customize/loading.js',
'/common/test.js',
'/common/jquery-ui/jquery-ui.min.js',
'/bower_components/bootstrap-tokenfield/dist/bootstrap-tokenfield.js',
'css!/common/tippy.css',
'css!/common/tippy/tippy.css',
'css!/common/jquery-ui/jquery-ui.min.css'
], function ($, Messages, Util, Hash, Notifier, AppConfig,
Alertify, Tippy, Pages, h, Test) {
Alertify, Tippy, Pages, h, Loading, Test) {
var UI = {};
/*
@ -141,13 +153,15 @@ define([
};
dialog.frame = function (content) {
return h('div.alertify', {
return $(h('div.alertify', {
tabindex: 1,
}, [
h('div.dialog', [
h('div', content),
])
]);
])).click(function (e) {
e.stopPropagation();
})[0];
};
/**
@ -182,11 +196,17 @@ define([
]);
};
UI.tokenField = function (target) {
UI.tokenField = function (target, autocomplete) {
var t = {
element: target || h('input'),
};
var $t = t.tokenfield = $(t.element).tokenfield();
var $t = t.tokenfield = $(t.element).tokenfield({
autocomplete: {
source: autocomplete,
delay: 100
},
showAutocompleteOnFocus: false
});
t.getTokens = function (ignorePending) {
var tokens = $t.tokenfield('getTokens').map(function (token) {
@ -209,10 +229,17 @@ define([
t.preventDuplicates = function (cb) {
$t.on('tokenfield:createtoken', function (ev) {
// Close the suggest list when a token is added because we're going to wipe the input
var $input = $t.closest('.tokenfield').find('.token-input');
$input.autocomplete('close');
var val;
ev.attrs.value = ev.attrs.value.toLowerCase();
if (t.getTokens(true).some(function (t) {
if (t === ev.attrs.value) { return ((val = t)); }
if (t === ev.attrs.value) {
ev.preventDefault();
return ((val = t));
}
})) {
ev.preventDefault();
if (typeof(cb) === 'function') { cb(val); }
@ -240,7 +267,7 @@ define([
return t;
};
dialog.tagPrompt = function (tags, cb) {
dialog.tagPrompt = function (tags, existing, cb) {
var input = dialog.textInput();
var tagger = dialog.frame([
@ -254,7 +281,7 @@ define([
dialog.nav(),
]);
var field = UI.tokenField(input).preventDuplicates(function (val) {
var field = UI.tokenField(input, existing).preventDuplicates(function (val) {
UI.warn(Messages._getKey('tags_duplicate', [val]));
});
@ -395,7 +422,7 @@ define([
stopListening(listener);
cb();
});
listener = listenForKeys(close, close, ok);
listener = listenForKeys(close, close);
var $ok = $(ok).click(close);
document.body.appendChild(frame);
@ -409,7 +436,8 @@ define([
cb = cb || function () {};
opt = opt || {};
var input = dialog.textInput();
var inputBlock = opt.password ? UI.passwordInput() : dialog.textInput();
var input = opt.password ? $(inputBlock).find('input')[0] : inputBlock;
input.value = typeof(def) === 'string'? def: '';
var message;
@ -425,7 +453,7 @@ define([
var cancel = dialog.cancelButton(opt.cancel);
var frame = dialog.frame([
message,
input,
inputBlock,
dialog.nav([ cancel, ok, ]),
]);
@ -512,6 +540,50 @@ define([
Alertify.error(Util.fixHTML(msg));
};
UI.passwordInput = function (opts, displayEye) {
opts = opts || {};
var attributes = merge({
type: 'password'
}, opts);
var input = h('input.cp-password-input', attributes);
var reveal = UI.createCheckbox('cp-password-reveal', Messages.password_show);
var eye = h('span.fa.fa-eye.cp-password-reveal');
$(reveal).find('input').on('change', function () {
if($(this).is(':checked')) {
$(input).prop('type', 'text');
$(input).focus();
return;
}
$(input).prop('type', 'password');
$(input).focus();
});
$(eye).mousedown(function () {
$(input).prop('type', 'text');
$(input).focus();
}).mouseup(function(){
$(input).prop('type', 'password');
$(input).focus();
}).mouseout(function(){
$(input).prop('type', 'password');
$(input).focus();
});
if (displayEye) {
$(reveal).hide();
} else {
$(eye).hide();
}
return h('span.cp-password-container', [
input,
reveal,
eye
]);
};
/*
* spinner
*/
@ -539,48 +611,100 @@ define([
var LOADING = 'cp-loading';
var getRandomTip = function () {
/*var getRandomTip = function () {
if (!Messages.tips || !Object.keys(Messages.tips).length) { return ''; }
var keys = Object.keys(Messages.tips);
var rdm = Math.floor(Math.random() * keys.length);
return Messages.tips[keys[rdm]];
};*/
var loading = {
error: false,
driveState: 0,
padState: 0
};
UI.addLoadingScreen = function (config) {
config = config || {};
var loadingText = config.loadingText;
var hideTips = config.hideTips || AppConfig.hideLoadingScreenTips;
var hideLogo = config.hideLogo;
var $loading, $container;
if ($('#' + LOADING).length) {
$loading = $('#' + LOADING); //.show();
var todo = function () {
var $loading = $('#' + LOADING); //.show();
$loading.css('display', '');
$loading.removeClass('cp-loading-hidden');
$('.cp-loading-spinner-container').show();
if (!config.noProgress && !$loading.find('.cp-loading-progress').length) {
var progress = h('div.cp-loading-progress', [
h('p.cp-loading-progress-drive'),
h('p.cp-loading-progress-pad')
]);
$(progress).hide();
$loading.find('.cp-loading-container').append(progress);
} else if (config.noProgress) {
$loading.find('.cp-loading-progress').remove();
}
if (loadingText) {
$('#' + LOADING).find('p').text(loadingText);
$('#' + LOADING).find('#cp-loading-message').show().text(loadingText);
} else {
$('#' + LOADING).find('p').text('');
$('#' + LOADING).find('#cp-loading-message').hide().text('');
}
$container = $loading.find('.cp-loading-container');
loading.error = false;
};
if ($('#' + LOADING).length) {
todo();
} else {
$loading = $(Pages.loadingScreen());
$container = $loading.find('.cp-loading-container');
if (hideLogo) {
$loading.find('img').hide();
Loading();
todo();
}
};
UI.updateLoadingProgress = function (data, isDrive) {
var $loading = $('#' + LOADING);
if (!$loading.length || loading.error) { return; }
$loading.find('.cp-loading-progress').show();
var $progress;
if (isDrive) {
// Drive state
if (loading.driveState === -1) { return; } // Already loaded
$progress = $loading.find('.cp-loading-progress-drive');
if (!$progress.length) { return; } // Can't find the box to display data
// If state is -1, remove the box, drive is loaded
if (data.state === -1) {
loading.driveState = -1;
$progress.remove();
} else {
$loading.find('img').show();
if (data.state < loading.driveState) { return; } // We should not display old data
// Update the current state
loading.driveState = data.state;
data.progress = data.progress || 100;
data.msg = Messages['loading_drive_'+ Math.floor(data.state)] || '';
$progress.html(data.msg);
if (data.progress) {
$progress.append(h('div.cp-loading-progress-bar', [
h('div.cp-loading-progress-bar-value', {style: 'width:'+data.progress+'%;'})
]));
}
}
} else {
// Pad state
if (loading.padState === -1) { return; } // Already loaded
$progress = $loading.find('.cp-loading-progress-pad');
if (!$progress.length) { return; } // Can't find the box to display data
// If state is -1, remove the box, pad is loaded
if (data.state === -1) {
loading.padState = -1;
$progress.remove();
} else {
if (data.state < loading.padState) { return; } // We should not display old data
// Update the current state
loading.padState = data.state;
data.progress = data.progress || 100;
data.msg = Messages['loading_pad_'+data.state] || '';
$progress.html(data.msg);
if (data.progress) {
$progress.append(h('div.cp-loading-progress-bar', [
h('div.cp-loading-progress-bar-value', {style: 'width:'+data.progress+'%;'})
]));
}
var $spinner = $loading.find('.cp-loading-spinner-container');
$spinner.show();
$('body').append($loading);
}
if (Messages.tips && !hideTips) {
var $loadingTip = $('<div>', {'id': 'cp-loading-tip'});
$('<span>', {'class': 'tips'}).text(getRandomTip()).appendTo($loadingTip);
$loadingTip.css({
'bottom': $('body').height()/2 - $container.height()/2 + 20 + 'px'
});
$('body').append($loadingTip);
}
};
UI.removeLoadingScreen = function (cb) {
@ -591,7 +715,7 @@ define([
$('#' + LOADING).addClass("cp-loading-hidden");
setTimeout(cb, 750);
//$('#' + LOADING).fadeOut(750, cb);
loading.error = false;
var $tip = $('#cp-loading-tip').css('top', '')
// loading.less sets transition-delay: $wait-time
// and transition: opacity $fadeout-time
@ -605,18 +729,27 @@ define([
// jquery.fadeout can get stuck
};
UI.errorLoadingScreen = function (error, transparent, exitable) {
if (!$('#' + LOADING).is(':visible') || $('#' + LOADING).hasClass('cp-loading-hidden')) {
var $loading = $('#' + LOADING);
if (!$loading.is(':visible') || $loading.hasClass('cp-loading-hidden')) {
UI.addLoadingScreen({hideTips: true});
}
loading.error = true;
$loading.find('.cp-loading-progress').remove();
$('.cp-loading-spinner-container').hide();
$('#cp-loading-tip').remove();
if (transparent) { $('#' + LOADING).css('opacity', 0.8); }
$('#' + LOADING).find('p').html(error || Messages.error);
if (transparent) { $loading.css('opacity', 0.9); }
var $error = $loading.find('#cp-loading-message').show();
if (error instanceof Element) {
$error.html('').append(error);
} else {
$error.html(error || Messages.error);
}
if (exitable) {
$(window).focus();
$(window).keydown(function (e) {
if (e.which === 27) {
$('#' + LOADING).hide();
$loading.hide();
loading.error = false;
if (typeof(exitable) === "function") { exitable(); }
}
});
@ -637,7 +770,7 @@ define([
UI.getFileIcon = function (data) {
var $icon = UI.getIcon();
if (!data) { return $icon; }
var href = data.href;
var href = data.href || data.roHref;
var type = data.type;
if (!href && !type) { return $icon; }
@ -660,18 +793,40 @@ define([
});
};
var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500;
$.extend(true, Tippy.defaults, {
placement: 'bottom',
performance: true,
delay: [delay, 0],
//sticky: true,
theme: 'cryptpad',
arrow: true,
maxWidth: '200px',
flip: true,
popperOptions: {
modifiers: {
preventOverflow: { boundariesElement: 'window' }
}
},
//arrowType: 'round',
dynamicTitle: true,
arrowTransform: 'scale(2)',
zIndex: 100000001
});
UI.addTooltips = function () {
var MutationObserver = window.MutationObserver;
var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500;
var addTippy = function (i, el) {
if (el._tippy) { return; }
if (el.nodeName === 'IFRAME') { return; }
Tippy(el, {
position: 'bottom',
distance: 0,
performance: true,
delay: [delay, 0],
sticky: true
var opts = {
distance: 15
};
Array.prototype.slice.apply(el.attributes).filter(function (obj) {
return /^data-tippy-/.test(obj.name);
}).forEach(function (obj) {
opts[obj.name.slice(11)] = obj.value;
});
Tippy(el, opts);
};
// This is the robust solution to remove dangling tooltips
// The mutation observer does not always find removed nodes.
@ -680,13 +835,13 @@ define([
var out = false;
var xId = $(x).attr('aria-describedby');
if (xId) {
if (xId.indexOf('tippy-tooltip-') === 0) {
if (xId.indexOf('tippy-') === 0) {
return true;
}
}
$(x).find('[aria-describedby]').each(function (i, el) {
var id = el.getAttribute('aria-describedby');
if (id.indexOf('tippy-tooltip-') !== 0) { return; }
if (id.indexOf('tippy-') !== 0) { return; }
out = true;
});
return out;
@ -720,5 +875,61 @@ define([
});
};
UI.createCheckbox = Pages.createCheckbox;
UI.createRadio = Pages.createRadio;
UI.cornerPopup = function (text, actions, footer, opts) {
opts = opts || {};
var minimize = h('div.cp-corner-minimize.fa.fa-window-minimize');
var maximize = h('div.cp-corner-maximize.fa.fa-window-maximize');
var popup = h('div.cp-corner-container', [
minimize,
maximize,
h('div.cp-corner-filler', { style: "width:130px;" }),
h('div.cp-corner-filler', { style: "width:90px;" }),
h('div.cp-corner-filler', { style: "width:60px;" }),
h('div.cp-corner-filler', { style: "width:40px;" }),
h('div.cp-corner-filler', { style: "width:20px;" }),
h('div.cp-corner-text', text),
h('div.cp-corner-actions', actions),
Pages.setHTML(h('div.cp-corner-footer'), footer)
]);
$(minimize).click(function () {
$(popup).addClass('cp-minimized');
});
$(maximize).click(function () {
$(popup).removeClass('cp-minimized');
});
if (opts.hidden) {
$(popup).addClass('cp-minimized');
}
if (opts.big) {
$(popup).addClass('cp-corner-big');
}
var hide = function () {
$(popup).hide();
};
var show = function () {
$(popup).show();
};
var deletePopup = function () {
$(popup).remove();
};
$('body').append(popup);
return {
popup: popup,
hide: hide,
show: show,
delete: deletePopup
};
};
return UI;
});

@ -89,7 +89,7 @@ define([
};
/* Used to accept friend requests within apps other than /contacts/ */
Msg.addDirectMessageHandler = function (cfg) {
Msg.addDirectMessageHandler = function (cfg, href) {
var network = cfg.network;
var proxy = cfg.proxy;
if (!network) { return void console.error('Network not ready'); }
@ -97,13 +97,12 @@ define([
var msg;
if (sender === network.historyKeeper) { return; }
try {
var parsed = Hash.parsePadUrl(window.location.href);
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets(parsed.type, parsed.hash);
if (!parsed.hashData) { return; }
var chan = parsed.hashData.channel;
var chan = secret.channel;
// Decrypt
var keyStr = parsed.hashData.key;
var cryptor = Crypto.createEditCryptor(keyStr);
var key = cryptor.cryptKey;
var key = secret.keys ? secret.keys.cryptKey : Hash.decodeBase64(secret.key);
var decryptMsg;
try {
decryptMsg = Crypto.decrypt(message, key);
@ -113,7 +112,7 @@ define([
if (!decryptMsg) { return; }
// Parse
msg = JSON.parse(decryptMsg);
if (msg[1] !== parsed.hashData.channel) { return; }
if (msg[1] !== chan) { return; }
var msgData = msg[2];
var msgStr;
if (msg[0] === "FRIEND_REQ") {
@ -197,15 +196,14 @@ define([
var network = cfg.network;
var netfluxId = data.netfluxId;
var parsed = Hash.parsePadUrl(data.href);
var secret = Hash.getSecrets(parsed.type, parsed.hash);
if (!parsed.hashData) { return; }
// Message
var chan = parsed.hashData.channel;
var chan = secret.channel;
var myData = createData(cfg.proxy);
var msg = ["FRIEND_REQ", chan, myData];
// Encryption
var keyStr = parsed.hashData.key;
var cryptor = Crypto.createEditCryptor(keyStr);
var key = cryptor.cryptKey;
var key = secret.keys ? secret.keys.cryptKey : Hash.decodeBase64(secret.key);
var msgStr = Crypto.encrypt(JSON.stringify(msg), key);
// Send encrypted message
if (pendingRequests.indexOf(netfluxId) === -1) {

@ -205,7 +205,7 @@ define([
if (content === oldThumbnailState) { return; }
oldThumbnailState = content;
Thumb.fromDOM(opts, function (err, b64) {
Thumb.setPadThumbnail(common, opts.href, b64);
Thumb.setPadThumbnail(common, opts.href, null, b64);
});
};
var nafa = Util.notAgainForAnother(mkThumbnail, Thumb.UPDATE_INTERVAL);
@ -240,25 +240,25 @@ define([
Thumb.addThumbnail = function(thumb, $span, cb) {
return addThumbnail(null, thumb, $span, cb);
};
var getKey = function (href) {
var parsed = Hash.parsePadUrl(href);
return 'thumbnail-' + parsed.type + '-' + parsed.hashData.channel;
var getKey = function (type, channel) {
return 'thumbnail-' + type + '-' + channel;
};
Thumb.setPadThumbnail = function (common, href, b64, cb) {
Thumb.setPadThumbnail = function (common, href, channel, b64, cb) {
cb = cb || function () {};
var k = getKey(href);
var parsed = Hash.parsePadUrl(href);
channel = channel || common.getMetadataMgr().getPrivateData().channel;
var k = getKey(parsed.type, channel);
common.setThumbnail(k, b64, cb);
};
Thumb.displayThumbnail = function (common, href, $container, cb) {
Thumb.displayThumbnail = function (common, href, channel, password, $container, cb) {
cb = cb || function () {};
var parsed = Hash.parsePadUrl(href);
var k = getKey(href);
var k = getKey(parsed.type, channel);
var whenNewThumb = function () {
var secret = Hash.getSecrets('file', parsed.hash);
var hexFileName = Util.base64ToHex(secret.channel);
var secret = Hash.getSecrets('file', parsed.hash, password);
var hexFileName = secret.channel;
var src = Hash.getBlobPathFromHex(hexFileName);
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var key = Nacl.util.decodeBase64(cryptKey);
var key = secret.keys && secret.keys.cryptKey;
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (e) {
if (e === 'XHR_ERROR') { return; }
@ -270,7 +270,7 @@ define([
if (!v) {
v = 'EMPTY';
}
Thumb.setPadThumbnail(common, href, v, function (err) {
Thumb.setPadThumbnail(common, href, hexFileName, v, function (err) {
if (!metadata.thumbnail) { return; }
addThumbnail(err, metadata.thumbnail, $container, cb);
});

File diff suppressed because it is too large Load Diff

@ -83,6 +83,21 @@ define([], function () {
}).join('');
};
// given an array of Uint8Arrays, return a new Array with all their values
Util.uint8ArrayJoin = function (AA) {
var l = 0;
var i = 0;
for (; i < AA.length; i++) { l += AA[i].length; }
var C = new Uint8Array(l);
i = 0;
for (var offset = 0; i < AA.length; i++) {
C.set(AA[i], offset);
offset += AA[i].length;
}
return C;
};
Util.deduplicateString = function (array) {
var a = array.slice();
for(var i=0; i<a.length; i++) {
@ -122,17 +137,14 @@ define([], function () {
else if (bytes >= oneMegabyte) { return 'MB'; }
};
// given a path, asynchronously return an arraybuffer
Util.fetch = function (src, cb) {
var done = false;
var CB = function (err, res) {
if (done) { return; }
done = true;
cb(err, res);
};
var CB = Util.once(cb);
var xhr = new XMLHttpRequest();
xhr.open("GET", src, true);
xhr.responseType = "arraybuffer";
xhr.onerror = function (err) { CB(err); };
xhr.onload = function () {
if (/^4/.test(''+this.status)) {
return CB('XHR_ERROR');
@ -142,6 +154,22 @@ define([], function () {
xhr.send(null);
};
Util.dataURIToBlob = function (dataURI) {
var byteString = atob(dataURI.split(',')[1]);
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to an ArrayBuffer
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
// write the ArrayBuffer to a blob, and you're done
var bb = new Blob([ab], {type: mimeString});
return bb;
};
Util.throttle = function (f, ms) {
var to;
var g = function () {

@ -5,6 +5,7 @@ define([
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/outer/network-config.js',
'/bower_components/chainpad/chainpad.dist.js',
], function (Crypto, CPNetflux, Util, Hash, Realtime, NetConfig) {
var finish = function (S, err, doc) {
if (S.done) { return; }
@ -20,9 +21,9 @@ define([
}
};
var makeConfig = function (hash) {
var makeConfig = function (hash, password) {
// We can't use cryptget with a file or a user so we can use 'pad' as hash type
var secret = Hash.getSecrets('pad', hash);
var secret = Hash.getSecrets('pad', hash, password);
if (!secret.keys) { secret.keys = secret.key; } // support old hashses
var config = {
websocketURL: NetConfig.getWebsocketURL(),
@ -47,8 +48,10 @@ define([
if (typeof(cb) !== 'function') {
throw new Error('Cryptget expects a callback');
}
opt = opt || {};
var config = makeConfig(hash, opt.password);
var Session = { cb: cb, };
var config = makeConfig(hash);
config.onReady = function (info) {
var rt = Session.session = info.realtime;
@ -64,9 +67,11 @@ define([
if (typeof(cb) !== 'function') {
throw new Error('Cryptput expects a callback');
}
opt = opt || {};
var config = makeConfig(hash);
var config = makeConfig(hash, opt.password);
var Session = { cb: cb, };
config.onReady = function (info) {
var realtime = Session.session = info.realtime;
Session.network = info.network;

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save