diff --git a/.gitignore b/.gitignore index 741aedaf7..f78ce644a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,7 @@ data npm-debug.log pins/ blob/ +block/ blobstage/ +block/ privileged.conf diff --git a/.jshintignore b/.jshintignore index 9d6d3c2fb..d936e314f 100644 --- a/.jshintignore +++ b/.jshintignore @@ -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 diff --git a/.lesshintrc b/.lesshintrc index 693029bf1..651613c98 100644 --- a/.lesshintrc +++ b/.lesshintrc @@ -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": { diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..c93d79324 --- /dev/null +++ b/CHANGELOG.md @@ -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. diff --git a/Dockerfile b/Dockerfile index da60937bf..5b22f39bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 new file mode 100644 index 000000000..ec11ddc55 --- /dev/null +++ b/Dockerfile.arm64 @@ -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"] diff --git a/bower.json b/bower.json index f9ee60c20..79859659e 100644 --- a/bower.json +++ b/bower.json @@ -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" diff --git a/check-account-deletion.js b/check-account-deletion.js new file mode 100644 index 000000000..39bbf02f5 --- /dev/null +++ b/check-account-deletion.js @@ -0,0 +1,76 @@ +/* jshint esversion: 6, node: true */ +const Fs = require('fs'); +const nThen = require('nthen'); +const Pinned = require('./pinned'); +const Nacl = require('tweetnacl'); + +const hashesFromPinFile = (pinFile, fileName) => { + var pins = {}; + pinFile.split('\n').filter((x)=>(x)).map((l) => JSON.parse(l)).forEach((l) => { + switch (l[0]) { + case 'RESET': { + pins = {}; + if (l[1] && l[1].length) { l[1].forEach((x) => { pins[x] = 1; }); } + //jshint -W086 + // fallthrough + } + case 'PIN': { + l[1].forEach((x) => { pins[x] = 1; }); + break; + } + case 'UNPIN': { + l[1].forEach((x) => { delete pins[x]; }); + break; + } + default: throw new Error(JSON.stringify(l) + ' ' + fileName); + } + }); + return Object.keys(pins); +}; + +var escapeKeyCharacters = function (key) { + return key && key.replace && key.replace(/\//g, '-'); +}; + + +const dataIdx = process.argv.indexOf('--data'); +let edPublic; +if (dataIdx === -1) { + const hasEdPublic = process.argv.indexOf('--ed'); + if (hasEdPublic === -1) { return void console.error("Missing ed argument"); } + edPublic = escapeKeyCharacters(process.argv[hasEdPublic+1]); +} else { + const deleteData = JSON.parse(process.argv[dataIdx+1]); + if (!deleteData.toSign || !deleteData.proof) { return void console.error("Invalid arguments"); } + // Check sig + const ed = Nacl.util.decodeBase64(deleteData.toSign.edPublic); + const signed = Nacl.util.decodeUTF8(JSON.stringify(deleteData.toSign)); + const proof = Nacl.util.decodeBase64(deleteData.proof); + if (!Nacl.sign.detached.verify(signed, proof, ed)) { return void console.error("Invalid signature"); } + edPublic = escapeKeyCharacters(deleteData.toSign.edPublic); +} + +let data = []; +let pinned = []; + +nThen((waitFor) => { + let f = './pins/' + edPublic.slice(0, 2) + '/' + edPublic + '.ndjson'; + Fs.readFile(f, waitFor((err, content) => { + if (err) { throw err; } + pinned = hashesFromPinFile(content.toString('utf8'), f); + })); +}).nThen((waitFor) => { + Pinned.load(waitFor((d) => { + data = Object.keys(d); + }), { + exclude: [edPublic + '.ndjson'] + }); +}).nThen(() => { + console.log('Pads pinned by this user and not pinned by anybody else:'); + pinned.forEach((p) => { + if (data.indexOf(p) === -1) { + console.log(p); + } + }); +}); + diff --git a/config.example.js b/config.example.js index 3aace0d4a..98d1889fd 100644 --- a/config.example.js +++ b/config.example.js @@ -2,7 +2,7 @@ /* globals module */ -var domain = ' http://localhost:3000/'; +var domain = 'http://localhost:3000/'; // You can `kill -USR2` the node process and it will write out a heap dump. // If your system doesn't support dumping, comment this out and install with @@ -12,6 +12,10 @@ var domain = ' http://localhost:3000/'; // to enable this feature, uncomment the line below: // require('heapdump'); + +// we prepend a space because every usage expects it +// requiring admins to preserve it is unnecessarily confusing +domain = ' ' + domain; module.exports = { // the address you want to bind to, :: means all ipv4 and ipv6 addresses @@ -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, diff --git a/container-start.sh b/container-start.sh index 990a83cd2..8e274b19a 100755 --- a/container-start.sh +++ b/container-start.sh @@ -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 diff --git a/customize.dist/CryptPadlogo_op5.svg b/customize.dist/CryptPadlogo_op5.svg new file mode 100644 index 000000000..aa5acb201 --- /dev/null +++ b/customize.dist/CryptPadlogo_op5.svg @@ -0,0 +1 @@ +CryptPadlogo \ No newline at end of file diff --git a/customize.dist/ckeditor-config.js b/customize.dist/ckeditor-config.js index 36cac87f0..d8936c2c4 100644 --- a/customize.dist/ckeditor-config.js +++ b/customize.dist/ckeditor-config.js @@ -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"]}, diff --git a/customize.dist/fonts/cptools/fonts/cptools.svg b/customize.dist/fonts/cptools/fonts/cptools.svg new file mode 100644 index 000000000..2ea458f12 --- /dev/null +++ b/customize.dist/fonts/cptools/fonts/cptools.svg @@ -0,0 +1,12 @@ + + + +Generated by IcoMoon + + + + + + + + \ No newline at end of file diff --git a/customize.dist/fonts/cptools/fonts/cptools.ttf b/customize.dist/fonts/cptools/fonts/cptools.ttf new file mode 100644 index 000000000..179ba4d98 Binary files /dev/null and b/customize.dist/fonts/cptools/fonts/cptools.ttf differ diff --git a/customize.dist/fonts/cptools/fonts/cptools.woff b/customize.dist/fonts/cptools/fonts/cptools.woff new file mode 100644 index 000000000..04cd47172 Binary files /dev/null and b/customize.dist/fonts/cptools/fonts/cptools.woff differ diff --git a/customize.dist/fonts/cptools/style.css b/customize.dist/fonts/cptools/style.css new file mode 100644 index 000000000..83c233e62 --- /dev/null +++ b/customize.dist/fonts/cptools/style.css @@ -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"; +} diff --git a/customize.dist/images/cover-faq.jpg b/customize.dist/images/cover-faq.jpg new file mode 100644 index 000000000..27be3af99 Binary files /dev/null and b/customize.dist/images/cover-faq.jpg differ diff --git a/customize.dist/images/cover-features.jpg b/customize.dist/images/cover-features.jpg new file mode 100644 index 000000000..6652c9b28 Binary files /dev/null and b/customize.dist/images/cover-features.jpg differ diff --git a/customize.dist/images/cover-privacy.jpg b/customize.dist/images/cover-privacy.jpg new file mode 100644 index 000000000..ff873c99d Binary files /dev/null and b/customize.dist/images/cover-privacy.jpg differ diff --git a/customize.dist/loading-logo.png b/customize.dist/loading-logo.png new file mode 100644 index 000000000..23753684f Binary files /dev/null and b/customize.dist/loading-logo.png differ diff --git a/customize.dist/loading.js b/customize.dist/loading.js new file mode 100644 index 000000000..e8f8a9453 --- /dev/null +++ b/customize.dist/loading.js @@ -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 = [ + '', + '', + '
', + '
', + '', + '
', + '

', + '
' + ].join(''); + return function () { + var intr; + var append = function () { + if (!document.body) { return; } + clearInterval(intr); + document.body.appendChild(elem); + }; + intr = setInterval(append, 100); + append(); + }; +}); diff --git a/customize.dist/login.js b/customize.dist/login.js index 1f8cebab5..ab9fabfa8 100644 --- a/customize.dist/login.js +++ b/customize.dist/login.js @@ -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,13 +68,27 @@ define([ // should never happen if (channelHex.length !== 32) { throw new Error('invalid channel id'); } - opt.channel64 = Util.hexToBase64(channelHex); + var channel64 = Util.hexToBase64(channelHex); - opt.userHash = '/1/edit/' + [opt.channel64, opt.keys.editKeyStr].join('/'); + // 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; }; + + 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; + }; + var loadUserObject = function (opt, cb) { var config = { websocketURL: NetConfig.getWebsocketURL(), @@ -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, - }; + // 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; - // use the derived key to generate an object - loadUserObject(opt, function (err, rt) { + 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; + } + + 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,38 +241,14 @@ 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 (!isRegister && isProxyEmpty(rt.proxy)) { - rt.network.disconnect(); // clean up after yourself - return void cb('NO_SUCH_USER', res); - } + if (shouldImport) { setMergeAnonDrive(); } - // they tried to register, but those exact credentials exist - if (isRegister && !isProxyEmpty(rt.proxy)) { - rt.network.disconnect(); - 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; - proxy.login_name = uname; - proxy[Constants.displayNameKey] = uname; - sessionStorage.createReadme = 1; - Feedback.send('REGISTRATION', true); - } else { - Feedback.send('LOGIN', true); - } - - if (shouldImport) { - sessionStorage.migrateAnonDrive = 1; - } + // 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)` @@ -170,12 +257,152 @@ define([ // `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 && !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; + setCreateReadme(); + if (shouldImport) { + setMergeAnonDrive(); + } else { + proxy.version = 6; + } + + Feedback.send('REGISTRATION', true); + } else { + Feedback.send('LOGIN', true); + } + + 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"); + + // 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; diff --git a/customize.dist/pages.js b/customize.dist/pages.js index 4cd4f49f4..22e33b01b 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -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([ ]); } + var button = h('button.navbar-toggler', { + 'type':'button', + /*'data-toggle':'collapse', + 'data-target':'#menuCollapse', + 'aria-controls': 'menuCollapse', + 'aria-expanded':'false', + 'aria-label':'Toggle navigation'*/ + }, h('i.fa.fa-bars ')); + + $(button).click(function () { + if ($('#menuCollapse').is(':visible')) { + return void $('#menuCollapse').slideUp(); + } + $('#menuCollapse').slideDown(); + }); + return h('nav.navbar.navbar-expand-lg', h('a.navbar-brand', { href: '/index.html'}), - h('button.navbar-toggler', { - 'type':'button', - 'data-toggle':'collapse', - 'data-target':'#menuCollapse', - 'aria-controls': 'menuCollapse', - 'aria-expanded':'false', - 'aria-label':'Toggle navigation' - }, h('i.fa.fa-bars ')), + button, h('div.collapse.navbar-collapse.justify-content-end#menuCollapse', [ //h('a.nav-item.nav-link', { href: '/what-is-cryptpad.html'}, Msg.topbar_whatIsCryptpad), // Moved the FAQ h('a.nav-item.nav-link', { href: '/faq.html'}, Msg.faq_link), @@ -120,8 +151,8 @@ define([ h('div.container-fluid.cp-about-intro', [ h('div.container', [ h('center', [ - h('h1', Msg.about), - setHTML(h('p'), 'CryptPad is created inside of the Research Team at XWiki SAS, 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.'), + h('h1', Msg.about), + 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'), '

Aaron transitioned into distributed systems development from a background in jazz and live stage performance.
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.
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.
He spends his spare time experimenting with guitars, photography, science fiction, and spicy food.

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

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

'), @@ -239,113 +270,83 @@ define([ Pages['/features.html'] = function () { return h('div#cp-main', [ infopageTopbar(), - h('div.container.cp-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-fluid.cp_cont_features',[ + h('div.container',[ + h('center', h('h1', Msg.features_title)), + ]), + ]), + 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', Msg.features_f_register)) + ]), + ]), + ]), + ]), ]), - h('div#cp-features-register', [ - h('a', { - href: '/register/' - }, h('button.cp-features-register-button', 'Register for free')) - ]) ]), infopageFooter() ]); @@ -354,25 +355,35 @@ define([ Pages['/privacy.html'] = function () { return h('div#cp-main', [ infopageTopbar(), - h('div.container.cp-container', [ - h('center', h('h1', Msg.policy_title)), - h('h2', Msg.policy_whatweknow), + h('.container-fluid.cp-privacy-top', [ + h('div.container',[ + h('center', h('h1', Msg.policy_title)), + ]), + ]), + 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('center', h('h1', Msg.faq_title)), - h('p.cp-faq-header', h('a.nav-item.nav-link', { + h('div.container-fluid.cp-faq', [ + h('div.container',[ + h('center', h('h1', Msg.faq_title)), + ]), + ]), + 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($('')[0], Msg.register_importRecent) - /*h('label', { - 'for': 'import-recent', - }, Msg.register_importRecent),*/ + Pages.createCheckbox('import-recent', Msg.register_importRecent, true) ]), h('div.checkbox-container', [ - h('input#accept-terms', { - name: 'accept-terms', - type: 'checkbox' - }), - setHTML($('')[0], Msg.register_acceptTerms) - /*setHTML(h('label', { - 'for': 'accept-terms', - }), Msg.register_acceptTerms),*/ + $(Pages.createCheckbox('accept-terms')).find('.cp-checkmark-label').append(Msg.register_acceptTerms).parent()[0] ]), h('button#register.btn.cp-login-register', Msg.login_register) ]) @@ -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($('')[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(), ])]; }; diff --git a/customize.dist/src/less2/404.less b/customize.dist/src/less2/404.less deleted file mode 100644 index 33a852cd0..000000000 --- a/customize.dist/src/less2/404.less +++ /dev/null @@ -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"; } - diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less index b845a5d6f..0df305ddc 100644 --- a/customize.dist/src/less2/include/alertify.less +++ b/customize.dist/src/less2/include/alertify.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 () { +.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,76 +232,83 @@ } } - nav { + button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button) { - text-align: right; + background-color: @colortheme_alertify-cancel; + box-sizing: border-box; + position: relative; + outline: 0; + display: inline-block; + align-items: center; + padding: 0 6px; + margin: 6px 8px; + line-height: 36px; + min-height: 36px; + white-space: nowrap; + min-width: 88px; + text-align: center; + text-transform: uppercase; + font-size: 14px; + text-decoration: none; + cursor: pointer; + border-radius: 0; - button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button) { - - background-color: @alertify-btn-bg; - box-sizing: border-box; - position: relative; - outline: 0; - display: inline-block; - align-items: center; - padding: 0 6px; - margin: 6px 8px; - line-height: 36px; - min-height: 36px; - white-space: nowrap; - min-width: 88px; - text-align: center; - text-transform: uppercase; - font-size: 14px; - text-decoration: none; - cursor: pointer; - border-radius: 0; - - color: @alertify-btn-fg; - border: 1px solid transparent; - - &.safe, &.danger { - color: @colortheme_old-base; - white-space: normal; - font-weight: bold; - } - &.danger { - background-color: @colortheme_alertify-red; - &:hover, &:active { - background-color: lighten(@colortheme_alertify-red, 5%); - } - } - - &.safe { - background-color: @colortheme_alertify-green; - &:hover, &:active { - background-color: lighten(@colortheme_alertify-green, 10%); - } - } - - &.primary { - background-color: @colortheme_alertify-primary; - color: @colortheme_alertify-primary-text; - &:hover, &:active { - background-color: darken(@colortheme_alertify-primary, 10%); - } - } + color: @alertify-btn-fg; + border: 1px solid @colortheme_alertify-cancel-border; + &.safe, &.danger { + color: @colortheme_old-base; + white-space: normal; + font-weight: bold; + } + &.danger { + background-color: @colortheme_alertify-red; + border-color: @colortheme_alertify-red-border; + color: @colortheme_alertify-red-color; &:hover, &:active { - background-color: @alertify-btn-bg-hover; - } - - &:focus { - border: 1px dotted @alertify-base; - } - &::-moz-focus-inner { - border: 0; + background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-red, 10%), lighten(@colortheme_alertify-red, 10%)); } } - button.btn { - margin: 6px 4px; + &.safe { + background-color: @colortheme_alertify-green; + border-color: @colortheme_alertify-green-border; + color: @colortheme_alertify-green-color; + &:hover, &:active { + 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: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-primary, 10%), lighten(@colortheme_alertify-primary, 10%)); + } + } + + &:hover, &:active { + background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-cancel, 10%), lighten(@colortheme_alertify-cancel, 10%)); + } + + &:focus { + //border: 1px dotted @alertify-base; + box-shadow: 0px 0px 5px @colortheme_alertify-primary; + outline: none; + } + &::-moz-focus-inner { + border: 0; + } + } + + button.btn { + margin: 6px 4px; + } + + nav { + text-align: right; } } } @@ -392,3 +410,4 @@ } } } + diff --git a/customize.dist/src/less2/include/app-noscroll.less b/customize.dist/src/less2/include/app-noscroll.less index c66019d61..fcd8f5d01 100644 --- a/customize.dist/src/less2/include/app-noscroll.less +++ b/customize.dist/src/less2/include/app-noscroll.less @@ -1,13 +1,8 @@ -// html -.app-noscroll_main () { - height: 100%; - width: 100%; - padding: 0px; - margin: 0px; - overflow: hidden; - box-sizing: border-box; - position: relative; - body { +.app-noscroll_main() { + --LessLoader_require: LessLoader_currentFile(); +} +& { + .cp-app-noscroll { height: 100%; width: 100%; padding: 0px; @@ -15,6 +10,17 @@ overflow: hidden; box-sizing: border-box; position: relative; + border: 0; + body { + height: 100%; + width: 100%; + padding: 0px; + margin: 0px; + overflow: hidden; + box-sizing: border-box; + position: relative; + border: 0; + } } } diff --git a/customize.dist/src/less2/include/app-print.less b/customize.dist/src/less2/include/app-print.less index 957170044..1625ff874 100644 --- a/customize.dist/src/less2/include/app-print.less +++ b/customize.dist/src/less2/include/app-print.less @@ -1,46 +1,52 @@ -.app-print_main () { - // Current scope is - @media print { - height: auto; - max-height: none; - overflow: visible; - display: block; - @page { - margin: 0; - size: landscape; - } - // Slide app - body.cp-app-slide { +.app-print_main() { + --LessLoader_require: LessLoader_currentFile(); +} +& { + .cp-app-print { + // Current scope is + @media print { + height: auto; + max-height: none; + overflow: visible; display: block; - .CodeMirror, #cme_toolbox { - display: none; + @page { + margin: 0; + size: landscape; } - * { - visibility: hidden; - height: auto; - max-height: none; - } - .cp-app-slide-viewer #cp-app-slide-print { + // Slide app + body.cp-app-slide { display: block; - visibility: visible; - * { - visibility: visible; + .CodeMirror, #cme_toolbox { + display: none; + } + * { + visibility: hidden; + height: auto; + max-height: none; + } + .cp-app-slide-viewer #cp-app-slide-print { + display: block; + visibility: visible; + * { + visibility: visible; + } + } + .cp-app-slide-viewer #cp-app-slide-modal { + display: none !important; + } + .cp-app-slide-viewer { + flex: 1 !important; + overflow: visible !important; + } + .cp-toolbar-userlist-drawer { + display: none !important; + } + #cp-app-slide-editor { + height: auto; + display: block; } - } - .cp-app-slide-viewer #cp-app-slide-modal { - display: none !important; - } - .cp-app-slide-viewer { - flex: 1 !important; - overflow: visible !important; - } - .cp-toolbar-userlist-drawer { - display: none !important; - } - #cp-app-slide-editor { - height: auto; - display: block; } } } } + diff --git a/customize.dist/src/less2/include/avatar.less b/customize.dist/src/less2/include/avatar.less index b935db786..87b4a3a32 100644 --- a/customize.dist/src/less2/include/avatar.less +++ b/customize.dist/src/less2/include/avatar.less @@ -1,40 +1,65 @@ -@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; - font-size: 16px; - display: flex; - align-items: center; - .cp-avatar-default, media-tag { - display: inline-flex; - width: @width; - height: @width; - justify-content: center; - align-items: center; - border-radius: 4px; overflow: hidden; - box-sizing: content-box; - } - .cp-avatar-default { - .tools_unselectable(); - background: white; - color: black; - font-size: @width/1.2; - } - media-tag { - min-height: @width; - min-width: @width; - max-height: @width; - max-width: @width; - img { - min-width: 100%; - min-height: 100%; - max-width: none; - max-height: none; // To override 'media-tag img' in slide.less + text-overflow: ellipsis; + font-size: 16px; + display: flex; + align-items: center; + .cp-avatar-default, media-tag { + display: inline-flex; + + width: @avatar-width; + width: var(--avatar-width); + + height: @avatar-width; + height: var(--avatar-width); + + justify-content: center; + align-items: center; + border-radius: 4px; + overflow: hidden; + box-sizing: content-box; + } + .cp-avatar-default { + .tools_unselectable(); + background: white; + color: black; + font-size: @avatar-font-size; + font-size: var(--avatar-font-size); + } + media-tag { + 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%; + max-width: none; + max-height: none; // To override 'media-tag img' in slide.less + } } - } } } diff --git a/customize.dist/src/less2/include/checkmark.less b/customize.dist/src/less2/include/checkmark.less index 30c6e7d4a..da5a07a26 100644 --- a/customize.dist/src/less2/include/checkmark.less +++ b/customize.dist/src/less2/include/checkmark.less @@ -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(); // Text .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; } } diff --git a/customize.dist/src/less2/include/colortheme-all.less b/customize.dist/src/less2/include/colortheme-all.less index 4544da76e..be048d7bc 100644 --- a/customize.dist/src/less2/include/colortheme-all.less +++ b/customize.dist/src/less2/include/colortheme-all.less @@ -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"; diff --git a/customize.dist/src/less2/include/colortheme.less b/customize.dist/src/less2/include/colortheme.less index 3227da446..fce9686a6 100644 --- a/customize.dist/src/less2/include/colortheme.less +++ b/customize.dist/src/less2/include/colortheme.less @@ -2,6 +2,9 @@ @colortheme_app-font-size: 16px; @colortheme_app-font: @colortheme_app-font-size @colortheme_font; +@colortheme_logo-1: #326599; +@colortheme_logo-2: #4591c4; + @colortheme_link-color: #0275D8; @colortheme_link-color-visited: #005999; @colortheme_info-background: #fafafa; @@ -15,23 +18,42 @@ @colortheme_cp-red: #FA5858; // remove red @colortheme_cp-green: #46E981; -@colortheme_modal-bg: #222; -@colortheme_modal-fg: #fff; -@colortheme_modal-link: #eee; -@colortheme_modal-link-visited: lighten(@colortheme_modal-link, 10%); -@colortheme_modal-dim: rgba(0, 0, 0, 0.4); +@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_loading-bg: #222; +@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: 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: @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_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: rgba(0, 0, 0, 0.8); +@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; +@cryptpad_color_light_blue: #00b7d8; -@colortheme_checkmark-back0: #ffffff; -@colortheme_checkmark-back0-active: #bbbbbb; -@colortheme_checkmark-back1: #FF0073; -@colortheme_checkmark-col1: #ffffff; -@colortheme_checkmark-back2: #FFFFFF; -@colortheme_checkmark-col2: #000000; +@colortheme_checkmark-back0: @colortheme_form-bg-alt; +@colortheme_checkmark-back0-active: @colortheme_form-border; +@colortheme_checkmark-back1: @colortheme_form-bg; +@colortheme_checkmark-col1: @colortheme_form-color; +@colortheme_checkmark-back2: @colortheme_form-bg-alt; +@colortheme_checkmark-col2: @colortheme_form-color-alt; +@colortheme_checkmark-disabled: #AAA; diff --git a/customize.dist/src/less2/include/contextmenu.less b/customize.dist/src/less2/include/contextmenu.less new file mode 100644 index 000000000..1a9049b54 --- /dev/null +++ b/customize.dist/src/less2/include/contextmenu.less @@ -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; + } + } + } +} diff --git a/customize.dist/src/less2/include/corner.less b/customize.dist/src/less2/include/corner.less new file mode 100644 index 000000000..174c27eef --- /dev/null +++ b/customize.dist/src/less2/include/corner.less @@ -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%); + } + } + } + } +} + diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index 4fd0a5a1d..e5c71a3db 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -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-logo { + height: 300px; + width: 300px; + margin-top: 50px; + flex: 0 1 auto; /* allows shrink */ + min-height: 0; + text-align: center; + img { + max-width: 100%; + max-height: 100%; + } + } } #cp-creation { - flex: 0 1 auto; /* allows shrink */ - min-height: 0; overflow: auto; text-align: center; + background: @colortheme_creation-modal-bg; + color: @colortheme_creation-modal; font: @colortheme_app-font; - width: 100%; outline: none; + width: 700px; + max-width: 90vw; + height: 500px; + max-height: calc(~"100vh - 20px"); + margin: 50px; + flex-shrink: 0; + display: flex; + flex-flow: column; + & > div { - width: 60vw; + width: 100%; max-width: 100%; - margin: 40px auto; + margin: auto; text-align: left; } - .cp-creation-create, .cp-creation-settings { + .cp-creation-title { + color: @colortheme_creation-modal-title; + font-weight: bold; + margin: 15px; + } + + .cp-creation-create { margin-top: 0px; - @creation-button: #30B239; button { .tools_unselectable(); padding: 15px; - background: @creation-button; + background: linear-gradient(to right, @colortheme_logo-2, @colortheme_logo-1); color: #FFF; font-weight: bold; margin: 3px 10px; @@ -50,15 +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 { - font-size: 18px; - color: white; - &:hover { - color: #AAA; - text-decoration: none; - } + } + .cp-creation-help, .cp-creation-warning { + font-size: 18px; + color: @colortheme_form-warning; + &:hover { + color: @colortheme_form-warning-hov; + text-decoration: none; } } .cp-creation-slider { display: block; overflow: hidden; max-height: 0px; - transition: max-height 0.5s ease-in-out; - width: 100%; - margin-top: 10px; + max-width: 0px; + //margin-top: 10px; &.active { - max-height: 40px; + transition: max-height 0.5s ease-in-out; + max-width: 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,52 +328,78 @@ .cp-creation-deleted-container { text-align: center; .cp-creation-deleted { - background: #111; + margin: 0 10px; + background: @colortheme_loading-bg; + color: @colortheme_loading-color; padding: 10px; text-align: center; font-weight: bold; display: inline-block; } } + } - .checkmark_main(30px); - - @media screen and (max-width: @browser_media-narrow-screen) { - & > div { - width: 95%; - margin: 10px auto; + @media screen and (max-height: 700px) { + #cp-creation-container { + .cp-creation-logo { + //flex-shrink: 0; + display: none; + } } } - @media screen and (max-width: @browser_media-medium-screen) { - #cp-creation-form { - div.cp-creation-template { - margin: 0; - padding: 5px; - .cp-creation-template-container { - .cp-creation-template-element { - flex-flow: row; - margin: 1px; - padding: 5px; - width: 155px; - img { - display: none; + @media screen and (max-width: 500px) { + #cp-creation { + #cp-creation-form { + & > div { + width: 95%; + margin: 10px auto; + } + .cp-creation-expire { + &.active { + label { + flex: 1; } - .fa { - font-size: 18px; - width: 20px; - height: 20px; - line-height: 20px; - display: inline !important; - } - .cp-creation-template-element-name { - margin: 0; - margin-left: 5px; + .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; + padding: 5px; + .cp-creation-template-container { + .cp-creation-template-element { + flex-flow: row; + margin: 1px; + padding: 5px; + width: 155px; + img { + display: none; + } + .fa { + font-size: 18px; + width: 20px; + height: 20px; + line-height: 20px; + display: inline !important; + } + .cp-creation-template-element-name { + margin: 0; + margin-left: 5px; + } } } } } } } - - } } diff --git a/customize.dist/src/less2/include/dropdown.less b/customize.dist/src/less2/include/dropdown.less index 3cf32537e..ac393fe3a 100644 --- a/customize.dist/src/less2/include/dropdown.less +++ b/customize.dist/src/less2/include/dropdown.less @@ -1,8 +1,11 @@ -@import (once) "./colortheme-all.less"; -@import (once) "./tools.less"; +@import (reference) "./colortheme-all.less"; +@import (reference) "./tools.less"; /* The container
- 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; diff --git a/customize.dist/src/less2/include/fileupload.less b/customize.dist/src/less2/include/fileupload.less index 3ed3e793e..c10abeec4 100644 --- a/customize.dist/src/less2/include/fileupload.less +++ b/customize.dist/src/less2/include/fileupload.less @@ -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 @@ } } } - diff --git a/customize.dist/src/less2/include/font.less b/customize.dist/src/less2/include/font.less index ec8e77f0f..627a9a9f8 100644 --- a/customize.dist/src/less2/include/font.less +++ b/customize.dist/src/less2/include/font.less @@ -1,9 +1,18 @@ -.font_neuropolitical () { - @font-face { - font-family: Neuropolitical; - src: url("/customize/fonts/neuropolitical.ttf"); - } +.font_main () { + --LessLoader_require: LessLoader_currentFile(); } -.font_open-sans () { - @import (once) '/customize/fonts/open-sans.less'; + +// 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"); +} + +@import (once) '/customize/fonts/open-sans.less'; + diff --git a/customize.dist/src/less2/include/framework.less b/customize.dist/src/less2/include/framework.less index 4bdb2b0b8..4853f663b 100644 --- a/customize.dist/src/less2/include/framework.less +++ b/customize.dist/src/less2/include/framework.less @@ -1,10 +1,62 @@ -@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 + ); + .alertify_main(); + .corner_main(); + .contextmenu_main(); + .fileupload_main(); + .tokenfield_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, @@ -12,7 +64,15 @@ ); .fileupload_main(); .alertify_main(); - .tokenfield_main(); - .creation_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; } } diff --git a/customize.dist/src/less2/include/help.less b/customize.dist/src/less2/include/help.less index 90f23119b..4dd396a29 100644 --- a/customize.dist/src/less2/include/help.less +++ b/customize.dist/src/less2/include/help.less @@ -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 @@ } } } + diff --git a/customize.dist/src/less2/include/icon-colors.less b/customize.dist/src/less2/include/icon-colors.less index a2f2215cb..c7edeecb3 100644 --- a/customize.dist/src/less2/include/icon-colors.less +++ b/customize.dist/src/less2/include/icon-colors.less @@ -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; } } diff --git a/customize.dist/src/less2/include/icons.less b/customize.dist/src/less2/include/icons.less index 03a6af5ab..c2ddfef32 100644 --- a/customize.dist/src/less2/include/icons.less +++ b/customize.dist/src/less2/include/icons.less @@ -10,7 +10,6 @@ text-overflow: ellipsis; padding-top: 5px; padding-bottom: 5px; - border: 1px solid white; .cp-icons-name { width: 100%; @@ -26,7 +25,7 @@ word-wrap: break-word; } &.cp-icons-element-selected { - background-color: white; + background-color: rgba(0,0,0,0.2); color: #666; } .fa { diff --git a/customize.dist/src/less2/include/infopages.less b/customize.dist/src/less2/include/infopages.less index b07b90aa8..3df7b4c1d 100644 --- a/customize.dist/src/less2/include/infopages.less +++ b/customize.dist/src/less2/include/infopages.less @@ -1,10 +1,25 @@ -@import (once) "./colortheme-all.less"; +@import (reference) "./colortheme-all.less"; +@import (reference) "./font.less"; -@infopages_infobar-height: 64px; -@infopages_padding: 32px; +.infopages_link () { + text-decoration: none; + color: #0275D8; + cursor: pointer; + display: inline-flex; + &:hover { + transform: scale(1.05); + } +} -// Basic setup for info pages, this should be used at the global level .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 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 () { + // Apply this to the top bar div .cp-topbar { background: #fff; z-index: 10000; //Z infopage toolbar @@ -149,58 +152,72 @@ margin-right: 0.5em; } } + + // navigation top bar + .navbar { + background: #fff; + .navbar-brand { + display: block; + background-image: url(/customize/CryptPad_logo_color.svg); + background-repeat: no-repeat; + background-size: contain; + height: 50px; + width: 250px; + @media (max-width: 326px) { + width: 180px; + } + margin-right: 0; + } + a { + border: 2px solid transparent; + white-space: nowrap; + } + .nav-link { + padding: 0.5em 0.7em; + &:hover { + color: @cryptpad_color_light_blue; + } + } + .cp-register-btn { + border: 2px solid #4591C4; + display: inline-block; + } + button:focus { + outline: none; + } + .navbar-toggler { + margin-top: 10px; + color: #4591C4; + } + } + @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; + } + .cp-register-btn { + margin-right: 13px; + text-align: center; + } + } + + //footer general styles + + .footer-title { + font-weight: bold; + font-size: 1.2em; + color: #1E1F1F; + } } -// navigation top bar -.navbar { - background: #fff; - .navbar-brand { - display: block; - background-image: url(/customize/CryptPad_logo_color.svg); - background-repeat: no-repeat; - background-size: contain; - height: 50px; - width: 250px; - } - a { - border: 2px solid transparent; - white-space: nowrap; - } - .nav-link { - padding: 0.5em 0.7em; - &:hover { - transform: scale(1.05); - }; - } - .cp-register-btn { - border: 2px solid #4591C4; - display: inline-block; - } - button:focus { - outline: none; - } - .navbar-toggler { - margin-top: 10px; - color: #4591C4; - } -} -@media (max-width: 991px) { - #menuCollapse { - text-align: right; - } - .navbar-nav a { - text-align: right !important; - } - .cp-register-btn { - margin-right: 13px; - text-align: center; - } -} - -//footer general styles - -.footer-title { - font-weight: bold; - font-size: 1.2em; - color: #1E1F1F; -} diff --git a/customize.dist/src/less2/include/leftside-menu.less b/customize.dist/src/less2/include/leftside-menu.less index 28ae57af4..baca3ac4d 100644 --- a/customize.dist/src/less2/include/leftside-menu.less +++ b/customize.dist/src/less2/include/leftside-menu.less @@ -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() { } diff --git a/customize.dist/src/less2/include/limit-bar.less b/customize.dist/src/less2/include/limit-bar.less index b2ea5f230..3ed7bd454 100644 --- a/customize.dist/src/less2/include/limit-bar.less +++ b/customize.dist/src/less2/include/limit-bar.less @@ -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; diff --git a/customize.dist/src/less2/include/markdown-toolbar.less b/customize.dist/src/less2/include/markdown-toolbar.less deleted file mode 100644 index 4fb466525..000000000 --- a/customize.dist/src/less2/include/markdown-toolbar.less +++ /dev/null @@ -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; } - } - } -} diff --git a/customize.dist/src/less2/include/markdown.less b/customize.dist/src/less2/include/markdown.less index 470f81a82..1ba97a0f1 100644 --- a/customize.dist/src/less2/include/markdown.less +++ b/customize.dist/src/less2/include/markdown.less @@ -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 - diff --git a/customize.dist/src/less2/include/modal.less b/customize.dist/src/less2/include/modal.less index 3bd674527..07c533b15 100644 --- a/customize.dist/src/less2/include/modal.less +++ b/customize.dist/src/less2/include/modal.less @@ -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,62 +17,68 @@ } } -.cp-modal-container { - display: none; - - z-index: 100000; //Z modal container - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - background-color: @colortheme_modal-dim; - - .cp-modal { - background-color: @colortheme_modal-bg; - color: @colortheme_modal-fg; - box-shadow: @variables_shadow; - - padding: @variables_padding; +.modal_main() { + --LessLoader_require: LessLoader_currentFile(); +} +& { + .cp-modal-container { + display: none; + z-index: 100000; //Z modal container position: absolute; - top: 15vh; bottom: 15vh; - left: 10vw; right: 10vw; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: @colortheme_modal-dim; - overflow: auto; - - font-family: @colortheme_font; - text-align: center; - - & > p { - margin-bottom: 1em; - } - - .cp-modal-form { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: center; - } - - input { - background-color: @colortheme_modal-input; + .cp-modal { + background-color: @colortheme_modal-bg; color: @colortheme_modal-fg; - border: 0; - padding: 8px 12px; - margin: 1em; - width: 300px; - } + box-shadow: @variables_shadow; - .cp-modal-close { - text-shadow: none; - color: inherit; + padding: @variables_padding; position: absolute; - top: 0; - right: 0; - margin: @variables_padding; - cursor: pointer; + top: 15vh; bottom: 15vh; + left: 10vw; right: 10vw; + + overflow: auto; + + font-family: @colortheme_font; + text-align: center; + + & > p { + margin-bottom: 1em; + } + + .cp-modal-form { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + } + + input { + background-color: @colortheme_modal-input; + color: @colortheme_modal-input-fg; + border: 0; + padding: 8px 12px; + margin: 1em; + width: 300px; + } + + .cp-modal-close { + text-shadow: none; + color: inherit; + + position: absolute; + top: 0; + right: 0; + margin: @variables_padding; + cursor: pointer; + } } } } + diff --git a/customize.dist/src/less2/include/password-input.less b/customize.dist/src/less2/include/password-input.less new file mode 100644 index 000000000..a2f2fb044 --- /dev/null +++ b/customize.dist/src/less2/include/password-input.less @@ -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; + } + } +} diff --git a/customize.dist/src/less2/include/sidebar-layout.less b/customize.dist/src/less2/include/sidebar-layout.less index ede03942e..07471183a 100644 --- a/customize.dist/src/less2/include/sidebar-layout.less +++ b/customize.dist/src/less2/include/sidebar-layout.less @@ -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; diff --git a/customize.dist/src/less2/include/tippy.less b/customize.dist/src/less2/include/tippy.less new file mode 100644 index 000000000..794159ba7 --- /dev/null +++ b/customize.dist/src/less2/include/tippy.less @@ -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; + } + } +} diff --git a/customize.dist/src/less2/include/tokenfield.less b/customize.dist/src/less2/include/tokenfield.less index 6604b4481..faa302b0a 100644 --- a/customize.dist/src/less2/include/tokenfield.less +++ b/customize.dist/src/less2/include/tokenfield.less @@ -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; diff --git a/customize.dist/src/less2/include/toolbar-history.less b/customize.dist/src/less2/include/toolbar-history.less index abf14f3ac..cb41c2112 100644 --- a/customize.dist/src/less2/include/toolbar-history.less +++ b/customize.dist/src/less2/include/toolbar-history.less @@ -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; } diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index 50c51f611..858c0a8d9 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -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 @@ } } } - } diff --git a/customize.dist/src/less2/include/tools.less b/customize.dist/src/less2/include/tools.less index 9fd2df5bc..b8c36cbcd 100644 --- a/customize.dist/src/less2/include/tools.less +++ b/customize.dist/src/less2/include/tools.less @@ -19,6 +19,7 @@ } .tools_unselectable () { + user-select: none; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; diff --git a/customize.dist/src/less2/loading.less b/customize.dist/src/less2/loading.less deleted file mode 100644 index 8c7e8f5e4..000000000 --- a/customize.dist/src/less2/loading.less +++ /dev/null @@ -1,73 +0,0 @@ -/* -WARNING: THIS FILE DOES NOTHING -It exists only as a proposal of what CSS you should use in loading.js -The CSS inside of loading.js is precompiled in order to save 200ish milliseconds to the loading screen. -*/ -@import (once) "./include/colortheme-all.less"; -@import (once) "./include/browser.less"; - -#cp-loading { - transition: opacity 0.75s, visibility 0s 0.75s; - visibility: visible; - opacity: 1; - position: fixed; - z-index: 10000000; // #loading - top: 0px; - bottom: 0px; - left: 0px; - right: 0px; - background: @colortheme_loading-bg; - color: @colortheme_loading-color; - text-align: center; - font-size: 1.5em; - .cp-loading-container { - margin-top: 50vh; - transform: translateY(-50%); - } - .cp-loading-cryptofist { - margin-left: auto; - margin-right: auto; - height: 300px; - margin-bottom: 2em; - @media screen and (max-height: @browser_media-short-screen) { - display: none; - } - } - .cp-loading-spinner-container { - position: relative; - height: 100px; - > div { - height: 100px; - } - } - &.cp-loading-hidden { - opacity: 0; - visibility: hidden; - } -} -#cp-loading-tip { - position: fixed; - z-index: 10000000; // loading tip - top: 80%; - left: 0; - right: 0; - text-align: center; - - transition: opacity 750ms; - transition-delay: 3000ms; - @media screen and (max-height: @browser_media-medium-screen) { - display: none; - } - span { - background: @colortheme_loading-bg; - color: @colortheme_loading-color; - text-align: center; - font-size: 1.5em; - opacity: 0.7; - font-family: @colortheme_font; - padding: 15px; - max-width: 60%; - display: inline-block; - } -} - diff --git a/customize.dist/src/less2/main.less b/customize.dist/src/less2/main.less deleted file mode 100644 index 89dccef65..000000000 --- a/customize.dist/src/less2/main.less +++ /dev/null @@ -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"; } - diff --git a/customize.dist/src/less2/pages/page-404.less b/customize.dist/src/less2/pages/page-404.less index c6afae5b0..9ff9cdcc3 100644 --- a/customize.dist/src/less2/pages/page-404.less +++ b/customize.dist/src/less2/pages/page-404.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 { diff --git a/customize.dist/src/less2/pages/page-about.less b/customize.dist/src/less2/pages/page-about.less index 83354abd7..23ce14e93 100644 --- a/customize.dist/src/less2/pages/page-about.less +++ b/customize.dist/src/less2/pages/page-about.less @@ -1,115 +1,118 @@ -@import (once) "../include/infopages.less"; -@import (once) "../include/colortheme-all.less"; +@import (reference) "../include/infopages.less"; +@import (reference) "../include/colortheme-all.less"; -.infopages_main(); -.infopages_topbar(); -#cp-main { - background: #fff; -} -.cp-about-intro { - padding-top: 3em; - padding-bottom: 3em; - background-image: url(/customize/bkabout.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-container { - .row { +&.cp-page-about { + .infopages_main(); + + #cp-main { background: #fff; } - .cp-bio-avatar { - padding-right: 0; - @media (max-width: 991px) { - padding-right: 15px; - } - img { - @media (max-width: 991px) { - margin: 0 auto; - display: block; + .cp-about-intro { + padding-top: 3em; + padding-bottom: 3em; + background-image: url(/customize/bkabout.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-bio-avatar-right { - padding-right: 15px; - padding-left: 0; - @media (max-width: 991px) { - padding-left: 15px; + .cp-container { + .row { + background: #fff; + } + .cp-bio-avatar { + padding-right: 0; + @media (max-width: 991px) { + padding-right: 15px; + } + img { + @media (max-width: 991px) { + margin: 0 auto; + display: block; + } + } + } + .cp-bio-avatar-right { + padding-right: 15px; + padding-left: 0; + @media (max-width: 991px) { + padding-left: 15px; + } } } -} -.cp-develop-about { - .cp-icon-cent { - width: 6rem; - background: #fff; - border-radius: 50%; - height: 6rem; - box-shadow: 0 5px 15px rgba(69,145,196, 0.3); - margin: 0 auto; - background-image: url(/customize/code.svg); - background-repeat: no-repeat; - margin-top: 1em; - margin-bottom: 1.5em; - background-position: 50%; - background-size: 4rem; + .cp-develop-about { + .cp-icon-cent { + width: 6rem; + background: #fff; + border-radius: 50%; + height: 6rem; + box-shadow: 0 5px 15px rgba(69,145,196, 0.3); + margin: 0 auto; + background-image: url(/customize/code.svg); + background-repeat: no-repeat; + margin-top: 1em; + margin-bottom: 1.5em; + background-position: 50%; + background-size: 4rem; + } + h2 { + margin-top: 0; + font-weight: 600; + color: #1E1F1F; + margin-bottom: 1.5em; + } } - h2 { - margin-top: 0; - font-weight: 600; - color: #1E1F1F; - margin-bottom: 1.5em; - } -} -.cp-profile-det { - padding-left: 30px; - h3 { - color: #1E1F1F; - font-weight: 700; - } - p { - color: #3F4141; + .cp-profile-det { + padding-left: 30px; + h3 { + color: #1E1F1F; + font-weight: 700; + } + p { + color: #3F4141; + margin-bottom: 1em; + } + hr { + margin-left: 0; + width: 15rem; + border-top: 2px solid @cryptpad_color_blue; + } margin-bottom: 1em; } - hr { - margin-left: 0; - width: 15rem; - border-top: 2px solid @cryptpad_color_blue; - } - margin-bottom: 1em; -} -.cp-soc-media { - font-size: 1.5em; - color: @cryptpad_color_blue; - padding-right: 1em; - display: inline-block; - &:hover { - transform: scale(1.1); - } - &:visited { + .cp-soc-media { + font-size: 1.5em; color: @cryptpad_color_blue; + padding-right: 1em; + display: inline-block; + &:hover { + transform: scale(1.1); + } + &:visited { + color: @cryptpad_color_blue; + } + } + .cp-contrib { + margin-top: 3em; + .cp-icon-cent { + background-image: url(/customize/source-branch.svg); + background-position: 60%; } -} -.cp-contrib { - margin-top: 3em; - .cp-icon-cent { - background-image: url(/customize/source-branch.svg); - background-position: 60%; + } + .cp-margin-bot { + margin-bottom: 1.5em; } } -.cp-margin-bot { - margin-bottom: 1.5em; -} + diff --git a/customize.dist/src/less2/pages/page-contact.less b/customize.dist/src/less2/pages/page-contact.less index 4d2f9ad0e..10150f597 100644 --- a/customize.dist/src/less2/pages/page-contact.less +++ b/customize.dist/src/less2/pages/page-contact.less @@ -1,90 +1,92 @@ -@import (once) "../include/infopages.less"; -@import (once) "../include/colortheme-all.less"; +@import (reference) "../include/infopages.less"; +@import (reference) "../include/colortheme-all.less"; -.infopages_main(); -.infopages_topbar(); +&.cp-page-contact { + .infopages_main(); -.fa { - padding-right: 0.25em; -} -#cp-main { - background-color: #fff; -} -.cp-container { - background: #fff; - .cp-iconCont { - h4 { - margin-top: 1.5em; - margin-bottom: 1.5em; - } - div { - .card { - padding: 4em 1em 0.5em 1em; - box-shadow: 0 5px 15px rgba(69,145,196, 0.3); - border-color: #fff; - text-align: center; - margin-bottom: 1em; - &:hover, &:focus { - text-decoration: none; - transform: scale(1.05); - } - @media (max-width: 1200px) and (min-width: 769px) { - min-height: 139px; - } - @media (max-width: 768px) and (min-width: 576px) { - min-height: 164px; - } - @media (max-width: 496px) { - min-height: 140px; - } - @media (max-width: 335px) { - min-height: 162px; - } - } - &:nth-child(2) { - .card { - background-image: url(/customize/images/twitter.svg); - background-repeat: no-repeat; - background-position: 50% 10%; - background-size: 3rem; - } - } - &:nth-child(3) { - .card { - background-image: url(/customize/images/issue.svg); - background-repeat: no-repeat; - background-position: 50% 10%; - background-size: 3rem; - } - } - &:nth-child(4) { - .card { - background-image: url(/customize/images/sayhi.svg); - background-repeat: no-repeat; - background-position: 50% 10%; - background-size: 3rem; - } - } - &:nth-child(5) { - .card { - background-image: url(/customize/images/email.svg); - background-repeat: no-repeat; - background-position: 50% 10%; - background-size: 3rem; - } - } - } - } -} -.cp-contdet { - padding-top: 3em; - padding-bottom: 3em; - background-image: url(/customize/images/bkcontact.jpg); - background-size: cover; - background-repeat: no-repeat; - background-position: center; - h1 { - font-weight: 700; - color: #fff; + .fa { + padding-right: 0.25em; + } + #cp-main { + background-color: #fff; + } + .cp-container { + background: #fff; + .cp-iconCont { + h4 { + margin-top: 1.5em; + margin-bottom: 1.5em; + } + div { + .card { + padding: 4em 1em 0.5em 1em; + box-shadow: 0 5px 15px rgba(69,145,196, 0.3); + border-color: #fff; + text-align: center; + margin-bottom: 1em; + &:hover, &:focus { + text-decoration: none; + transform: scale(1.05); + } + @media (max-width: 1200px) and (min-width: 769px) { + min-height: 139px; + } + @media (max-width: 768px) and (min-width: 576px) { + min-height: 164px; + } + @media (max-width: 496px) { + min-height: 140px; + } + @media (max-width: 335px) { + min-height: 162px; + } + } + &:nth-child(2) { + .card { + background-image: url(/customize/images/twitter.svg); + background-repeat: no-repeat; + background-position: 50% 10%; + background-size: 3rem; + } + } + &:nth-child(3) { + .card { + background-image: url(/customize/images/issue.svg); + background-repeat: no-repeat; + background-position: 50% 10%; + background-size: 3rem; + } + } + &:nth-child(4) { + .card { + background-image: url(/customize/images/sayhi.svg); + background-repeat: no-repeat; + background-position: 50% 10%; + background-size: 3rem; + } + } + &:nth-child(5) { + .card { + background-image: url(/customize/images/email.svg); + background-repeat: no-repeat; + background-position: 50% 10%; + background-size: 3rem; + } + } + } + } + } + .cp-contdet { + padding-top: 3em; + padding-bottom: 3em; + background-image: url(/customize/images/bkcontact.jpg); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + h1 { + font-weight: 700; + color: #fff; + } } } + diff --git a/customize.dist/src/less2/pages/page-faq.less b/customize.dist/src/less2/pages/page-faq.less index 99df97f9f..96ba157e1 100644 --- a/customize.dist/src/less2/pages/page-faq.less +++ b/customize.dist/src/less2/pages/page-faq.less @@ -1,37 +1,86 @@ -@import (once) "../include/infopages.less"; -@import (once) "../include/colortheme-all.less"; +@import (reference) "../include/infopages.less"; +@import (reference) "../include/colortheme-all.less"; -.infopages_main(); -.infopages_topbar(); +&.cp-page-faq { + .infopages_main(); -.cp-faq-header { - padding: 0; - font-size: 1.2em; - a { - padding: 0; + #cp-main { + background: #fff; } -} -.cp-faq-container { - .cp-faq-questions-q { - color: #3a84b6; - padding: 0; - margin-bottom: 0; - margin-top: 5px; - cursor: pointer; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; + .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-questions-q:hover { - color: #2e688f; - text-decoration: underline; + + .cp-faq-ques-det { + .cp-faq-header { + a { + padding: 0; + h4 { + margin-top: 1.5rem; + margin-bottom: 1.5rem; + .cp-brand-font { + font-family: "Neuropolitical"; + } + } + } + } } - .cp-faq-questions-a { - display: none; - padding: 0; + .cp-faq-container { + .cp-faq-questions-items { + background: #3a84b6; + color: #fff; + padding: 1rem 1rem 0.5rem 1rem; + margin-bottom: 1rem; + } + .cp-faq-questions-q { + padding: 0; + margin-bottom: 0.5rem; + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -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-a { + display: none; + padding: 0.5rem; + margin-bottom: 0.5rem; + background-color: #fff; + color: #212529; + } + margin-bottom: 1.5rem; } } diff --git a/customize.dist/src/less2/pages/page-features.less b/customize.dist/src/less2/pages/page-features.less index 37f2be245..adaf174c7 100644 --- a/customize.dist/src/less2/pages/page-features.less +++ b/customize.dist/src/less2/pages/page-features.less @@ -1,67 +1,89 @@ -@import (once) "../include/infopages.less"; -@import (once) "../include/colortheme-all.less"; +@import (reference) "../include/infopages.less"; +@import (reference) "../include/colortheme-all.less"; -.infopages_main(); -.infopages_topbar(); +&.cp-page-features { + .infopages_main(); -@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; + #cp-main { + background-color: #fff; } - 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; + .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; } } - td:nth-child(4) { - color: @features_notes; - font-size: 14px; - font-style: italic; - } - td:first-child { - font-weight: bold; - } - .yes, .no, .part { + #cp-features-register { text-align: center; + padding: 20px; } - .yes { background-color: @features_yes; } - .no { background-color: @features_no; } - .part { background-color: @features_part; } - .left { - text-align: left; - } -} - -#cp-features-register { - text-align: center; - padding: 20px; -} -.cp-features-register-button { - font-size: 20px; - color: #fff; - background: @cryptpad_color_blue; - border: 2px solid @cryptpad_color_blue; - border-radius: 0; - &:hover { - transform: scale(1.05); + .cp-features-register-button { + font-size: 20px; + color: #fff; + background: @cryptpad_color_blue; + border: 2px solid @cryptpad_color_blue; + 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 + } + } } } diff --git a/customize.dist/src/less2/pages/page-index.less b/customize.dist/src/less2/pages/page-index.less index 55671f913..167423ad1 100644 --- a/customize.dist/src/less2/pages/page-index.less +++ b/customize.dist/src/less2/pages/page-index.less @@ -1,190 +1,193 @@ -@import (once) "../include/infopages.less"; -@import (once) "../include/colortheme-all.less"; +@import (reference) "../include/infopages.less"; +@import (reference) "../include/colortheme-all.less"; -.infopages_main(); -.infopages_topbar(); +&.cp-page-index { + .infopages_main(); -@background_lighter: rgba(0,0,0,0.1); -@background_darker: rgba(0,0,0,0.4); -#cp-main { - color: #FFF; - background: linear-gradient( @background_darker, @background_lighter ), url('/customize/bg14.jpg'); - background-size: cover; - background-position: center; - min-height: 100vh; - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - .container { - @media only screen and (max-device-width : 576px) { - margin-top: 6em; + @background_lighter: rgba(0,0,0,0.1); + @background_darker: rgba(0,0,0,0.4); + #cp-main { + color: #FFF; + background: linear-gradient( @background_darker, @background_lighter ), url('/customize/bg14.jpg'); + background-size: cover; + background-position: center; + min-height: 100vh; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + .container { + @media only screen and (max-device-width : 576px) { + margin-top: 6em; + } } } -} -body { - font-family: "Open Sans", Helvetica; -} -.cp-right { - .cp-register-btn { - padding: 0.5em 1em 0.7em 1em; - border: 2px solid #fff; - &:hover { - transform: scale(1.05); - } - } - .cp-login-btn { - color: #fff; - padding: 0.5em 1em 0.7em 1em; - &:hover { - transform: scale(1.05); - } - } -} -.cp-title { - display: flex; - align-items: center; - flex-direction: column; - margin-top: 1.5em; - img { - height: 20vh; - margin-bottom: 1.5em; + body { + font-family: "Open Sans", Helvetica; } - margin-left: 0; - h1 { - font-family: "Neuropolitical"; - //font-family: Garamond, Baskerville, "Baskerville Old Face", "Hoefler Text", "Times New Roman", Times, serif; - //font-family: "Raleway"; - font-size: 45px; - } - p { - //font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; - font-size: 20px; - //font-style: italic; - } -} -.navbar { - background: transparent; - width: 100%; - @media only screen and (max-device-width: 991px) { - margin-top: 0; - } - .navbar-brand { - background-image: url(/customize/CryptPad-white-logo.svg); - } - a { - color: #fff; - &:visited { - color: rgba(255,255,255,.9); - }; - } - .nav-link { - &:hover { - transform: scale(1.05); - }; - } - .cp-register-btn { + .cp-right { + .cp-register-btn { + padding: 0.5em 1em 0.7em 1em; border: 2px solid #fff; + &:hover { + transform: scale(1.05); + } + } + .cp-login-btn { + color: #fff; + padding: 0.5em 1em 0.7em 1em; + &:hover { + transform: scale(1.05); + } + } } - .navbar-toggler { - margin-top: 10px; - color: #fff; + .cp-title { + display: flex; + align-items: center; + flex-direction: column; + margin-top: 1.5em; + img { + height: 20vh; + margin-bottom: 1.5em; + } + margin-left: 0; + h1 { + font-family: "Neuropolitical"; + //font-family: Garamond, Baskerville, "Baskerville Old Face", "Hoefler Text", "Times New Roman", Times, serif; + //font-family: "Raleway"; + font-size: 45px; + } + p { + //font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; + font-size: 20px; + //font-style: italic; + } } -} -@callout-padding: 15px; -a:hover { - text-decoration: none; -} -.bs-callout { - display: flex; - align-items: stretch; - margin: 25px 0; - background: rgba(255,255,255,0.6); - color: black; - transition: all .1s ease-in-out; - box-sizing: border-box; - height: 5em; - position: relative; - a { + .navbar { + background: transparent; + width: 100%; + @media only screen and (max-device-width: 991px) { + margin-top: 0; + } + .navbar-brand { + background-image: url(/customize/CryptPad-white-logo.svg); + } + a { + color: #fff; + &:visited { + color: rgba(255,255,255,.9); + }; + } + .nav-link { + &:hover { + color: inherit; + transform: scale(1.05); + }; + } + .cp-register-btn { + border: 2px solid #fff; + } + .navbar-toggler { + margin-top: 10px; + color: #fff; + } + } + @callout-padding: 15px; + a:hover { + text-decoration: none; + } + .bs-callout { + display: flex; + align-items: stretch; + margin: 25px 0; + background: rgba(255,255,255,0.6); color: black; - &:hover { text-decoration-line: none; } - } - div h4 { - @media only screen and (min-device-width: 576px) and (max-device-width: 767px) { - font-size: 1.3em; + transition: all .1s ease-in-out; + box-sizing: border-box; + height: 5em; + position: relative; + a { + color: black; + &:hover { text-decoration-line: none; } + } + div h4 { + @media only screen and (min-device-width: 576px) and (max-device-width: 767px) { + font-size: 1.3em; + } } } -} -h4 { - margin: 0; -} -.cp-callout-more-moremsg,.cp-callout-more-lessmsg { - transform: none !important; -} -.bs-callout div { - display: flex; - align-items: center; - justify-content: center; - height: 100%; - position: absolute; - left: 5em; -} -.bs-callout+.bs-callout { - margin-top: -5px; -} - -.bs-callout:hover { - //color: white; - transform: scale(1.05); - cursor: pointer; -} -.bs-callout:hover.cp-callout-more { + h4 { + margin: 0; + } + .cp-callout-more-moremsg,.cp-callout-more-lessmsg { transform: none !important; -} -.bs-callout .fa { - display: flex; - align-items: center; - font-size: 2em; - padding-left: 0.57em; - width: 2em; - transition: width 0.1s; - color: #fff; -} -.cp-callout-pad .fa { background-color: @colortheme_pad-bg; } -.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-whiteboard .fa { background-color: @colortheme_whiteboard-bg; } -.cp-callout-recent .fa { background-color: @colortheme_drive-bg; } -.cp-hidden { display: none !important; } -.cp-callout-more { - display: inline-block; - align-content: center; - height: 2em; - border-radius: 1em; - margin-left: auto; - margin-right: auto; - margin-top: 0; - background: none; - width: 100%; - div { - .infopages_link(); + } + .bs-callout div { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + position: absolute; + left: 5em; + } + .bs-callout+.bs-callout { + margin-top: -5px; + } + + .bs-callout:hover { + //color: white; + transform: scale(1.05); + cursor: pointer; + } + .bs-callout:hover.cp-callout-more { + transform: none !important; + } + .bs-callout .fa { + display: flex; + align-items: center; + font-size: 2em; + padding-left: 0.57em; + width: 2em; + transition: width 0.1s; color: #fff; - .fa { - font-size: inherit; - padding: 0; - width: 1em; - padding-left: 5px; + } + .cp-callout-pad .fa { background-color: @colortheme_pad-bg; } + .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; } + .cp-callout-more { + display: inline-block; + align-content: center; + height: 2em; + border-radius: 1em; + margin-left: auto; + margin-right: auto; + margin-top: 0; + background: none; + width: 100%; + div { + .infopages_link(); + color: #fff; + .fa { + font-size: inherit; + padding: 0; + width: 1em; + padding-left: 5px; + } + } + } + @media (min-width: 576px) and (max-width: 767px) { + .container { + padding-left: 0; + padding-right: 0; + } + div#cp-main.cp-page-index .cp-topbar .navbar-toggler-left { + left: 5px; } } } -@media (min-width: 576px) and (max-width: 767px) { - .container { - padding-left: 0; - padding-right: 0; - } - div#cp-main.cp-page-index .cp-topbar .navbar-toggler-left { - left: 5px; - } -} diff --git a/customize.dist/src/less2/pages/page-login.less b/customize.dist/src/less2/pages/page-login.less index 77dead84c..c92a08bde 100644 --- a/customize.dist/src/less2/pages/page-login.less +++ b/customize.dist/src/less2/pages/page-login.less @@ -1,68 +1,71 @@ -@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"; -.infopages_main(); -.infopages_topbar(); -.alertify_main(); +&.cp-page-login { + .infopages_main(); + .alertify_main(); + .checkmark_main(20px); -.form-group { - .extra { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - } -} -.cp-container { - #data { - background: #4591C4; - padding-top: 3em; - padding-bottom: 7em; - padding-left: 30px; - padding-right: 30px; - p { - color: #fff; - } - h2 { - font-weight: 700; - color: @cryptpad_header_col; + .form-group { + .extra { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; } } - #userForm { - padding-top: 3em; - padding-bottom: 2em; - .form-control { - border-radius: 0; - color: @cryptpad_text_col; - margin-top: 1em; - &:focus { - border-color: @cryptpad_color_blue; + .cp-container { + #data { + background: #4591C4; + padding-top: 3em; + padding-bottom: 7em; + padding-left: 30px; + padding-right: 30px; + p { + color: #fff; + } + h2 { + font-weight: 700; + color: @cryptpad_header_col; } } - .checkbox-container { - color: @cryptpad_text_col; + #userForm { + padding-top: 3em; + padding-bottom: 2em; + .form-control { + border-radius: 0; + color: @cryptpad_text_col; + margin-top: 1em; + &:focus { + border-color: @cryptpad_color_blue; + } + } + .checkbox-container { + color: @cryptpad_text_col; + } + } + .align-items-center { + box-shadow: 0 5px 15px rgba(69,145,196, 0.3); + background: #fff; + } + .extra { + margin-top: 1em; + .login { + background: @cryptpad_color_blue; + color: #fff; + padding: 10px; + border-radius: 0; + &:hover { + transform: scale(1.05); + } + } } } - .align-items-center { - box-shadow: 0 5px 15px rgba(69,145,196, 0.3); - background: #fff; - } - .extra { - margin-top: 1em; - .login { - background: @cryptpad_color_blue; - color: #fff; - padding: 10px; - border-radius: 0; - &:hover { - transform: scale(1.05); - } - } + .cp-container { + padding-top: 3em; + min-height: 66vh; } } -.cp-container { - padding-top: 3em; - min-height: 66vh; -} + diff --git a/customize.dist/src/less2/pages/page-privacy.less b/customize.dist/src/less2/pages/page-privacy.less index a1f8b2955..2f6418e40 100644 --- a/customize.dist/src/less2/pages/page-privacy.less +++ b/customize.dist/src/less2/pages/page-privacy.less @@ -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(); + + #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; + } + } +} -.infopages_main(); -.infopages_topbar(); diff --git a/customize.dist/src/less2/pages/page-register.less b/customize.dist/src/less2/pages/page-register.less index 6336c6aa3..314ad7236 100644 --- a/customize.dist/src/less2/pages/page-register.less +++ b/customize.dist/src/less2/pages/page-register.less @@ -1,142 +1,142 @@ -@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"; -.infopages_main(); -.infopages_topbar(); -.alertify_main(); +&.cp-page-register { + .infopages_main(); -.cp-container { - .form-group { - .checkbox-container { - &:nth-of-type(1) { - margin-top: 2em; + .alertify_main(); + .checkmark_main(20px); + + .cp-container { + .form-group { + .checkbox-container { + &:nth-of-type(1) { + margin-top: 2em; + } + &:last-of-type { + margin-bottom: 1em; + } } - &:last-of-type { - margin-bottom: 1em; + #register { + &.btn { + padding: .5rem .5rem; + } + margin-top: 16px; + font-size: 1.25em; + min-width: 30%; } } - #register { - &.btn { - padding: .5rem .5rem; - } - margin-top: 16px; - font-size: 1.25em; - min-width: 30%; // conflict? - width: 30%; - @media (max-width: 500px) { - width: 45%; - } + padding-bottom: 3em; + min-height: 5vh; + } + .alertify { + // workaround for alertify making empty p + p:empty { + display: none; } } - padding-bottom: 3em; - min-height: 5vh; -} -.alertify { - // workaround for alertify making empty p - p:empty { - display: none; - } -} -.cp-register-wel { - padding-top: 6em; - padding-bottom: 20em; - background-image: url(/customize/bkregister.jpg); - background-size: cover; - background-repeat: no-repeat; - background-position: center; - h1 { - font-weight: 700; - color: #fff; - text-shadow: 0 1px 5px rgba(0,0,0,.2); - } -} - -.cp-register-det { - margin-top: -7em; - background: #fff; - box-shadow: 0 5px 15px rgba(69,145,196, 0.3); - - #data { - // Old browsers - background: #4591C4; - - // Chrome 10-25, Safari 5.1-6 - background: -webkit-linear-gradient(to right, #FF7C4F, #4592C4); // lesshint duplicateProperty: false - - // W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ - background: linear-gradient(to right, #FF7C4F, #4592C4); // lesshint duplicateProperty: false - - padding-top: 3em; - padding-bottom: 7em; - padding-left: 30px; - padding-right: 30px; - p { - color: #fff; - li { - margin-bottom: 1em; - } - .fa { - font-size: 1.5em; - padding-right: 10px; - color: #000; - } - } - h3 { - font-weight: 700; - margin-bottom: 1em; - } - } - #userForm { - padding-top: 3em; - padding-bottom: 2em; - .form-control { - border-radius: 0; - color: @cryptpad_text_col; - margin-top: 1em; - &:focus { - border-color: @cryptpad_color_blue; - } - } - .checkbox-container { - color: @cryptpad_text_col; - } - } - .cp-login-register { - color: @cryptpad_color_blue; - background: #fff; - border: 2px solid @cryptpad_color_blue; - border-radius: 0; - &:hover { - transform: scale(1.05); - } - } -} -.cp-register-test { - margin-top: 3em; - hr { - width: 15rem; - border-top: 2px solid @cryptpad_color_blue; - margin-top: 0; - margin-bottom: 2em; - } - p { - margin-bottom: 0; - } - .cp-test-source { - font-style: italic; - } - .test-details { - padding-left: 4em; - background-image: url(/customize/testimonial.svg); + .cp-register-wel { + padding-top: 6em; + padding-bottom: 20em; + background-image: url(/customize/bkregister.jpg); + background-size: cover; background-repeat: no-repeat; - background-position: left top; - background-size: 3em; - color: @cryptpad_text_col; + background-position: center; + h1 { + font-weight: 700; + color: #fff; + text-shadow: 0 1px 5px rgba(0,0,0,.2); + } } + .cp-register-det { + margin-top: -7em; + background: #fff; + box-shadow: 0 5px 15px rgba(69,145,196, 0.3); + + #data { + // Old browsers + background: #4591C4; + + // Chrome 10-25, Safari 5.1-6 + background: -webkit-linear-gradient(to right, #FF7C4F, #4592C4); // lesshint duplicateProperty: false + + // W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ + background: linear-gradient(to right, #FF7C4F, #4592C4); // lesshint duplicateProperty: false + + padding-top: 3em; + padding-bottom: 7em; + padding-left: 30px; + padding-right: 30px; + p { + color: #fff; + li { + margin-bottom: 1em; + } + .fa { + font-size: 1.5em; + padding-right: 10px; + color: #000; + } + } + h3 { + font-weight: 700; + margin-bottom: 1em; + } + } + #userForm { + padding-top: 3em; + padding-bottom: 2em; + .form-control { + border-radius: 0; + color: @cryptpad_text_col; + margin-top: 1em; + &:focus { + border-color: @cryptpad_color_blue; + } + } + .checkbox-container { + color: @cryptpad_text_col; + } + } + .cp-login-register { + color: @cryptpad_color_blue; + background: #fff; + border: 2px solid @cryptpad_color_blue; + border-radius: 0; + &:hover { + transform: scale(1.05); + } + } + } + .cp-register-test { + margin-top: 3em; + hr { + width: 15rem; + border-top: 2px solid @cryptpad_color_blue; + margin-top: 0; + margin-bottom: 2em; + } + p { + margin-bottom: 0; + } + .cp-test-source { + font-style: italic; + } + .test-details { + padding-left: 4em; + background-image: url(/customize/testimonial.svg); + background-repeat: no-repeat; + background-position: left top; + background-size: 3em; + color: @cryptpad_text_col; + } + + } + #cp-main { + background: #fff; + } } -#cp-main { - background: #fff; -} + diff --git a/customize.dist/src/less2/pages/page-terms.less b/customize.dist/src/less2/pages/page-terms.less index a1f8b2955..7370f34f3 100644 --- a/customize.dist/src/less2/pages/page-terms.less +++ b/customize.dist/src/less2/pages/page-terms.less @@ -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_main(); -.infopages_topbar(); diff --git a/customize.dist/src/less2/pages/page-what-is-cryptpad.less b/customize.dist/src/less2/pages/page-what-is-cryptpad.less index 0e02ce41a..f26188fe7 100644 --- a/customize.dist/src/less2/pages/page-what-is-cryptpad.less +++ b/customize.dist/src/less2/pages/page-what-is-cryptpad.less @@ -1,43 +1,45 @@ -@import (once) "../include/infopages.less"; -@import (once) "../include/colortheme-all.less"; +@import (reference) "../include/infopages.less"; +@import (reference) "../include/colortheme-all.less"; -.infopages_main(); -.infopages_topbar(); +&.cp-page-what-is-cryptpad { + .infopages_main(); -.cp-what-is { - padding-top: 3em; - padding-bottom: 3em; - background-image: url(/customize/bkwhat.jpg); - background-size: cover; - background-repeat: no-repeat; - background-position: center; - color: #fff; - h1 { - font-weight: 700; - } -} -#cp-main { - background: #fff; -} -.cp-container { - padding-top: 3em; - padding-bottom: 3em; - h2 { - margin-top: 0; - font-weight: 700; - color: @cryptpad_header_col; - } - p { - color: @cryptpad_text_col; - } - #zeroknowledge { - width: 65%; - } - .row { - margin-bottom: 1.5em; - } - img { - display: block; - margin: 0 auto; - } + .cp-what-is { + padding-top: 3em; + padding-bottom: 3em; + background-image: url(/customize/bkwhat.jpg); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + color: #fff; + h1 { + font-weight: 700; + } + } + #cp-main { + background: #fff; + } + .cp-container { + padding-top: 3em; + padding-bottom: 3em; + h2 { + margin-top: 0; + font-weight: 700; + color: @cryptpad_header_col; + } + p { + color: @cryptpad_text_col; + } + #zeroknowledge { + width: 65%; + } + .row { + margin-bottom: 1.5em; + } + img { + display: block; + margin: 0 auto; + } + } } + diff --git a/customize.dist/src/less2/readme.md b/customize.dist/src/less2/readme.md index 08a26ec9b..062b5baff 100644 --- a/customize.dist/src/less2/readme.md +++ b/customize.dist/src/less2/readme.md @@ -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 `
` -* 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. diff --git a/customize.dist/template.js b/customize.dist/template.js index ddfd1c1d8..661ce6687 100644 --- a/customize.dist/template.js +++ b/customize.dist/template.js @@ -3,7 +3,7 @@ define([ '/common/hyperscript.js', '/customize/pages.js', - 'less!/bower_components/components-font-awesome/css/font-awesome.min.css', + 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', ], function ($, h, Pages) { $(function () { var $body = $('body'); @@ -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); diff --git a/customize.dist/translations/messages.de.js b/customize.dist/translations/messages.de.js index 57e94ac16..4f70ced16 100644 --- a/customize.dist/translations/messages.de.js +++ b/customize.dist/translations/messages.de.js @@ -1,171 +1,1205 @@ - define(function () { - var out = {}; +define(function () { + var out = {}; - // translations must set this key for their language to be available in - // the language dropdowns that are shown throughout Cryptpad's interface - out._languageName = 'German'; + // translations must set this key for their language to be available in + // the language dropdowns that are shown throughout Cryptpad's interface + out._languageName = 'German'; - out.main_title = "Cryptpad: Echtzeitzusammenarbeit, ohne Preisgabe von Informationen"; - out.main_slogan = "Einigkeit ist Stärke - Zusammenarbeit der Schlüssel"; // Der Slogan sollte evtl. besser englisch bleiben. + out.main_title = "Cryptpad: Echtzeitzusammenarbeit ohne Preisgabe von Informationen"; + out.main_slogan = "Einigkeit ist Stärke - Zusammenarbeit der Schlüssel"; - out.type = {}; - out.type.pad = 'Pad'; - out.type.code = 'Code'; - out.type.poll = 'Umfrage'; - out.type.slide = 'Präsentation'; + out.type = {}; + out.type.pad = 'Pad'; + out.type.code = 'Code'; + out.type.poll = 'Umfrage'; + out.type.kanban = 'Kanban'; + out.type.slide = 'Präsentation'; + out.type.drive = 'CryptDrive'; + out.type.whiteboard = 'Whiteboard'; + out.type.file = 'Datei'; + out.type.media = 'Medien'; + out.type.todo = 'Aufgabe'; + out.type.contacts = 'Kontakte'; - out.common_connectionLost = 'Serververbindung verloren'; + out.button_newpad = 'Neues Pad'; + out.button_newcode = 'Neues Code Pad'; + out.button_newpoll = 'Neue Umfrage'; + out.button_newslide = 'Neue Präsentation'; + out.button_newwhiteboard = 'Neues Whiteboard'; + out.button_newkanban = 'Neues Kanban'; - out.disconnected = 'Getrennt'; - out.synchronizing = 'Synchronisiert'; - out.reconnecting = 'Verbindung wird neu aufgebaut...'; - out.lag = 'Lag'; - out.readonly = 'Nur-Lesen'; - out.anonymous = "Anonymous"; - out.yourself = "Du"; - out.anonymousUsers = "anonyme Nutzer*innen"; - out.anonymousUser = "anonyme Nutzer*in"; - out.users = "Nutzer*innen"; - out.and = "Und"; - out.viewer = "Betrachter*in"; - out.viewers = "Betrachter*innen"; - out.editor = "Bearbeiter*in"; - out.editors = "Bearbeiter*innen"; + // NOTE: Remove updated_0_ if we need an updated_1_ + out.updated_0_common_connectionLost = "Die Verbindung zum Server ist abgebrochen
Du verwendest jetzt das Dokument schreibgeschützt, bis die Verbindung wieder funktioniert."; + out.common_connectionLost = out.updated_0_common_connectionLost; - out.greenLight = "Alles funktioniert bestens"; - out.orangeLight = "Deine langsame Verbindung kann die Nutzung beinträchtigen"; - out.redLight = "Du wurdest von dieser Sitzung getrennt"; + out.websocketError = 'Verbindung zum Websocket fehlgeschlagen...'; + out.typeError = "Dieses Dokument ist nicht mit dem Programm kompatibel"; + out.onLogout = 'Du bist ausgeloggt. {0}Klicke hier{1}, um wieder einzuloggen,
oder drücke die Escapetaste, um dein Dokument schreibgeschützt zu benutzen.'; + out.wrongApp = "Der Inhalt dieser Echtzeitsitzung kann nicht in Deinem Browser angezeigt werden. Bitte lade die Seite neu."; + out.padNotPinned = 'Dieses Dokument wird nach 3 Monaten ohne Zugang auslaufen, {0}logge dich ein{1} or {2}registriere dich{3}, um das Auslaufen zu verhindern.'; + out.anonymousStoreDisabled = "Der Webmaster dieses CryptPad Server hat die anonyme Verwendung deaktiviert. Du muss dich einloggen, um CryptDrive zu verwenden."; + out.expiredError = 'Dieses Dokument ist abgelaufen und ist nicht mehr verfügbar.'; + out.deletedError = 'Dieses Dokument wurde von seinem Besitzer gelöscht und ist nicht mehr verfügbar.'; + out.inactiveError = 'Dieses Dokument ist wegen Inaktivität gelöscht worden. Drücke auf die Esc-Taste, um ein neues Dokument zu erstellen.'; + out.chainpadError = 'Ein kritischer Fehler ist beim Aktualisieren deines Dokuments aufgetreten. Dieses Dokument ist schreibgeschützt, damit du sicherstellen kannst, dass kein Inhalt verloren geht.
'+ + 'Drücke auf Esc, um das Dokument schreibgeschützt zu lesen, oder lade es neu, um das Editierien wieder aufzunehmen.'; + out.errorCopy = ' Du kannst noch den Inhalt woanders hin kopieren, nachdem du Esc gedrückt hast.
Wenn du die Seite verlässt, verschwindet der Inhalt für immer!'; + out.errorRedirectToHome = 'Drücke Esc um zu deinem CryptDrive zurückzukehren.'; + out.newVersionError = "Eine neue Version von CryptPad ist verfügbar.
" + + "Lade die Seite neu um die neue version zu benutzen, oder drücke Esc um im Offline-Modus weiterzuarbeiten."; - out.importButtonTitle = 'Importiere eine lokale Datei in dieses Dokument'; + out.loading = "Laden..."; + out.error = "Fehler"; + out.saved = "Gespeichert"; + out.synced = "Alles gespeichert"; + out.deleted = "Dokumente, die von deinem CryptDrive gelöscht wurden"; + out.deletedFromServer = "Dokumente, die vom Server gelöscht wurden"; - out.exportButtonTitle = 'Exportiere dieses Dokument in eine lokale Datei'; - out.exportPrompt = 'Wie möchtest du die Datei nennen?'; + out.realtime_unrecoverableError = "Es ist ein nicht reparierbarer Fehler aufgetreten.. Klicke auf OK, um neuzuladen."; + out.disconnected = 'Getrennt'; + out.synchronizing = 'Synchronisieren'; + out.reconnecting = 'Verbindung wird aufgebaut'; + out.typing = "Es wird getippt"; + out.initializing = "Starten..."; + out.forgotten = 'Zum Papierkorb verschoben'; + out.errorState = 'Kritischer Fehler: {0}'; + out.lag = 'Verspätung'; + out.readonly = 'schreibgeschützt'; + out.anonymous = "Anonym"; + out.yourself = "Du"; + out.anonymousUsers = "anonyme Nutzer*innen"; + out.anonymousUser = "anonyme Nutzer*in"; + out.users = "Nutzer*innen"; + out.and = "Und"; + out.viewer = "Betrachter*in"; + out.viewers = "Betrachter*innen"; + out.editor = "Bearbeiter*in"; + out.editors = "Bearbeiter*innen"; + out.userlist_offline = "Du bist aktuell offline, die Benutzerliste ist nicht verfügbar."; - out.changeNamePrompt = 'Ändere deinen Namen (oder lasse dieses Feld leer um anonym mitzuarbeiten): '; + out.language = "Sprache"; - out.clickToEdit = "Zum Bearbeiten klicken"; + out.comingSoon = "Kommt bald..."; - out.forgetButtonTitle = 'Entferne dieses Dokument von deiner Startseitenliste'; - out.forgetPrompt = 'Mit dem Klick auf OK wird das Dokument aus deinem lokalen Speicher gelöscht. Fortfahren?'; + out.newVersion = 'CryptPad wurde aktualisiert!
' + + 'Entdecke, was neu in dieser Version ist:
'+ + 'Release notes for CryptPad {0}'; - out.shareButton = 'Teilen'; - out.shareSuccess = 'Die URL wurde in die Zwischenablage kopiert'; + out.upgrade = "aufrüsten"; + out.upgradeTitle = "Rüste dein Konto auf, um mehr Speicherplatz zu haben"; - out.presentButtonTitle = "Präsentationsmodus starten"; + out.upgradeAccount = "Konto aufrüsten"; + out.MB = "MB"; + out.GB = "GB"; + out.KB = "KB"; - out.backgroundButtonTitle = 'Die Hintergrundfarbe der Präsentation ändern'; - out.colorButtonTitle = 'Die Textfarbe im Präsentationsmodus ändern'; + out.supportCryptpad = "CryptPad unterstützen"; - // Hierfür fehlt eine passende Übersetzung... + out.formattedMB = "{0} MB"; + out.formattedGB = "{0} GB"; + out.formattedKB = "{0} KB"; - out.editShare = "Mitarbeits-URL teilen"; - out.editShareTitle = "Mitarbeits-URL in die Zwischenablage kopieren"; - out.viewShare = "Nur-Lesen-URL teilen"; - out.viewShareTitle = "Nur-Lesen-URL in die Zwischenablage kopieren"; - out.viewOpen = "In neuem Tab anzeigen"; - out.viewOpenTitle = "Dokument im Nur-Lesen-Modus in neuem Tab öffnen."; + out.greenLight = "Alles funktioniert bestens"; + out.orangeLight = "Deine langsame Verbindung kann die Nutzung beeinträchtigen"; + out.redLight = "Du wurdest von dieser Sitzung getrennt"; - out.notifyJoined = "{0} ist der gemeinsamen Sitzung beigetreten"; - out.notifyRenamed = "{0} heißt nun {1}"; - out.notifyLeft = "{0} hat die gemeinsame Sitzung verlassen"; + out.pinLimitReached = "Du hast Deine Speicherplatzgrenze erreicht"; + out.updated_0_pinLimitReachedAlert = "Du hast Deine Speicherplatzgrenze erreicht. Neue Dokumente werden nicht mehr in Deinem CryptDrive gespeichert.
" + + 'Du kannst entweder ein Dokument von deinem CryptDrive entfernen oder ein Premiumangebot anfordern, damit deine Grenze erhöht wird.'; + out.pinLimitReachedAlert = out.updated_0_pinLimitReachedAlert; + out.pinLimitReachedAlertNoAccounts = out.pinLimitReached; + out.pinLimitNotPinned = "Du hast deine Speicherplatzgrenze erreicht.
"+ + "Dieses Dokument ist nicht in deinem CryptDrive gespeichert."; + out.pinLimitDrive = "Du hast deine Speicherplatzgrenze erreicht.
" + + "Du kannst keine neue Dokumente gestalten."; - out.tryIt = 'Probier\'s aus!'; + out.moreActions = "Mehr Aktionen"; - out.okButton = 'OK (Enter)'; - out.cancelButton = 'Abbrechen (ESC)'; + out.importButton = "Importieren"; + out.importButtonTitle = 'Importiere eine lokale Datei in dieses Dokument'; - // Polls + out.exportButton = "Exportieren"; + out.exportButtonTitle = 'Exportiere dieses Dokument in eine lokale Datei'; + out.exportPrompt = 'Wie möchtest du die Datei nennen?'; - out.poll_title = "Datumsplaner ohne Preisgabe von Infos"; - out.poll_subtitle = "Echtzeit-planen ohne Preisgabe von Infos"; + out.changeNamePrompt = 'Ändere deinen Namen (oder lasse dieses Feld leer, um anonym mitzuarbeiten): '; + out.user_rename = "Bearbeite deinen Name"; + out.user_displayName = "Name"; + out.user_accountName = "Kontoname"; - out.poll_p_save = "Deine Einstellungen werden sofort automatisch gesichert."; - out.poll_p_encryption = "Alle Eingaben sind verschlüsselt, deshalb haben nur Leute im Besitz des Links Zugriff. Selbst der Server sieht nicht was du änderst."; + out.clickToEdit = "Zum Bearbeiten klicken"; + out.saveTitle = "Bitte gebe den Titel ein (Enter)"; - out.wizardLog = "Klicke auf den Button links oben um zur Umfrage zurückzukehren."; - out.wizardTitle = "Nutze den Assistenten um deine Umfrage zu erstellen."; - out.wizardConfirm = "Bist du wirklich bereit die angegebenen Optionen bereits zu deiner Umfrage hinzuzufügen?"; + out.forgetButtonTitle = 'Entferne dieses Dokument von deiner Startseitenliste'; + out.forgetButtonTitle = 'Dieses Dokument zum Papierkorb verschieben'; + out.forgetPrompt = 'Mit dem Klick auf OK wird das Dokument aus deinem lokalen Speicher gelöscht. Fortfahren?'; + out.movedToTrash = 'Dieses Dokument liegt im Papierkorb.
Du kannst zum CryptDrive navigieren'; + out.shareButton = 'Teilen'; + out.shareSuccess = 'Die URL wurde in die Zwischenablage kopiert'; - out.poll_publish_button = "Veröffentlichen"; - out.poll_admin_button = "Admin"; - out.poll_create_user = "Neuen Benutzer hinzufügen"; - out.poll_create_option = "Neue Option hinzufügen"; - out.poll_commit = "Einchecken"; + out.userListButton = "Benutzerliste"; - out.poll_closeWizardButton = "Assistent schließen"; - out.poll_closeWizardButtonTitle = "Assistent schließen"; - out.poll_wizardComputeButton = "Optionen übernehmen"; - out.poll_wizardClearButton = "Tabelle leeren"; - out.poll_wizardDescription = "Erstelle die Optionen automatisch, indem du eine beliebige Anzahl von Daten und Zeiten eingibst."; - out.poll_wizardAddDateButton = "+ Daten"; - out.poll_wizardAddTimeButton = "+ Zeiten"; + out.userAccountButton = "Dein Konto"; - out.poll_optionPlaceholder = "Option"; - out.poll_userPlaceholder = "Dein Name"; + out.newButton = 'Neu'; + out.newButtonTitle = 'Neues Dokument gestalten'; + out.uploadButton = 'Hochladen'; + out.uploadButtonTitle = 'Eine neue Datei in den aktuelle Ordner hochladen'; + + out.saveTemplateButton = "Als Vorlage speichern"; + out.saveTemplatePrompt = "Bitte gib einen Titel für die Vorlage ein"; + out.templateSaved = "Vorlage gespeichert!"; + out.selectTemplate = "Bitte wähle eine Vorlage oder drucke die Esc Taste"; + out.useTemplate = "Mit einer Vorlage starten?"; //Would you like to "You have available templates for this type of pad. Do you want to use one?"; + out.useTemplateOK = 'Wähle eine Vorlage (Enter)'; + out.useTemplateCancel = 'Frisch starten (Esc)'; + out.template_import = "Eine Vorlage importieren"; + out.template_empty = "Keine Vorlage verfügbar"; + + out.previewButtonTitle = "Die Markdownvorschau (un)sichtbar machen"; + + out.presentButtonTitle = "Zum Präsentationsmodus wechseln"; + + out.backgroundButtonTitle = 'Hintergrundfarbe'; + out.colorButtonTitle = 'Die Hintergrundfarbe des Präsentationsmodus bearbeiten'; + + out.propertiesButton = "Eigenschaften"; + out.propertiesButtonTitle = 'Die Eigenschaften des Dokuments ansehen'; + + out.printText = "Drucken"; + out.printButton = "Drucken (enter)"; + out.printButtonTitle = "Deine Präsentation ausdrucken oder als PDF Dateien exportieren"; + out.printOptions = "Druckeinstellungen"; + out.printSlideNumber = "Foliennummer anzeigen"; + out.printDate = "Datum anzeigen"; + out.printTitle = "Titel der Präsentation anzeigen"; + out.printCSS = "Custom CSS Regeln (CSS):"; + out.printTransition = "Animierte Übergänge aktivieren"; + out.printBackground = "Ein Hintergrundbild verwenden"; + out.printBackgroundButton = "Bitte ein Bild wählen"; + out.printBackgroundValue = "Aktueller Hintergrund: {0}"; + out.printBackgroundNoValue = "Kein Hintergrundbild gewählt"; + out.printBackgroundRemove = "Das Hintergrundbild wählen"; + + out.filePickerButton = "Eine Datei deines CryptDrives einbetten"; + out.filePicker_close = "Schliessen"; + out.filePicker_description = "Bitte wähle eine Datei aus deinem CryptDrive oder lade eine neue hoch"; + out.filePicker_filter = "Namensfilter"; + out.or = 'oder'; + + out.tags_title = "Tags (nur für dich)"; + out.tags_add = "Die Tags dieser Seite bearbeiten"; + out.tags_searchHint = "Dateien mit Tags in deinem CryptDrive suchen"; + out.tags_searchHint = "Die Suche mit dem Tag # in deinem CryptDrive starten."; + out.tags_notShared = "Deine Tags sind nicht mit anderen Benutzern geteilt"; + out.tags_duplicate = "Doppeltes Tag: {0}"; + out.tags_noentry = "Du kannst kein Tag bei einem gelöschten Dokument hinzufügen!"; + + out.slideOptionsText = "Einstellungen"; + out.slideOptionsTitle = "Präsentationseinstellungen"; + out.slideOptionsButton = "Speichern (enter)"; + out.slide_invalidLess = "Ungültiges Custom-Stil"; + + out.languageButton = "Sprache"; + out.languageButtonTitle = "Bitte wähle die Sprache für die Syntaxhervorhebung"; + out.themeButton = "Farbschema"; + out.themeButtonTitle = "Wähle das Farbschema für Code und Folieneditor"; + + out.editShare = "Mitarbeits-URL teilen"; + out.editShareTitle = "Mitarbeit-URL in die Zwischenablage kopieren"; + out.editOpen = "Die Mitarbeits-URL in einem neuen Tab öffnen"; + out.editOpenTitle = "Öffne dieses Dokument im Mitarbeitmodus in einem neuen Tab"; + out.viewShare = "Schreibgeschützte URL teilen"; + out.viewShareTitle = "Schreibgeschützte URL in die Zwischenablage kopieren"; + out.viewOpen = "In neuem Tab anzeigen"; + out.viewOpenTitle = "Dokument schreibgeschützt in neuem Tab öffnen."; + out.fileShare = "Link kopieren"; + out.getEmbedCode = "Einbettungscode anzeigen"; + out.viewEmbedTitle = "Das Dokument in eine externe Webseite einbetten"; + out.viewEmbedTag = "Um dieses Dokument einzubetten, platziere dieses iframe an der gewünschten Stelle Deiner HTML Seite. Du kannst es mit CSS oder HTML Attributen gestalten"; + out.fileEmbedTitle = "Die Datei in einer externen Seite einbetten"; + out.fileEmbedScript = "Um diese Datei einzubetten, füge dieses Skript einmal in Deiner Webseite ein, damit das Media-Tag geladen wird:"; + out.fileEmbedTag = "Dann platziere das Media-Tag an der gewünschten Stelle der Seite:"; + + out.notifyJoined = "{0} ist in der Mitarbeits-Sitzung "; + out.notifyRenamed = "{0} ist jetzt als {1} bekannt"; + out.notifyLeft = "{0} hat die Mitarbeits-Sitzung verlassen"; + + out.okButton = 'OK (enter)'; + + out.cancel = "Abbrechen"; + out.cancelButton = 'Abbrechen (esc)'; + out.doNotAskAgain = "Nicht mehr fragen (Esc)"; + + out.show_help_button = "Hilfe anzeigen"; + out.hide_help_button = "Hilfe verbergen"; + out.help_button = "Hilfe"; + + out.historyText = "Verlauf"; + out.historyButton = "Den Dokumentverlauf anzeigen"; + out.history_next = "früher"; + out.history_prev = "Zur früheren Version wechseln"; + out.history_goTo = "Zur genannten Version wechseln"; + out.history_loadMore = "Weiteren Verlauf laden"; + out.history_close = "Zurück"; + out.history_closeTitle = "Verlauf schliessen"; + out.history_restore = "wiederherstellen"; + out.history_restoreTitle = "Die gewählte Version des Dokuments wiederherstellen"; + out.history_restorePrompt = "Bist du sicher, dass du die aktuelle Version mit der angezeigten ersetzen möchtest?"; + out.history_restoreDone = "Version wiederhergestellt"; + out.history_version = "Version:"; + + // Ckeditor + out.openLinkInNewTab = "Link im neuen Tab öffnen"; + out.pad_mediatagTitle = "Media-Tag Einstellungen"; + out.pad_mediatagWidth = "Breite (px)"; + out.pad_mediatagHeight = "Höhe (px)"; + + // Kanban + out.kanban_newBoard = "Neues Kanban-Bord"; + out.kanban_item = "Item {0}"; // Item number for initial content + out.kanban_todo = "Zu bearbeiten"; + out.kanban_done = "Erledigt"; + out.kanban_working = "In Bearbeitung"; + out.kanban_deleteBoard = "Bist du sicher, dass du dieses Bord löschen möchtest?"; + out.kanban_addBoard = "Ein Bord hinzufügen"; + out.kanban_removeItem = "Dieses Item entfernen"; + out.kanban_removeItemConfirm = "Bist du sicher, dass du dieses Item löschen möchtest?"; + + // Polls + out.poll_title = "Terminplaner ohne Preisgabe von Daten"; + out.poll_subtitle = "Echtzeit-planen ohne Preisgabe von Daten"; + + out.poll_p_save = "Deine Einstellungen werden sofort automatisch gesichert."; + out.poll_p_encryption = "Alle Eingaben sind verschlüsselt, deshalb haben nur Leute Zugriff, die den Link kennen. Selbst der Server sieht nicht was Du änderst."; + + out.wizardLog = "Klicke auf den Button links oben um zur Umfrage zurückzukehren."; + out.wizardTitle = "Nutze den Assistenten um deine Umfrage zu erstellen."; + out.wizardConfirm = "Bist du wirklich bereit, die angegebenen Optionen bereits zu deiner Umfrage hinzuzufügen?"; + + out.poll_publish_button = "Veröffentlichen"; + out.poll_admin_button = "Admin"; + out.poll_create_user = "Neuen Benutzer hinzufügen"; + out.poll_create_option = "Neue Option hinzufügen"; + out.poll_commit = "Einchecken"; + + out.poll_closeWizardButton = "Assistent schließen"; + out.poll_closeWizardButtonTitle = "Assistent schließen"; + out.poll_wizardComputeButton = "Optionen übernehmen"; + out.poll_wizardClearButton = "Tabelle leeren"; + out.poll_wizardDescription = "Erstelle die Optionen automatisch, indem du eine beliebige Anzahl von Daten und Zeiten eingibst."; + out.poll_wizardAddDateButton = "+ Daten"; + out.poll_wizardAddTimeButton = "+ Zeiten"; + + out.poll_optionPlaceholder = "Option"; + out.poll_userPlaceholder = "Dein Name"; out.poll_removeOption = "Bist du sicher, dass du diese Option entfernen möchtest?"; out.poll_removeUser = "Bist du sicher, dass du diese(n) Nutzer*in entfernen möchtest?"; - out.poll_titleHint = "Titel"; - out.poll_descriptionHint = "Beschreibe deine Abstimmung und publiziere sie mit dem 'Veröffentlichen'-Knopf wenn du fertig bis. Jeder mit dem Link kann die Beschreibung ändern."; + out.poll_titleHint = "Titel"; + out.poll_descriptionHint = "Beschreibe deine Abstimmung und publiziere sie mit dem 'Veröffentlichen'-Knopf wenn du fertig bist."+ + " Die Beschreibung kann mit Markdown Syntax geschrieben werden und Du kannst Media-Elemente von deinem CryptPad einbetten." + + "Jeder, der den Link kennt, kann die Beschreibung ändern, aber es ist keine gute Praxis."; - // index.html + out.poll_remove = "Entfernen"; + out.poll_edit = "Bearbeiten"; + out.poll_locked = "Gesperrt"; + out.poll_unlocked = "Editierbar"; - out.main_howitworks = 'Wie es funktioniert'; - out.main_p2 = 'Dieses Projekt nutzt den CKEditor visuellen Editor, CodeMirror, und die ChainPad realtime engine.'; - out.main_howitworks_p1 = 'CryptPad nutzt eine Variante des Operational transformation Algorithmus. Dieser kann mit Hilfe der Nakamoto Blockchain, einem Konstrukt, das durch das Bitcoin-Projekt Bekanntheit erlangte, verteilten Konsens (distributed consensus) finden. Damit ist der Algorithmus nicht auf einen zentralen Server angewiesen um Konflikte zu lösen — der Server muss also nichts vom Inhalt der Pads wissen.'; + out.poll_bookmark_col = 'Setze ein Lesezeichen auf dieser Spalte, damit sie immer gleich editierbar und links angezeigt wird.'; + out.poll_bookmarked_col = 'Dieses ist die Spalte mit Lesezeichen für dich. Sie wird immer editierbar und links angezeigt.'; + out.poll_total = 'SUMME'; - out.main_about_p2 = 'Für Fragen und Kommentare kannst du uns tweeten, ein Ticket auf Github öffnen, hi auf irc sagen (irc.freenode.net), oder eine Mail zukommen lassen.'; + out.poll_comment_list = "Komentare"; + out.poll_comment_add = "Einen Kommentar hinzufügen"; + out.poll_comment_submit = "Schicken"; + out.poll_comment_remove = "Diesen Kommentar entfernen"; + out.poll_comment_placeholder = "Dein Kommentar"; - out.button_newpad = 'Neues WYSIWYG-Pad erstellen'; - out.button_newcode = 'Neues Code-Pad erstellen'; - out.button_newpoll = 'Neue Abstimmung erstellen'; - out.button_newslide = 'Neue Präsentation erstellen'; + out.poll_comment_disabled = "Diese Umfrage mit dem ✓ Knopf veröffentlichen, damit Kommentare möglich sind."; - // privacy.html + // Canvas + out.canvas_clear = "Löschen"; + out.canvas_delete = "Abschnitt entfernen"; + out.canvas_disable = "Zeichnung deaktivieren"; + out.canvas_enable = "Zeichnung aktivieren"; + out.canvas_width = "Breite"; + out.canvas_opacity = "Deckkraft"; + out.canvas_opacityLabel = "Deckkraft: {0}"; + out.canvas_widthLabel = "Breite: {0}"; + out.canvas_saveToDrive = "Dieses Bild in deinem CryptDrive speichern"; + out.canvas_currentBrush = "Aktueller Pinsel"; + out.canvas_chooseColor = "Eine Farbe wählen"; + out.canvas_imageEmbed = "Ein Bild aus deinem Rechner einbetten"; - out.policy_title = 'Cryptpad Datenschutzbestimmungen'; - out.policy_whatweknow = 'Was wir über dich wissen'; - out.policy_whatweknow_p1 = 'Als Programm, das im Web gehostet wird, hat Cryptpad Zugriff auf die Metadaten, die vom HTTP-Protokoll exponiert werden. Inbegriffen ist deine IP-Adresse und diverse andere HTTP-Header, die es ermöglichen deinen Browser zu identifizieren. Um zu sehen welche Daten dein Browser preis gibt kanst du die Seite WhatIsMyBrowser.com besuchen.'; - out.policy_whatweknow_p2 = 'Wir nutzen Piwik, eine open source Analyseplatform um mehr über unsere Nutzer*innen zu lernen. Piwik teilt uns mit, wie du Cryptpad gefunden hast — durch direkten Zugriff, mit Hilfe eine Suchmaschine oder über einen Link auf einer anderen Seite wie z.B. Reddit oder Twitter. Außerdem lernen wir mehr über deinen Besuch, welchen Link du auf den Informationsseiten klickst und wie lange du auf diesen Seiten verweilst.'; - out.policy_howweuse = 'Wie wir das Wissen anwenden'; - out.policy_howweuse_p1 = 'Wir nutzen diese Informationen um besser entscheiden zu können wie Cryptpad beworben werden kann und um genutzte Strategien zu evaluieren. Informationen über deinen Standort helfen uns abzuschätzen welche Sprachen wir besser unterstützen sollten.'; - out.policy_howweuse_p2 = "Informationen zu deinem Browser (ob du auf einem Desktop oder Smartphone arbeitest) hilft uns außerdem dabei zu entscheiden, welche Features priorisiert werden sollen. Unser Entwicklerteam ist klein, deshalb ist es uns wichtig Entscheidungen derart zu treffen, dass die Erfahrung der größten Zahl von Nutzer*innen verbessert wird."; - out.policy_whatwetell = 'Was wir anderen über die erzählen'; - out.policy_whatwetell_p1 = 'Wir reichen keine von uns gesammelten Daten weiter, außer im Falle einer gerichtlichen Anordnung.'; - out.policy_links = 'Links zu anderen Seiten'; - out.policy_links_p1 = 'Diese Seite beinhaltet Links zu anderen Seiten, teilweise werden diese von anderen Organisationen verwaltet. Wir sind nicht für den Umgang mit der Privatsphäre und die Inhalte der anderen Seiten verantwortlich. Generell werden Links zu externen Seiten in einem neuem Fenster geöffnet, um zu verdeutlichen, dass du Cryptpad.fr verlässt.'; - out.policy_ads = 'Werbung'; - out.policy_ads_p1 = 'Wir zeigen keine Onlinewerbung, können aber zu Organisationen verlinken, die unsere Forschung finanzieren.'; - out.policy_choices = 'Deine Möglichkeiten'; - out.policy_choices_open = 'Unser Code ist open source, deshalb kannst du jederzeit deine eigene Cryptpad-Instanz hosten.'; - out.policy_choices_vpn = 'Wenn du unsere gehostete Instanz nutzen möchtest bitten wir dich darum IP-Adresse zu verschleiern, das geht zum Beispiel mit dem Tor browser bundle, oder einem VPN-Zugang.'; - out.policy_choices_ads = 'Wenn du unsere Analysesoftware blockieren möchtest kannst du Adblock-Software wie Privacy Badger verwenden.'; + // Profile + out.profileButton = "Profil"; // dropdown menu + out.profile_urlPlaceholder = 'URL'; + out.profile_namePlaceholder = 'Angezeigter Name'; + out.profile_avatar = "Avatar"; + out.profile_upload = " Einen neuen Avatar hochladen"; + out.profile_uploadSizeError = "Fehler: Dein Avatar muss kleiner als {0} sein"; + out.profile_uploadTypeError = "Fehler: Der Typ dieses Bildes wird nicht unterstützt. Unterstütze Typen sind: {0}"; + out.profile_error = "Fehler bei der Erstellung deines Profils: {0}"; + out.profile_register = "Du muss dich einloggen, um ein Profil zu erstellen!"; + out.profile_create = "Ein Profil erstellen"; + out.profile_description = "Beschreibung"; + out.profile_fieldSaved = 'Neuer Wert gespeichert: {0}'; - // terms.html + out.profile_inviteButton = "Sich in Verbindung setzen"; + out.profile_inviteButtonTitle ='Ein Link erstellen, damit dieser Benutzer sich mit dir in Verbindung setzt.'; + out.profile_inviteExplanation = "Ein Klick auf OK wird einen Link erstellen, der eine sichere Chatsession nur mit {0} erlaubt.

Dieser Link kann öffentlich gepostet werden."; + out.profile_viewMyProfile = "Mein Profil anzeigen"; - out.tos_title = "Cryptpad Nutzungsbedingungen"; - out.tos_legal = "Sei nicht bösartig, missbrauchend und mach nichts illegales."; - out.tos_availability = "Wir hoffen, dass dir dieser Service nützt, aber Erreichbarkeit und Performanz können nicht garantiert werden. Bitte exportiere deine Daten regelmäßig."; - out.tos_e2ee = "Cryptpad Dokumente können von allen gelesen oder bearbeitet werden, die den \"fragment identifier\" des Dokuments erraten oder auf eine andere Art davon erfahren. Wir empfehlen dir Ende-Zu-Ende verschlüsselte Nachrichtentechnik (e2ee) zum Versenden der URLs zu nutzen. Wir übernehmen keine Haftung falls eine URL erschlichen oder abgegriffen wird."; - out.tos_logs = "Metadaten, die dein Browser bereitstellt, können geloggt werden, um den Service aufrechtzuerahlten."; - out.tos_3rdparties = "Wir geben keine Individualdaten an dritte Weiter, außer auf richterliche Anordnung."; + // contacts/userlist + out.userlist_addAsFriendTitle = 'Benutzer "{0}" als Kontakt hinzufügen'; + out.userlist_thisIsYou = 'Das bist du ("{0}")'; + out.userlist_pending = "Warte..."; + out.contacts_title = "Kontakte"; + out.contacts_addError = 'Fehler bei dem Hinzufügen des Kontakts in die Liste'; + out.contacts_added = 'Verbindungseinladung angenommen.'; + out.contacts_rejected = 'Verbindungseinladung abgelehnt'; + out.contacts_request = 'Benutzer {0} möchtet dich als Kontakt hinzufügen. Annehmen?'; + out.contacts_send = 'Schicken'; + out.contacts_remove = 'Diesen Kontakt entfernen'; + out.contacts_confirmRemove = 'Bist du sicher, dass du {0} von der Kontaktliste entfernen möchtest?'; + out.contacts_typeHere = "Gebe eine Nachricht ein..."; + + out.contacts_info1 = "Diese ist deine Kontaktliste. Ab hier, kannst du:"; + out.contacts_info2 = "Auf den Avatar eines Kontakts klicken, um mit diesem Benutzer zu chatten"; + out.contacts_info3 = "Den Avatar doppelklicken, um sein Profil anzuzeigen"; + out.contacts_info4 = "Jeder Teilnehmer kann den Chatverlauf löschen"; + + out.contacts_removeHistoryTitle = 'Den Chatverlauf löschen'; + out.contacts_confirmRemoveHistory = 'Bist du sicher, dass du den Chatverlauf komplett löschen willst? Die Daten sind dann weg.'; + out.contacts_removeHistoryServerError = 'Es gab einen Fehler bei dem Löschen des Chatverlaufs. Versuche es später noch einmal'; + out.contacts_fetchHistory = "Den früheren Verlauf laden"; + + // File manager + out.fm_rootName = "Dokumente"; + out.fm_trashName = "Papierkorb"; + out.fm_unsortedName = "Dateien (ohne Ordnung)"; + out.fm_filesDataName = "Alle Dateien"; + out.fm_templateName = "Vorlagen"; + out.fm_searchName = "Suchen"; + out.fm_recentPadsName = "Zuletzt geöffnete Dokumente"; + out.fm_ownedPadsName = "Eigene"; + out.fm_tagsName = "Tags"; + out.fm_searchPlaceholder = "Suchen..."; + out.fm_newButton = "Neu"; + out.fm_newButtonTitle = "Ein neues Dokument oder Ordner erstellen, oder eine Datei in den aktuellen Ordner importieren"; + out.fm_newFolder = "Neuer Ordner"; + out.fm_newFile = "Neues Dokument"; + out.fm_folder = "Ordner"; + out.fm_folderName = "Ordnername"; + out.fm_numberOfFolders = "# von Ordnern"; + out.fm_numberOfFiles = "# von Dateien"; + out.fm_fileName = "Dateiname"; + out.fm_title = "Titel"; + out.fm_type = "Typ"; + out.fm_lastAccess = "Zuletzt besucht"; + out.fm_creation = "Erstellung"; + out.fm_forbidden = "Verbotene Aktion"; + out.fm_originalPath = "Herkunft Pfad"; + out.fm_openParent = "Im Ordner zeigen"; + out.fm_noname = "Dokument ohne Titel"; + out.fm_emptyTrashDialog = "Soll der Papierkorb wirklich gelöscht werden?"; + out.fm_removeSeveralPermanentlyDialog = "Bist du sicher, dass du diese {0} Elemente dauerhaft aus deinem CryptDrive entfernen willst?"; + out.fm_removePermanentlyDialog = "Bist du sicher, dass du dieses Element dauerhaft aus deinem CryptDrive entfernen willst?"; + out.fm_removeSeveralDialog = "Bist Du sicher, dasss du diese {0} Elemente aus dem Papierkorb entfernen willst?"; + out.fm_removeDialog = "Bist du sicher, dass du {0} zum Papierkorb zu verschieben?"; + out.fm_deleteOwnedPad = "Bist du sicher, dass du dieses Dokument aus dem Server dauerhaft löschen willst?"; + out.fm_deleteOwnedPads = "Bist du sicher, dass du diese Dokumente dauerhaft aus dem Server entfernen möchtest?"; + out.fm_restoreDialog = "Bist du sicher, dass du {0} zurück zum originalen Ordner verschieben möchtests?"; + out.fm_unknownFolderError = "Der Ordner, der gerade gewählt oder letzlich besucht wurde, existiert nicht mehr. Der übergeordnete Ordner wird geöffnet..."; + out.fm_contextMenuError = "Fehler bei der Öfnnung des Kontextmenü für dieses Element. Wenn dieses Problem wieder erscheint, versuche die Seite neu zu laden."; + out.fm_selectError = "Fehler bei der Selektierung des Zielelements. Wenn dieses Problem wieder erscheint, versuche die Seite neu zu laden."; + out.fm_categoryError = "Fehler beim Öffnen der selektierten Kategorie. Der Stamm-Ordner wird angezeigt."; + out.fm_info_root = "Erstelle hier so viele Ordner, wie du willst, um deine Dateien und Dokumente zu organisieren."; + out.fm_info_unsorted = 'Hier sind alle Dateien, die Du besucht hast, die noch nicht in "Dokumente" sortiert sind oder zum Papierkorb verschoben wurden.'; + out.fm_info_template = 'Hier sind alle Dokumente, die als Vorlage gespeichert wurden und die du wiederverwenden kannst, um ein neues Dokument zu erstellen.'; + out.fm_info_recent = "Liste der zuletzt geöffneten Dokumente."; + out.updated_0_fm_info_trash = 'Leere den Papierkorb, um mehr freien Platz in deinem CryptDrive zu erhalten.'; + out.fm_info_trash = out.updated_0_fm_info_trash; + out.fm_info_allFiles = 'Beinhaltet alle Dateien von "Dokumente", "Unklassifiziert" und "Papierkorb". Dateien können hier nicht verschoben werden.'; + out.fm_info_anonymous = 'Du bist nicht eingeloggt, daher laufen die Dokumente nach 3 Monaten aus (mehr dazu lesen). ' + + 'Der Zugang zu den Dokumenten ist in deinem Browser gespeichert, daher wird das Löschen des Browserverlaufs auch die Dokumente verschwinden lassen.
' + + 'Registriere dich oder logge dich ein, um sie dauerhaft zu machen.
'; + out.fm_info_owned = "Diese Dokumente sind deine eigenen. Das heisst, dass du sie vom Server entfernen kannst, wann Du willst. Wenn du das machst, dann wird es auch keinen Zugriff zu diesem für andere Benutzer geben."; + out.fm_alert_backupUrl = "Backuplink für dieses CryptDrive.
" + + "Es ist hoch empfohlen diesen Link geheim zu halten.
" + + "Du kannst es benutzen, um deine gesamten Dateien abzurufen, wenn dein Browserspeicher gelöscht wurde.
" + + "Jede Person, die diesen Link hat, kann die Dateien in deinem CryptDrive bearbeiten oder löschen.
"; + out.fm_alert_anonymous = "Hallo, du benutzt CryptPad anonym. Das ist in Ordnung aber Dokumente können nach einer Inaktivitätsperiode gelöscht werden. " + + "Wir haben fortgeschrittene Aktionen aus dem anonymen CryptDrive entfernt, weil wir klar machen wollen, dass es kein sicherer Platz ist, Dinge zu lagern." + + 'Du kannst lesen, weshalb wir das machen und weshalb du wirklich ' + + 'registrieren oder einloggen solltest.'; + out.fm_backup_title = 'Backup link'; + out.fm_nameFile = 'Wie soll diese Datei heissen?'; + out.fm_error_cantPin = "Interner Serverfehler. Bitte lade die Seite neu und versuche es wieder."; + out.fm_viewListButton = "Listenansicht"; + out.fm_viewGridButton = "Kachelansicht"; + out.fm_renamedPad = "Du hast einen speziellen Name für dieses Dokument gesetzt. Seine geteilter Titel ist:
{0}"; + out.fm_prop_tagsList = "Tags"; + out.fm_burnThisDriveButton = "Alle Informationen löschen, die CryptPad in deinem Browser hält"; + out.fm_burnThisDrive = "Bist Du sicher, dass du alles, was CryptPad in deinem Browser gespeichert hat, löschen möchtest?
" + + "Das wird dein CryptDrive und seinen Verlauf in deinem Browser löschen, Dokumente werden noch (verschlüsselt) auf unserem Server bleiben."; + out.fm_padIsOwned = "Dieses Dokument ist dein Eigenes"; + out.fm_padIsOwnedOther = "Dieses Dokument ist von einem anderen Benutzer"; + out.fm_deletedPads = "Dieses Dokument existiert nicht mehr auf dem Server, es wurde von Deinem CryptDrive gelöscht: {0}"; + out.fm_tags_name = "Tag Bezeichnung"; + out.fm_tags_used = "Anzahl"; + + // File - Context menu + out.fc_newfolder = "Neuer Ordner"; + out.fc_rename = "Unbenennen"; + out.fc_open = "Öffnen"; + out.fc_open_ro = "Öffnen (schreibgeschützt)"; + out.fc_delete = "Zum Papierkorb verschieben"; + out.fc_delete_owned = "Vom Server löschen"; + out.fc_restore = "Restaurieren"; + out.fc_remove = "Von deinem CryptDrive entfernen"; + out.fc_empty = "Den Papierkorb leeren"; + out.fc_prop = "Eigenschaften"; + out.fc_hashtag = "Tags"; + out.fc_sizeInKilobytes = "Grösse in Kilobytes"; + + // fileObject.js (logs) + out.fo_moveUnsortedError = "Du kannst einen Ordner nicht in die Liste von allen Pads verschieben"; + out.fo_existingNameError = "Dieser Dokumentname existiert schon in diesem Verzeichnis. Bitte wähle einen Anderen."; + out.fo_moveFolderToChildError = "Du kannst einen Ordner nicht in einen seiner Nachfolger verschieben"; + out.fo_unableToRestore = "Es hat nicht funktioniert, diese Datei an ihrem Herkunftort wiederherzustellen. Du kannst versuchen, sie an einen anderen Ort zu verschieben."; + out.fo_unavailableName = "Ein Dokument oder Ordner mit diesem Namen existiert in diesem Ordner schon. Bitte benenne sie zuerst um, und versuche es dann erneut."; + + out.fs_migration = "Dein CryptDrive wird gerade zu einer neueren Version aktualisiert. Daher muss die Seite neugeladen werden.
Bitte lade die Seite neu, um sie weiter zu verwenden."; + + // login + out.login_login = "Einloggen"; + out.login_makeAPad = 'Ein Dokument anonym erstellen'; + out.login_nologin = "Lokale Dokumente ansehen"; + out.login_register = "Registrieren"; + out.logoutButton = "Ausloggen"; + out.settingsButton = "Einstellungen"; + + out.login_username = "Benutzername"; + out.login_password = "Passwort"; + out.login_confirm = "Passwort bestätigen"; + out.login_remember = "Mein Login speichern"; + + out.login_hashing = "Dein Passwort wird gerade durchgerechnet, das kann etwas dauern."; + + out.login_hello = 'Hallo {0},'; // {0} is the username + out.login_helloNoName = 'Hallo,'; + out.login_accessDrive = 'Dein CryptDrive ansehen'; + out.login_orNoLogin = 'oder'; + + out.login_noSuchUser = 'Ungültiger Benutzername oder Passwort. Versuche es erneut oder registriere dich'; + out.login_invalUser = 'Der Benutzername kann nicht leer sein'; + out.login_invalPass = 'Der Passwort kann nicht leer sein'; + out.login_unhandledError = 'Ein Fehler ist aufgetreten:('; + + out.register_importRecent = "Die Dokumente aus deiner anonymen Sitzung importieren"; + out.register_acceptTerms = "Ich bin mit den Nutzungsbedingungen einverstanden"; + out.register_passwordsDontMatch = "Passwörter sind nicht gleich!"; + out.register_passwordTooShort = "Passwörter müssen mindestens {0} Zeichen haben."; + + out.register_mustAcceptTerms = "Du musst mit den Nutzungsbedingungen einverstanden sein."; + out.register_mustRememberPass = "Wir können dein Passwort nicht zurücksetzen, falls du es vergisst. Es ist äusserst wichtig, dass du es dir merkst! Bitte markiere das Kästchen."; + + out.register_whyRegister = "Wieso solltest Du dich registrieren?"; + out.register_header = "Willkommen zu CryptPad"; + out.register_explanation = [ + "

Lass uns ein Paar Punkte überprüfen:

", + "
    ", + "
  • Dein Passwort ist dein Geheimnis, um alle deine Dokumente zu verschlüsseln. Wenn du es verlierst, gibt es keine Methode, die Daten wiederzufinden.
  • ", + "
  • Du kannst die Dokumente, die du zuletzt angesehen hast, importieren, damit sie in deinem CryptDrive sind.
  • ", + "
  • Wenn du den Rechner mit anderen teilst, musst du ausloggen, wenn du fertig bist. Es ist nicht ausreichend, das Browserfensters oder den Browser zu schliessen.
  • ", + "
" + ].join(''); + + out.register_writtenPassword = "Ich habe meinen Benutzername und Passwort notiert. Weiter geht's."; + out.register_cancel = "Zurück"; + + out.register_warning = "\"Ohne Preisgabe von Daten\" heisst, dass niemand deine Daten wiederherstellen kann, wenn du dein Passwort verlierst."; + + out.register_alreadyRegistered = "Dieser Benutzer existiert schon, willst du dich einloggen?"; + + // Settings + out.settings_cat_account = "Konto"; + out.settings_cat_drive = "CryptDrive"; + out.settings_cat_code = "Code"; + out.settings_cat_pad = "Rich text"; + out.settings_cat_creation = "Neues Dokument"; + out.settings_cat_subscription = "Registrierung"; + out.settings_title = "Einstellungen"; + out.settings_save = "Speichern"; + + out.settings_backupCategory = "Backup"; + out.settings_backupTitle = "Eine Backup erstellen oder die Daten wiederherstellen"; + out.settings_backup = "Backup"; + out.settings_restore = "Wiederherstellen"; + + out.settings_resetNewTitle = "CryptDrive säubern"; + out.settings_resetButton = "Löschen"; + out.settings_reset = "Alle Dateien und Ordnern aus deinem CryptDrive löschen"; + out.settings_resetPrompt = "Diese Aktion wird alle Dokumente deines CryptDrives entfernen.
"+ + "Bist du sicher, dass du das tun möchtest?
" + + "Gebe I love CryptPad ein, um zu bestätigen."; // TODO: I love CryptPad should be localized + out.settings_resetDone = "Dein CryptDrive ist jetzt leer!"; + out.settings_resetError = "Prüftext inkorrekt. Dein CryptDrive wurde nicht verändert."; + + out.settings_resetTipsAction = "Zurücksetzen"; + out.settings_resetTips = "Tipps"; + out.settings_resetTipsButton = "Die Tipps für CryptDrive zurücksetzen"; + out.settings_resetTipsDone = "Alle Tipps sind wieder sichtbar."; + + out.settings_thumbnails = "Vorschaubilder"; + out.settings_disableThumbnailsAction = "Die Erstellung von Vorschaubilder in deinem CryptPad deaktivieren"; + out.settings_disableThumbnailsDescription = "Vorschaubilder werden automatisch erstellt und in deinem Browser gespeichert, wenn du ein Dokument besuchst. Du kannst dieses Feature hier deaktivieren."; + out.settings_resetThumbnailsAction = "Entfernen"; + out.settings_resetThumbnailsDescription = "Alle Vorschaubilder entfernen, die in Deinem Browser gespeichert sind."; + out.settings_resetThumbnailsDone = "Alle Vorschaubilder sind entfernt worden."; + + out.settings_importTitle = "Importiere die kürzlich besuchte Dokumente in Deinem CryptDrive"; + out.settings_import = "Importieren"; + out.settings_importConfirm = "Bist Du sicher, dass Du die kürzlich besuchte Dokumente in Deinem Konto importieren möchtest??"; + out.settings_importDone = "Import erledigt"; + + out.settings_userFeedbackTitle = "Rückmeldung"; + out.settings_userFeedbackHint1 = "CryptPad gibt grundlegende Rückmeldungen zum Server, um die Benutzer-Erfahrung zu verbessern können."; + out.settings_userFeedbackHint2 = "Der Inhalt deiner Dokumente wird nie mit dem Server geteilt."; + out.settings_userFeedback = "Rückmeldungen aktivieren"; + + out.settings_deleteTitle = "Löschung des Kontos"; + out.settings_deleteHint = "Die Löschung eines Kontos ist dauerhaft. Dein CryptDrive und eigene Dokumente werden alle von dem Server gelöscht. Die restliche Dokumente werden nach 90 Tage gelöscht, wenn niemand anderes diese bei sich gelagert hat."; + out.settings_deleteButton = "Dein Konto löschen"; + out.settings_deleteModal = "Gebe die folgende Information deinem CryptPad Adminstrator, damit er die Daten vom Server löschen kann."; + out.settings_deleteConfirm = "Wenn du OK klickst, wird dein Konto dauerhaft löschen. Bist Du sicher?"; + out.settings_deleted = "Dein Konto ist jetzt gelöscht. Drucke OK, um zum Homepage zu gelangen."; + + out.settings_anonymous = "Du bist nicht eingeloggt. Die Einstellungen hier gelten nur für diesem Browser."; + out.settings_publicSigningKey = "Öffentliche Schlüssel zum Unterschreiben"; + + out.settings_usage = "Verbrauch"; + out.settings_usageTitle = "Die Gesamtgrösse deiner Dokumente in MB"; // TODO: pinned ?? + out.settings_pinningNotAvailable = "Gepinnte Dokumente sind nur für angemeldete Benutzer verfügbar."; + out.settings_pinningError = "Etwas ging schief"; + out.settings_usageAmount = "Deine gepinnten Dokumente verwenden {0}MB"; + + out.settings_logoutEverywhereButton = "Ausloggen"; + out.settings_logoutEverywhereTitle = "Überall ausloggen"; + out.settings_logoutEverywhere = "Das Ausloggen in allen andere Websitzungen erzwingen"; + out.settings_logoutEverywhereConfirm = "Bist du sicher? Du wirst dich auf allen deinen Geräten wieder einloggen müssen."; + + out.settings_codeIndentation = 'Einrücken für den Code-Editor (Leerzeichen)'; + out.settings_codeUseTabs = "Mit Tabs einrücken (anstatt mit Leerzeichen)"; + + out.settings_padWidth = "Maximalgrösse des Editors"; + out.settings_padWidthHint = "Rich-text Dokumente benutzen normalerweise die grösste verfügbare Zeilenbreite, das kann manchmal schwer lesbar sein. Du kannst die Breite des Editors hier reduzieren."; + out.settings_padWidthLabel = "Die Breite des Editors reduzieren"; + + out.settings_creationSkip = "Den Erstellungsdialg für neue Dokumente überspringen"; + out.settings_creationSkipHint = "Dieser Erstellungsdialog erlaubt Einstellungen für mehr Kontrolle und Sicherheit bei deinen Dokumenten. Aber der zusätzliche Dialog verlangsamt die Arbeit. Mit dieser Option kannst du diese Dialog überspringen und die Standard-Einstellungen wählen."; + out.settings_creationSkipTrue = "Überspringen"; + out.settings_creationSkipFalse = "Anzeigen"; + + out.settings_templateSkip = "Die Wahl der Vorlage überspringen"; + out.settings_templateSkipHint = "Wenn du ein neues Dokument erstellst und Vorlagen vorhanden sind, erscheint ein Dialog, wo du die Vorlage wählen kannst. Hier kannst du diesen Dialog überspringen und somit keine Vorlage verwenden."; + + out.settings_changePasswordTitle = "Ändere dein Passwort"; + out.settings_changePasswordHint = "Ändere das Passwort deines Kontos ohne deine Daten zu verlieren. Du mußt einmal das jetzige Passwort eintragen und dann das gewünschte neue Passwort zweimal.
" + + "Wir können das Passwort nicht zurücksetzen, wenn du es vergisst, also sei besonders sorgfältig!"; + out.settings_changePasswordButton = "Passwort ändern"; + out.settings_changePasswordCurrent = "Jetziges Passwort"; + out.settings_changePasswordNew = "Neues Passwort"; + out.settings_changePasswordNewConfirm = "Neues Passwort bestätigen"; + out.settings_changePasswordConfirm = "Bist du sicher?"; + + out.upload_title = "Datei hochladen"; + out.upload_rename = "Willst du einen neuen Name für {0} geben, bevor es zum Server hochgeladen wird?
" + + "Die Dateieendung ({1}) wird automatisch hinzugefügt. "+ + "Dieser Name bleibt für immer und wird für die andere Benutzer sichtbar."; + out.upload_serverError = "Serverfehler: Die Datei kann aktuell nicht hochgeladen werden. "; + out.upload_uploadPending = "Ein anderes Hochlade-Vorgang läuft gerade. Willst du den abbrechen und deine neue Datei hochladen?"; + out.upload_success = "Deine Datei ({0}) wurde erfolgreich hochgeladen und in deinem CryptDrive hinzugefügt."; + out.upload_notEnoughSpace = "Der verfügbare Speicherplatz auf deinem CryptDrive reicht leider nicht für diese Datei."; + out.upload_notEnoughSpaceBrief = "Unzureichender Speicherplatz"; + out.upload_tooLarge = "Diese Datei ist zu gross, um hochgeladen zu werden."; + out.upload_tooLargeBrief = 'Datei zu gross'; + out.upload_choose = "Eine Datei wählen"; + out.upload_pending = "In der Warteschlange"; + out.upload_cancelled = "Abgebrochen"; + out.upload_name = "Dateiname"; + out.upload_size = "Grösse"; + out.upload_progress = "Fortschritt"; + out.upload_mustLogin = "Du muss eingeloggt sein, um Dateien hochzuladen"; + out.download_button = "Entschlüsseln und runterladen"; + out.download_mt_button = "Runterladen"; + out.download_resourceNotAvailable = "Diese Ressource war nicht verfügbar.."; + + out.todo_title = "CryptTodo"; + out.todo_newTodoNamePlaceholder = "Die Aufgabe prüfen..."; + out.todo_newTodoNameTitle = "Diese Aufgabe zu deiner ToDo-Liste hinzufügen"; + out.todo_markAsCompleteTitle = "Diese Aufgabe als erledigt markieren"; + out.todo_markAsIncompleteTitle = "Diese Aufgabe als nicht erledigt markieren"; + out.todo_removeTaskTitle = "Diese Aufgabe aus deiner ToDo-Liste entfernen"; + + // pad + out.pad_showToolbar = "Werkzeugsleiste anzeigen"; + out.pad_hideToolbar = "Werkzeugsleiste verbergen"; + + // markdown toolbar + out.mdToolbar_button = "Die Markdown-Werkzeugsleiste anzeigen oder verbergen"; + out.mdToolbar_defaultText = "Dein Text hier"; + out.mdToolbar_help = "Hilfe"; + out.mdToolbar_tutorial = "http://www.markdowntutorial.com/"; + out.mdToolbar_bold = "Fett"; + out.mdToolbar_italic = "Kursiv"; + out.mdToolbar_strikethrough = "Durchgestrichen"; + out.mdToolbar_heading = "Kopfzeile"; + out.mdToolbar_link = "Link"; + out.mdToolbar_quote = "Zitat"; + out.mdToolbar_nlist = "Nummerierte Liste"; + out.mdToolbar_list = "Aufzählung"; + out.mdToolbar_check = "Aufgabenliste"; + out.mdToolbar_code = "Code"; + + // index.html + + + //about.html + out.about_intro = 'CryptPad wurde erstellt im Research Team von XWiki SAS, einem kleinen Unternehmen in Paris, Frankreich, und Iasi, Rumänien. Das kernteam hat 3 Mitglieder, die an CryptPad arbeiten, sowie einige Mitwirkende innerhalb von XWiki SAS und außerhalb.'; + out.about_core = 'Core Developers'; + out.about_contributors = 'Key Contributors'; + + // contact.html + out.main_about_p22 = 'Uns antweeten'; + out.main_about_p23 = 'Eine Issue auf GitHub erstellen'; + out.main_about_p24 = 'Hallo sagen (Matrix)'; + out.main_about_p25 = 'uns ein Email schicken'; + out.main_about_p26 = 'Wenn Du Fragen oder Kommentare hast, freuen wir uns, von dir zu hören!'; + + out.main_info = "

Vertrauenswürdige Kollaboration

Lass deine Ideen gemeinsam wachsen, während die ohne Preisgabe deiner Daten-Technologie deinen Datenschutz sogar uns gegenüber sichert."; + out.main_catch_phrase = "Die Cloud ohne Preisgabe deiner Daten"; + + out.main_richText = 'Text-Editor'; + out.main_code = 'Code-Editor'; + out.main_slide = 'Präsentations-Editor'; + out.main_poll = 'Umfragen'; + out.main_drive = 'CryptDrive'; + + out.main_richTextPad = 'Rich Text Dokument'; + out.main_codePad = 'Markdown/Code Dokument'; + out.main_slidePad = 'Markdown Präsentation'; + out.main_pollPad = 'Umfrage oder Terminabstimmung'; + out.main_whiteboardPad = 'Whiteboard'; + out.main_kanbanPad = 'Kanban-Board'; + out.main_localPads = 'Lokale Dokumente'; + out.main_yourCryptDrive = 'Dein CryptDrive'; + out.main_footerText = "Mit CryptPad kannst du schnell kollaborative Dokumente erstellen, um Notizen oder Ideen zusammen mit anderen zu bearbeiten."; + + out.footer_applications = "Apps"; + out.footer_contact = "Kontakt"; + out.footer_aboutUs = "Über uns"; + + out.about = "Über uns"; + out.privacy = "Datenschutz"; + out.contact = "Kontakt"; + out.terms = "Nutzungsbedingungen"; + out.blog = "Blog"; + + out.topbar_whatIsCryptpad = "Was ist CryptPad"; + + // what-is-cryptpad.html + out.whatis_title = 'Was ist CryptPad'; + out.whatis_collaboration = 'Effektive und und leichte Zusammenarbeit'; + out.whatis_collaboration_p1 = 'Mit CryptPad kannst Du kollaborative Dokumente erstellen, um Notizen und Ideen gemeinsam zu bearbeiten. Wenn du dich registrierst und dich einloggst, bekommst du die Möglichkeit, Dateien hochzuladen und Ordner einzurichten, um alle deine Dokumente zu organisieren.'; + out.whatis_collaboration_p2 = 'Du kannst Zugang zu einem CryptPad teilen, indem du den Link teilst. Du kannst auch einen schreibgeschützten Zugang erstellen, um die Ergebnisse deiner Arbeit zu teilen, während du sie noch bearbeitest.'; + out.whatis_collaboration_p3 = 'Du kannst Rich-Text Dokumente mit dem CKEditor sowie Markdown Dokumente erstellen, die in Echtzeit angezeigt werden, während du tippst. Du kannst auch die Umfrage-App verwenden, um Termine unter mehrere Teilnehmern zu abzustimmen.'; + out.whatis_zeroknowledge = 'Zero Knowledge - Ohne Preisgabe deiner Daten'; + out.whatis_zeroknowledge_p1 = "Wir wollen nicht wissen, was Du gerade tippst. Und mit moderner Verschlüsselungstechnologie, kannst du sicher sein, dass wir es auch nicht können. CryptPad verwendet 100% Clientseitige Verschlüsselung, um den Inhalt vor uns, den Hostern dieser Website, zu schützen."; + out.whatis_zeroknowledge_p2 = 'Wenn du dich registrierst und dich einloggst, werden dein Benutzername und Passwort in einen Schlüssel umgerechnet mit einer Scrypt Schlüssel-Ableitungsfunktion. Weder dieser Schlüssel noch der Benutzername oder das Passwort werden zum Server geschickt. Stattdessen werden sie clientseitig benutzt, um den Inhalt deines CryptDrives zu entschlüsseln. Dieses beinhaltet alle Dokumente, die dir zugänglich sind.'; + out.whatis_zeroknowledge_p3 = 'Wenn du ein Dokument teilst, teilst du auch den kryptografischen Schlüssel, der Zugang zu diesem Dokument gibt. Da dieser Schlüssel im fragment identifier liegt, wird er nie direkt zum Server geschickt. Bitte lese unsere Blogeintrag über Datenschutz um mehr zu erfahren, welche Typen von Kontextinformation wir zugänglich und nicht zugänglich haben.'; + out.whatis_drive = 'Organisieren mit CryptDrive'; + out.whatis_drive_p1 = 'Sobald auf ein Dokument mit CryptPad zugegriffen wird, wird deses automatisch zu deinem CryptDrive hinzugefügt, im Stamm-Ordner. Später kannst du diese Dokumente in eigenen Ordnern organisieren oder du kannst es in den Papierkorb verschieben. CryptDrive erlaubt die Suche durch deine Dokumente, wie und wann Du willst.'; + out.whatis_drive_p2 = 'Mit dem einfachem Ziehen und Ablegen kannst Du die Dokumente auf deinem CryptDrive umplatzieren. Die Links zu diesen Dokumenten bleiben erhalten, damit Kollaboratoren nie Zugang verlieren.'; + out.whatis_drive_p3 = 'Du kannst auch Dateien in dein CryptDrive hochladen und mit deinen Kollegen teilen. Hochgeladene Dateien können genau so wie kollaborative Dokumente organisiert werden.'; + out.whatis_business = 'CryptPad im Business'; + out.whatis_business_p1 = 'Die Verschlüsselung ohne Preisgabe der Daten von CryptPad ist ausgezeichnet, um die Effektivität von existierenden Sicherheitsverfahren zu verbessern, indem die Zugangsberechtigungen des Unternehmens in der Kryptografie gespiegelt werden. Weil hochsensible Medien nur mit Angestelltenzugang entschlüsselt werden können, kann CryptPad das Jackpot der Hackers wegnehmen, was in der Natur von tradioneller IT liegt. Lese das CryptPad Whitepaper, um mehr zu erfahren, wie CryptPad deinem Unternehmen helfen kann.'; + out.whatis_business_p2 = 'CryptPad kann auf eigenen Rechnern installiert werden. CryptPad\'s Entwickler von XWiki SAS können kommerzielle Unterstützung, Customisierung und Entwicklung anbieten. Bitte schicke eine Email an sales@cryptpad.fr, um mehr zu erfahren.'; + + // privacy.html + out.policy_title = 'Cryptpad Datenschutzbestimmungen'; + out.policy_whatweknow = 'Was wir über dich wissen'; + out.policy_whatweknow_p1 = 'Als Programm, das im Web gehostet wird, hat Cryptpad Zugriff auf die Metadaten, die vom HTTP-Protokoll übertragen werden. Inbegriffen ist deine IP-Adresse und diverse andere HTTP-Header, die es ermöglichen deinen Browser zu identifizieren. Um zu sehen welche Daten dein Browser preisgibt, kannst du die Seite WhatIsMyBrowser.com besuchen.'; + out.policy_whatweknow_p2 = 'Wir nutzen Piwik, eine Open-Source Analyseplattform, um mehr über unsere Nutzer*innen zu erfahren. Piwik teilt uns mit, wie du Cryptpad gefunden hast — durch direkten Zugriff, mit Hilfe einer Suchmaschine oder über einen Link auf einer anderen Seite wie z.B. Reddit oder Twitter. Außerdem lernen wir mehr über deinen Besuch, welchen Link Du auf den Informationsseiten klickst und wie lange du auf diesen Seiten verweilst.'; + out.policy_howweuse = 'Wie wir das Wissen anwenden'; + out.policy_howweuse_p1 = 'Wir nutzen diese Informationen um besser entscheiden zu können, wie Cryptpad beworben werden kann und um derzeit genutzte Strategien zu evaluieren. Informationen über deinen Standort helfen uns, abzuschätzen welche Sprachen wir besser unterstützen sollten.'; + out.policy_howweuse_p2 = "Informationen zu Deinem Browser (ob Du auf einem Desktop oder Smartphone arbeitest) helfen uns außerdem dabei, zu entscheiden, welche Features priorisiert werden sollen. Unser Entwicklerteam ist klein, deshalb ist es uns wichtig, Entscheidungen derart zu treffen, dass möglichst viele Nutzer*innen davon profitieren."; + out.policy_whatwetell = 'Was wir anderen über dich (nicht) erzählen'; + out.policy_whatwetell_p1 = 'Wir reichen keine von uns gesammelten Daten weiter, außer im Falle einer gerichtlichen Anordnung.'; + out.policy_links = 'Links zu anderen Seiten'; + out.policy_links_p1 = 'Diese Seite beinhaltet Links zu anderen Seiten, teilweise werden diese von anderen Organisationen verwaltet. Wir sind nicht für den Umgang mit der Privatsphäre und die Inhalte der anderen Seiten verantwortlich. Generell werden Links zu externen Seiten in einem neuem Fenster geöffnet, um zu verdeutlichen, dass du Cryptpad.fr verlässt.'; + out.policy_ads = 'Werbung'; + out.policy_ads_p1 = 'Wir zeigen keine Onlinewerbung, können aber zu Organisationen verlinken, die unsere Forschung finanzieren.'; + out.policy_choices = 'Deine Möglichkeiten'; + out.policy_choices_open = 'Unser Code ist frei und offengelegt, deshalb kannst du jederzeit deine eigene Cryptpad-Instanz hosten.'; + out.policy_choices_vpn = 'Wenn du unsere gehostete Instanz nutzen möchtest ohne deine IP-Adresse zu offenbaren, bitten wir dich darum, deine IP-Adresse zu verschleiern, das geht zum Beispiel mit dem Tor browser bundle, oder einem VPN-Zugang.'; + out.policy_choices_ads = 'Wenn du unsere Analysesoftware blockieren möchtest kannst du Block-Software wie Privacy Badger verwenden.'; + + // features.html + out.features = "Funktionen"; + out.features_title = "Tabelle der Funktionen"; + out.features_feature = "Funktion"; + out.features_anon = "Anonymer Benutzer"; + out.features_registered = "Angemeldete Benutzer"; + out.features_notes = "Notizzen"; + out.features_f_pad = "Ein Dokument erstellen/bearbeiten/ansehen"; + out.features_f_pad_notes = "Rich Text, Code, Präsentation, Umfrage und Whiteboard Apps"; + out.features_f_history = "Verlauf"; + out.features_f_history_notes = "Jegliche Version deines Dokuments ansehen und zurückbringen"; + out.features_f_todo = "Eine ToDo-Liste erstellen"; + out.features_f_drive = "CryptDrive"; + out.features_f_drive_notes = "Einfache Funktionen für anonyme Benutzer"; + out.features_f_export = "Export/Import"; + out.features_f_export_notes = "Für Dokumente und CryptDrive"; + out.features_f_viewFiles = "Dateien ansehen"; + out.features_f_uploadFiles = "Dateien hochladen"; + out.features_f_embedFiles = "Dateien einbetten"; + out.features_f_embedFiles_notes = "Eine Datei in ein Dokument einbetten, die im CryptDrive steht"; + out.features_f_multiple = "Verwendung auf mehrere Geräte"; + out.features_f_multiple_notes = "Eine leichte Methode, deine Dokumente von jeglichem Gerät zu verwenden"; + out.features_f_logoutEverywhere = "Auf allen Geräten ausloggen"; + out.features_f_logoutEverywhere_notes = ""; // Used in the French translation to explain + out.features_f_templates = "Vorlagen verwenden"; + out.features_f_templates_notes = "Neue Vorlagen erstellen und neue Dokumente aus den Vorlagen erstellen"; + out.features_f_profile = "Ein Profil erstellen"; + out.features_f_profile_notes = "Persönliche Seite, mit ein Benutzerbild und eine Beschreibung"; + out.features_f_tags = "Tags anwenden"; + out.features_f_tags_notes = "Erlaubt dich in CryptDrive anhand Tags zu suchen"; + out.features_f_contacts = "Kontakte App"; + out.features_f_contacts_notes = "Kontakte hinzufügen und mit den in einer verschlüsselte Sitzung chatten"; + out.features_f_storage = "Speicherplatz"; + out.features_f_storage_anon = "Dokumente sind nach 3 Monate gelöscht"; + out.features_f_storage_registered = "Frei: 50MB
Premium: 5GB/20GB/50GB"; + out.features_f_register = "Kostenlose Anmeldung"; + + // faq.html + out.faq_link = "FAQ"; + out.faq_title = "Häufige Fragen"; + out.faq_whatis = "Was ist CryptPad?"; + out.faq = {}; + out.faq.keywords = { + title: 'Schlüsselkonzepte', + pad: { + q: "Was ist ein CryptPad Dokument?", + a: "Ein CryptPad Dokument wird manchmal einfach Pad genannt, übernommen von Etherpad, einem kollaborativen Echtzeit-Editor\n"+ + "Es beschreibt ein Dokument, das du in deinem Browser bearbeiten kannst, normalerweise mit der Möglichkeit für andere Personen, die Veränderungen gleichzeitig direkt zu sehen." + }, + owned: { + q: "What ist ein eigenes Dokument?", + a: "Ein eigenes Dokument ist ein Dokument mit einem definierten Eigentümer, der anhand einer Unterschrift mit öffentlichen Schlüssel erkannt wird." + + "Der Eigentümer eines Dokuments kann entscheiden, das Dokument zu löschen. In diesem Fall macht er das Dokument unverfügbar für weitere Kollaboration, egal ob das Dokument in deinem CryptDrive war oder nicht." + }, + expiring: { + q: "Was ist das Ablaufsdatum eines Dokuments?", + a: "Ein Dokument kann mit einem Ablaufsdatum versehen werden. Nach diesem Datum wird es automatisch vom Server gelöscht" + + " Das Ablaufdatum kann sowohl sehr nah (ein Paar Stunden) als sehr weit sein (hunderte Monate)." + + " Das Dokument und sein gesamter Verlauf wird nach dem Ablaufdatum dauerhauft unverfügbar, auch wenn es gerade noch bearbeitet wird.

" + + " Wenn ein Dokument ein Ablaufsadtum hat, kann mann dieses Datum in den Eigenschaften sehen: Entweder mit einem Rechtklick in CryptDrive oder mit der Eigenschaften-Ansicht, wenn das Dokument geöffnet ist." + }, + tag: { + q: "Wie kann ich Tags verwenden?", + a: "Du kannst Dokumente und auf CryptDrive hochgeladene Dateien taggen, das heisst mit einem Stichwort (Tag) versehen. Während der Bearbeitung gibt es dafür den Tag Knopf ()" + + " Wenn du die Dokumente und Dateien in deinem CryptDrive nach einem Tag durchsuchen willst, beginne den Suchbegriff mit einem Hashtag, zB #crypto." + }, + template: { + q: "Was ist eine Vorlage?", + a: "Eine Vorlage ist ein Dokument, dass du benutzen kannst, um den Anfangsinhalt für zukünftige Dokumente zu definieren." + + " Jedes existes existierende Dokument kann eine Vorlage werden, indem es in den Vorlagen Abschnitt des CryptDrives geschoben wird." + + " Du kannst auch eine Kopie eines Dokuments erstellen, die zur Vorlage wird, indem du auf der Vorlagen-Knopf () der Werkzeugleiste des Editors drückst." + }, + }; + out.faq.privacy = { + title: 'Privacy', + different: { + q: "Wie unterscheidet sich CryptPad von anderen online kollaborativen Editoren?", + a: "CryptPad verschlüsselt Veränderungen deiner Dokumente, bevor diese Information zum Server geschickt wird. Somit können wir nicht lesen, was du getippt hast." + }, + me: { + q: "Welche Informationen erhält der Server über mich?", + a: "Die Administratoren des Servers können die IP-Adresse der Personen sehen, die CryptPad besuchen." + + " Wir speichern nicht, welche Adresse welches Dokument besucht, aber wir könnten es tun, aber immer nur ohne den Inhalt des Dokuments zu kennen." + + " Wenn Du besorgt bist, dass wir diese Information analysieren, ist es am sichersten davon auszugehen, dass wir es tun, da wir nicht beweisen können, dass wir es nicht tun.

" + + + " Wir sammeln elementare technische Informationen darüber, wie CryptPad benutzt wird, wie die Grösse des Bildschirms auf dem Gerät und welche Knöpfe am meisten geklickt werden." + + " Das hilft uns, unsere Software besser zu machen. Aber diese Sammlung unterbleibt, solange Du bei Rückmeldung aktivieren keinen Haken setzt.

" + + + " Die Speicherungsgrössen und deren Grenzen sind mit dem öffentlichen Schlüssel eines Benutzers verbunden, aber wir verbinden nicht Namen oder Emailadressen mit diesen öffentlichen Schlüsseln.

" + + + " Du kannst mehr Informationen darüber in diesem Blogeintrag lesen." + }, + register: { + q: "Weisst der Server mehr über mich, wenn ich registriere?", + a: "Wir verlangen nicht Deine Emailadresse und der Server kennt Benutzername und Passwort auch dann nicht, wenn du dich registrierst. " + + " Statt dessen generiert das Registrierungs- und Anmeldeformular ein Schlüsselpaar mit deiner Eingabe. Nur der öffentliche Schlüssel dieses Schlüsselpaars wird zum Server geschickt." + + " Mit diesem öffentlichen Schlüssel könenn wir z.B. die Menge der Daten, die du benutzt, kontrollieren, denn jeder Benutzer hat eine beschränkte Quota.

" + + + " Wir benutzen die Rückmeldungs-Funktion, um den Server zu informieren, dass jemand mit deiner IP ein Konto registriert hat." + + " Damit können wir messen, wie viele Benutzer CryptPad Konten registrieren, und aus welchen Regionen. Somit können wir erfahren, welche Sprache besseren Support braucht.

" + + + " Wenn Du registrierst, erstellst Du einen öffentlichen Schlüssel, der benutzt wird, um den Server zu informieren, dass er Dokumente auch dann nicht löschen sollte, wenn sie nicht aktiv benutzt werden." + + " Diese Information zeigt dem Server, wie Du CryptPad benutzt, und dieses System erlaubt uns, die Dokumente zu löschen, wofür sich keiner mehr interessiert." + }, + other: { + q: "Was können andere Benutzer über micht erfahren?", + a: "Wenn du ein Dokument von jemand anderen bearbeitest, kommunizierst Du mit dem Server. Nur wir kennen Deine IP-Adresse. " + + " Andere Benutzern sehen deinen Benutzernamen, dein Benutzerbild, das Link deines Profils (wenn du eins hast), und deinen öffentlichen Schlüssel (um die Nachrichten zu diesen Benutzern zu verschlüsseln)." + }, + anonymous: { + q: "Macht mich CryptPad anonym?", + a: "Auch wenn CryptPad so konzipiert wurde, dass es so wenig wie möglich über dich kennt, es liefert keine strenge Anonymität" + + " Unsere Server haben einen Zugang zu deiner IP-Adresse, allerdings kannst du diese Information verbergen, indem du Tor verwendest." + + " Einfach Tor zu verwenden, ohne dein Verhalten zu ändern, garantiert auch keine Anonymität, da der Server Benutzer noch mit deren öffentlichen Schlüsseln identifizeren kann." + + " Wenn du denselben Schlüssel mit und ohne Tor benutzt, wird es möglich, deine Sitzung zu de-anonimisieren.

" + + + " Für Benutzer, die Datenschutz im normalen Umfang brauchen, ist wichtig, daß CryptPad, im Gegenteil zu anderen Onlinediensten, nicht verlangt, daß der Benutzer sich mit Namen, Telefonnummer oder Emailadressen identifiziert." + }, + policy: { + q: "Habt ihr eine Datenschutzerklärung?", + a: "Ja! Sie ist hier verfügbar." + } + }; + out.faq.security = { + title: 'Sicherheit', + proof: { + q: "Wie benutzt ihr Zero Knowledge Beweise?", + a: "Wir benutzen den Begriff Ohne Preisgabe von Daten (Zero Knowledge) nicht im Sinn eines Zero Knowledge Beweises aber im Sinn eines Zero Knowledge Webdienstes " + + " Ein Zero Knowledge Webdienst verschlüsselt die Benutzerdaten im Browser, ohne dass der Server je Zugang zu den unverschlüsselten Daten oder zu den Verschlüsselungschlüsseln hat.

" + + " Wir haben hier eine kurze Liste von Zero-Knowledge Webdiensten erstellt." + }, + why: { + q: "Wieso sollte ich CryptPad verwenden?", + a: "Unsere Position ist, dass Clouddienste nicht Zugang zu deinen Daten verlangen sollten, damit du sie mit deinen Kontakten und Mitarbeitern teilen kannst. " + + " Wenn du einen Webdienst benutzt, der nicht explizit eine Ankündigung macht, dass die keinen Zugang zu Deinen Information haben, ist es sehr wahrscheinlich, dass sie diese Information für andere Zwecke verwerten." + }, + compromised: { + q: "Liefert mir CryptPad einen Schutz, wenn auf mein Gerät zugegriffen wird?", + a: "Für den Fall, dass ein Gerät gestohlen wird, ermöglicht CryptPad, das Ausloggen aller Geräte - ausser dem, wo du gerade eingeloggt bist, zu erzwingen. " + + " Dafür gehe auf die Seite mit Deinen Einstellungen and drücke Überall ausloggen." + + " Alle andere Geräte, die mit diesem Konto verbunden sind, werden dann ausgeloggt. " + + " Alle früher verbundenen Geräte werden ausgeloggt, sobald sie CryptPad besuchen.

" + + + " Die Fernlogout Funktion, wie oben beschrieben, ist im Browser implementiert und nicht im Server. " + + " Somit schützt diese nicht von Regierungsagenturen. Aber es sollte ausreichend sein, wenn Du ein Logout vergessen hast, wenn Du auf einem mit anderen Benutzern geteilten Rechner warst." + }, + crypto: { + q: "Welche Kryptografie benutzt ihr?", + a: "CryptPad basiert auf zwei quelloffenen Kryptografiebibliotheken: " + + " tweetnacl.js und scrypt-async.js.

" + + " Scrypt ist ein Passwort-basierter Schlüsselableitungsalgorithmus. Wir benutzen es, um Deinen Benutzernamen und Kennwort in einem Schlüsselpaar umzuwandeln, das Deinen Zugang zum CryptDrive, und daher Deine gesamten Dokumente, sichert.

" + + + " Wir verwenden die Verschlüsselung xsalsa20-poly1305 und x25519-xsalsa20-poly1305 von tweetnacl, um Dokumente und Chat-Historie zu verschlüsseln." + } + }; + out.faq.usability = { + title: 'Usability', + register: { + q: "Was kriege ich, wenn ich registriere?", + a: "Registrierte Benutzer können eine Menge Funktionen verwenden, die unregistrierte nicht nutzen können. Es gibt hier eine Tabelle." + }, + share: { + q: "Wie kann ich den Zugang zu einem verschlüsselten Dokument mit Freunden teilen?", + a: "CryptPad legt den Verschlüsselungsschlüssel zu deinem Pad nach dem # Buchstabe in dem URL." + + " Alles was nach diesem Buchstaben kommt, wird nicht zum Server geschickt; also haben wir nie Zugang zu deinem Verschlüsselungsschlüssel." + + " Wenn du den Link deines Dokuments teilst, teilst Du auch die Fähigkeit zum Lesen und zum Bearbeiten." + }, + remove: { + q: "Ich habe ein Dokument aus meinem CryptDrive gelöscht, aber der Inhalt ist noch verfügbar. Wie kann ich es entfernen?", + a: "Nur eigene Dokumente, die erst in Februar 2018 eingeführt wurden, können gelöscht werden und zwar nur von deren Eigentümer" + + " (der Benutzer, der das Dokument original gestaltet hat). Wenn Du nicht der Eigentümer eines Dokuments bist, musst du den Eigentümer bitten, dass er dieses löscht." + + " Für ein Dokument, dessen Eigentümer du bist, kannst du auf dem Dokument in CryptDrive rechtsklicken und Vom Server löschen wählen. " + }, + forget: { + q: "Was passiert, wenn ich mein Passwort vergesse?", + a: " Leider: Wenn wir dein Passwort zurückerstellen könnten, könnten wir auch Zugang zu deinen Daten selber haben. " + + " Wenn du dein Passwort nicht aufgeschrieben und vergessen hast, kannst Du vielleicht die vergangenen Dokumente aus deinem Browserverlauf zurückgewinnen. " + }, + change: { + q: "Was ist, wenn ich mein Passwort wechseln möchte?", + a: "Es ist aktuell nicht möglich, dein CryptPad Passwort zu wechseln, obwohl wir diese Funktion bald planen." + }, + devices: { + q: "Ich bin auf zwei Geräten eingeloggt und sehe zwei unterschiedliche CryptDrives. Wie ist das möglich?", + a: "Es ist möglich, dass Du zweimal denselben Namen registriert hast, mit unterschiedlichen Passwörtern." + + " Weil der CyrptPad Server dich mit deiner kryptografischen Unterschrift und nicht mit deinem Namen identifiziert, kann er nicht verhindern, daß derselbe Name von mehreren verwendet wird." + + " Somit hat jede Benutzerkonto eine einzigartige Beutzername- und Passwortkombination. " + + " Angemeldete Benutzer können ihren Benutzernamen im oberen Teil der Einstellungsseite sehen." + }, + folder: { + q: "Kann ich ganze Ordner in CryptDrive teilen?", + a: "Wir arbeiten daran, eine Arbeitgruppenfunktion anzubieten, die Mitgliedern erlauben würde, ganze Ordnern sowie alle Dokumente darin zu teilen." + }, + feature: { + q: "Könnt ihr diese eine Funktion hinzufügen, die ich brauche?", + a: "Viele Funktionen existieren in CryptPad, weil Benutzern darum gebeten haben." + + " Unsere Kontaktseite hat eine Liste der Möglichkeiten, wie man mit uns in Kontakt treten kann.

" + + + "Leider können wir aber nicht garantieren, dass wir alle Funktionen entwickeln, um die Benutzer bitten." + + " Wenn eine Funktion kritisch für deine Organisation ist, kannst du Sponsor der Entwicklung dieser Funktion werden, und somit deren Realisierung sichern." + + " Bitte kontaktiere sales@cryptpad.fr für mehr Informationen.

" + + + "Auch wenn du nicht die Entwicklung einer Funktion sponsoren kannst, sind wir an Rückmeldungen interessiert, damit es uns hilft CryptPad zu verbessern." + + " Kontaktiere uns jederzeit mit einer der oben angegebenen Methoden." + } + }; + + out.faq.other = { + title: "Andere Fragen?", + pay: { + q: "Wieso soll ich zahlen, wenn so viele Funktionen sowieso kostenfrei sind?", + a: "Wir geben Sponsoren zusätzlichen Speicherplatz sowie die Möglichkeit, die Speicherplatzgrenzen ihrer Freunde zu erhöhen (lese mehr).

" + + + " Über diese diese kurzfristigen Vorteile hinaus kannst Du, wenn Du ein Premiumangebot annimmst, die aktive Weiterentwicklung von CryptPad fördern. Dieses beinhaltet Fehler zu beseitigen, neue Funktionen zu gestalten, und es erleichtern, CryptPad auf eigenen Servern zu installieren." + + " Zusätzlich hilfst du, anderen Anbiertern zu beweisen, dass Leute datenschutzschonende Technologien unterstützen. Wir hoffen, dass am Ende Geschäftmodelle, die auf dem Verkauf von Benutzerdaten basieren, Vergangenheit werden.

" + + + " Außerdem glauben wir, dass es gut ist, die Funktionen von CryptPad kostenfrei anzubieten, weil jeder persönlichen Datenschutz braucht, nicht nur diejenige mit Extraeinkommen." + + " Durch deine Unterstützung hilfst Du uns, zu ermöglichen, dass auch Menschen mit weniger Einkommen diese grundlegenden Funktionen geniessen können, ohne dass ein Preisetikett daran klebt." + }, + goal: { + q: "Was ist euer Ziel?", + a: "Durch die Verbesserung von datenschutzschonenden Technologien möchten wir die Erwartungen der Benutzer an den Datenschutz auf Cloudplattformen erhöhen." + + "Wir hoffen, dass unsere Arbeit andere Dienstanbieter in allen Bereichen anspornt, ähnliche oder bessere Dienste anzubieten. " + + "Trotz unser Optimismus wissen wir, dass ein grosser Teil des Netztes durch gezielte Werbung finanziert wird. " + + "Es gibt viel mehr Arbeit in der Richtung, als wir jemals schaffen können, und wir freuen uns über die Förderung, Unterstützung und Beiträge aus unserer Community." + }, + jobs: { + q: "Sucht Ihr Mitarbeiter*innen?", + a: "Ja! Bitte schicke eine kurze Vorstellung an jobs@xwiki.com." + }, + host: { + q: "Könnt ihr mir helfen, meine eigene Installation von CryptPad aufzubauen?", + a: "Wir bieten gerne Support für das Aufsetzen eines internen CryptPads für deine Organisation. Setze dich bitte mit sales@cryptpad.fr in Kontakt für mehr Information.", + }, + revenue: { + q: "Wie kann ich meine Einnahmen mit den Entwicklern teilen?", + a: " Wenn du deine eigene Installation von CrytPad betreibst und die Einnahmen für deine bezahlten Konten mit Entwicklern teilen möchtest, muß dein Server als Partnerservice konfiguriert werden.

" + + + "In Deinem CryptPad Verzeichnis befindet sich config.example.js, die erklärt, wie du deinen Server dafür konfigurieren musst. "+ + "Danach solltest du sales@cryptpad.fr kontaktieren, damit geprüft wird, dass dein Server richtig mit HTTPS konfiguriert ist und die Bezahlungsmethoden abgesprochen werden können. " + }, + }; + + // terms.html 995 + out.tos_title = "Cryptpad Nutzungsbedingungen"; + out.tos_legal = "Sei nicht bösartig oder missbrauchend und mach nichts illegales."; + out.tos_availability = "Wir hoffen, dass dir dieser Service nützt, aber Erreichbarkeit und Performanz können nicht garantiert werden. Bitte exportiere deine Daten regelmäßig."; + out.tos_e2ee = "Cryptpad Dokumente können von allen gelesen oder bearbeitet werden, die den \"fragment identifier\" des Dokuments erraten oder auf eine andere Art davon erfahren. Wir empfehlen dir Ende-Zu-Ende verschlüsselte Nachrichtentechnik (e2ee) zum Versenden der URLs zu nutzen. Wir übernehmen keine Haftung, falls eine URL erschlichen oder abgegriffen wird."; + out.tos_logs = "Metadaten, die dein Browser übermittelt, können geloggt werden, um den Service aufrechtzuerhalten."; + out.tos_3rdparties = "Wir geben keine Individualdaten an Dritte Weiter, außer auf richterliche Anordnung."; + + // 404 page + out.four04_pageNotFound = "Wir konnten die Seite, die du angefordert hast, nicht finden."; // BottomBar.html + // out.bottom_france = 'Mit in gemacht'; + // out.bottom_support = 'Ein XWiki SAS Labs Project mit Hilfe von OpenPaaS-ng'; - out.bottom_france = 'Mit in gemacht'; - out.bottom_support = 'Ein XWiki SAS Labs Project mit Hilfe von OpenPaaS-ng'; + // Header.html - // Header.html + out.updated_0_header_logoTitle = 'Zu deinem CryptDrive'; + out.header_logoTitle = out.updated_0_header_logoTitle; + out.header_homeTitle = 'Zu der CryptPad Homeseite'; - out.header_france = 'Mit von France und XWiki SAS'; + // Initial states + out.help = {}; - // TODO Hardcode cause YOLO - //out.header_xwiki = 'XWiki SAS'; - out.header_support = ' OpenPaaS-ng'; - out.header_logoTitle = 'Zur Hauptseite'; + out.help.title = "Mit CryptPad anfangen"; + out.help.generic = { + more: 'Erfahre mehr wie CryptPad für dich arbeiten kann, indem du unsere FAQ liest.', + share: 'Benutze das Teilen-Menü (), um Links zu schicken, die zur Mitarbeit beim Lesen oder Bearbeiten einladen.', + stored: 'Jedes Dokument, dass du besuchst, ist automatisch in deinem CryptDrive gespeichert.', + }; - return out; + out.help.text = { + formatting: 'Du kannst die Werkzeugleiste anzeigen oder verbergen indem du auf oder klickst.', + embed: 'Registrierte Benutzer können mit Bilder oder Dateien einbetten, die in deren CryptDrive gespeichert sind.', + history: 'Du kannst das Menü Verlauf benutzen, um frühere Version anzusehen oder zurückbringen.', + }; + + out.help.pad = { + export: 'Du kannst den Export als PDF benutzen, indem Du auf dem Knopf in dem Formatierungs-Werkzeugleiste drückst.', + }; + + out.help.code = { + modes: 'Benutze das Dropdown Menü im Untermenü , um die Syntaxhervorhebung oder das Farbschema zu wechseln.', + }; + + out.help.slide = { + markdown: 'Schreibe Folien in Markdown and separiere sie mit der Zeile ---.', + present: 'Starte die Präsentation mit dem Knopf .', + settings: 'Verändere die Präsentationseinstellungen (Hintergrund, Transition, Anzeige der Seitenummer, etc) mit dem Knopf in dem Submenü .', + colors: 'Verändere Text- und Hintergrundfarbe mit den Knöpfen und .', + }; + + out.help.poll = { + decisions: 'Treffe Entscheidungen gemeinsam mit deinen Bekannten', + options: 'Mache Vorschläge und teile deine Präferenzen mit', + choices: 'Klicke die Zellen in deiner Spalte, um zwischen ja (), viellecht (~), oder nein () zu wählen', + submit: 'Klicke auf Schicken, damit deine Entscheidung für andere sichtbar wird', + }; + + out.help.whiteboard = { + colors: 'Ein Doppelklick auf Farben erlaubt, die Palette zu verändern', + mode: 'Deaktiviere den Zeichenmodus, um die vorhandenen Striche zu ziehen und zu verlängern', + embed: 'Bette Bilder von deiner Festplatte ein oder von deinem CryptDrive und exportiere sie als PNG zu deiner Festplatte oder zu deinem CryptDrive Knopf in der rechten oberen Ecke', + task: 'Verschiebe Items von einem Bord zum anderen durch Ziehen und Ablegen', + color: 'Ändere die Farben durch Klicken auf den farbigen Teil neben dem Bordtitel', + }; + + out.initialState = [ + '

', + 'Dies ist is CryptPad, der Echtzeit-Kollaborativ-Editor ohne Preisgabe deiner Daten. Alles wird beim Tippen direkt gespeichert.', + '
', + 'Teile den Link zu diesem Pad, um mit Bekannten zusammen zu arbeiten, oder verwende den Knopf , um einen schreibgeschützten Link  zu teilen, der die Ansicht, aber nicht die Bearbeitung erlaubt.', + '

', + ].join(''); + + out.codeInitialState = [ + '# CryptPad\'s Zero Knowledge Kollaborativer Code Editor ohne Preisgabe deiner Daten\n', + '\n', + '* Was du hier tippst, ist verschlüsselt. Nur Personen die das vollen Link haben können es zugreifen.\n', + '* Du kannst die Programmierungsprache für die Syntaxhervorhebung sowie das Farbschema oben rechts wählen.' + ].join(''); + + out.slideInitialState = [ + '# CryptSlide\n', + '1. Schreibe deine Präsentation mit der Markdown Syntax\n', + ' - Mehr über Markdown [hier](http://www.markdowntutorial.com/) erfahren\n', + '2. Trenne deine Folien mit ---\n', + '3. Klicke auf den "Abspielen" Knopf, um das Ergebnis zu sehen.', + ' - Deine Folien werden in Echtzeit aktualisiert' + ].join(''); + + // Readme + out.driveReadmeTitle = "Was ist CryptPad?"; + out.readme_welcome = "Willkommen zu CryptPad !"; + out.readme_p1 = "Willkommen zu CryptPad, hier kannst du deine Notizen aufschreiben, allein oder mit Bekannten."; + out.readme_p2 = "Dieses Dokument gibt dir einen kurzen Überblick, wie du CryptPad verwenden kann, um Notizen zu schreiben und und mit anderen zusammen zu arbeiten."; + out.readme_cat1 = "Lerne CryptDrive kennen"; + out.readme_cat1_l1 = "Ein Dokument erstellen: Klicke in Deinem CryptDrive {0}, dann {1} und Du kannst ein Dokuemnt erstellen."; // 0: New, 1: Rich Text + out.readme_cat1_l2 = "Ein Dokument Deines CryptDrives öffnen: Doppelklicke auf das Symbol eines Dokument, um es zu öffnen."; + out.readme_cat1_l3 = "Deine Dokumente organisieren: Wenn du eingeloggst bist, wird jedes Dokument, das du besuchst, im {0} Bereich deines CryptDrives angezeigt"; + out.readme_cat1_l3_l1 = "Im Abschnitt {0} deines CryptDrives kannst du Dateien zwischen Ordnern ziehen und ablegen oder neue Ordner anlegen."; // 0: Documents + out.readme_cat1_l3_l2 = "Ein Rechtklick auf Symbole kann zusätzliche Menüfunktionen anbieten."; + out.readme_cat1_l4 = "Verschiebe deine alten Dokumente in den Papierkorb: Du kannst Deine Dokumente zu {0} verschieben, genauso, wie du es zu einem Ordner machst."; // 0: Trash + out.readme_cat2 = "Dokumente wie ein Profi gestalten"; + out.edit = "bearbeiten"; + out.view = "ansehen"; + out.readme_cat2_l1 = "Der Knopf {0} in deinem Dokument erlaubt dir, anderen einen Mitbearbeitungszugang zu geben (entweder zu {1} oder {2})."; + out.readme_cat2_l2 = "Der Titel eines Dokuments kann mit einem Klick auf den Stift geändert werden."; + out.readme_cat3 = "Entdecke CryptPad Apps"; + out.readme_cat3_l1 = "Mit dem CryptPad Codeeditor kannst du Code wie JavaScript, Markdown, oder HTML bearbeiten"; + out.readme_cat3_l2 = "Mit dem CryptPad Präsentationseditor kannst du schnell Vorträge mit Hilfe von Markdwon gestalten"; + out.readme_cat3_l3 = "Mit der CryptPad Umfrage kannst du schnell Abstimmungen durchführen, insbesondere, um Meetings zu planen, die in den Kalender von allen passen."; + + // Tips + out.tips = {}; + out.tips.shortcuts = "`ctrl+b`, `ctrl+i` and `ctrl+u` sind Tatstenkürzeln um fett, kurziv, oder unterschrieben zu markieren."; + out.tips.indent = "In bezifferten oder einfache Listen kannst du TAB und SHIFT-TAB benutzen, um den Einzug zu erhöhen oder reduzieren."; + out.tips.store = "Jedes Mal, wenn du ein Dokument besuchst und eingeloggt bist, wird es in deinem CryptDrive gespeichert."; + out.tips.marker = "Du kannst Text in einem Dokument mit \"Marker\" Menü in dem Stilmenü markieren."; + out.tips.driveUpload = "Registrierte Benutzer können verschlüsselte Dateien aus ihrer Festplatte hochladen, indem sie sie einfach verschieben und in ihrem CryptDrive ablegen."; + out.tips.filenames = "Du kannst Dateien in deinem CryptDrive neubenennen. Dieser Name ist nur für dich."; + out.tips.drive = "Eingeloggte Benutzern können ihre Dateien in ihrem CryptDrive organisieren. Dieses ist mit einem Klick auf das CryptPad Symbol oben links erreichbar, wenn man in einem Dokument ist."; + out.tips.profile = "Registrierte Benutzer können ihr Profil mit dem Benutzer Menü oben rechts bearbeiten."; + out.tips.avatars = "Du kannst ein Benutzerbild in dein Profil hochladen. Andere sehen es, wenn sie in einem Dokument zusammenarbeiten."; + out.tips.tags = "Bringe Tags auf deinen Dokumenten an und starte eine Suche-nach-Tags mit dem # Zeichen in dem CryptDrive Suche."; + + out.feedback_about = "Wenn Du das liest, fragst du dich, weshalb dein Browser Anfragen an Webseiten schickt, wenn manche Aktionen ausgeführt werden."; + out.feedback_privacy = "Wir kümmern uns um deinen Datenschutz, aber gleichzeitig wollen wir, dass die Benutzung von CryptPad sehr leicht ist. Deshalb wollen wir erfahren, welche UI-Funktion am wichtigsten für unsere Benutzer ist, indem wir diese mit einer genauen Parameterbeschreibung anfordern."; + out.feedback_optout = "Wenn du das aber nicht möchtest. besuche Deine Einstellungen, dort findest du ein Haken, wo du es deaktivieren kannst."; + + // Creation page + out.creation_404 = "Dieses Dokument existiert nicht mehr. Benutze das folgende Formular, um ein neues Dokument zu gestalten."; + out.creation_ownedTitle = "Dokumenttyp"; + out.creation_owned = "Eigenes Dokument"; // Creation page + out.creation_ownedTrue = "Eigenes Dokument"; // Settings + out.creation_ownedFalse = "Dokument von jemand anderem"; + out.creation_owned1 = "Ein eigenes Dokument kann vom Server gelöscht werden, wenn der Eigentümer so entscheidet. Die Löschung eines eigenes Dokuments bewirkt die Löschung aus allen anderen CryptDrives. "; + out.creation_owned2 = "Ein offenes Dokument hat keinen Eigentümer, also kann es nicht gelöscht werden, ausser es hat sein Ablaufdatum erreicht."; + out.creation_expireTitle = "Ablaufdatum"; + out.creation_expire = "Auslaufendes Dokument"; + out.creation_expireTrue = "Ein Ablaufdatum hinzufügen"; + out.creation_expireFalse = "Unbegrenzt"; + out.creation_expireHours = "Stunde(n)"; + out.creation_expireDays = "Tag(e)"; + out.creation_expireMonths = "Monat(e)"; + out.creation_expire1 = "Ein unbegrenztes Dokument wird nicht vom Server entfernt solange der Eigentümer es nicht löscht."; + out.creation_expire2 = "Ein auslaufendes Dokument hat eine begrenzte lebensdauer, nach der es automatisch vom Server und aus den CryptDrives anderer Leute entfernt wird."; + out.creation_password = "Passwort hinzufügen"; + out.creation_noTemplate = "Keine Vorlage"; + out.creation_newTemplate = "Neue Vorlage"; + out.creation_create = "Erstellen"; + out.creation_saveSettings = "Dieses Dialog nicht mehr anzeigen"; + out.creation_settings = "Mehr Einstellungen zeigen"; + out.creation_rememberHelp = "Geh zu deiner Einstellungen, um diese Einstellung wieder vorzunehmen"; + // Properties about creation data + out.creation_owners = "Eigentümer"; + out.creation_ownedByOther = "Eigentum eines anderen Benutzer"; + out.creation_noOwner = "Kein Eigentümer"; + out.creation_expiration = "Auslaufdatum"; + out.creation_passwordValue = "Passwort"; + out.creation_propertiesTitle = "Verfügbarkeit"; + out.creation_appMenuName = "Fortgeschrittenes Modus (Ctrl + E)"; + out.creation_newPadModalDescription = "Klicke auf einen Padtyp, um es zu erstellen. Du kannst auch die Tab-Taste benutzen, um zu navigieren, und die Enter-Taste zum Bestätigen. "; + out.creation_newPadModalDescriptionAdvanced = "Du kannst das Kästchen markieren (oder auf die Leertaste drücken, um den Wert zu ändern), um den Einstellungsdialog bei der Dokumenterstellung anzuzeigen (für eigene oder auslaufende Dokumente)."; + out.creation_newPadModalAdvanced = "Den Einstellungdialog bei der Dokumenterstellung anzeigen"; + + // Password prompt on the loading screen + out.password_info = "Das Pad, das du öffnen möchtest, ist mit einem Passowrt geschützt. Gib das richtige Passwort ein, um den Inhalt anzuzeigen."; + out.password_error = "Pad nicht gefunden!
Dieser Fehler kann zwei Ursachen haben: entweder ist das Passwort ungültig oder das Pad wurde vom Server gelöscht."; + out.password_placeholder = "Gib das Passwort hier ein..."; + out.password_submit = "Abschicken"; + out.password_show = "Anzeigen"; + + // Change password in pad properties + out.properties_addPassword = "Passwort hinzufügen"; + out.properties_changePassword = "Passwort ändern"; + out.properties_confirmNew = "Bist du sicher? Das Hinzufügen eines Passworts wird die URL dieses Pads ändern und die Chronik entfernen. Benutzer ohne Passwort werden den Zugang zu diesem Pad verlieren."; + out.properties_confirmChange = "Bist du sicher? Das Ändern des Passworts wird die Chronik entfernen. Benutzer ohne das neue Passwort werden den Zugang zu diesem Pad verlieren."; + out.properties_passwordError = "Ein Fehler ist aufgetreten beim Versuch das Passwort zu ändern. Bitte versuche es nochmal."; + out.properties_passwordWarning = "Das Password wurde erfolgreich geändert, aber dein CryptDrive konnte nicht aktualisiert werden. Du mußt möglicherweise die alte Version des Pads manuell entfernen.
Bitte klicke OK um die Seite neu zu laden und die Zugeriffsrechte zu aktualisieren."; + out.properties_passwordSuccess = "Das Password wurde erfolgreich geändert.
Bitte klicke OK um die Seite neu zu laden und die Zugeriffsrechte zu aktualisieren."; + out.properties_changePasswordButton = "Abschicken"; + + // New share modal + out.share_linkCategory = "Link teilen"; + out.share_linkAccess = "Zugangsrechte"; + out.share_linkEdit = "Bearbeiten"; + out.share_linkView = "Ansehen"; + out.share_linkOptions = "Linkoptionen"; + out.share_linkEmbed = "Einbettungsmodus (Werkzeugleiste und Benutzerliste sind verborgen)"; + out.share_linkPresent = "Anzeigemodus (Bearbeitbare Abschnittte sind verborgen)"; + out.share_linkOpen = "In einem neuen Tab öffnen"; + out.share_linkCopy = "In die Zwischenablage kopieren."; + out.share_embedCategory = "Einbetten"; + out.share_mediatagCopy = "Mediatag in die Zwischenablage kopieren"; + + // Loading info + out.loading_pad_1 = "Initialisiere Pad"; + out.loading_pad_2 = "Lade Padinhalt"; + out.loading_drive_1 = "Lade Daten"; + out.loading_drive_2 = "Aktualisiere Datenformat"; + out.loading_drive_3 = "Verifiziere Datenintegrität"; + + return out; }); + diff --git a/customize.dist/translations/messages.el.js b/customize.dist/translations/messages.el.js index 36a19da87..493be541d 100644 --- a/customize.dist/translations/messages.el.js +++ b/customize.dist/translations/messages.el.js @@ -31,7 +31,7 @@ define(function () { out.websocketError = 'Αδυναμία σύνδεσης στον διακομιστή...'; out.typeError = "Αυτό το pad δεν είναι συμβατό με την επιλεγμένη εφαρμογή"; - out.onLogout = 'Έχετε αποσυνδεθεί, κάντε "κλικ" εδώ για να συνδεθείτε
ή πατήστε Escape για να προσπελάσετε το έγγραφο σε λειτουργία ανάγνωσης μόνο.'; + out.onLogout = 'Έχετε αποσυνδεθεί, {0}κάντε "κλικ" εδώ{1} για να συνδεθείτε
ή πατήστε Escape για να προσπελάσετε το έγγραφο σε λειτουργία ανάγνωσης μόνο.'; out.wrongApp = "Αδυναμία προβολής του περιεχομένου αυτής της συνεδρίας στον περιηγητή σας. Παρακαλώ δοκιμάστε επαναφόρτωση της σελίδας."; out.loading = "Φόρτωση..."; diff --git a/customize.dist/translations/messages.es.js b/customize.dist/translations/messages.es.js index 9e246294f..fc905870a 100644 --- a/customize.dist/translations/messages.es.js +++ b/customize.dist/translations/messages.es.js @@ -152,7 +152,7 @@ define(function () { out.websocketError = "Error al conectarse al servidor WebSocket"; out.typeError = "Este documento no es compatible con la aplicación seleccionada"; - out.onLogout = "Tu sesión está cerrada, haz clic aquí para iniciar sesión
o pulsa Escape para acceder al documento en modo sólo lectura."; + out.onLogout = "Tu sesión está cerrada, {0}haz clic aquí{1} para iniciar sesión
o pulsa Escape para acceder al documento en modo sólo lectura."; out.loading = "Cargando..."; out.error = "Error"; out.language = "Idioma"; diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 3648abad2..c9998bcff 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -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 = "Connexion au serveur perdue
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, cliquez ici pour vous authentifier
ou appuyez sur Échap pour accéder au pad en mode lecture seule.'; + out.onLogout = 'Vous êtes déconnecté de votre compte utilisateur, {0}cliquez ici{1} pour vous authentifier
ou appuyez sur Échap pour accéder au pad en mode lecture seule.'; out.wrongApp = "Impossible d'afficher le contenu de ce document temps-réel dans votre navigateur. Vous pouvez essayer de recharger la page."; out.padNotPinned = 'Ce pad va expirer après 3 mois d\'inactivité, {0}connectez-vous{1} ou {2}enregistrez-vous{3} pour le préserver.'; out.anonymousStoreDisabled = "L'administrateur de cette instance de CryptPad a désactivé le drive pour les utilisateurs non enregistrés. Vous devez vous connecter pour pouvoir utiliser CryptDrive."; @@ -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.
' + 'Appuyez sur Échap pour voir le pad ou rechargez la page pour pouvoir le modifier à nouveau.'; out.errorCopy = ' Vous pouvez toujours copier son contenu ailleurs en appuyant sur Échap.
Dés que vous aurez quitté la page, il sera impossible de le récupérer.'; + out.errorRedirectToHome = 'Appuyez sur Échap pour retourner vers votre CryptDrive.'; + out.newVersionError = "Une nouvelle version de CryptPad est disponible.
" + + "Rechargez la page pour utiliser la nouvelle version, ou appuyez sur Échap pour accéder au contenu actuel en mode hors-ligne."; 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:
{0}"; + 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 ?
" + @@ -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.
" + + "Nous ne pouvons pas réinitialiser votre mot de passe si vous le perdez, donc soyez très prudent !"; + 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 {0} avant son stockage en ligne ?
" + - "L'extension du fichier ({1}) sera ajoutée automatiquement. "+ - "Ce nom sera permanent et visible par les autres utilisateurs."; + out.upload_modal_title = "Options d'importation du fichier"; + out.upload_modal_filename = "Nom (extension {0} 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é. En savoir plus..."; - // 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) CKEditor, l\'éditeur de code source CodeMirror, et le moteur temps-réel ChainPad.'; - out.main_howitworks_p1 = 'CryptPad utilise une variante de l\'algorithme d\'Operational transformation qui est capable de trouver un consensus distribué en utilisant une chaîne de bloc Nakamoto, un outil popularisé par le Bitcoin. 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\'XWiki SAS, 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 nous tweeter, ouvrir une issue sur GitHub, venir dire bonjour sur notre salle Matrix ou IRC (#cryptpad sur irc.freenode.net), ou bien encore nous envoyer un email.'; 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 = "

Collaborez avec confiance


Développez vos idées en groupe avec des documents partagés; la technologie Zero Knowledge 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'allons pas regarder vos pads. Avec la technologie Zero Knowledge de CryptPad, nous ne pouvons pas le faire. Apprenez-en plus sur notre manière de protéger vos données."; - 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 CkEditor temps-réel et Zero Knowledge.'; out.main_code = 'Éditeur de code'; - out.main_code_p = 'Modifiez votre code collaborativement grâce à notre application CodeMirror 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 CryptPad Whitepaper."; + 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 CryptPad Whitepaper."; out.whatis_business_p2 = "CryptPad est déployable sur site et les développeurs CryptPad chez XWiki SAS peuvent effectuer du développement, des personnalisations et du support commercial. Contactez-nous à sales@cryptpad.fr 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
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 CryptPad ?"; 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 Modèles du CryptDrive.
" + "Il est également possible de créer une copie d'un pad en tant que modèle en cliquant sur le bouton (Sauver en tant que modèle) dans la barre d'outils des éditeurs." }, + abandoned: { + q: "Qu'est-ce qu'un pad abandonné?", + a: "Un pad abandonné 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.
" + "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.

" + "Nous utilisons également notre fonctionnalité de retour d'expérience 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.

" + - "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 = 'Fait avec amour en France par XWiki SAS'; - out.header_support = ' OpenPaaS-ng'; 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 ou de votre CryptDrive et exporter le contenu en tant que PNG sur votre disque ou votre CryptDrive ' }; + out.help.kanban = { + add: 'Ajoutez un tableau en utilisant le bouton 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 = [ '

', @@ -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 illimité ne sera pas supprimé du serveur à moins que son propriétaire ne le décide."; out.creation_expire2 = "Un pad à durée de vie sera supprimé automatiquement du serveur et du CryptDrive des utilisateurs lorsque cette durée sera dépassée."; + out.creation_password = "Ajouter un mot de passe"; out.creation_noTemplate = "Pas de modèle"; out.creation_newTemplate = "Nouveau modèle"; out.creation_create = "Créer"; @@ -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 Tab pour sélectionner un type et appuyer sur Entrée pour valider."; out.creation_newPadModalDescriptionAdvanced = "Cochez la case si vous souhaitez voir l'écran de création de pads (pour les pads avec propriétaire ou à durée de vie). Vous pouvez appuyer sur Espace pour changer sa valeur."; out.creation_newPadModalAdvanced = "Afficher l'écran de création de pads"; + // Password prompt on the loadind screen + out.password_info = "Le pad auquel vous essayez d'accéder est protégé par un mot de passe. Entrez le bon mot de passe pour accéder à son contenu."; + out.password_error = "Pad introuvable !
Cette erreur peut provenir de deux facteurs. Soit le mot de passe est faux, soit le pad a été supprimé du serveur."; + out.password_placeholder = "Tapez le mot de passe ici..."; + out.password_submit = "Valider"; + out.password_show = "Afficher"; + + // 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.
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.
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; }); diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 48476d2aa..b76afc8b3 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -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 = "Server Connection Lost
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, click here to log in
or press Escape to access your pad in read-only mode.'; + out.onLogout = 'You are logged out, {0}click here{1} to log in
or press Escape to access your pad in read-only mode.'; out.wrongApp = "Unable to display the content of that realtime session in your browser. Please try to reload that page."; out.padNotPinned = 'This pad will expire after 3 months of inactivity, {0}login{1} or {2}register{3} to preserve it.'; out.anonymousStoreDisabled = "The webmaster of this CryptPad instance has disabled the store for anonymous users. You have to log in to be able to use CryptDrive."; @@ -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.
' + 'Hit Esc to continue to view this pad, or reload to try editing again.'; out.errorCopy = ' You can still copy the content to another location by pressing Esc.
Once you leave this page, it will disappear forever!'; + out.errorRedirectToHome = 'Press Esc to be redirected to your CryptDrive.'; + out.newVersionError = "A new version of CryptPad is available.
" + + "Reload to use the new version, or press escape to access your content in offline mode."; 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:
{0}"; + 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?
" + @@ -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 = "Automatic pad storage results in all the pads you visit being stored in your CryptDrive.
" + + "Manual (always ask) results in the pads not being stored but a reminder will appear to ask you if you want to store them in CryptDrive.
" + + "Manual (never ask) 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.
" + + "We can't reset your password if you forget it, so be very careful!"; + 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 {0} before uploading it to the server?
" + - "The file extension ({1}) will be added automatically. "+ - "This name will be permanent and visible to other users."; + out.upload_modal_title = "File upload options"; + out.upload_modal_filename = "File name (extension {0} 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. Learn more..."; + 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 CKEditor Visual Editor, CodeMirror, and the ChainPad realtime engine.'; - out.main_howitworks_p1 = 'CryptPad uses a variant of the Operational transformation algorithm which is able to find distributed consensus using a Nakamoto Blockchain, a construct popularized by Bitcoin. 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 XWiki SAS, 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!
You can tweet us, open an issue on GitHub. Come say hi on our Matrix channel or IRC (#cryptpad on irc.freenode.net), or send us an email.'; 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 = "

Collaborate in Confidence

Grow your ideas together with shared documents while Zero Knowledge technology secures your privacy; even from us."; 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 won't look at your pads, with CryptPad's revolutionary Zero Knowledge Technology we can't. Learn more about how we protect your Privacy and Security."; - 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 CkEditor application.'; out.main_code = 'Code editor'; - out.main_code_p = 'Edit code from your software collaboratively with our realtime Zero Knowledge CodeMirror 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 CryptPad Whitepaper 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 CryptPad Whitepaper to learn more about how it can help your business."; out.whatis_business_p2 = 'CryptPad is deployable on premises and the CryptPad developers at XWiki SAS are able to offer commercial support, customization and development. Reach out to sales@cryptpad.fr 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
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 CryptPad?"; 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 Templates section in your CryptDrive." + " You can also create a copy of a pad to be used as a template by clicking the template button () in the editor's toolbar." }, + abandoned: { + q: "What is an abandoned pad?", + a: "An abandoned pad 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 feedback 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.

" + - "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 = 'With love from France by XWiki SAS'; - - out.header_support = ' OpenPaaS-ng'; 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 or your CryptDrive and export them as PNG to your disk or your CryptDrive ' }; + out.help.kanban = { + add: 'Add new boards using the 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 = [ '

', @@ -1141,6 +1188,7 @@ define(function () { out.creation_expireMonths = "Month(s)"; out.creation_expire1 = "An unlimited pad will not be removed from the server until its owner deletes it."; out.creation_expire2 = "An expiring pad has a set lifetime, after which it will be automatically removed from the server and other users' CryptDrives."; + out.creation_password = "Add a password"; out.creation_noTemplate = "No template"; out.creation_newTemplate = "New template"; out.creation_create = "Create"; @@ -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 Tab to select the type and press Enter to confirm."; out.creation_newPadModalDescriptionAdvanced = "You can check the box (or press Space to change its value) if you want to display the pad creation screen (for owned pads, expiring pads, etc.)."; out.creation_newPadModalAdvanced = "Display the pad creation screen"; + // Password prompt on the 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!
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.
Press OK to reload and update your acces rights."; + out.properties_passwordSuccess = "The password was successfully changed.
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 Settings 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; }); diff --git a/customize.dist/translations/messages.pt-br.js b/customize.dist/translations/messages.pt-br.js index fbf63d067..a2feea5f9 100644 --- a/customize.dist/translations/messages.pt-br.js +++ b/customize.dist/translations/messages.pt-br.js @@ -38,7 +38,7 @@ define(function () { out.websocketError = 'Incapaz de se conectar com o servidor websocket...'; out.typeError = "Este bloco não é compatível com a aplicação selecionada"; - out.onLogout = 'você foi desconectado, clique aqui para se conectar,
ou pressione ESC para acessar seu bloco em modo somente leitura.'; + out.onLogout = 'você foi desconectado, {0}clique aqui{1} para se conectar,
ou pressione ESC para acessar seu bloco em modo somente leitura.'; out.wrongApp = "Incapaz de mostrar o conteúdo em tempo real no seu navegador. Por favor tente recarregar a página."; out.loading = "Carregando..."; diff --git a/customize.dist/translations/messages.ro.js b/customize.dist/translations/messages.ro.js index 56ec8b05d..fe24c7057 100644 --- a/customize.dist/translations/messages.ro.js +++ b/customize.dist/translations/messages.ro.js @@ -13,7 +13,7 @@ define(function () { out.common_connectionLost = out.updated_0_common_connectionLost; out.websocketError = "Conexiune inexistentă către serverul websocket..."; out.typeError = "Această filă nu este compatibilă cu aplicația aleasă"; - out.onLogout = "Nu mai ești autentificat, apasă aici să te autentifici
sau apasă Escapesă accesezi fila în modul citire."; + out.onLogout = "Nu mai ești autentificat, {0}apasă aici{1} să te autentifici
sau apasă Escapesă accesezi fila în modul citire."; out.wrongApp = "Momentan nu putem arăta conținutul sesiunii în timp real în fereastra ta. Te rugăm reîncarcă pagina."; out.loading = "Încarcă..."; out.error = "Eroare"; diff --git a/customize.dist/translations/messages.zh.js b/customize.dist/translations/messages.zh.js index 25b7fe8c8..422f00f2c 100644 --- a/customize.dist/translations/messages.zh.js +++ b/customize.dist/translations/messages.zh.js @@ -31,7 +31,7 @@ define(function () { out.websocketError = '無法連結上 websocket 伺服器...'; out.typeError = "這個編輯檔與所選的應用程式並不相容"; - out.onLogout = '你已登出, 點擊這裏 來登入
或按Escape 來以唯讀模型使用你的編輯檔案'; + out.onLogout = '你已登出, {0}點擊這裏{1} 來登入
或按Escape 來以唯讀模型使用你的編輯檔案'; out.wrongApp = "無法在瀏覽器顯示即時期間的內容,請試著再重新載入本頁。"; out.loading = "載入中..."; diff --git a/delete-inactive.js b/delete-inactive.js index 16f41da45..9acf15f69 100644 --- a/delete-inactive.js +++ b/delete-inactive.js @@ -33,7 +33,7 @@ nThen(function (waitFor) { sem.take(function (give) { Fs.unlink(f.filename, give(function (err) { if (err) { return void console.error(err + " " + f.filename); } - console.log(f.filename + " " + f.size + " " + (+f.atime) + " " + (+new Date())); + console.log(f.filename + " " + f.size + " " + (+f.mtime) + " " + (+new Date())); })); }); }); diff --git a/docs/example.nginx.conf b/docs/example.nginx.conf index d56920bf5..e8454dc31 100644 --- a/docs/example.nginx.conf +++ b/docs/example.nginx.conf @@ -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"; + 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"; - if ($uri = /pad/inner.html) { - set $scriptSrc "'self' 'unsafe-eval' 'unsafe-inline'"; + 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; @@ -72,17 +78,26 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - + } + location ^~ /blob/ { + add_header Cache-Control max-age=31536000; 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 ^~ /block/ { + add_header Cache-Control max-age=0; + try_files $uri =404; + } + + 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; } - diff --git a/package.json b/package.json index 8365867be..c4a064c99 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pinned.js b/pinned.js index 41a832241..d5df9373a 100644 --- a/pinned.js +++ b/pinned.js @@ -15,6 +15,7 @@ const hashesFromPinFile = (pinFile, fileName) => { switch (l[0]) { case 'RESET': { pins = {}; + if (l[1] && l[1].length) { l[1].forEach((x) => { pins[x] = 1; }); } //jshint -W086 // fallthrough } @@ -32,7 +33,7 @@ const hashesFromPinFile = (pinFile, fileName) => { return Object.keys(pins); }; -module.exports.load = function (cb) { +module.exports.load = function (cb, config) { nThen((waitFor) => { Fs.readdir('./pins', waitFor((err, list) => { if (err) { @@ -49,7 +50,10 @@ module.exports.load = function (cb) { sema.take((returnAfter) => { Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => { if (err) { throw err; } - list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); }); + list2.forEach((ff) => { + if (config && config.exclude && config.exclude.indexOf(ff) > -1) { return; } + fileList.push('./pins/' + f + '/' + ff); + }); }))); }); }); @@ -76,4 +80,4 @@ if (!module.parent) { console.log(x + ' ' + JSON.stringify(data[x])); }); }); -} \ No newline at end of file +} diff --git a/pinneddata.js b/pinneddata.js index 378f34ad7..346062fb6 100644 --- a/pinneddata.js +++ b/pinneddata.js @@ -3,14 +3,21 @@ const Fs = require('fs'); const Semaphore = require('saferphore'); const nThen = require('nthen'); +/* + takes contents of a pinFile (UTF8 string) + and the pin file's name + returns an array of of channel ids which are pinned + + throw errors on pin logs with invalid pin data +*/ const hashesFromPinFile = (pinFile, fileName) => { var pins = {}; pinFile.split('\n').filter((x)=>(x)).map((l) => JSON.parse(l)).forEach((l) => { switch (l[0]) { case 'RESET': { pins = {}; - //jshint -W086 - // fallthrough + if (l[1] && l[1].length) { l[1].forEach((x) => { pins[x] = 1; }); } + break; } case 'PIN': { l[1].forEach((x) => { pins[x] = 1; }); @@ -26,6 +33,11 @@ const hashesFromPinFile = (pinFile, fileName) => { return Object.keys(pins); }; +/* + takes an array of pinned file names + and a global map of stats indexed by public keys + returns the sum of the size of those pinned files +*/ const sizeForHashes = (hashes, dsFileStats) => { let sum = 0; hashes.forEach((h) => { @@ -39,23 +51,31 @@ const sizeForHashes = (hashes, dsFileStats) => { return sum; }; +// do twenty things at a time const sema = Semaphore.create(20); let dirList; -const fileList = []; -const dsFileStats = {}; -const out = []; -const pinned = {}; +const fileList = []; // array which we reuse for a lot of things +const dsFileStats = {}; // map of stats +const out = []; // what we return at the end +const pinned = {}; // map of pinned files +// define a function: 'load' which takes a config +// and a callback module.exports.load = function (config, cb) { nThen((waitFor) => { + // read the subdirectories in the datastore Fs.readdir('./datastore', waitFor((err, list) => { if (err) { throw err; } dirList = list; })); }).nThen((waitFor) => { + // iterate over all subdirectories dirList.forEach((f) => { + // process twenty subdirectories simultaneously sema.take((returnAfter) => { + // get the list of files in every subdirectory + // and push them to 'fileList' Fs.readdir('./datastore/' + f, waitFor(returnAfter((err, list2) => { if (err) { throw err; } list2.forEach((ff) => { fileList.push('./datastore/' + f + '/' + ff); }); @@ -63,14 +83,19 @@ module.exports.load = function (config, cb) { }); }); }).nThen((waitFor) => { - + // read the subdirectories in 'blob' Fs.readdir('./blob', waitFor((err, list) => { if (err) { throw err; } + // overwrite dirList dirList = list; })); }).nThen((waitFor) => { + // iterate over all subdirectories dirList.forEach((f) => { + // process twenty subdirectories simultaneously sema.take((returnAfter) => { + // get the list of files in every subdirectory + // and push them to 'fileList' Fs.readdir('./blob/' + f, waitFor(returnAfter((err, list2) => { if (err) { throw err; } list2.forEach((ff) => { fileList.push('./blob/' + f + '/' + ff); }); @@ -78,24 +103,34 @@ module.exports.load = function (config, cb) { }); }); }).nThen((waitFor) => { + // iterate over the fileList fileList.forEach((f) => { + // process twenty files simultaneously sema.take((returnAfter) => { + // get the stats of each files Fs.stat(f, waitFor(returnAfter((err, st) => { if (err) { throw err; } st.filename = f; + // push them to a big map of stats dsFileStats[f.replace(/^.*\/([^\/\.]*)(\.ndjson)?$/, (all, a) => (a))] = st; }))); }); }); }).nThen((waitFor) => { + // read the subdirectories in the pinstore Fs.readdir('./pins', waitFor((err, list) => { if (err) { throw err; } dirList = list; })); }).nThen((waitFor) => { + // set file list to an empty array + // fileList = [] ?? fileList.splice(0, fileList.length); dirList.forEach((f) => { + // process twenty directories at a time sema.take((returnAfter) => { + // get the list of files in every subdirectory + // and push them to 'fileList' (which is empty because we keep reusing it) Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => { if (err) { throw err; } list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); }); @@ -103,70 +138,147 @@ module.exports.load = function (config, cb) { }); }); }).nThen((waitFor) => { + // iterate over the list of pin logs fileList.forEach((f) => { + // twenty at a time sema.take((returnAfter) => { + // read the full content Fs.readFile(f, waitFor(returnAfter((err, content) => { if (err) { throw err; } + // get the list of channels pinned by this log const hashes = hashesFromPinFile(content.toString('utf8'), f); - const size = sizeForHashes(hashes, dsFileStats); if (config.unpinned) { hashes.forEach((x) => { pinned[x] = 1; }); } else { + // get the size of files pinned by this log + // but only if we're gonna use it + let size = sizeForHashes(hashes, dsFileStats); + // we will return a list of values + // [user_public_key, size_of_files_they_have_pinned] out.push([f, Math.floor(size / (1024 * 1024))]); } }))); }); }); }).nThen(() => { + // handle all the information you've processed so far if (config.unpinned) { + // the user wants data about what has not been pinned + + // by default we concern ourselves with pads and files older than infinity (everything) let before = Infinity; + + // but you can override this with config if (config.olderthan) { before = config.olderthan; - if (isNaN(before)) { + // FIXME validate inputs before doing the heavy lifting + if (isNaN(before)) { // make sure the supplied value is a number return void cb('--olderthan error [' + config.olderthan + '] not a valid date'); } } + + // you can specify a different time for blobs... let blobsbefore = before; if (config.blobsolderthan) { + // use the supplied date if it exists blobsbefore = config.blobsolderthan; if (isNaN(blobsbefore)) { return void cb('--blobsolderthan error [' + config.blobsolderthan + '] not a valid date'); } } let files = []; + // iterate over all the stats that you've saved Object.keys(dsFileStats).forEach((f) => { + // we only care about files which are not in the pin map if (!(f in pinned)) { + // check if it's a blob or a 'pad' const isBlob = dsFileStats[f].filename.indexOf('.ndjson') === -1; - if ((+dsFileStats[f].atime) >= ((isBlob) ? blobsbefore : before)) { return; } + + // if the mtime is newer than the specified value for its file type, ignore this file + + if ((+dsFileStats[f].mtime) >= ((isBlob) ? blobsbefore : before)) { return; } + + // otherwise push it to the list of files, with its filename, size, and mtime files.push({ filename: dsFileStats[f].filename, size: dsFileStats[f].size, - atime: dsFileStats[f].atime + mtime: dsFileStats[f].mtime }); } }); + + // return the list of files cb(null, files); } else { + // if you're not in 'unpinned' mode, sort by size (ascending) out.sort((a,b) => (a[1] - b[1])); + // and return the sorted data cb(null, out.slice()); } }); }; + +// This script can be called directly on its own +// or required as part of another script if (!module.parent) { - let config = {}; + // if no parent, it is being invoked directly + let config = {}; // build the config from command line arguments... + + // --unpinned gets the list of unpinned files + // if you don't pass this, it will list the size of pinned data per user if (process.argv.indexOf('--unpinned') > -1) { config.unpinned = true; } + + // '--olderthan' must be used in conjunction with '--unpinned' + // if you pass '--olderthan' with a string date or number, it will limit + // results only to pads older than the supplied time + // it defaults to 'infinity', or no filter at all const ot = process.argv.indexOf('--olderthan'); - config.olderthan = ot > -1 && new Date(process.argv[ot+1]); + if (ot > -1) { + config.olderthan = Number(process.argv[ot+1]) ? new Date(Number(process.argv[ot+1])) + : new Date(process.argv[ot+1]); + } + + // '--blobsolderthan' must be used in conjunction with '--unpinned' + // if you pass '--blobsolderthan with a string date or number, it will limit + // results only to blobs older than the supplied time + // it defaults to using the same value passed '--olderthan' const bot = process.argv.indexOf('--blobsolderthan'); - config.blobsolderthan = bot > -1 && new Date(process.argv[bot+1]); + if (bot > -1) { + config.blobsolderthan = Number(process.argv[bot+1]) ? new Date(Number(process.argv[bot+1])) + : new Date(process.argv[bot+1]); + } + + // call our big function directly + // pass our constructed configuration and a callback module.exports.load(config, function (err, data) { - if (err) { throw new Error(err); } - if (!Array.isArray(data)) { return; } + if (err) { throw new Error(err); } // throw errors + if (!Array.isArray(data)) { return; } // if the returned value is not an array, you're done if (config.unpinned) { - data.forEach((f) => { console.log(f.filename + " " + f.size + " " + (+f.atime)); }); + // display the list of unpinned files with their size and mtime + data.forEach((f) => { console.log(f.filename + " " + f.size + " " + (+f.mtime)); }); } else { + // display the list of public keys and the size of the data they have pinned in megabytes data.forEach((x) => { console.log(x[0] + ' ' + x[1] + ' MB'); }); } }); } + + +/* Example usage of this script... + +# display the list of public keys and the size of the data the have pinned in megabytes +node pinneddata.js + +# display the list of unpinned pads and blobs with their size and mtime +node pinneddata.js --unpinned + +# display the list of unpinned pads and blobs older than 12345 with their size and mtime +node pinneddata.js --unpinned --olderthan 12345 + + +# display the list of unpinned pads older than 12345 and unpinned blobs older than 123 +# each with their size and mtime +node pinneddata.js --unpinned --olderthan 12345 --blobsolderthan 123 + +*/ diff --git a/readme.md b/readme.md index 150d21bba..d12e2e376 100644 --- a/readme.md +++ b/readme.md @@ -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 diff --git a/rpc.js b/rpc.js index ae4971ae0..f64fddd48 100644 --- a/rpc.js +++ b/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: diff --git a/server.js b/server.js index 157482368..b2f26f3f7 100644 --- a/server.js +++ b/server.js @@ -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); }); diff --git a/www/assert/main.js b/www/assert/main.js index eb470eb35..e0b437f58 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -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); diff --git a/www/code/app-code.less b/www/code/app-code.less index f4153e95d..37a18b4ee 100644 --- a/www/code/app-code.less +++ b/www/code/app-code.less @@ -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"; - -.framework_main( - @bg-color: @colortheme_code-bg, - @warn-color: @colortheme_code-warn, - @color: @colortheme_code-color -); - -// body &.cp-app-code { + .framework_main( + @bg-color: @colortheme_code-bg, + @warn-color: @colortheme_code-warn, + @color: @colortheme_code-color + ); + display: flex; flex-flow: column; max-height: 100%; diff --git a/www/code/inner.js b/www/code/inner.js index c2e5851bf..3d6b16d37 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -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 = ''; + var secret = Hash.getSecrets('file', parsed.hash, data.password); + var src = Hash.getBlobPathFromHex(secret.channel); + var key = Hash.encodeBase64(secret.keys.cryptKey); + var mt = ''; editor.replaceSelection(mt); } }; diff --git a/www/common/LessLoader.js b/www/common/LessLoader.js index 063b3d024..d9e7d3078 100644 --- a/www/common/LessLoader.js +++ b/www/common/LessLoader.js @@ -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) { - inject(css, url); - return void cb(); + var loadSubmodulesAndInject = function (css, url, cb, stack) { + inject(css, url); + 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*/) { + 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); }); }); }; diff --git a/www/common/application_config_internal.js b/www/common/application_config_internal.js index 0b2d9da90..80e4973a0 100644 --- a/www/common/application_config_internal.js +++ b/www/common/application_config_internal.js @@ -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; }); diff --git a/www/common/boot2.js b/www/common/boot2.js index a9d0606db..fc3968fb3 100644 --- a/www/common/boot2.js +++ b/www/common/boot2.js @@ -34,7 +34,7 @@ define([ url: '/common/feedback.html?NO_LOCALSTORAGE=' + (+new Date()), }); }); - window.alert("CryptPad needs localStorage to work, try a different browser"); + window.alert("CryptPad needs localStorage to work. Try changing your cookie permissions, or using a different browser"); }; window.onerror = function (e) { diff --git a/www/common/common-constants.js b/www/common/common-constants.js index 494267ed3..908134bec 100644 --- a/www/common/common-constants.js +++ b/www/common/common-constants.js @@ -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' }; }); diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 870b19dfe..d5066b757 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -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 (!keys.editKeyStr) { return; } - return '/1/edit/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.editKeyStr)+'/'; - }; - var getViewHashFromKeys = Hash.getViewHashFromKeys = function (chanKey, keys) { - if (typeof keys === 'string') { - return; + 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; } - return '/1/view/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.viewKeyStr)+'/'; }; - var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) { - return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/'; + var getViewHashFromKeys = Hash.getViewHashFromKeys = function (secret) { + var version = secret.version; + var data = secret.keys; + if (version === 0) { return; } + if (version === 1) { + if (!data.viewKeyStr) { return; } + return '/1/view/' + hexToBase64(secret.channel) + + '/'+Crypto.b64RemoveSlashes(data.viewKeyStr)+'/'; + } + if (version === 2) { + if (!data.viewKeyStr) { return; } + var pass = secret.password ? 'p/' : ''; + return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass; + } }; + var getFileHashFromKeys = Hash.getFileHashFromKeys = function (secret) { + var version = secret.version; + var data = secret.keys; + if (version === 0) { return; } + if (version === 1) { + return '/1/' + hexToBase64(secret.channel) + '/' + + Crypto.b64RemoveSlashes(data.fileKeyStr) + '/'; + } + 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) { diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 24c7098c0..b33767f48 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -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(); - } else { - $loading.find('img').show(); - } - var $spinner = $loading.find('.cp-loading-spinner-container'); - $spinner.show(); - $('body').append($loading); + Loading(); + todo(); } - if (Messages.tips && !hideTips) { - var $loadingTip = $('

', {'id': 'cp-loading-tip'}); - $('', {'class': 'tips'}).text(getRandomTip()).appendTo($loadingTip); - $loadingTip.css({ - 'bottom': $('body').height()/2 - $container.height()/2 + 20 + 'px' - }); - $('body').append($loadingTip); + }; + UI.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 { + 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+'%;'}) + ])); + } + } } }; 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; }); diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js index 13d132219..1c75927fc 100644 --- a/www/common/common-messaging.js +++ b/www/common/common-messaging.js @@ -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) { diff --git a/www/common/common-thumbnail.js b/www/common/common-thumbnail.js index 455b095c9..767b0e92a 100644 --- a/www/common/common-thumbnail.js +++ b/www/common/common-thumbnail.js @@ -205,7 +205,7 @@ define([ if (content === oldThumbnailState) { return; } oldThumbnailState = content; Thumb.fromDOM(opts, function (err, b64) { - Thumb.setPadThumbnail(common, opts.href, b64); + Thumb.setPadThumbnail(common, opts.href, null, b64); }); }; var nafa = Util.notAgainForAnother(mkThumbnail, Thumb.UPDATE_INTERVAL); @@ -240,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); }); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 8e150a3ed..2fc10a6d3 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -13,32 +13,40 @@ define([ '/customize/messages.js', '/customize/application_config.js', '/bower_components/nthen/index.js', - - 'css!/common/tippy.css', + 'css!/customize/fonts/cptools/style.css' ], function ($, Config, Util, Hash, Language, UI, Constants, Feedback, h, MediaTag, Clipboard, Messages, AppConfig, NThen) { var UIElements = {}; // Configure MediaTags to use our local viewer - if (MediaTag && MediaTag.PdfPlugin) { - MediaTag.PdfPlugin.viewer = '/common/pdfjs/web/viewer.html'; + if (MediaTag) { + MediaTag.setDefaultConfig('pdf', { + viewer: '/common/pdfjs/web/viewer.html' + }); } UIElements.updateTags = function (common, href) { - var sframeChan = common.getSframeChannel(); - sframeChan.query('Q_TAGS_GET', href || null, function (err, res) { - if (err || res.error) { - if (res.error === 'NO_ENTRY') { - UI.alert(Messages.tags_noentry); + var existing, tags; + NThen(function(waitFor) { + common.getSframeChannel().query("Q_GET_ALL_TAGS", null, waitFor(function(err, res) { + if (err || res.error) { return void console.error(err || res.error); } + existing = Object.keys(res.tags).sort(); + })); + }).nThen(function (waitFor) { + common.getPadAttribute('tags', waitFor(function (err, res) { + if (err) { + if (err === 'NO_ENTRY') { + UI.alert(Messages.tags_noentry); + } + waitFor.abort(); + return void console.error(err); } - return void console.error(err || res.error); - } - UI.dialog.tagPrompt(res.data, function (tags) { - if (!Array.isArray(tags)) { return; } - sframeChan.event('EV_TAGS_SET', { - tags: tags, - href: href, - }); + tags = res || []; + }), href); + }).nThen(function () { + UI.dialog.tagPrompt(tags, existing, function (newTags) { + if (!Array.isArray(newTags)) { return; } + common.setPadAttribute('tags', newTags, null, href); }); }); }; @@ -62,25 +70,21 @@ define([ var getPropertiesData = function (common, cb) { var data = {}; NThen(function (waitFor) { + common.getPadAttribute('password', waitFor(function (err, val) { + data.password = val; + })); + }).nThen(function (waitFor) { + var base = common.getMetadataMgr().getPrivateData().origin; common.getPadAttribute('href', waitFor(function (err, val) { - var base = common.getMetadataMgr().getPrivateData().origin; - - var parsed = Hash.parsePadUrl(val); - if (parsed.hashData.mode === "view") { - data.roHref = base + val; - return; - } - - // We're not in a read-only pad + if (!val) { return; } data.href = base + val; - // Get Read-only href - if (parsed.hashData.type !== "pad") { return; } - var i = data.href.indexOf('#') + 1; - var hBase = data.href.slice(0, i); - var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash); - if (!hrefsecret.keys) { return; } - var viewHash = Hash.getViewHashFromKeys(hrefsecret.channel, hrefsecret.keys); - data.roHref = hBase + viewHash; + })); + common.getPadAttribute('roHref', waitFor(function (err, val) { + if (!val) { return; } + data.roHref = base + val; + })); + common.getPadAttribute('channel', waitFor(function (err, val) { + data.channel = val; })); common.getPadAttribute('atime', waitFor(function (err, val) { data.atime = val; @@ -121,22 +125,89 @@ define([ $d.append(UI.dialog.selectable(owners, { id: 'cp-app-prop-owners', })); - /* TODO - if (owned) { - var $deleteOwned = $('button').text(Messages.fc_delete_owned).click(function () { - }); - $d.append($deleteOwned); - }*/ - var expire = Messages.creation_expireFalse; - if (data.expire && typeof (data.expire) === "number") { - expire = new Date(data.expire).toLocaleString(); + if (!data.noExpiration) { + var expire = Messages.creation_expireFalse; + if (data.expire && typeof (data.expire) === "number") { + expire = new Date(data.expire).toLocaleString(); + } + $('