diff --git a/.dockerignore b/.dockerignore index 880c21fe3..f2099e1fc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,7 @@ data Dockerfile docker-compose.yml +.dockerignore .git -.gitignore \ No newline at end of file +.gitignore +node_modules diff --git a/.flowconfig b/.flowconfig index 4a58bdcde..1c97394ee 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,5 +1,6 @@ [ignore] - +.*/bower_components/.* +.*/node_modules/lesshint/* [include] [libs] diff --git a/.gitignore b/.gitignore index fc1136152..741aedaf7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ datastore +tasks www/bower_components/* node_modules /config.js diff --git a/.jshintignore b/.jshintignore index aab1b485b..c9c5d3eb8 100644 --- a/.jshintignore +++ b/.jshintignore @@ -11,6 +11,9 @@ www/common/hyperscript.js www/common/tippy.min.js www/pad/wysiwygarea-plugin.js -www/pad2/wysiwygarea-plugin.js +www/pad/mediatag-plugin.js +www/pad/mediatag-plugin-dialog.js www/common/media-tag-nacl.min.js + +customize/ diff --git a/.jshintrc b/.jshintrc index 4928c524d..c95e9dbc4 100644 --- a/.jshintrc +++ b/.jshintrc @@ -7,7 +7,6 @@ "iterator": true, "latedef": true, "nocomma": true, - "notypeof": true, "shadow": false, "undef": true, "unused": true, @@ -19,5 +18,8 @@ "require", "module", "__dirname" - ] + ], + "globals": { + "self": true + } } diff --git a/.lesshintrc b/.lesshintrc new file mode 100644 index 000000000..693029bf1 --- /dev/null +++ b/.lesshintrc @@ -0,0 +1,60 @@ +{ + "fileExtensions": [".less"], + + // These rules are almost certainly crap and will not catch bugs (Caleb) + "newlineAfterBlock": { "enabled": false }, // not just a newline but an entire empty line after each block + "spaceAroundOperator": { "enabled": false }, // disallow calc(10px+10px); + "hexLength": { "enabled": false }, // require long hex color codes or require short where possible + "hexNotation": { "enabled": false }, // require hex lowercase + "propertyOrdering": { "enabled": false }, // require attributes to be in alphabetical order D: + "stringQuotes": { "enabled": false }, // force quoting of strings with ' or " (silly) + "importPath": { "enabled": false }, // require imports to not have .less, ridiculous + "qualifyingElement": { "enabled": false }, // disallow div.xxx and require .xxx + "decimalZero": { "enabled": false }, // disallow .5em + "borderZero": { "enabled": false }, // disallow border: none; + "selectorNaming": { "enabled": false }, // this would be crap because classes are what they are. + "zeroUnit": { "enabled": false }, + "singleLinePerProperty": { "enabled": false }, + "_singleLinePerProperty": { + "enabled": true, + "allowSingleLineRules": true + }, + "spaceAroundComma": { "enabled": false }, + "importantRule": { "enabled": false }, + "universalSelector": { "enabled": false }, + "idSelector": { "enabled": false }, + "singleLinePerSelector": { "enabled": false }, + "spaceBetweenParens": { "enabled": false }, + "maxCharPerLine": { "enabled": false }, // using lesshint flags can cause long lines + "comment": { "enabled": false }, // ban multi-line comments ? + + // These rules should be discussed, if they're crap then they should be moved up. + "colorVariables": { "enabled": false }, // require all colors to be stored as variables first... + "variableValue": { "enabled": false }, // any attribute types which should always be variables ? color? + "spaceBeforeBrace": { "enabled": true },//{ "enabled": true, "style": "one_space" }, + + // Turn everything else on + "spaceAfterPropertyColon": { "enabled": true }, + "finalNewline": { "enabled": true }, // require an empty line at the end of the file (enabled for now) + "attributeQuotes": { "enabled": true }, + "depthLevel": { + "depth": 1 // TODO(cjd) This is obviously not triggering, even with 1 + }, + "duplicateProperty": { "enabled": true }, + "emptyRule": { "enabled": true }, + "hexValidation": { "enabled": true }, // disallow actual garbage color hex codes (e.g. #ab) + "propertyUnits": { + "valid": ["rem", "vw", "em", "px"], // These units are allowed for all properties + "invalid": ["pt"], // The 'pt' unit is not allowed under any circumstances + "properties": { + //"line-height": [] // No units are allowed for line-height + } + }, + "spaceAfterPropertyName": { "enabled": true, "style": "no_space" }, + "spaceAfterPropertyValue": { "enabled": true, "style": "no_space" }, + "spaceAroundBang": { "enabled": true, "style": "before" }, + "trailingSemicolon": { "enabled": true }, + "trailingWhitespace": { "enabled": true }, + "urlFormat": { "enabled": true, "style": "relative" }, + "urlQuotes": { "enabled": true } +} diff --git a/.travis.yml b/.travis.yml index 09967f1f1..047f0502e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,4 @@ language: node_js -env: - matrix: - - "BROWSER='firefox::Windows 10'" - - "BROWSER='chrome::Windows 10'" - #- "BROWSER='MicrosoftEdge:14.14393:Windows 10'" - #- "BROWSER='internet explorer:11.103:Windows 10'" - #- "BROWSER='safari:10.0:macOS 10.12'" - #- "BROWSER='safari:9.0:OS X 10.11'" branches: only: - master @@ -14,15 +6,7 @@ branches: - staging node_js: - "6.6.0" -before_script: +script: - npm run-script lint - - cp config.example.js config.js - - npm install bower - - ./node_modules/bower/bin/bower install - - node ./server.js & - - sleep 2 -addons: - sauce_connect: - username: "cjdelisle" - access_key: - secure: "pgGh8YGXLPq6fpdwwK2jnjRtwXPbVWQ/HIFvwX7E6HBpzxxcF2edE8sCdonWW9TP2LQisZFmVLqoSnZWMnjBr2CBAMKMFvaHQDJDQCo4v3BXkID7KgqyKmNcwW+FPfSJ5MxNBro8/GE/awkhZzJLYGUTS5zi/gVuIUwdi6cHI8s=" + - npm run-script flow + - docker build -t xwiki/cryptpad . diff --git a/Dockerfile b/Dockerfile index 96a5ff520..da60937bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ COPY . /cryptpad WORKDIR /cryptpad RUN apk add --no-cache git tini \ - && npm install \ + && npm install --production \ && npm install -g bower \ && bower install --allow-root diff --git a/TestSelenium.js b/TestSelenium.js index fccd4e067..352b293eb 100644 --- a/TestSelenium.js +++ b/TestSelenium.js @@ -20,20 +20,44 @@ if (process.env.SAUCE_USERNAME !== undefined) { "accessKey": process.env.SAUCE_ACCESS_KEY, }).forBrowser(browserArray[0], browserArray[1], browserArray[2]).build(); } else { - driver = new WebDriver.Builder().withCapabilities({ browserName: "chrome" }).build(); + driver = new WebDriver.Builder().withCapabilities({ + browserName: process.env.BROWSER || "chrome" + }).build(); } var SC_GET_DATA = "return (window.__CRYPTPAD_TEST__) ? window.__CRYPTPAD_TEST__.getData() : '[]'"; var failed = false; -var nt = nThen; +var nt = nThen(function (waitFor) { + driver.get('http://localhost:3000/auth/').then(waitFor()); +}).nThen(function (waitFor) { + console.log('initialized'); + driver.manage().addCookie({name: 'test', value: 'auto'}).then(waitFor()); +}).nThen; + [ - //'/register/#?test=test', - '/assert/#?test=test', - // '/auth/#?test=test' // TODO(cjd): Not working on automatic tests, understand why. -].forEach(function (path) { + // login test must happen after register test + ['/register/', {}], + ['/login/', {}], + + ['/assert/', {}], + ['/auth/', {}], + + ['/pad/#/1/edit/1KXFMz5L+nLgvHqXVJjyiQ/IUAE6IzVVg5UIYFOPglmVxvV/', {}], + ['/pad/#/1/view/1KXFMz5L+nLgvHqXVJjyiQ/O4kuSnJyviGVlz3qpcr4Fxc8fIK6uTeB30MfMkh86O8/', {}], + + ['/code/#/1/edit/CWtkq8Qa2re7W1XvXZRDYg/2G7Gse5UZ8dLyGAXUdCV2fLL/', {}], + ['/code/#/1/view/CWtkq8Qa2re7W1XvXZRDYg/G1pVa1EL26JRAjk28b43W7Ftc3AkdBblef1U58F3iDk/', {}], + + ['/slide/#/1/edit/uwKqgj8Ezh2dRaFUWSlrRQ/JkJtAb-hNzfESZEHreAeULU1/', {}], + ['/slide/#/1/view/uwKqgj8Ezh2dRaFUWSlrRQ/Xa8jXl+jWMpwep41mlrhkqbRuVKGxlueH80Pbgeu5Go/', {}], + + ['/poll/#/1/edit/lHhnKHSs0HBsl2UGfSJoLw/ZXSsAq4BORIixuFaLVBFcxoq/', {}], + ['/poll/#/1/view/lHhnKHSs0HBsl2UGfSJoLw/TGul8PhswwLh1klHpBto6yEntWtKES2+tetYrrYec4M/', {}] + +].forEach(function (x) { if (failed) { return; } - var url = 'http://localhost:3000' + path; + var url = 'http://localhost:3000' + x[0]; nt = nt(function (waitFor) { var done = waitFor(); console.log('\n\n-----TEST ' + url + ' -----'); @@ -70,6 +94,10 @@ var nt = nThen; if (done) { setTimeout(logMore, 50); } })); }; + driver.manage().addCookie({ + name: 'test', + value: encodeURIComponent(JSON.stringify({ test:'auto', opts: x[1] })) + }); driver.get(url).then(waitFor(logMore)); }).nThen; }); diff --git a/and_so_it_begins.png b/and_so_it_begins.png index 0295eba2e..64f59af17 100644 Binary files a/and_so_it_begins.png and b/and_so_it_begins.png differ diff --git a/bower.json b/bower.json index 9c88d0033..e2d61a268 100644 --- a/bower.json +++ b/bower.json @@ -21,7 +21,7 @@ "jquery": "~2.1.3", "tweetnacl": "0.12.2", "components-font-awesome": "^4.6.3", - "ckeditor": "~4.7", + "ckeditor": "4.7.3", "codemirror": "^5.19.0", "requirejs": "2.3.5", "marked": "0.3.5", @@ -29,18 +29,27 @@ "json.sortify": "~2.1.0", "secure-fabric.js": "secure-v1.7.9", "hyperjson": "~1.4.0", - "textpatcher": "^1.3.0", - "chainpad-json-validator": "^0.2.0", "chainpad-crypto": "^0.1.3", - "chainpad-listmap": "^0.3.0", + "chainpad-listmap": "^0.4.2", + "chainpad": "^5.0.0", + "chainpad-netflux": "^0.6.1", "file-saver": "1.3.1", "alertifyjs": "1.0.11", "scrypt-async": "1.2.0", "require-css": "0.1.10", "less": "^2.7.2", - "bootstrap": "#v4.0.0-alpha.6", + "bootstrap": "^v4.0.0", "diff-dom": "2.1.1", "nthen": "^0.1.5", - "open-sans-fontface": "^1.4.2" + "open-sans-fontface": "^1.4.2", + "bootstrap-tokenfield": "^0.12.1", + "localforage": "^1.5.2", + "html2canvas": "^0.4.1", + "croppie": "^2.5.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/check-accounts.js b/check-accounts.js new file mode 100644 index 000000000..4f37e51fd --- /dev/null +++ b/check-accounts.js @@ -0,0 +1,40 @@ +/* globals Buffer */ +var Https = require('https'); +var Config = require("./config.js"); +var Package = require("./package.json"); + +var body = JSON.stringify({ + domain: Config.myDomain, + adminEmail: Config.adminEmail, + version: Package.version, +}); + +var options = { + host: 'accounts.cryptpad.fr', + path: '/api/getauthorized', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(body) + } +}; + +Https.request(options, function (response) { + if (!('' + response.statusCode).match(/^2\d\d$/)) { + throw new Error('SERVER ERROR ' + response.statusCode); + } + var str = ''; + response.on('data', function (chunk) { + str += chunk; + }); + response.on('end', function () { + try { + var json = JSON.parse(str); + console.log(json); + } catch (e) { + throw new Error(e); + } + }); +}).on('error', function (e) { + console.error(e); +}).end(body); diff --git a/config.example.js b/config.example.js index ce38b5c3a..3aace0d4a 100644 --- a/config.example.js +++ b/config.example.js @@ -3,6 +3,15 @@ globals module */ 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 +// `npm install --production` +// See: https://strongloop.github.io/strongloop.com/strongblog/how-to-heap-snapshots/ + +// to enable this feature, uncomment the line below: +// require('heapdump'); + module.exports = { // the address you want to bind to, :: means all ipv4 and ipv6 addresses @@ -84,6 +93,8 @@ module.exports = { // cross-domain iframe. It can simply host the same content as CryptPad. // httpSafeOrigin: "https://some-other-domain.xyz", + httpUnsafeOrigin: domain, + /* your server's websocket url is configurable * (default: '/cryptpad_websocket') * @@ -114,7 +125,9 @@ module.exports = { 'terms', 'about', 'contact', - 'what-is-cryptpad' + 'what-is-cryptpad', + 'features', + 'faq' ], /* Limits, Donations, Subscriptions and Contact @@ -159,6 +172,41 @@ module.exports = { */ defaultStorageLimit: 50 * 1024 * 1024, + /* + * CryptPad allows administrators to give custom limits to their friends. + * add an entry for each friend, identified by their user id, + * which can be found on the settings page. Include a 'limit' (number of bytes), + * a 'plan' (string), and a 'note' (string). + * + * hint: 1GB is 1024 * 1024 * 1024 bytes + */ + customLimits: { + /* + "https://my.awesome.website/user/#/1/cryptpad-user1/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=": { + limit: 20 * 1024 * 1024 * 1024, + plan: 'insider', + note: 'storage space donated by my.awesome.website' + }, + "https://my.awesome.website/user/#/1/cryptpad-user2/GdflkgdlkjeworijfkldfsdflkjeEAsdlEnkbx1vVOo=": { + limit: 10 * 1024 * 1024 * 1024, + plan: 'insider', + note: 'storage space donated by my.awesome.website' + } + */ + }, + + /* some features may require that the server be able to schedule tasks + far into the future, such as: + > "three months from now, this channel should expire" + To disable these features, set 'enableTaskScheduling' to false + */ + enableTaskScheduling: true, + + /* if you would like the list of scheduled tasks to be stored in + a custom location, change the path below: + */ + taskPath: './tasks', + /* * 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 @@ -201,6 +249,14 @@ module.exports = { */ pinPath: './pins', + /* Pads that are not 'pinned' by any registered user can be set to expire + * after a configurable number of days of inactivity (default 90 days). + * The value can be changed or set to false to remove expiration. + * Expired pads can then be removed using a cron job calling the + * `delete-inactive.js` script with node + */ + inactiveTime: 90, // days + /* CryptPad allows logged in users to upload encrypted files. Files/blobs * are stored in a 'blob-store'. Set its location here. */ @@ -278,4 +334,12 @@ module.exports = { // '/etc/apache2/ssl/my_public_cert.crt', // '/etc/apache2/ssl/my_certificate_authorities_cert_chain.ca' //], + + /* You can get a repl for debugging the server if you want it. + * to enable this, specify the debugReplName and then you can + * connect to it with `nc -U /tmp/repl/.sock` + * If you run multiple cryptpad servers, you need to use different + * repl names. + */ + //debugReplName: "cryptpad" }; diff --git a/cryptofist.png b/cryptofist.png index 973083a2b..ce480a1b5 100644 Binary files a/cryptofist.png and b/cryptofist.png differ diff --git a/customize.dist/404.html b/customize.dist/404.html new file mode 100644 index 000000000..9298a5377 --- /dev/null +++ b/customize.dist/404.html @@ -0,0 +1,16 @@ + + + + + CryptPad: Zero Knowledge, Collaborative Real Time Editing + + + + + + + diff --git a/customize.dist/alt-favicon.png b/customize.dist/alt-favicon.png index 52ec0a4bc..23753684f 100644 Binary files a/customize.dist/alt-favicon.png and b/customize.dist/alt-favicon.png differ diff --git a/customize.dist/application_config.js b/customize.dist/application_config.js index a1cb37bba..0cebf4b51 100644 --- a/customize.dist/application_config.js +++ b/customize.dist/application_config.js @@ -1,55 +1,10 @@ -define(function() { - var config = {}; - - /* 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', 'file', 'todo', 'contacts']; - config.registeredOnlyTypes = ['file', 'contacts']; - - /* Cryptpad apps use a common API to display notifications to users - * by default, notifications are hidden after 5 seconds - * You can change their duration here (measured in milliseconds) - */ - config.notificationTimeout = 5000; - - config.enablePinning = true; - - config.whiteboardPalette = [ - '#000000', // black - '#FFFFFF', // white - '#848484', // grey - '#8B4513', // saddlebrown - '#FF0000', // red - '#FF8080', // peach? - '#FF8000', // orange - '#FFFF00', // yellow - '#80FF80', // light green - '#00FF00', // green - '#00FFFF', // cyan - '#008B8B', // dark cyan - '#0000FF', // blue - '#FF00FF', // fuschia - '#FF00C0', // hot pink - '#800080', // purple - ]; - - config.enableTemplates = true; - - config.enableHistory = true; - - /* user passwords are hashed with scrypt, and salted with their username. - this value will be appended to the username, causing the resulting hash - to differ from other CryptPad instances if customized. This makes it - such that anyone who wants to bruteforce common credentials must do so - again on each CryptPad instance that they wish to attack. - - WARNING: this should only be set when your CryptPad instance is first - created. Changing it at a later time will break logins for all existing - users. - */ - config.loginSalt = ''; - config.badStateTimeout = 30000; - - return config; +/* + * You can override the configurable values from this file. + * The recommended method is to make a copy of this file (/customize.dist/application_config.js) + in a 'customize' directory (/customize/application_config.js). + * If you want to check all the configurable values, you can open the internal configuration file + but you should not change it directly (/common/application_config_internal.js) +*/ +define(['/common/application_config_internal.js'], function (AppConfig) { + return AppConfig; }); diff --git a/customize.dist/bkabout.jpg b/customize.dist/bkabout.jpg index 01a51c176..d9434609e 100644 Binary files a/customize.dist/bkabout.jpg and b/customize.dist/bkabout.jpg differ diff --git a/customize.dist/bkregister.jpg b/customize.dist/bkregister.jpg index 03660433f..a67a3b03e 100644 Binary files a/customize.dist/bkregister.jpg and b/customize.dist/bkregister.jpg differ diff --git a/customize.dist/bkwhat.jpg b/customize.dist/bkwhat.jpg index 3549f7acb..90a2841e3 100644 Binary files a/customize.dist/bkwhat.jpg and b/customize.dist/bkwhat.jpg differ diff --git a/customize.dist/ckeditor-config.js b/customize.dist/ckeditor-config.js index ac7989294..36cac87f0 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'; + config.removeButtons= 'Source,Maximize,Anchor'; // 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'; + config.extraPlugins= 'autolink,colorbutton,colordialog,font,indentblock,justify,mediatag,print'; config.toolbarGroups= [ // {"name":"clipboard","groups":["clipboard","undo"]}, //{"name":"editing","groups":["find","selection"]}, @@ -23,7 +23,8 @@ CKEDITOR.editorConfig = function( config ) { {"name":"basicstyles","groups":["basicstyles","cleanup"]}, {"name":"paragraph","groups":["list","indent","blocks","align","bidi"]}, {"name":"styles"}, - {"name":"colors"}]; + {"name":"colors"}, + {"name":"print"}]; config.font_defaultLabel = 'Arial'; config.fontSize_defaultLabel = '16'; @@ -67,4 +68,4 @@ CKEDITOR.editorConfig = function( config ) { CKEDITOR.document._appendStyleSheet = CKEDITOR.document.appendStyleSheet; CKEDITOR.tools.buildStyleHtml = function (x) { return CKEDITOR.tools._buildStyleHtml(fix(x)); }; CKEDITOR.document.appendStyleSheet = function (x) { return CKEDITOR.document._appendStyleSheet(fix(x)); }; -}()); \ No newline at end of file +}()); diff --git a/www/common/credential.js b/customize.dist/credential.js similarity index 88% rename from www/common/credential.js rename to customize.dist/credential.js index 432cc0511..8635bf6c4 100644 --- a/www/common/credential.js +++ b/customize.dist/credential.js @@ -5,6 +5,13 @@ define([ var Cred = {}; var Scrypt = window.scrypt; + Cred.MINIMUM_PASSWORD_LENGTH = typeof(AppConfig.minimumPasswordLength) === 'number'? + AppConfig.minimumPasswordLength: 8; + + Cred.isLongEnoughPassword = function (passwd) { + return passwd.length >= Cred.MINIMUM_PASSWORD_LENGTH; + }; + var isString = Cred.isString = function (x) { return typeof(x) === 'string'; }; diff --git a/customize.dist/cryptpad-new-logo-colors-logoonly.png b/customize.dist/cryptpad-new-logo-colors-logoonly.png index 6ad292df0..722438d54 100644 Binary files a/customize.dist/cryptpad-new-logo-colors-logoonly.png and b/customize.dist/cryptpad-new-logo-colors-logoonly.png differ diff --git a/customize.dist/delta-words.js b/customize.dist/delta-words.js new file mode 100644 index 000000000..4eec0d0b3 --- /dev/null +++ b/customize.dist/delta-words.js @@ -0,0 +1,61 @@ +define([ + '/bower_components/chainpad/chainpad.dist.js', +], function (ChainPad) { + var Diff = ChainPad.Diff; + + var isSpace = function (S, i) { + return /^\s$/.test(S.charAt(i)); + }; + + var leadingBoundary = function (S, offset) { + if (/\s/.test(S.charAt(offset))) { return offset; } + while (offset > 0) { + offset--; + if (isSpace(S, offset)) { offset++; break; } + } + return offset; + }; + + var trailingBoundary = function (S, offset) { + if (isSpace(S, offset)) { return offset; } + while (offset < S.length && !/\s/.test(S.charAt(offset))) { + offset++; + } + return offset; + }; + + var opsToWords = function (previous, current) { + var output = []; + Diff.diff(previous, current).forEach(function (op) { + // ignore deleted sections... + var offset = op.offset; + var toInsert = op.toInsert; + + // given an operation, check whether it is a word fragment, + // if it is, expand it to its word boundaries + var first = current.slice(leadingBoundary(current, offset), offset); + var last = current.slice(offset + toInsert.length, trailingBoundary(current, offset + toInsert.length)); + + var result = first + toInsert + last; + // concat-in-place + Array.prototype.push.apply(output, result.split(/\s+/)); + }); + return output.filter(Boolean); + }; + + var runningDiff = function (getter, f, time) { + var last = getter(); + // first time through, send all the words :D + f(opsToWords("", last)); + return setInterval(function () { + var current = getter(); + + // find inserted words... + var words = opsToWords(last, current); + last = current; + f(words); + }, time); + }; + + return runningDiff; +}); diff --git a/customize.dist/faq.html b/customize.dist/faq.html new file mode 100644 index 000000000..d485505dd --- /dev/null +++ b/customize.dist/faq.html @@ -0,0 +1,17 @@ + + + + + CryptPad: Zero Knowledge, Collaborative Real Time Editing + + + + + + + + + diff --git a/customize.dist/features.html b/customize.dist/features.html new file mode 100644 index 000000000..31d4c99f8 --- /dev/null +++ b/customize.dist/features.html @@ -0,0 +1,16 @@ + + + + + CryptPad: Zero Knowledge, Collaborative Real Time Editing + + + + + + + + diff --git a/customize.dist/four-oh-four.js b/customize.dist/four-oh-four.js new file mode 100644 index 000000000..bcae8b4b2 --- /dev/null +++ b/customize.dist/four-oh-four.js @@ -0,0 +1,87 @@ +define([ + 'jquery', + '/api/config', + '/common/hyperscript.js', + '/common/outer/local-store.js', + '/customize/messages.js', + + 'less!/customize/src/less2/pages/page-404.less', +], function ($, Config, h, LocalStore, Messages) { + var urlArgs = Config.requireConf.urlArgs; + var img = h('img#cp-logo', { + src: '/customize/cryptpad-new-logo-colors-logoonly.png?' + urlArgs + }); + + var brand = h('h1#cp-brand', 'CryptPad'); + var message = h('h2#cp-scramble', Messages.four04_pageNotFound); + var title = h('h2#cp-title', "404"); + + var loggedIn = LocalStore.isLoggedIn(); + var link = h('a#cp-link', { + href: loggedIn? '/drive/': '/', + }, loggedIn? Messages.header_logoTitle: Messages.header_homeTitle); + + if (Config.httpUnsafeOrigin && Config.httpUnsafeOrigin !== window.location.origin + && window.parent) { + $(link).click(function (e) { + e.preventDefault(); + window.parent.location = Config.httpUnsafeOrigin + $(link).attr('href').slice(1); + }); + } + + var content = h('div#cp-main', [ + img, + brand, + title, + message, + link, + ]); + document.body.appendChild(content); + + var die = function (n) { return Math.floor(Math.random() * n); }; + var randomChar = function () { + return String.fromCharCode(die(94) + 34); + }; + var mutate = function (S, i, c) { + var A = S.split(""); + A[i] = c; + return A.join(""); + }; + + var take = function (A) { + var n = die(A.length); + var choice = A[n]; + A.splice(n, 1); + return choice; + }; + + var makeDecryptor = function (el, t, difficulty, cb) { + var Orig = el.innerText; + var options = []; + el.innerText = el.innerText.split("").map(function (c, i) { + Orig[i] = c; + options.push(i); + return randomChar(); + }).join(""); + + return function f () { + if (die(difficulty) === 0) { + var choice = take(options); + el.innerText = mutate(el.innerText, choice, Orig.charAt(choice)); + } else { // make a superficial change + el.innerText = mutate(el.innerText, + options[die(options.length)], + randomChar()); + } + setTimeout(options.length > 0? f: cb, t); + }; + }; + + makeDecryptor(brand, 70, 2, function () { })(); + makeDecryptor(title, 50, 14, function () { })(); + makeDecryptor(link, 20, 4, function () {})(); + makeDecryptor(message, 12, 3, function () { + console.log('done'); + })(); +}); + diff --git a/customize.dist/header.js b/customize.dist/header.js deleted file mode 100644 index ecef95f56..000000000 --- a/customize.dist/header.js +++ /dev/null @@ -1,54 +0,0 @@ -define([ - 'jquery', - '/customize/application_config.js', - '/common/cryptpad-common.js', - '/api/config', -], function ($, Config, Cryptpad, ApiConfig) { - - window.APP = { - Cryptpad: Cryptpad, - }; - - var Messages = Cryptpad.Messages; - - $(function () { - // Language selector - var $sel = $('#language-selector'); - Cryptpad.createLanguageSelector(undefined, $sel); - $sel.find('button').addClass('btn').addClass('btn-secondary'); - $sel.show(); - - var $upgrade = $('#upgrade'); - - var showUpgrade = function (text, feedback, url) { - if (ApiConfig.removeDonateButton) { return; } - if (localStorage.plan) { return; } - if (!text) { return; } - $upgrade.text(text).show(); - $upgrade.click(function () { - Cryptpad.feedback(feedback); - window.open(url,'_blank'); - }); - }; - - // User admin menu - var $userMenu = $('#user-menu'); - var userMenuCfg = { - $initBlock: $userMenu, - 'static': true - }; - var $userAdmin = Cryptpad.createUserAdminMenu(userMenuCfg); - $userAdmin.find('button').addClass('btn').addClass('btn-secondary'); - - $(window).click(function () { - $('.cryptpad-dropdown').hide(); - }); - - if (Cryptpad.isLoggedIn() && ApiConfig.allowSubscriptions) { - showUpgrade(Messages.upgradeAccount, "HOME_UPGRADE_ACCOUNT", Cryptpad.upgradeURL); - } else { - showUpgrade(Messages.supportCryptpad, "HOME_SUPPORT_CRYPTPAD", Cryptpad.donateURL); - } - }); -}); - diff --git a/customize.dist/images/AaronMacSween.jpg b/customize.dist/images/AaronMacSween.jpg index 2ee96ea6f..95afcda67 100644 Binary files a/customize.dist/images/AaronMacSween.jpg and b/customize.dist/images/AaronMacSween.jpg differ diff --git a/customize.dist/images/CalebJames.jpg b/customize.dist/images/CalebJames.jpg index 65d5a6cae..0e29dd116 100644 Binary files a/customize.dist/images/CalebJames.jpg and b/customize.dist/images/CalebJames.jpg differ diff --git a/customize.dist/images/Catalin.jpg b/customize.dist/images/Catalin.jpg index 109a5404b..508c0692e 100644 Binary files a/customize.dist/images/Catalin.jpg and b/customize.dist/images/Catalin.jpg differ diff --git a/customize.dist/images/LudovicDuboist.jpg b/customize.dist/images/LudovicDuboist.jpg index 6a9988942..09bf4afba 100644 Binary files a/customize.dist/images/LudovicDuboist.jpg and b/customize.dist/images/LudovicDuboist.jpg differ diff --git a/customize.dist/images/Pierre-new.jpg b/customize.dist/images/Pierre-new.jpg index 4f197aeb0..c8dafbe41 100644 Binary files a/customize.dist/images/Pierre-new.jpg and b/customize.dist/images/Pierre-new.jpg differ diff --git a/customize.dist/images/YannFlory.jpg b/customize.dist/images/YannFlory.jpg index 19d10ec56..9c358d1a6 100644 Binary files a/customize.dist/images/YannFlory.jpg and b/customize.dist/images/YannFlory.jpg differ diff --git a/customize.dist/images/aaron.jpg b/customize.dist/images/aaron.jpg index f746cf7e4..f443f4490 100644 Binary files a/customize.dist/images/aaron.jpg and b/customize.dist/images/aaron.jpg differ diff --git a/customize.dist/images/atest.jpg b/customize.dist/images/atest.jpg index 4ce7014fe..a6d6e0fa9 100644 Binary files a/customize.dist/images/atest.jpg and b/customize.dist/images/atest.jpg differ diff --git a/customize.dist/images/avatar.png b/customize.dist/images/avatar.png index 019c0e657..f6ee7d7d7 100644 Binary files a/customize.dist/images/avatar.png and b/customize.dist/images/avatar.png differ diff --git a/customize.dist/images/bkcontact.jpg b/customize.dist/images/bkcontact.jpg new file mode 100644 index 000000000..bae61ba1a Binary files /dev/null and b/customize.dist/images/bkcontact.jpg differ diff --git a/customize.dist/images/caleb.jpg b/customize.dist/images/caleb.jpg index 1903d87b9..415ff0a9b 100644 Binary files a/customize.dist/images/caleb.jpg and b/customize.dist/images/caleb.jpg differ diff --git a/customize.dist/images/drive_screenshot.png b/customize.dist/images/drive_screenshot.png index b1170561c..74f89c06d 100644 Binary files a/customize.dist/images/drive_screenshot.png and b/customize.dist/images/drive_screenshot.png differ diff --git a/customize.dist/images/email.svg b/customize.dist/images/email.svg new file mode 100644 index 000000000..e2e566e6e --- /dev/null +++ b/customize.dist/images/email.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + diff --git a/customize.dist/images/github.svg b/customize.dist/images/github.svg new file mode 100644 index 000000000..16cd68786 --- /dev/null +++ b/customize.dist/images/github.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/customize.dist/images/irc.svg b/customize.dist/images/irc.svg new file mode 100644 index 000000000..58a9e707d --- /dev/null +++ b/customize.dist/images/irc.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + diff --git a/customize.dist/images/issue.svg b/customize.dist/images/issue.svg new file mode 100644 index 000000000..27d5276c7 --- /dev/null +++ b/customize.dist/images/issue.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + diff --git a/customize.dist/images/logo_white.png b/customize.dist/images/logo_white.png index 81928ffb1..8219cc2b0 100644 Binary files a/customize.dist/images/logo_white.png and b/customize.dist/images/logo_white.png differ diff --git a/customize.dist/images/ludovic.jpg b/customize.dist/images/ludovic.jpg index 684e0692e..02842eee3 100644 Binary files a/customize.dist/images/ludovic.jpg and b/customize.dist/images/ludovic.jpg differ diff --git a/customize.dist/images/pad_screenshot.png b/customize.dist/images/pad_screenshot.png index cbc837f58..cbb155697 100644 Binary files a/customize.dist/images/pad_screenshot.png and b/customize.dist/images/pad_screenshot.png differ diff --git a/customize.dist/images/pierre.jpg b/customize.dist/images/pierre.jpg index fea27c787..79f514438 100644 Binary files a/customize.dist/images/pierre.jpg and b/customize.dist/images/pierre.jpg differ diff --git a/customize.dist/images/sayhi.svg b/customize.dist/images/sayhi.svg new file mode 100644 index 000000000..22c7463cb --- /dev/null +++ b/customize.dist/images/sayhi.svg @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/customize.dist/images/twitter.svg b/customize.dist/images/twitter.svg new file mode 100644 index 000000000..bed259b76 --- /dev/null +++ b/customize.dist/images/twitter.svg @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/customize.dist/images/yann.jpg b/customize.dist/images/yann.jpg index 6c5e144fd..ed381e86c 100644 Binary files a/customize.dist/images/yann.jpg and b/customize.dist/images/yann.jpg differ diff --git a/customize.dist/login.js b/customize.dist/login.js new file mode 100644 index 000000000..1f8cebab5 --- /dev/null +++ b/customize.dist/login.js @@ -0,0 +1,286 @@ +define([ + 'jquery', + '/bower_components/chainpad-listmap/chainpad-listmap.js', + '/bower_components/chainpad-crypto/crypto.js', + '/common/common-util.js', + '/common/outer/network-config.js', + '/customize/credential.js', + '/bower_components/chainpad/chainpad.dist.js', + '/common/common-realtime.js', + '/common/common-constants.js', + '/common/common-interface.js', + '/common/common-feedback.js', + '/common/outer/local-store.js', + '/customize/messages.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) { + var Exports = { + Cred: Cred, + }; + + var Nacl = window.nacl; + var allocateBytes = function (bytes) { + var dispense = Cred.dispenser(bytes); + + var opt = {}; + + // dispense 18 bytes of entropy for your encryption key + var encryptionSeed = dispense(18); + // 16 bytes for a deterministic channel key + var channelSeed = dispense(16); + // 32 bytes for a curve key + var curveSeed = dispense(32); + + var curvePair = Nacl.box.keyPair.fromSecretKey(new Uint8Array(curveSeed)); + opt.curvePrivate = Nacl.util.encodeBase64(curvePair.secretKey); + opt.curvePublic = Nacl.util.encodeBase64(curvePair.publicKey); + + // 32 more for a signing key + var edSeed = opt.edSeed = dispense(32); + + // derive a private key from the ed seed + var signingKeypair = Nacl.sign.keyPair.fromSeed(new Uint8Array(edSeed)); + + opt.edPrivate = Nacl.util.encodeBase64(signingKeypair.secretKey); + opt.edPublic = Nacl.util.encodeBase64(signingKeypair.publicKey); + + var keys = opt.keys = Crypto.createEditCryptor(null, encryptionSeed); + + // 24 bytes of base64 + keys.editKeyStr = keys.editKeyStr.replace(/\//g, '-'); + + // 32 bytes of hex + var channelHex = opt.channelHex = Util.uint8ArrayToHex(channelSeed); + + // should never happen + if (channelHex.length !== 32) { throw new Error('invalid channel id'); } + + opt.channel64 = Util.hexToBase64(channelHex); + + opt.userHash = '/1/edit/' + [opt.channel64, opt.keys.editKeyStr].join('/'); + + return opt; + }; + + var loadUserObject = function (opt, cb) { + var config = { + websocketURL: NetConfig.getWebsocketURL(), + channel: opt.channelHex, + data: {}, + validateKey: opt.keys.validateKey, // derived validation key + crypto: Crypto.createEncryptor(opt.keys), + logLevel: 1, + classic: true, + ChainPad: ChainPad, + owners: [opt.edPublic] + }; + + var rt = opt.rt = Listmap.create(config); + rt.proxy + .on('ready', function () { + setTimeout(function () { cb(void 0, rt); }); + }) + .on('disconnect', function (info) { + cb('E_DISCONNECT', info); + }); + }; + + var isProxyEmpty = function (proxy) { + return Object.keys(proxy).length === 0; + }; + + Exports.loginOrRegister = function (uname, passwd, isRegister, shouldImport, cb) { + if (typeof(cb) !== 'function') { return; } + + // Usernames are all lowercase. No going back on this one + uname = uname.toLowerCase(); + + // validate inputs + if (!Cred.isValidUsername(uname)) { return void cb('INVAL_USER'); } + if (!Cred.isValidPassword(passwd)) { return void cb('INVAL_PASS'); } + if (isRegister && !Cred.isLongEnoughPassword(passwd)) { + return void cb('PASS_TOO_SHORT'); + } + + Cred.deriveFromPassphrase(uname, passwd, 128, function (bytes) { + // results... + var res = { + register: isRegister, + }; + + // run scrypt to derive the user's keys + var opt = res.opt = allocateBytes(bytes); + + // use the derived key to generate an object + loadUserObject(opt, function (err, rt) { + if (err) { return void cb(err); } + + res.proxy = rt.proxy; + res.realtime = rt.realtime; + res.network = rt.network; + + // they're registering... + res.userHash = opt.userHash; + res.userName = uname; + + // export their signing key + res.edPrivate = opt.edPrivate; + res.edPublic = opt.edPublic; + + 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); + } + + // 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; + } + + // We have to call whenRealtimeSyncs asynchronously here because in the current + // version of listmap, onLocal calls `chainpad.contentUpdate(newValue)` + // asynchronously. + // The following setTimeout is here to make sure whenRealtimeSyncs is called after + // `contentUpdate` so that we have an update userDoc in chainpad. + setTimeout(function () { + Realtime.whenRealtimeSyncs(rt.realtime, function () { + LocalStore.login(res.userHash, res.userName, function () { + setTimeout(function () { cb(void 0, res); }); + }); + }); + }); + }); + }); + }; + Exports.redirect = function () { + if (sessionStorage.redirectTo) { + var h = sessionStorage.redirectTo; + var parser = document.createElement('a'); + parser.href = h; + if (parser.origin === window.location.origin) { + delete sessionStorage.redirectTo; + window.location.href = h; + return; + } + } + window.location.href = '/drive/'; + }; + + var hashing; + Exports.loginOrRegisterUI = function (uname, passwd, isRegister, shouldImport, testing, test) { + if (hashing) { return void console.log("hashing is already in progress"); } + hashing = true; + + var proceed = function (result) { + hashing = false; + if (test && typeof test === "function" && test()) { return; } + Realtime.whenRealtimeSyncs(result.realtime, function () { + Exports.redirect(); + }); + }; + + // setTimeout 100ms to remove the keyboard on mobile devices before the loading screen + // pops up + window.setTimeout(function () { + UI.addLoadingScreen({ + 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 () { + Exports.loginOrRegister(uname, passwd, isRegister, shouldImport, function (err, result) { + var proxy; + if (result) { proxy = result.proxy; } + + if (err) { + switch (err) { + case 'NO_SUCH_USER': + UI.removeLoadingScreen(function () { + UI.alert(Messages.login_noSuchUser, function () { + hashing = false; + }); + }); + break; + case 'INVAL_USER': + UI.removeLoadingScreen(function () { + UI.alert(Messages.login_invalUser, function () { + hashing = false; + }); + }); + break; + case 'INVAL_PASS': + UI.removeLoadingScreen(function () { + UI.alert(Messages.login_invalPass, function () { + hashing = false; + }); + }); + break; + case 'PASS_TOO_SHORT': + UI.removeLoadingScreen(function () { + var warning = Messages._getKey('register_passwordTooShort', [ + Cred.MINIMUM_PASSWORD_LENGTH + ]); + UI.alert(warning, function () { + hashing = false; + }); + }); + break; + case 'ALREADY_REGISTERED': + // logMeIn should reset registering = false + UI.removeLoadingScreen(function () { + UI.confirm(Messages.register_alreadyRegistered, function (yes) { + if (!yes) { return; } + proxy.login_name = uname; + + if (!proxy[Constants.displayNameKey]) { + proxy[Constants.displayNameKey] = uname; + } + LocalStore.eraseTempSessionValues(); + proceed(result); + }); + }); + break; + default: // UNHANDLED ERROR + hashing = false; + UI.errorLoadingScreen(Messages.login_unhandledError); + } + return; + } + + if (testing) { return void proceed(result); } + + proceed(result); + }); + }, 500); + }, 200); + }; + + return Exports; +}); diff --git a/customize.dist/main-favicon.png b/customize.dist/main-favicon.png index 354d88ece..212975b6d 100644 Binary files a/customize.dist/main-favicon.png and b/customize.dist/main-favicon.png differ diff --git a/customize.dist/main.js b/customize.dist/main.js index 4049ec659..008226211 100644 --- a/customize.dist/main.js +++ b/customize.dist/main.js @@ -1,30 +1,19 @@ define([ 'jquery', - '/customize/application_config.js', - '/common/cryptpad-common.js', - '/customize/header.js', -], function ($, Config, Cryptpad) { - - window.APP = { - Cryptpad: Cryptpad, - }; - - var Messages = Cryptpad.Messages; + '/common/outer/local-store.js', + '/customize/messages.js', +], function ($, LocalStore, Messages) { $(function () { var $main = $('#mainBlock'); - $(window).click(function () { - $('.cryptpad-dropdown').hide(); - }); - // main block is hidden in case javascript is disabled $main.removeClass('hidden'); // Make sure we don't display non-translated content (empty button) $main.find('#data').removeClass('hidden'); - if (Cryptpad.isLoggedIn()) { + if (LocalStore.isLoggedIn()) { if (window.location.pathname === '/') { window.location = '/drive/'; return; @@ -32,162 +21,9 @@ define([ $main.find('a[href="/drive/"] div.pad-button-text h4') .text(Messages.main_yourCryptDrive); - - var name = localStorage[Cryptpad.userNameKey] || sessionStorage[Cryptpad.userNameKey]; - var $loggedInBlock = $main.find('#loggedIn'); - var $hello = $loggedInBlock.find('#loggedInHello'); - var $logout = $loggedInBlock.find('#loggedInLogOut'); - - if (name) { - $hello.text(Messages._getKey('login_hello', [name])); - } else { - $hello.text(Messages.login_helloNoName); - } - $('#buttons').find('.nologin').hide(); - - $logout.click(function () { - Cryptpad.logout(function () { - window.location.reload(); - }); - }); - - $loggedInBlock.removeClass('hidden'); - } - else { - $main.find('#userForm').removeClass('hidden'); - $('#name').focus(); } - - var displayCreateButtons = function () { - var $parent = $('#buttons'); - var options = []; - var $container = $('
', {'class': 'dropdown-bar'}).appendTo($parent); - Config.availablePadTypes.forEach(function (el) { - if (el === 'drive') { return; } - if (!Cryptpad.isLoggedIn() && Config.registeredOnlyTypes && - Config.registeredOnlyTypes.indexOf(el) !== -1) { return; } - options.push({ - tag: 'a', - attributes: { - 'class': 'newdoc', - 'href': '/' + el + '/', - 'target': '_blank' - }, - content: Messages['button_new' + el] // Pretty name of the language value - }); - }); - var dropdownConfig = { - text: Messages.login_makeAPad, // Button initial text - options: options, // Entries displayed in the menu - container: $container - }; - var $block = Cryptpad.createDropdown(dropdownConfig); - $block.find('button').addClass('btn').addClass('btn-primary'); - $block.appendTo($parent); - }; - - /* Log in UI */ - var Login; - // deferred execution to avoid unnecessary asset loading - var loginReady = function (cb) { - if (Login) { - if (typeof(cb) === 'function') { cb(); } - return; - } - require([ - '/common/login.js', - ], function (_Login) { - Login = Login || _Login; - if (typeof(cb) === 'function') { cb(); } - }); - }; - - var $uname = $('#name').on('focus', loginReady); - - var $passwd = $('#password') - // background loading of login assets - .on('focus', loginReady) - // enter key while on password field clicks signup - .on('keyup', function (e) { - if (e.which !== 13) { return; } // enter - $('button.login').click(); - }); - - $('button.login').click(function () { - // setTimeout 100ms to remove the keyboard on mobile devices before the loading screen pops up - window.setTimeout(function () { - Cryptpad.addLoadingScreen(Messages.login_hashing); - // We need a setTimeout(cb, 0) otherwise the loading screen is only displayed after hashing the password - window.setTimeout(function () { - loginReady(function () { - var uname = $uname.val(); - var passwd = $passwd.val(); - Login.loginOrRegister(uname, passwd, false, function (err, result) { - if (!err) { - var proxy = result.proxy; - - // successful validation and user already exists - // set user hash in localStorage and redirect to drive - if (proxy && !proxy.login_name) { - proxy.login_name = result.userName; - } - - proxy.edPrivate = result.edPrivate; - proxy.edPublic = result.edPublic; - - Cryptpad.whenRealtimeSyncs(result.realtime, function () { - Cryptpad.login(result.userHash, result.userName, function () { - document.location.href = '/drive/'; - }); - }); - return; - } - switch (err) { - case 'NO_SUCH_USER': - Cryptpad.removeLoadingScreen(function () { - Cryptpad.alert(Messages.login_noSuchUser); - }); - break; - case 'INVAL_USER': - Cryptpad.removeLoadingScreen(function () { - Cryptpad.alert(Messages.login_invalUser); - }); - break; - case 'INVAL_PASS': - Cryptpad.removeLoadingScreen(function () { - Cryptpad.alert(Messages.login_invalPass); - }); - break; - default: // UNHANDLED ERROR - Cryptpad.errorLoadingScreen(Messages.login_unhandledError); - } - }); - }); - }, 0); - }, 100); + $(window).click(function () { + $('.cp-dropdown-content').hide(); }); - /* End Log in UI */ - - var addButtonHandlers = function () { - $('button.register').click(function () { - var username = $('#name').val(); - var passwd = $('#password').val(); - sessionStorage.login_user = username; - sessionStorage.login_pass = passwd; - document.location.href = '/register/'; - }); - $('button.gotodrive').click(function () { - document.location.href = '/drive/'; - }); - - $('button#loggedInLogout').click(function () { - $('#user-menu .logout').click(); - }); - }; - - displayCreateButtons(); - - addButtonHandlers(); - console.log("ready"); }); }); diff --git a/customize.dist/messages.js b/customize.dist/messages.js old mode 100644 new mode 100755 index 2f31a516e..bffb95654 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -1,6 +1,4 @@ (function () { -var LS_LANG = "CRYPTPAD_LANG"; - // add your module to this map so it gets used var map = { 'fr': 'Français', @@ -10,39 +8,34 @@ var map = { 'pt-br': 'Português do Brasil', 'ro': 'Română', 'zh': '繁體中文', + 'el': 'Ελληνικά', }; -var getStoredLanguage = function () { return localStorage.getItem(LS_LANG); }; -var getBrowserLanguage = function () { return navigator.language || navigator.userLanguage; }; -var getLanguage = function () { +var messages = {}; +var LS_LANG = "CRYPTPAD_LANG"; +var getStoredLanguage = function () { return localStorage && localStorage.getItem(LS_LANG); }; +var getBrowserLanguage = function () { return navigator.language || navigator.userLanguage || ''; }; +var getLanguage = messages._getLanguage = function () { + if (window.cryptpadLanguage) { return window.cryptpadLanguage; } if (getStoredLanguage()) { return getStoredLanguage(); } - var l = getBrowserLanguage() || ''; - if (Object.keys(map).indexOf(l) !== -1) { - return l; - } + var l = getBrowserLanguage(); // Edge returns 'fr-FR' --> transform it to 'fr' and check again - return Object.keys(map).indexOf(l.split('-')[0]) !== -1 ? l.split('-')[0] : 'en'; + return map[l] ? l : + (map[l.split('-')[0]] ? l.split('-')[0] : 'en'); }; var language = getLanguage(); -var req = ['jquery', '/customize/translations/messages.js']; +var req = ['/common/common-util.js', '/customize/translations/messages.js']; if (language && map[language]) { req.push('/customize/translations/messages.' + language + '.js'); } -define(req, function($, Default, Language) { - - var externalMap = JSON.parse(JSON.stringify(map)); - +define(req, function(Util, Default, Language) { map.en = 'English'; var defaultLanguage = 'en'; - var messages; - - if (!Language || !language || language === defaultLanguage || language === 'default' || !map[language]) { - messages = Default; - } - else { + Util.extend(messages, Default); + if (Language && language !== defaultLanguage) { // Add the translated keys to the returned object - messages = $.extend(true, {}, Default, Language); + Util.extend(messages, Language); } messages._languages = map; @@ -50,46 +43,71 @@ define(req, function($, Default, Language) { messages._checkTranslationState = function (cb) { if (typeof(cb) !== "function") { return; } - var missing = []; + var allMissing = []; var reqs = []; - Object.keys(externalMap).forEach(function (code) { + Object.keys(map).forEach(function (code) { + if (code === defaultLanguage) { return; } reqs.push('/customize/translations/messages.' + code + '.js'); }); require(reqs, function () { var langs = arguments; - Object.keys(externalMap).forEach(function (code, i) { + Object.keys(map).forEach(function (code, i) { + if (code === defaultLanguage) { return; } var translation = langs[i]; - var updated = {}; - Object.keys(Default).forEach(function (k) { - if (/^updated_[0-9]+_/.test(k) && !translation[k]) { - var key = k.split('_').slice(2).join('_'); - // Make sure we don't already have an update for that key. It should not happen - // but if it does, keep the latest version - if (updated[key]) { - var ek = updated[key]; - if (parseInt(ek.split('_')[1]) > parseInt(k.split('_')[1])) { return; } + var missing = []; + var checkInObject = function (ref, translated, path) { + var updated = {}; + Object.keys(ref).forEach(function (k) { + if (/^updated_[0-9]+_/.test(k) && !translated[k]) { + var key = k.split('_').slice(2).join('_'); + // Make sure we don't already have an update for that key. It should not happen + // but if it does, keep the latest version + if (updated[key]) { + var ek = updated[key]; + if (parseInt(ek.split('_')[1]) > parseInt(k.split('_')[1])) { return; } + } + updated[key] = k; } - updated[key] = k; - } - }); - Object.keys(Default).forEach(function (k) { - if (/^_/.test(k) || k === 'driveReadme') { return; } - if (!translation[k] || updated[k]) { - if (updated[k]) { - missing.push([code, k, 2, 'out.' + updated[k]]); - return; + }); + Object.keys(ref).forEach(function (k) { + if (/^_/.test(k) || k === 'driveReadme') { return; } + var nPath = path.slice(); + nPath.push(k); + if (!translated[k] || updated[k]) { + if (updated[k]) { + var uPath = path.slice(); + uPath.unshift('out'); + missing.push([code, nPath, 2, uPath.join('.') + '.' + updated[k]]); + return; + } + return void missing.push([code, nPath, 1]); } - missing.push([code, k, 1]); - } - }); - Object.keys(translation).forEach(function (k) { - if (/^_/.test(k) || k === 'driveReadme') { return; } - if (!Default[k]) { - missing.push([code, k, 0]); - } + if (typeof ref[k] !== typeof translated[k]) { + return void missing.push([code, nPath, 3]); + } + if (typeof ref[k] === "object" && !Array.isArray(ref[k])) { + checkInObject(ref[k], translated[k], nPath); + } + }); + Object.keys(translated).forEach(function (k) { + if (/^_/.test(k) || k === 'driveReadme') { return; } + var nPath = path.slice(); + nPath.push(k); + if (typeof ref[k] === "undefined") { + missing.push([code, nPath, 0]); + } + }); + }; + checkInObject(Default, translation, []); + // Push the removals at the end + missing.sort(function (a, b) { + if (a[2] === 0 && b[2] !== 0) { return 1; } + if (a[2] !== 0 && b[2] === 0) { return -1; } + return 0; }); + Array.prototype.push.apply(allMissing, missing); // Destructive concat }); - cb(missing); + cb(allMissing); }); }; @@ -99,66 +117,17 @@ define(req, function($, Default, Language) { var text = messages[key]; if (typeof(text) === 'string') { return text.replace(/\{(\d+)\}/g, function (str, p1) { - return argArray[p1] || null; + if (typeof(argArray[p1]) === 'string' || typeof(argArray[p1]) === "number") { + return argArray[p1]; + } + console.error("Only strings and numbers can be used in _getKey params!"); + return ''; }); } else { return text; } }; - // Add handler to the language selector - var storeLanguage = function (l) { - localStorage.setItem(LS_LANG, l); - }; - messages._initSelector = function ($select) { - var selector = $select || $('#language-selector'); - - if (!selector.length) { return; } - - // Select the current language in the list - selector.setValue(language || 'English'); - - // Listen for language change - $(selector).find('a.languageValue').on('click', function () { - var newLanguage = $(this).attr('data-value'); - storeLanguage(newLanguage); - if (newLanguage !== language) { - window.location.reload(); - } - }); - }; - - var translateText = function (i, e) { - var $el = $(e); - var key = $el.data('localization'); - $el.html(messages[key]); - }; - var translateAppend = function (i, e) { - var $el = $(e); - var key = $el.data('localization-append'); - $el.append(messages[key]); - }; - var translateTitle = function () { - var $el = $(this); - var key = $el.data('localization-title'); - $el.attr('title', messages[key]); - }; - var translatePlaceholder = function () { - var $el = $(this); - var key = $el.data('localization-placeholder'); - $el.attr('placeholder', messages[key]); - }; - messages._applyTranslation = function () { - $('[data-localization]').each(translateText); - $('[data-localization-append]').each(translateAppend); - $('[data-localization-title]').each(translateTitle); - $('[data-localization-placeholder]').each(translatePlaceholder); - $('#pad-iframe').contents().find('[data-localization]').each(translateText); - $('#pad-iframe').contents().find('[data-localization-append]').each(translateAppend); - $('#pad-iframe').contents().find('[data-localization-title]').each(translateTitle); - $('#pad-iframe').contents().find('[data-localization-placeholder]').each(translatePlaceholder); - }; - messages.driveReadme = '["BODY",{"class":"cke_editable cke_editable_themed cke_contents_ltr cke_show_borders","contenteditable":"true","spellcheck":"false","style":"color: rgb(51, 51, 51);"},' + '[["H1",{},["'+messages.readme_welcome+'"]],["P",{},["'+messages.readme_p1+'"]],["P",{},["'+messages.readme_p2+'"]],["HR",{},[]],["H2",{},["'+messages.readme_cat1+'",["BR",{},[]]]],["UL",{},[["LI",{},["'+messages._getKey("readme_cat1_l1", ['",["STRONG",{},["'+messages.newButton+'"]],"', '",["STRONG",{},["'+messages.type.pad+'"]],"'])+'"]],["LI",{},["'+messages.readme_cat1_l2+'"]],["LI",{},["'+messages._getKey("readme_cat1_l3", ['",["STRONG",{},["'+messages.fm_unsortedName+'"]],"'])+'",["UL",{},[["LI",{},["'+messages._getKey("readme_cat1_l3_l1", ['",["STRONG",{},["'+messages.fm_rootName+'"]],"'])+'"]],["LI",{},["'+messages.readme_cat1_l3_l2+'"]]]]]],["LI",{},["'+messages._getKey("readme_cat1_l4", ['",["STRONG",{},["'+messages.fm_trashName+'"]],"'])+'",["BR",{},[]]]]]],["P",{},[["BR",{},[]]]],["H2",{},["'+messages.readme_cat2+'",["BR",{},[]]]],["UL",{},[["LI",{},["'+messages._getKey("readme_cat2_l1", ['",["STRONG",{},["'+messages.shareButton+'"]],"', '",["STRONG",{},["'+messages.edit+'"]],"', '",["STRONG",{},["'+messages.view+'"]],"'])+'"]],["LI",{},["'+messages.readme_cat2_l2+'"]]]],["P",{},[["BR",{},[]]]],["H2",{},["'+messages.readme_cat3+'"]],["UL",{},[["LI",{},["'+messages.readme_cat3_l1+'"]],["LI",{},["'+messages.readme_cat3_l2+'"]],["LI",{},["'+messages.readme_cat3_l3+'",["BR",{},[]]]]]]],' + '{"metadata":{"defaultTitle":"' + messages.driveReadmeTitle + '","title":"' + messages.driveReadmeTitle + '"}}]'; diff --git a/customize.dist/pages.js b/customize.dist/pages.js index bc50ac22d..4cd33f038 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -1,11 +1,11 @@ define([ '/api/config', '/common/hyperscript.js', - '/common/cryptpad-common.js', - 'jquery' -], function (Config, h, Cryptpad, $) { + '/customize/messages.js', + 'jquery', + '/customize/application_config.js', +], function (Config, h, Msg, $, AppConfig) { var Pages = {}; - var Msg = Cryptpad.Messages; var urlArgs = Config.requireConf.urlArgs; var setHTML = function (e, html) { @@ -50,11 +50,6 @@ define([ h('p', Msg.main_footerText) ]) ], ''), - /* footerCol(null, [ - footLink('/about.html', 'about'), - footLink('/terms.html', 'terms'), - footLink('/privacy.html', 'privacy'), - ], 'CryptPad'),*/ footerCol('footer_applications', [ footLink('/drive/', 'main_drive'), footLink('/pad/', 'main_richText'), @@ -77,7 +72,7 @@ define([ ]) ]) ]), - h('div.cp-version-footer', "CryptPad v1.14.0 (Ouroboros)") + h('div.cp-version-footer', "CryptPad v1.29.0 (toSource)") ]); }; @@ -97,17 +92,34 @@ define([ ]); } - return h('nav.navbar.navbar-toggleable-md', - h('button.navbar-toggler.navbar-toggler-right', {'type':'button'}, {'data-toggle':'collapse'}, {'data-target':'#menuCollapse'}, {'aria-controls': 'menuCollapse'}, {'aria-expanded':'false'}, {'aria-label':'Toggle navigation'}, - [h('i.fa.fa-bars ') - ]), - h('a.navbar-brand', { href: '/index.html'}), - h('div.collapse.navbar-collapse.justify-content-end#menuCollapse', [ - h('a.nav-item.nav-link', { href: '/what-is-cryptpad.html'}, Msg.topbar_whatIsCryptpad), - h('a.nav-item.nav-link', { href: 'https://blog.cryptpad.fr/'}, Msg.blog), - h('a.nav-item.nav-link', { href: '/contact.html'}, Msg.contact), - h('a.nav-item.nav-link', { href: '/about.html'}, Msg.about), - ].concat(rightLinks)) + 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'}), + 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), + h('a.nav-item.nav-link', { href: 'https://blog.cryptpad.fr/'}, Msg.blog), + h('a.nav-item.nav-link', { href: '/features.html'}, Msg.features), + h('a.nav-item.nav-link', { href: '/privacy.html'}, Msg.privacy), + h('a.nav-item.nav-link', { href: '/contact.html'}, Msg.contact), + h('a.nav-item.nav-link', { href: '/about.html'}, Msg.about), + ].concat(rightLinks)) ); }; @@ -146,10 +158,10 @@ define([ ]), ]), h('div.row.align-items-center',[ - h('div.col-12.col-sm-12.col-md-12.col-lg-6.push-lg-6.cp-bio-avatar.cp-bio-avatar-right', [ + h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-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.pull-lg-6.cp-profile-det',[ + h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-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.

'), @@ -197,10 +209,10 @@ define([ ]), ]), h('div.row.align-items-center',[ - h('div.col-12.col-sm-12.col-md-12.col-lg-6.push-lg-6.cp-bio-avatar.cp-bio-avatar-right', [ + h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-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.pull-lg-6.cp-profile-det',[ + h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-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.

'), @@ -233,13 +245,128 @@ 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#cp-features-register', [ + h('a', { + href: '/register/' + }, h('button.cp-features-register-button', 'Register for free')) + ]) + ]), + infopageFooter() + ]); + }; + 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('p', Msg.policywhatweknow_p1), + setHTML(h('p'), Msg.policy_whatweknow_p1), h('h2', Msg.policy_howweuse), h('p', Msg.policy_howweuse_p1), @@ -262,6 +389,50 @@ define([ ]); }; + Pages['/faq.html'] = function () { + var categories = []; + var faq = Msg.faq; + Object.keys(faq).forEach(function (c) { + var questions = []; + Object.keys(faq[c]).forEach(function (q) { + var item = faq[c][q]; + if (typeof item !== "object") { return; } + var answer = h('p.cp-faq-questions-a'); + var hash = c + '-' + q; + var question = h('p.cp-faq-questions-q#' + hash); + $(question).click(function () { + if ($(answer).is(':visible')) { + return void $(answer).slideUp(); + } + $(answer).slideDown(); + }); + questions.push(h('div.cp-faq-questions-items', [ + setHTML(question, item.q), + setHTML(answer, item.a) + ])); + }); + categories.push(h('div.cp-faq-category', [ + h('h3', faq[c].title), + h('div.cp-faq-category-questions', questions) + ])); + }); + var hash = window.location.hash; + if (hash) { + $(categories).find(hash).click(); + } + 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', { + href: '/what-is-cryptpad.html' + }, Msg.faq_whatis)), + h('div.cp-faq-container', categories) + ]), + infopageFooter() + ]); + }; + Pages['/terms.html'] = function () { return h('div#cp-main', [ infopageTopbar(), @@ -280,9 +451,45 @@ define([ Pages['/contact.html'] = function () { return h('div#cp-main', [ infopageTopbar(), + h('div.container-fluid.cp-contdet', [ + h('row.col-12.col-sm-12', + h('h1.text-center', Msg.contact ) + ) + ]), h('div.container.cp-container', [ - h('center', h('h1', Msg.contact)), - setHTML(h('p'), Msg.main_about_p2) + h('div.row.cp-iconCont.align-items-center', [ + 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('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('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('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('a.card', {href : "mailto:research@xwiki.com"}, + h('div.card-body', + setHTML(h('p'), Msg.main_about_p25) + ) + ) + ), + ]), ]), infopageFooter(), ]); @@ -311,13 +518,13 @@ define([ ]), ]), h('div.row.align-items-center', [ - h('div.col-12.col-sm-12.col-md-12.col-lg-6.push-lg-6', [ + h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-2', [ setHTML(h('h2'), Msg.whatis_zeroknowledge), setHTML(h('p'), Msg.whatis_zeroknowledge_p1), setHTML(h('p'), Msg.whatis_zeroknowledge_p2), setHTML(h('p'), Msg.whatis_zeroknowledge_p3), ]), - h('div.col-12.col-sm-12.col-md-12.col-lg-6.pull-lg-6', [ + h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-1', [ h('img#zeroknowledge', { src: '/customize/images/zeroknowledge_small.png?' + urlArgs }), ]), ]), @@ -344,8 +551,61 @@ define([ ]); }; + var isAvailableType = function (x) { + if (!Array.isArray(AppConfig.availablePadTypes)) { return true; } + return AppConfig.availablePadTypes.some(function (type) { + return x.indexOf(type) > -1; + }); + }; + Pages['/'] = Pages['/index.html'] = function () { 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' ] + ].filter(function (x) { + return isAvailableType(x[1]); + }) + .map(function (x, i) { + var s = 'div.bs-callout.cp-callout-' + x[0]; + if (i > 2) { s += '.cp-more.cp-hidden'; } + return h('a', [ + { href: x[1] }, + h(s, [ + h('i.fa.' + x[3]), + h('div.pad-button-text', [ h('h4', x[2]) ]) + ]) + ]); + }); + + var more = icons.length < 4? undefined: h('div.bs-callout.cp-callout-more', [ + h('div.cp-callout-more-lessmsg.cp-hidden', [ + "see less ", + h('i.fa.fa-caret-up') + ]), + h('div.cp-callout-more-moremsg', [ + "see more ", + h('i.fa.fa-caret-down') + ]), + { + onclick: function () { + if (showingMore) { + $('.cp-more, .cp-callout-more-lessmsg').addClass('cp-hidden'); + $('.cp-callout-more-moremsg').removeClass('cp-hidden'); + } else { + $('.cp-more, .cp-callout-more-lessmsg').removeClass('cp-hidden'); + $('.cp-callout-more-moremsg').addClass('cp-hidden'); + } + showingMore = !showingMore; + } + } + ]); + return [ h('div#cp-main', [ infopageTopbar(), @@ -357,44 +617,8 @@ define([ h('p', Msg.main_catch_phrase) ]), h('div.col-12.col-sm-6', [ - [ - [ '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.cp-more.cp-hidden', '/poll/', Msg.main_pollPad, 'fa-calendar' ], - [ 'whiteboard.cp-more.cp-hidden', '/whiteboard/', Msg.main_whiteboardPad, 'fa-paint-brush' ], - [ 'recent.cp-more.cp-hidden', '/drive/', Msg.main_localPads, 'fa-hdd-o' ] - ].map(function (x) { - return h('a', [ - { href: x[1] }, - h('div.bs-callout.cp-callout-' + x[0], [ - h('i.fa.' + x[3]), - h('div.pad-button-text', [ h('h4', x[2]) ]) - ]) - ]); - }), - h('div.bs-callout.cp-callout-more', [ - h('div.cp-callout-more-lessmsg.cp-hidden', [ - "see less ", - h('i.fa.fa-caret-up') - ]), - h('div.cp-callout-more-moremsg', [ - "see more ", - h('i.fa.fa-caret-down') - ]), - { - onclick: function () { - if (showingMore) { - $('.cp-more, .cp-callout-more-lessmsg').addClass('cp-hidden'); - $('.cp-callout-more-moremsg').removeClass('cp-hidden'); - } else { - $('.cp-more, .cp-callout-more-lessmsg').removeClass('cp-hidden'); - $('.cp-callout-more-moremsg').addClass('cp-hidden'); - } - showingMore = !showingMore; - } - } - ]) + icons, + more ]) ]) ]), @@ -402,19 +626,25 @@ define([ ]; }; - var loadingScreen = function () { - return h('div#loading', - h('div.loadingContainer', [ - h('img.cryptofist', { + 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.spinnerContainer', + h('div.cp-loading-spinner-container', h('span.fa.fa-circle-o-notch.fa-spin.fa-4x.fa-fw')), h('p'), ]) ); }; + var hiddenLoader = function () { + var loader = loadingScreen(); + loader.style.display = 'none'; + return loader; + }; + Pages['/user/'] = Pages['/user/index.html'] = function () { return h('div#container'); }; @@ -437,6 +667,9 @@ define([ setHTML(h('p.register-explanation'), Msg.register_explanation) ]), h('div#userForm.form-group.hidden.col-md-6', [ + h('a', { + href: '/features.html' + }, Msg.register_whyRegister), h('input.form-control#username', { type: 'text', autocomplete: 'off', @@ -483,13 +716,14 @@ define([ h('div.row.cp-register-test',[ h('hr'), h('div.col-12', [ - setHTML(h('p.test-details'), Msg.register_testimonial), - h('a.cp-test-source.pull-right', { href : 'http://boingboing.net/2016/09/26/cryptpad-a-freeopen-end-to.html'}, Msg.register_testimonial_name) + setHTML(h('p.test-details'), " \"Tools like Etherpad and Google Docs [...] all share a weakness, which is that whomever owns the document server can see everything you're typing. Cryptpad is a free/open project that uses some of the ideas behind blockchain to implement a \"zero-knowledge\" version of a collaborative document editor, ensuring that only the people working on a document can see it.\" "), + h('a.cp-test-source.pull-right', { href : 'http://boingboing.net/2016/09/26/cryptpad-a-freeopen-end-to.html'}, "Cory Doctorow") ]) ]) ]), infopageFooter(), + hiddenLoader(), ])]; }; @@ -515,185 +749,147 @@ define([ 'name': 'password', 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),*/ + ]), h('div.extra', [ - h('button.login.first.btn', Msg.login_login), - h('button#register.btn.register.cp-login-register', Msg.login_register) + h('button.login.first.btn', Msg.login_login) ]) ]) ]), ]), infopageFooter(), + hiddenLoader(), ])]; }; var appToolbar = function () { - return h('div#toolbar.toolbar-container'); + return h('div#cp-toolbar.cp-toolbar-container'); }; Pages['/whiteboard/'] = Pages['/whiteboard/index.html'] = function () { return [ appToolbar(), - h('div#canvas-area', h('canvas#canvas', { - width: 600, - height: 600 - })), - h('div#controls', { + h('div#cp-app-whiteboard-canvas-area', + h('div#cp-app-whiteboard-container', + h('canvas#cp-app-whiteboard-canvas', { + width: 600, + height: 600 + }) + ) + ), + h('div#cp-app-whiteboard-controls', { style: { display: 'block', } }, [ - h('button#clear.btn.btn-danger', Msg.canvas_clear), ' ', - h('button#toggleDraw.btn.btn-secondary', Msg.canvas_disable), - h('button#delete.btn.btn-secondary', { + h('button#cp-app-whiteboard-clear.btn.btn-danger', Msg.canvas_clear), ' ', + h('button#cp-app-whiteboard-toggledraw.btn.btn-secondary', Msg.canvas_disable), + h('button#cp-app-whiteboard-delete.btn.btn-secondary', { style: { display: 'none', } }, Msg.canvas_delete), - h('div.range-group', [ + h('div.cp-app-whiteboard-range-group', [ h('label', { - 'for': 'width' + 'for': 'cp-app-whiteboard-width' }, Msg.canvas_width), - h('input#width', { + h('input#cp-app-whiteboard-width', { type: 'range', - value: "5", min: "1", max: "100" }), - h('span#width-val', '5px') + h('span#cp-app-whiteboard-width-val', '5px') ]), - h('div.range-group', [ + h('div.cp-app-whiteboard-range-group', [ h('label', { - 'for': 'opacity', + 'for': 'cp-app-whiteboard-opacity', }, Msg.canvas_opacity), - h('input#opacity', { + h('input#cp-app-whiteboard-opacity', { type: 'range', - value: "1", min: "0.1", max: "1", step: "0.1" }), - h('span#opacity-val', '100%') + h('span#cp-app-whiteboard-opacity-val', '100%') ]), - h('span.selected', [ + h('span.cp-app-whiteboard-selected.cp-app-whiteboard-unselectable', [ h('img', { title: Msg.canvas_currentBrush }) ]) ]), - setHTML(h('div#colors'), ' '), - loadingScreen(), - h('div#cursors', { + setHTML(h('div#cp-app-whiteboard-colors'), ' '), + h('div#cp-app-whiteboard-cursors', { style: { display: 'none', background: 'white', 'text-align': 'center', } }), - h('div#pickers'), + h('div#cp-app-whiteboard-pickers'), + h('div#cp-app-whiteboard-media-hidden') ]; }; Pages['/poll/'] = Pages['/poll/index.html'] = function () { return [ appToolbar(), - h('div#content', [ - h('div#poll', [ - h('div#howItWorks', [ - h('h1', 'CryptPoll'), - setHTML(h('h2'), Msg.poll_subtitle), - h('p', Msg.poll_p_save), - h('p', Msg.poll_p_encryption) - ]), - h('div.upper', [ - h('button#publish.btn.btn-success', { - style: { display: 'none' } - }, Msg.poll_publish_button), - h('button#admin.btn.btn-primary', { - style: { display: 'none' }, - title: Msg.poll_admin_button - }, Msg.poll_admin_button), - h('button#help.btn.btn-secondary', { - title: Msg.poll_show_help_button - }, Msg.poll_show_help_button) - ]), - h('div.realtime', [ + h('div#cp-app-poll-content', [ + h('div#cp-app-poll-form', [ + h('div.cp-app-poll-realtime', [ h('br'), - h('center', [ - h('textarea#description', { + h('div', [ + h('textarea#cp-app-poll-description', { rows: "5", cols: "50", + placeholder: Msg.poll_descriptionHint, disabled: true }), + h('div#cp-app-poll-description-published'), h('br') ]), - h('div#tableContainer', [ - h('div#tableScroll'), - h('button#create-user.btn.btn-secondary', { + h('div#cp-app-poll-table-container', [ + h('div#cp-app-poll-table-scroll', [h('table')]), + h('button#cp-app-poll-create-user.btn.btn-secondary', { title: Msg.poll_create_user - }, h('span.fa.fa-plus')), - h('button#create-option.btn.btn-secondary', { + }, Msg.poll_commit), + h('button#cp-app-poll-create-option.btn.btn-secondary', { title: Msg.poll_create_option }, h('span.fa.fa-plus')), - h('button#commit.btn.btn-secondary', { - title: Msg.poll_commit - }, h('span.fa.fa-check')) - ]) + ]), + h('div#cp-app-poll-comments', [ + h('h2#cp-app-poll-comments-add-title', Msg.poll_comment_add), + h('div#cp-app-poll-comments-add', [ + h('input.cp-app-poll-comments-add-name', { + type: 'text', + placeholder: Msg.anonymous + }), + h('textarea.cp-app-poll-comments-add-msg', { + placeholder: Msg.poll_comment_placeholder + }), + h('button.cp-app-poll-comments-add-submit.btn.btn-secondary', + Msg.poll_comment_submit), + h('button.cp-app-poll-comments-add-cancel.btn.btn-secondary', + Msg.cancel) + ]), + h('h2#cp-app-poll-comments-list-title', Msg.poll_comment_list), + h('div#cp-app-poll-comments-list') + ]), + h('div#cp-app-poll-nocomments', Msg.poll_comment_disabled) ]) ]) - ]), - loadingScreen() - ]; - }; - - Pages['/drive/'] = Pages['/drive/index.html'] = function () { - return loadingScreen(); - }; - - Pages['/file/'] = Pages['/file/index.html'] = function () { - return loadingScreen(); - }; - - Pages['/contacts/'] = Pages['/contacts/index.html'] = function () { - return loadingScreen(); - }; - - Pages['/pad/'] = Pages['/pad/index.html'] = function () { - return loadingScreen(); - }; - - Pages['/code/'] = Pages['/code/index.html'] = function () { - return loadingScreen(); - }; - - Pages['/slide/'] = Pages['/slide/index.html'] = function () { - return loadingScreen(); - }; - - Pages['/invite/'] = Pages['/invite/index.html'] = function () { - return loadingScreen(); - }; - - Pages['/settings/'] = Pages['/settings/index.html'] = function () { - return [ - h('div#toolbar'), - h('div#container'), - loadingScreen() - ]; - }; - - Pages['/profile/'] = Pages['/profile/index.html'] = function () { - return [ - h('div#toolbar'), - h('div#container'), - loadingScreen() - ]; - }; - - Pages['/todo/'] = Pages['/todo/index.html'] = function () { - return [ - h('div#toolbar'), - h('div#container'), - loadingScreen() + ]) ]; }; diff --git a/customize.dist/src/less/bar.less b/customize.dist/src/less/bar.less deleted file mode 100644 index 91744728b..000000000 --- a/customize.dist/src/less/bar.less +++ /dev/null @@ -1,92 +0,0 @@ -/* Bottom Bar */ -@import (once) "../less2/include/colortheme.less"; - -.top-bar, .bottom-bar { - position:fixed; - height:4%; - height: 2.5em; - - display: inline-block; - width: 100%; - background: @base; - border-top: 1px solid @cp-outline; - - a { - color: @cp-green; - text-decoration: none; - } - p { - margin: -1px; - font-family: @colortheme_font; - - font-size: 20px; - display:block; - margin-left: 10px; - padding-top:3px; - color: @fore; - } - img { - margin-right: 4px; - position: relative; - } - - .big { - @media screen and (max-width: @media-not-big) { - display: none; - } - @media screen and (min-width: @media-not-small) { - display: inline-block; - } - } - .small { - @media screen and (max-width: @media-not-big) { - display: inline-block; - } - @media screen and (min-width: @media-not-small) { - display: none; - } - img { - height: 1.25em; - } - } - -} -.bottom-bar { - bottom: 0px; - right: 0px; -} -.top-bar { - top: 0px; - right: 0px; -} - -.bottom-bar-left { - display:block; - float:left; - padding-left:10px; -} -.bottom-bar-left p { - float: right; -} -.bottom-bar-right { - display:block; - float:right; - padding-right:20px -} -.bottom-bar-center { - width: 20%; - position: absolute; - left: 40%; - text-align: center; -} -.bottom-bar-heart { - top: 2px; -} -.bottom-bar-xwiki { - top: 3px; -} -.bottom-bar-openpaas { - top: 3px; - max-width: 100px; -} - diff --git a/customize.dist/src/less/cryptpad.less b/customize.dist/src/less/cryptpad.less deleted file mode 100644 index 1c2c39f46..000000000 --- a/customize.dist/src/less/cryptpad.less +++ /dev/null @@ -1,689 +0,0 @@ -@import "./variables.less"; -@import "./mixins.less"; - -@import "../less2/include/alertify.less"; -@import "../less2/include/colortheme.less"; -@import "../less2/include/modal.less"; -@import "../less2/include/font.less"; -@import "./bar.less"; -@import "./loading.less"; -@import "./dropdown.less"; -@import "./topbar.less"; -@import "./footer.less"; - -@toolbar-green: #5cb85c; - -.font_open-sans(); - -.alertify_main(); - -html.cp, .cp body { - font-size: .875em; - background-color: @page-white; //@base; - color: @fore; - - height: 100%; -} -.fa { - cursor: default; // Fix for Edge displaying the text cursor on every icon -} - -.cp { - -// add font for tooltips -.tippy-popper { - font: 16px @colortheme_font; -} - -// override bootstrap colors -.btn-primary { - background-color: @cp-blue; - &:hover { - color: #fff; - background-color: #025aa5; - border-color: #01549b; - } -} - -body { - font-size: 1rem; - font-weight: 400; - line-height: 2rem; - margin: 0; -} - -a.github-corner > svg { - fill: @cp-blue; - color: @old-base; -} - -.lato { - font-family: lato, Helvetica, sans-serif; - font-size: 1.02em; -} - -.unselectable { - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -h1,h2,h3,h4,h5,h6 { - color: @fore; - - font-family: @colortheme_font; - -webkit-font-feature-settings: 'dlig' 1,'liga' 1,'lnum' 1,'kern' 1; - -moz-font-feature-settings: 'dlig' 1,'liga' 1,'lnum' 1,'kern' 1; - font-feature-settings: 'dlig' 1,'liga' 1,'lnum' 1,'kern' 1; - font-style: normal; - font-weight: 600; - margin-top: 0; -} - -h1 { - line-height: 3rem; - font-size: 2.05714rem; - margin-bottom: .21999rem; - padding-top: .78001rem; -} - -h2 { - font-size: 1.95312rem; - margin-bottom: .18358rem; - padding-top: .81642rem; -} - -h2,h3 { - line-height: 3rem; -} - -h3 { - font-size: 1.64571rem; - margin-bottom: .07599rem; - padding-top: .92401rem; -} - -h4 { - font-size: 1.5625rem; - margin-bottom: .54686rem; - padding-top: .45314rem; -} - -h5 { - font-size: 1.25rem; - margin-bottom: -.56251rem; - padding-top: .56251rem; -} - -h6 { - font-size: 1rem; - margin-bottom: -.65001rem; - padding-top: .65001rem; -} - -p { - a:not(.btn) { - cursor: pointer; - color: @cp-link; - - text-decoration: none; - - &:hover { - color: @cp-link-hover; - } - &:visited { - color: @cp-link-visited; - } - } -} -a.btn { - font-family: sans-serif; -} - -img { - height: auto; - max-width: 100%; -} - -p { - padding-top: .66001rem; - margin-top: 0; -} - -p,pre { - margin-bottom: 1.33999rem; -} - -p, pre, td, a, table, tr { - .lato; -} - -body.html { - display:flex; - flex-flow: column; -} - -// Main page -.page { - width: 100%; - margin-left: auto; - margin-right: auto; - background: @page-white; - padding: 10px 0;//@main-border-width; - position: relative; - - .info-container { - color: #121212; - width: 800px; - max-width: 100%; - margin: 0 auto; - &>div{ - padding: 10px; - width: 400px; - max-width: 100%; - position: relative; - display: inline-block; - vertical-align: middle; - &:not(.image) { - @media screen and (max-width: @media-not-big) { - width: 100%; - left: 0; - } - } - &.image { - width:300px; - text-align: center; - @media screen and (max-width: @media-not-big) { - display: none; - } - } - } - } - - &.first { - //margin-top: ~"min(calc(100vh - 150px), 650px)"; - @media screen and (max-width: @media-not-big) { - //margin-top: 0; - } - } - &.even { - //background: darken(@base, 1%); - } - &.category { - background: @category-bg; - } - - .app { - display: inline-block; - width: 300px; - vertical-align: middle; - margin: 0px 25px; - white-space: normal; - max-width: ~"calc(50% - 50px)"; - @media screen and (max-width: 500px) { - display: block; - max-width: 100%; - margin: 0 auto; - } - } - .app-container { - width: 1400px; - max-width: 100%; - margin: 0 auto; - } - .app-row { - display: flex; - justify-content: center; - flex-flow: row wrap; - max-width: 100%; - margin: 0 auto; - @media screen and (max-width: 1399px) { - display: flex; - } - img { - @media screen and (max-width: @media-not-big) { - display: none; - } - } - } - - .left { - //left: 10%; //@main-border-width; - } - .right { - left: 100px; //@main-border-width; - } - - h1, h2, h3, h4, h5, h6 { - padding: 0; - } - - @media screen and (max-width: @media-not-big) { - padding: 10px 5vh; - } - - p { - font-size: 18px; - //text-align: justify; - } -} - -.btn-default { - &:hover { - background-color: #d8d8d8; - } -} - -#main { - .mainOverlay { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: #000; - opacity: 0.35; - } -} -noscript { - #noscriptContainer { - color: black; - position: absolute; - top: @topbar-height; - left: 0; - bottom: 0; - right: 0; - z-index: 2; - #noscript { - width: 1000px; - display: flex; - align-items: center; - justify-content: center; - height: 100%; - margin-left: auto; - margin-right: auto; - position: relative; - font-size: 25px; - text-align: center; - color: @main-color; - } - } -} -#main { - background: @main-bg; - background-size: cover; - background-attachment: fixed; - background-position: center; - height: ~"calc(100vh - 115px)"; - min-height: 450px; - .hidden { - display: none !important; - } -} -#main_other { - padding: 0 @main-border-width; - background-color: @page-white; -} - -.category { - margin-top: 5px; -} - -#mainBlock { - flex: 1; -} - -#main, #main_other { - position: relative; - left: 0; - right: 0; - margin: auto; - z-index: 1; - - font-size: medium; - - #align-container { - display: flex; - align-items: center; - justify-content: center; - height: 100%; - margin-left: auto; - margin-right: auto; - width: 1000px; - max-width: 90%; - position: relative; - } - - #main-container { - display: inline-block; - } - - #userForm .extra { - p { - font-size: 28px; - padding: 15px; - text-align: center; - } - } - - #data { - p { - margin: 0; - padding: 0; - font-size: 28px; - line-height: 1.5em; - &.register-explanation { - font-size: 18px; - } - } - h1, h2 { - font-weight: normal; - font-size: 48px; - line-height: 1.2em; - color: @main-color; - padding: 0; - } - - h5 { - font-size: 1em; - color: @main-color; - } - width: 600px; - max-width: 60%; - color: @main-color; - padding: 0 15px; - box-sizing: border-box; - display: inline-block; - - #tryit { - margin-top: 20px; - margin-bottom: 5px; - } - } - - #loggedIn { - float: right; - color: @main-color; - display: inline-block; - width: 350px; - max-width: 35%; - text-align: center; - font-weight: bold; - button { - font-weight: bold; - cursor: pointer; - } - p { - margin: 20px; - padding: 0; - font-size: 20px; - line-height: 1.5em; - } - - } - - #userForm { - float: right; - display: inline-block; - width: 400px; - max-width: 40%; - padding: 10px; - box-sizing: border-box; - font-family: @colortheme_font; - color: @main-color; - - label { - margin-bottom: 0; - margin-left: 5px; - vertical-align: middle; - } - - button { - font-weight: bold; - width: 100%; - cursor: pointer; - &.half { - width: ~"calc(50% - 10px)"; - &:not(.first) { - float: right; - } - } - } - - p { - margin: 0; - padding: 0; - &.buttons { - margin-bottom: 10px; - .dropdown-bar { - button { - white-space: normal; - text-align: left; - .fa { - float: right; - } - } - a { - color: black; - &:hover, :visited { - color: black !important; - } - } - display: block; - } - } - &.separator { - margin: 5px 0 15px 0; - text-align: center; - font-weight: bold; - font-size: 1.1em; - } - a { - color: @main-color; - font-weight:bold; - font-size: 14px; - &:hover, :visited { - color: @main-color !important; - } - } - } - - .driveLink { - padding-left: 1rem; //Bootstrap padding in buttons - font-size: 1em; - } - - &> * { - margin-bottom: 10px; - } - } - @media screen and (max-width: @media-not-big) { - #align-container { - transform: initial; - position: relative; - display: block; - width: 90%; - left: 0; - } - #main-container { - position: relative; - transform: unset; - top:0; - - } - #data { - text-align: center; - } - #userForm, #loggedIn, #data { - transform: initial; - position: relative; - display: block; - width: 100%; - max-width: 100%; - margin: 10px 0; - box-sizing: border-box; - float: none; - } - #userForm, #loggedIn { - //border: 1px solid #888; - } - position: relative; - height: auto; - } - - .buttons { - margin-top: 15px; - } -} - -/* buttons */ - -.create, .action { - display: inline-block; - @thick: 2px; - border: 0; - background-color: @cp-darkblue; - color: @topbar-button-color; - - font-weight: bold; - font-size: large; - margin-right: 5px; - margin-left: 5px; - &:hover { - color: darken(@topbar-button-color, 20%); - } -} - -// currently only used in /user/ -.panel { - background-color: @dark-base; -} - -/* Tables - * Currently only used by /poll/ - */ - -// form things -.bottom-left { - .bottom-left; -} - -.top-left { - .top-left; -} - -.remove { - color: @cp-red; - cursor: pointer !important; -} -} - -/* Pin limit */ -.limit-container { - display: inline-flex; - flex-flow: column-reverse; - width: 100%; - margin-top: 20px; - .cryptpad-limit-bar { - display: inline-block; - max-width: 100%; - margin: 3px; - box-sizing: border-box; - border: 1px solid #999; - background: white; - position: relative; - text-align: center; - vertical-align: middle; - width: ~"calc(100% - 6px)"; - height: 25px; - line-height: 25px; - overflow: hidden; - .usage { - height: 100%; - display: inline-block; - background: blue; - position: absolute; - left: 0; - z-index:1; - &.normal { - background: @toolbar-green; - } - &.warning { - background: orange; - } - &.above { - background: red; - } - } - .usageText { - position: relative; - color: black; - text-shadow: 1px 0 2px white, 0 1px 2px white, -1px 0 2px white, 0 -1px 2px white; - z-index: 2; - font-size: @main-font-size; - font-weight: bold; - } - } - .upgrade { - padding: 0; - line-height: 25px; - height: 25px; - margin: 0 3px; - border-radius: 0; - } -} - -/* Upload status table */ -#uploadStatusContainer { - .modal_base(); - position: absolute; - left: 10vw; right: 10vw; - bottom: 10vh; - opacity: 0.9; - box-sizing: border-box; - z-index: 10000; - display: none; - #uploadStatus { - width: 80vw; - tr:nth-child(1) { - background-color: darken(@colortheme_modal-bg, 20%); - td { - text-align: center; - font-weight: bold; - padding: 0.25em; - } - } - @upload_pad_h: 0.25em; - @upload_pad_v: 0.5em; - - td { - padding: @upload_pad_h @upload_pad_v; - } - .upProgress { - width: 200px; - position: relative; - text-align: center; - box-sizing: border-box; - } - .progressContainer { - position: absolute; - width: 0px; - left: @upload_pad_v; - top: @upload_pad_h; bottom: @upload_pad_h; - background-color: rgba(0,0,255,0.3); - z-index: -1; - } - .upCancel { text-align: center; } - .fa.cancel { - color: rgb(255, 0, 115); - } - } -} - -// hack for our cross-origin iframe -#cors-store { - display: none; -} diff --git a/customize.dist/src/less/dropdown.less b/customize.dist/src/less/dropdown.less deleted file mode 100644 index 7f0b5e7b2..000000000 --- a/customize.dist/src/less/dropdown.less +++ /dev/null @@ -1,115 +0,0 @@ -@import (once) "../less2/include/colortheme.less"; - -/* The container
- needed to position the dropdown content */ -.dropdown-bar { - position: relative; - display: inline-block; - - .dropbtn { - } - - &:hover { - .dropbtn { - } - } - - .fa { - font-family: FontAwesome; - } - - button { - .fa-caret-down{ - margin-right: 0px; - margin-left: 5px; - } - * { - .unselectable(); - cursor: default; - } - } - - .dropdown-bar-content { - display: none; - position: absolute; - background-color: @dropdown-bg; - min-width: 250px; - box-shadow: 0px 1px 5px 0px rgba(0, 0, 0, 0.2); - z-index: 1000; - max-height: 300px; - overflow-y: auto; - font-family: @colortheme_font; - font-size: 16px; - line-height: 1em; - - &.left { - right: 0; - } - - &:hover { - display: block; - } - - a { - color: @dropdown-color; - padding: 5px 16px; - text-decoration: none; - display: flex; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - float: none; - text-align: left; - font: @dropdown-font; - line-height: 1em; - - - .fa { - width: 20px; - text-align: center; - margin-right: 5px !important; - } - - &:hover { - background-color: @dropdown-bg-hover; - color: @dropdown-color; - } - - &.active { - background-color: @dropdown-bg-active; - color: @dropdown-color; - } - } - - hr { - margin: 5px 0px; - height: 1px; - background: #bbb; - } - - p { - min-width: 160px; - padding: 5px; - margin: 0; - white-space: normal; - text-align: left; - color: black; - font-size: 14px; - * { - font-size: 14px; - } - h2 { - color: black; - font-weight: bold; - text-align: center; - background-color: #EEEEEE; - padding: 5px 0px; - margin: 5px 0px; - font-size: 16px; - white-space: normal; - } - } - } -} - diff --git a/customize.dist/src/less/footer.less b/customize.dist/src/less/footer.less deleted file mode 100644 index 97b5bda8c..000000000 --- a/customize.dist/src/less/footer.less +++ /dev/null @@ -1,30 +0,0 @@ -@import "./variables.less"; -@import (once) "../less2/include/colortheme.less"; - -footer { - background: @category-bg; - font-family: @colortheme_font; - padding-top: 1em; - font-size: 1.2em; - a { - color: @cp-link; - &:visited { - color: @cp-link-visited; - } - &:hover { - color: @cp-link-hover; - } - } - li.title { - font-size: 1.2em; - font-weight: bold; - } - div.version-footer { - background-color: @old-base; - color: @old-fore; - text-align: center; - width: 100%; - padding-top: 10px; - padding-bottom: 10px; - } -} diff --git a/customize.dist/src/less/loading.less b/customize.dist/src/less/loading.less deleted file mode 100644 index e7cb8060a..000000000 --- a/customize.dist/src/less/loading.less +++ /dev/null @@ -1,60 +0,0 @@ -@import "./variables.less"; -@import (once) "../less2/include/colortheme.less"; - -.cp #loading { - position: fixed; - z-index: 9999; - top: 0px; - bottom: 0px; - left: 0px; - right: 0px; - background: @bg-loading; - color: @color-loading; - text-align: center; - font-size: 1.5em; - .loadingContainer { - margin-top: 50vh; - transform: translateY(-50%); - } - .cryptofist { - margin-left: auto; - margin-right: auto; - height: 300px; - margin-bottom: 2em; - @media screen and (max-height: @media-short-screen) { - display: none; - } - } - .spinnerContainer { - position: relative; - height: 100px; - > div { - height: 100px; - } - } -} -.cp #loadingTip { - position: fixed; - z-index: 99999; - top: 80%; - left: 0; - right: 0; - text-align: center; - - transition: opacity 750ms; - transition-delay: 3000ms; - @media screen and (max-height: @media-medium-screen) { - display: none; - } - span { - background-color: @bg-loading; - color: @color-loading; - 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/less/mixins.less b/customize.dist/src/less/mixins.less deleted file mode 100644 index 68cca3cb4..000000000 --- a/customize.dist/src/less/mixins.less +++ /dev/null @@ -1,212 +0,0 @@ -@import "/customize/src/less/variables.less"; - -.fontface(@family, @src, @style: normal, @weight: 400, @fmt: 'truetype'){ - @font-face { - font-family: @family; - src: url(@src) format(@fmt); - font-weight: @weight; - font-style: @style; - } -} - -.transform(...) { - -webkit-transform: @arguments; - -moz-transform: @arguments; - -o-transform: @arguments; - -ms-transform: @arguments; - transform: @arguments; -} - -.translate(@x:0, @y:0) { - .transform(translate(@x, @y)); -} - -.bottom-left(@s: 5px) { border-bottom-left-radius: @s; } -.top-left(@s: 5px) { border-top-left-radius: @s; } - -.size (@n) { - // font-size: @n * 1vmin; - // line-height: @n * 1.1vmin; - font-size: @n * 10%; - // line-height: @n * 11%; - line-height: 110%; -} - -.two-part-gradient (@start, @end) { - background: -webkit-linear-gradient(@start, @end); /* For Safari 5.1 to 6.0 */ - background: -o-linear-gradient(@start, @end); /* For Opera 11.1 to 12.0 */ - background: -moz-linear-gradient(@start, @end); /* For Firefox 3.6 to 15 */ - background: linear-gradient(@start, @end); /* Standard syntax */ -} - -.placeholderColor (@color) { - &::-webkit-input-placeholder { /* WebKit, Blink, Edge */ - color: @color;; - } - &:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ - color: @color; - opacity: 1; - } - &::-moz-placeholder { /* Mozilla Firefox 19+ */ - color: @color; - opacity: 1; - } - &:-ms-input-placeholder { /* Internet Explorer 10-11 */ - color: @color; - } - &::-ms-input-placeholder { /* Microsoft Edge */ - color: @color; - } -} - -.avatar (@width) { - &.avatar { - overflow: hidden; - text-overflow: ellipsis; - font-size: 16px; - display: flex; - align-items: center; - &.clickable { - cursor: pointer; - &:hover { - background-color: rgba(0,0,0,0.3); - } - } - .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; - } - .default { - .unselectable(); - background: white; - color: black; - font-size: @width/1.2; - } - .right-col { - order: 10; - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - display: flex; - flex-flow: column; - .name { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - .friend { - padding: 0; - } - } - 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 - } - } - } -} - -.leftsideCategory { - .unselectable(); - padding: 5px 20px; - margin: 15px 0; - cursor: pointer; - height: @toolbar-line-height; - line-height: @toolbar-line-height - 10px; - .fa { - width: 25px; - } - &:hover { - background: rgba(0,0,0,0.05); - } - &.active { - background: white; - } -} - -.fileIcon { - li { - display: inline-block; - margin: 10px 10px; - width: 140px; - height: 140px; - text-align: center; - vertical-align: top; - overflow: hidden; - text-overflow: ellipsis; - padding-top: 5px; - padding-bottom: 5px; - - &:not(.selected):not(.selectedTmp) { - border: 1px solid #CCC; - } - .name { - width: 100%; - height: 48px; - margin: 8px 0; - display: inline-block; - //align-items: center; - //justify-content: center; - overflow: hidden; - //text-overflow: ellipsis; - word-wrap: break-word; - } - .truncated { - display: block; - position: absolute; - bottom: 0px; - left: 0; right: 0; - text-align: center; - } - img.icon { - height: 48px; - max-height: none; - max-width: none; - margin: 8px 0; - } - .fa { - display: block; - margin: auto; - font-size: 48px; - margin: 8px 0; - text-align: center; - &.listonly { - display: none; - } - } - } -} - -.sidebarButton { - button.btn { - background-color: @button-bg; - border-color: darken(@button-bg, 10%); - color: white; - &:hover { - background-color: darken(@button-bg, 10%); - } - &.btn-danger { - background-color: @button-red-bg; - border-color: darken(@button-red-bg, 10%); - color: white; - &:hover { - background-color: darken(@button-red-bg, 10%); - } - } - } -} diff --git a/customize.dist/src/less/sidebar-layout.less b/customize.dist/src/less/sidebar-layout.less deleted file mode 100644 index d16d9c019..000000000 --- a/customize.dist/src/less/sidebar-layout.less +++ /dev/null @@ -1,79 +0,0 @@ -@import '/customize/src/less/variables.less'; -@import '/customize/src/less/mixins.less'; - -@leftside-bg: #eee; -@leftside-color: #000; -@rightside-color: #000; -@description-color: #777; - -@button-width: 400px; - - -.cp { - input[type="text"] { - padding-left: 10px; - } - #container { - font-size: 16px; - display: flex; - flex: 1; - min-height: 0; - #leftSide { - color: @leftside-color; - width: 250px; - background: @leftside-bg; - display: flex; - flex-flow: column; - .categories { - flex: 1; - .category { - .leftsideCategory(); - } - } - } - #rightSide { - flex: 1; - padding: 5px 20px; - color: @rightside-color; - overflow: auto; - .element { - label:not(.noTitle), .label { - display: block; - font-weight: bold; - margin-bottom: 0; - } - .description { - display: block; - color: @description-color; - margin-bottom: 5px; - } - margin-bottom: 20px; - } - [type="text"], button { - vertical-align: middle; - height: 40px; - box-sizing: border-box; - } - .inputBlock { - display: inline-flex; - width: @button-width; - input { - flex: 1; - border-radius: 0.25em 0 0 0.25em; - border: 1px solid #adadad; - border-right: 0px; - } - button { - border-radius: 0 0.25em 0.25em 0; - //border: 1px solid #adadad; - border-left: 0px; - } - } - &>div { - margin: 10px 0; - } - .sidebarButton; - } - } -} - diff --git a/customize.dist/src/less/toolbar.less b/customize.dist/src/less/toolbar.less deleted file mode 100644 index 7e28c08d3..000000000 --- a/customize.dist/src/less/toolbar.less +++ /dev/null @@ -1,1096 +0,0 @@ -@import "./variables.less"; -@import "./mixins.less"; - -@import "./dropdown.less"; -@import (once) "../less2/include/colortheme.less"; - -.unselectable { - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.cke_reset_all * { - color: inherit; -} - - -// Classes used in common-interface.js -.padColor { color: @toolbar-pad-bg; } -.codeColor { color: @toolbar-code-bg; } -.slideColor { color: @toolbar-slide-bg; } -.pollColor { color: @toolbar-poll-bg; } -.fileColor { color: @toolbar-file-bg; } -.friendsColor { color: @toolbar-friends-bg; } -.whiteboardColor { color: @toolbar-whiteboard-bg; } -.driveColor { color: @toolbar-drive-bg; } -.settingsColor { color: @toolbar-settings-bg; } -.profileColor { color: @toolbar-settings-bg; } -.defaultColor { color: @toolbar-default-bg; } -.todoColor { color:@toolbar-todo-bg; } - -.toolbar-container { - display: flex; -} -#cke_editor1 .cke_inner { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - display: flex; - flex-flow: column; -} -.cke_toolbox_main { - display: inline-block; - margin-bottom: -3px; -} -#cke_1_contents { - flex: 1; - margin-top: -1px; - display: flex; - overflow: visible; - iframe { - min-height: 100%; - width: 100%; - } -} - -body .userlist-drawer { - font: @main-font-size @colortheme_font; - min-width: 175px; - width: 175px; - display: block; - overflow-y: auto; - overflow-x: hidden; - padding: 10px; - box-sizing: border-box; - .close { - position: absolute; - margin-top: -10px; - margin-left: 149px; - font-size: 15px; - opacity: 0.5; - cursor: pointer; - text-shadow: unset; - &:hover { - opacity: 1; - } - } - h2 { - color: inherit; - text-align: center; - padding: 5px 0px; - margin: 5px 0px; - font: inherit; - font-weight: bold; - white-space: normal; - line-height: auto; - } - text-align:baseline; - .viewer { - font-style: italic; - padding: 5px; - background: rgba(0,0,0,0.1); - margin: 2px 0; - } - - & > p { - font: @main-font-size @colortheme_font; - margin: 0; - padding: 0; - display: block; - } - - .userlist-others { - display: flex; - flex-flow: column; - margin: 10px 0; - margin-bottom: 20px; - &>span { - padding: 5px; - margin: 2px 0; - background: rgba(0,0,0,0.1); - .avatar(30px); - .default, media-tag { - margin-right: 5px; - } - } - } - .friend { - display: inline-block; - width: 20px; - } -} - -body { - .addToolbarColors (@color, @bg-color) { - .userlist-drawer { - background-color: @bgcolor; - color: @color; - h2 { - background-color: darken(@bgcolor, 10%); - color: @color; - } - .friend { - &:hover { - color: darken(@color, 15%); - } - } - } - .cryptpad-toolbar { - background-color: @bgcolor; - color: @color; - .userlist-drawer { - background-color: @bgcolor; - width: 150px; - position: absolute; - left: 0; - top: 96px; - bottom: 0; - .close { - color: @color; - } - } - .cryptpad-spinner, .cryptpad-state { - font-size: @main-font-size; - color: @color; - } - .cryptpad-limit { - text-shadow: -1px 0 @color, 0 1px @color, 1px 0 @color, 0 -1px @color; - } - .cryptpad-lag { - .bars { - span { - background: @color; - border: 1px solid darken(@bgcolor, 20%); - } - } - } - .cryptpad-toolbar-leftside, .cryptpad-toolbar-rightside { - background-color: lighten(@bgcolor, 8%); - button:hover, button.active { - background-color: @bgcolor; - } - } - .hoverable:hover { - .editable, .pencilIcon { - cursor: text; - border: 1px solid darken(@bgcolor, 15%); - background: darken(@bgcolor, 10%); - transition: all 0.15s; - color: @color; - } - .editable { - cursor: text; - } - } - .saveIcon { - border: 1px solid darken(@bgcolor, 15%); - background: darken(@bgcolor, 10%); - color: @color; - &:hover { - background: darken(@bgcolor, 5%); - } - } - input { - border: 1px solid darken(@bgcolor, 15%); - background: darken(@bgcolor, 10%); - color: @color; - } - .dropdown-bar-content.left a { - color: black; - } - /*.dropdown-bar-content { - background: darken(@bgcolor, 5%); - border: 1px solid @color; - color: @color; - a { - color: @color; - &.active { - background-color: darken(@bgcolor, 10%); - color: @color; - } - &:hover { - background-color: @bgcolor; - color: @color; - } - } - hr { - background-color: darken(@bgcolor, 15%); - } - p { - h2 { - background-color: darken(@bgcolor, 10%); - } - .accountData { - background-color: @bgcolor; - } - } - }*/ - } - } - - &.app-pad { - @bgcolor: @toolbar-pad-bg; - @color: @toolbar-pad-color; - .addToolbarColors(@color, @bgcolor); - } - &.app-code { - @bgcolor: @toolbar-code-bg; - @color: @toolbar-code-color; - .addToolbarColors(@color, @bgcolor); - } - &.app-slide { - @bgcolor: @toolbar-slide-bg; - @color: @toolbar-slide-color; - .addToolbarColors(@color, @bgcolor); - } - &.app-poll { - @bgcolor: @toolbar-poll-bg; - @color: @toolbar-poll-color; - .addToolbarColors(@color, @bgcolor); - } - &.app-whiteboard { - @bgcolor: @toolbar-whiteboard-bg; - @color: @toolbar-whiteboard-color; - .addToolbarColors(@color, @bgcolor); - } - &.app-drive { - @bgcolor: @toolbar-drive-bg; - @color: @toolbar-drive-color; - .addToolbarColors(@color, @bgcolor); - } - &.app-file { - @bgcolor: @toolbar-file-bg; - @color: @toolbar-file-color; - .addToolbarColors(@color, @bgcolor); - } - &.app-contacts { - @bgcolor: @toolbar-friends-bg; - @color: @toolbar-friends-color; - .addToolbarColors(@color, @bgcolor); - } - &.app-settings { - @bgcolor: @toolbar-settings-bg; - @color: @toolbar-settings-color; - .addToolbarColors(@color, @bgcolor); - } - &.app-profile { - @bgcolor: @toolbar-profile-bg; - @color: @toolbar-profile-color; - .addToolbarColors(@color, @bgcolor); - } - &.app-todo { - @bgcolor: @toolbar-todo-bg; - @color: @toolbar-todo-color; - .addToolbarColors(@color, @bgcolor); - } - -} - -body .cryptpad-toolbar { - * { - outline-width: 0; - &:focus { - outline-width: 0; - } - } - - @toolbar-green: #5cb85c; - - box-sizing: border-box; - padding: 0px; - - //background-color: #BBBBFF; - background-color: @toolbar-default-bg; - color: @toolbar-default-color; - - - - .fa { - font: normal normal normal 14px/1 FontAwesome; - font-family: FontAwesome; - } - - .unselectable; - - font: @toolbar-button-font; - width: 100%; - z-index: 9001; - - .dropdown-bar { - //height: 100%; - //display: inline-block; - button { - height: 100%; - border-radius: 0; - margin: 0; - background: transparent; - } - } - - .separator { - content: ''; - display: inline-block; - background: #888; - margin: 7px 4px; - height: 18px; - width: 1px; - vertical-align: top; - } - .separator:last-child { - display: none; - } - - button { - transition: all 0.15s; - .unselectable(); - &.hidden { - display: none; - } - .drawer { - display: none; - } - // Bootstrap 4 colors (btn-secondary) - border: 1px solid transparent; - color: inherit; - font: @toolbar-button-font; - * { - color: inherit; - font: @toolbar-button-font; - } - } - .cryptpad-toolbar-rightside button, .cryptpad-toolbar-leftside button { - background: transparent; - &:hover { - background-color: rgba(50,50,50,0.3); - } - } - - button.upgrade { - font-size: 14px; - vertical-align: top; - margin-left: 10px; - } - .cryptpad-limit { - box-sizing: border-box; - height: 26px; - width: 26px; - display: inline-block; - padding: 3px; - margin: 0px 3px 0 6px; - vertical-align: middle; - line-height: @toolbar-top-height; - span { - color: red; - cursor: pointer; - margin: auto; - font-size: 20px; - } - } - - .clag () { - background: transparent - } - - .cryptpad-state { - line-height: @toolbar-top-height; - padding: 0 5px; - color: inherit; - &:empty { - display: none; - } - } - .cryptpad-lag { - display: inline-block; - vertical-align: top; - box-sizing: content-box; - text-align: center; - line-height: @toolbar-top-height; - .disconnected { - display: none; - color: inherit; - width: 28px; - margin: 8px; - font-size: 28px; - text-align: center; - vertical-align: middle; - } - .bars { - margin: 8px; - height: 26px; - line-height: 26px; - display: inline-block; - span { - display: inline-block; - width: 6px; - margin: 0; - margin-right: 1px; - background: white; - vertical-align: bottom; - box-sizing: border-box; - border: 1px solid black; - visibility: hidden; - transition: background 1s, border 1s; - &:last-child { - margin-right: 0; - } - &.bar1 { height: 6px; } - &.bar2 { height: 12px; } - &.bar3 { height: 18px; } - &.bar4 { height: 24px; } - } - } - &.dc { - .disconnected { - display: inline; - } - .bars { - display: none - } - } - &.lag0 { - span span { - .clag(); - } - } - &.lag1 { - .bar2, .bar3, .bar4 { .clag(); } - span span { - visibility: visible; - } - } - &.lag2 { - .bar3, .bar4 { .clag(); } - span span { - visibility: visible; - } - } - &.lag3 { - .bar4 { .clag(); } - span span { - visibility: visible; - } - } - &.lag4 { - span span { - visibility: visible; - } - } - } - div { - white-space: normal; - &.cryptpad-back { - padding: 0; - font-weight: bold; - cursor: pointer; - color: #000; - } - } - - button, select, .rightside-element { - height: @toolbar-line-height; - box-sizing: border-box; - padding: 3px 10px; - margin: 0; - - } - - .rightside-button { - float: right; - cursor: pointer; - } - - .leftside-button { - cursor: pointer; - float: left; - } - - .rightside-element { - vertical-align: middle; - white-space: nowrap; - &.float { - float: right; - } - } - - select { - border: 0px; - margin-left: 5px; - margin-right: 5px; - padding-left: 5px; - border: 1px solid #A6A6A6; - border-bottom-color: #979797; - vertical-align: top; - box-sizing: content-box; - option { - height: 24px; - } - } - - .big { - @media screen and (max-width: @media-not-big) { - display: none; - } - @media screen and (min-width: @media-not-small) { - display: inline-block; - } - } - .small { - @media screen and (max-width: @media-not-big) { - display: inline-block; - } - @media screen and (min-width: @media-not-small) { - display: none; - } - } - - .med-big { - @media screen and (max-width: @media-medium-screen) { - display: none; - } - @media screen and (min-width: (@media-medium-screen + 1px)) { - display: inline-block; - } - } - .med-small { - @media screen and (max-width: @media-medium-screen) { - display: inline-block; - } - @media screen and (min-width: (@media-medium-screen + 1px)) { - display: none; - } - } - - .large { - @media screen and (max-width: @media-narrow-screen) { - display: none; - } - @media screen and (min-width: (@media-narrow-screen + 1px)) { - display: inline-block; - } - } - .narrow { - @media screen and (max-width: @media-narrow-screen) { - display: inline-block; - } - @media screen and (min-width: (@media-narrow-screen + 1px)) { - display: none; - } - } - - &.notitle { - .filler { - flex: 1; - } - } - &:not(.notitle) { - .cryptpad-toolbar-top { - @media screen and (max-width: @media-medium-screen) { - flex-wrap: wrap; - height: auto; - .cryptpad-state { - display: none; - } - .filler { - flex: 1; - } - .cryptpad-title { - flex: auto; - width: 100%; - order: 10; - height: @toolbar-line-height; - line-height: initial; - margin: 0; - .hoverable { - width: 100%; - } - .editable { - max-width: ~"calc(100vw - 26px)"; - display: inline-block; - overflow: hidden; - text-overflow: ellipsis; - font-size: @main-font-size; - height: @toolbar-line-height; - box-sizing: border-box; - line-height: 20px; - } - .pencilIcon, .saveIcon { - box-sizing: border-box; - height: @toolbar-line-height; - line-height: @main-font-size; - display: inline-block; - - .fa { - font-size: @main-font-size; - } - } - input { - height: @toolbar-line-height; - font-size: @main-font-size; - flex: 1; - max-width: none; - } - } - } - } - } -} - -.app-slide { - @media screen and (max-width: @media-medium-screen) { - .cryptpad-toolbar-leftside { - flex-flow: row wrap; - width: 175px; - height: auto; - .cryptpad-spinner { order: 0; } - } - .cryptpad-toolbar-rightside { - height: 2*@toolbar-line-height; - } - } - @media screen and (max-width: 320px) { - .cryptpad-toolbar-leftside { - flex-flow: row wrap; - width: 175px; - height: auto; - padding-top: @toolbar-line-height; - .cryptpad-spinner { order: 0; } - } - .cryptpad-toolbar-rightside { - height: auto; - } - } -} - -.cryptpad-toolbar-top { - display: flex; - flex-flow: row; - height: @toolbar-top-height; - position: relative; - .filler { - height: 100%; - display: inline-block; - order: 4; - //flex: 1; - } - .cryptpad-title { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - order: 3; - height: 100%; - display: inline-flex; - align-items: center; - line-height: @toolbar-top-height; - margin: 0 10px; - .title { - font-size: 25px; - vertical-align: middle; - line-height: 25px; - white-space: nowrap; - } - .pageTitle { - padding: 0 5px; - } - .pencilIcon, .saveIcon { - display: flex; - align-items: center; - font-size: 20px; - vertical-align: middle; - line-height: 20px; - .fa { - font-size: 20px; - } - } - .readOnly { - margin-left: 10px; - font-size: 25px; - font-style: italic; - white-space: nowrap; - } - .hoverable { - display: inline-flex; - overflow: hidden; - } - .pencilIcon { - cursor: pointer; - border: 1px solid transparent; - padding: 5px; - border-collapse: collapse; - span { - cursor: pointer; - } - } - .saveIcon { - cursor: pointer; - padding: 5px; - border-collapse: collapse; - span { - cursor: pointer; - } - } - .editable { - overflow: hidden; - text-overflow: ellipsis; - border: 1px solid transparent; - padding: 5px; - border-collapse: collapse; - } - input { - max-width: ~"calc(100% - 40px)"; - flex: 1; - font-size: 1.5em; - vertical-align: middle; - box-sizing: border-box; - cursor: auto; - width: 300px; - font-size: 20px; - padding: 5px 5px; - height: 40px; - } - } - .cryptpad-link, .cryptpad-new { - font-size: 48px; - line-height: 64px; - width: @toolbar-top-height; - height: @toolbar-top-height; - padding: 0; - box-sizing: border-box; - display: inline-block; - - color: white; - a { - color: white; - } - transition: all 0.15s; - } - .cryptpad-new { - background-color: rgba(0,0,0,0.2); - &:hover { - background-color: rgba(0,0,0,0.3); - } - text-align: center; - font-size: 32px; - margin-left: 10px; - &> button { - display: flex; - align-items: center; - justify-content: center; - width: 64px; - height: 64px !important; // Allows us to have a nice square outline when focused - font-size: 1em; - color: inherit; - height: auto; - padding: 0px; - margin: 0; - &::before { - width: 100%; - text-align: center; - padding-top: 4px; - } - &:hover { - background-color: initial; - border-color: transparent; - } - span { - vertical-align: top; - font-size: 1em; - text-decoration: none; - color: inherit; - } - } - } - .cryptpad-link { - display: inline-flex; - align-items: center; - justify-content: center; - cursor: pointer; - background-color: rgba(0,0,0,0.4); - &:hover { - background-color: rgba(0,0,0,0.5); - } - order: 1; - .fa { - margin: 0; - } - a.cryptpad-logo { - cursor: pointer; - display: inline-flex; - text-decoration: none; - height: auto; - padding: 10px; - - img { - cursor: pointer; - height: 100%; - width: 100%; - } - } - } - .cryptpad-user { - height: 100%; - display: inline-flex; - order: 5; - line-height: @toolbar-top-height; - color: white; - .cryptpad-upgrade { order: 1; } - .cryptpad-new { order: 2; } - .cryptpad-user-dropdown { order: 3; } - .cryptpad-backup { order: 4; } - &> * { - display: inline-block; - height: 100%; - vertical-align: top; - } - .cryptpad-upgrade { - height: @toolbar-line-height; - vertical-align: middle; - cursor: pointer; - } - .cryptpad-user-dropdown { - z-index: 10000; - //margin-left: 20px; - height: 64px; - width: 64px; - padding: 0px; - box-sizing: border-box; - text-align: center; - background-color: rgba(0,0,0,0.3); - transition: all 0.15s; - &:hover { - background-color: rgba(0,0,0,0.4); - } - .dropdown-bar-content { - margin: 0; - } - button { - display: flex; - justify-content: center; - align-items: center; - height: 64px; - width: 64px; - padding: 0; - span { - text-align: center; - width: 100%; - cursor: default; - font-size: 32px; - } - &.avatar { - .avatar(48px); - media-tag { - margin: 8px; - } - border: 0; - } - } - } - p.accountData { - &> span { - font-weight: bold; - span { - font-weight: normal; - } - } - } - .cryptpad-backup { - margin: 0; - border-radius: 0; - background: transparent; - &:hover { - background-color: rgba(0,0,0,0.2); - } - } - } -} -.cryptpad-toolbar-leftside { - //height: @toolbar-line-height; - &:empty { - height: 0; - } - float: left; - display: inline-flex; - align-items: center; - //margin-bottom: -1px; - .cryptpad-dropdown-users { - pre { - /* needed for ckeditor */ - white-space: pre; - margin: 5px 0px; - } - } - button { - margin: 0px; - border-radius: 0; - height: 100%; - } - .dropdown-bar-content { - margin-top: -1px; - } - - & > span { - height: @toolbar-line-height; - } - - #userButtons { order: 1; } - .shareButton { order: 2; } - .cryptpad-spinner { order: 3; } - - #userButtons button { - width: 125px; - text-align: center; - } - .shareButton button { - width: 50px; - text-align: center; - } -} -.cryptpad-toolbar-rightside { - min-height: @toolbar-line-height; - overflow: hidden; - &:empty { - min-height: 0; - height: 0; - } - text-align: right; - &> button { - height: 100%; - margin: 0; - border-radius: 0; - padding: 0 10px; - } - .drawer-content:empty ~ .drawer-button { - display: none; - } - .drawer-content { - box-shadow: 0px 1px 5px 0px rgba(0, 0, 0, 0.2); - position: absolute; - right:0px; - margin-top: @toolbar-line-height; - min-width: 50px; - background: @dropdown-bg; - display: flex; - flex-flow: column; - z-index:10000; - color: black; - .fa { - font-size: 17px; - } - &> span { - box-sizing: border-box; - min-width: 150px; - height: @toolbar-line-height; - border-radius: 0; - border: 0; - } - button { - padding: 5px 16px; - text-align: left; - margin: 0; - border-radius: 0; - border: 0; - width: 100%; - line-height: 1em; - .drawer { - margin-left: 10px; - display: inline; - vertical-align: top; - } - &:hover { - background-color: @dropdown-bg-hover !important; - color: @dropdown-color; - } - } - } -} -.cryptpad-toolbar-history { - display: none; - text-align: center; - .next { - display: inline-block; - vertical-align: middle; - margin: 20px; - } - .previous { - display: inline-block; - vertical-align: middle; - margin: 20px; - } - .goto { - display: inline-block; - vertical-align: middle; - text-align: center; - input { width: 75px; } - } - .gotoInput { - vertical-align: middle; - } - button { - color: inherit; - background-color: rgba(0,0,0,0.2); - &:hover { - background-color: rgba(0,0,0,0.4); - } - } - .closeHistory { - background: white; - color: black; - &:hover { - background-color: #e6e6e6; - } - } - .fa-spinner { - font-size: 66px; - } -} -.cke_toolbox .cryptpad-toolbar-history { - input.gotoInput { - padding: 3px 3px; - } -} -.cryptpad-spinner { - line-height: @toolbar-line-height; - padding: 0 20px; - &> span.fa { - height: 20px; - width: 20px; - //margin: 8px; - line-height: 20px; - font-size: 20px; - text-align: center; - } -} -.cryptpad-readonly { - margin-right: 5px; - font-weight: bold; - text-transform: uppercase; -} -.cryptpad-dropdown-share { - a { - .fa { - margin-right: 5px; - } - } -} - -.lag { - height: 15px !important; - width: 15px !important; - border-radius: 50%; - border: 1px solid @cp-outline; -} -.lag-green { - background-color: @cp-green; -} -.lag-red { - background-color: @cp-red; -} -.lag-orange { - background-color: @cp-orange; -} - diff --git a/customize.dist/src/less/topbar.less b/customize.dist/src/less/topbar.less deleted file mode 100644 index 05472a605..000000000 --- a/customize.dist/src/less/topbar.less +++ /dev/null @@ -1,96 +0,0 @@ -@import "./variables.less"; -@import (once) "../less2/include/colortheme.less"; - -#cryptpadTopBar { - background: @topbar-back; - position: relative; - top: 0; - left: 0; - right: 0; - height: @topbar-height; - color: @topbar-color; - font-family: @colortheme_font; - padding: 5px; - box-sizing: border-box; - font-size: 30px; - - border-bottom: 1px solid darken(@topbar-back, 15%); - - &> span { - vertical-align: middle; - display: inline-block; - height: 100%; - } - .cryptpad-logo { - height: 40px; - vertical-align: middle; - } - - .slogan { - font-size: 20px; - color: @topbar-color; - line-height: 40px; - } - - .gotoMain { - color: @topbar-color; - height: 40px; - line-height: 40px; - &:hover { - text-decoration: none; - color: @cp-purple; - } - } - - .right { - float: right; - font-size: 20px; - margin: 0px 10px; - line-height: 40px; - - .buttonSuccess { - // Bootstrap 4 colors - color: #fff; - background: @toolbar-green; - border-color: @toolbar-green; - &:hover { - color: #fff; - background: #449d44; - border: 1px solid #419641; - } - span { - color: #fff; - } - .large { - margin-left: 5px; - } - } - - button { - .buttonTitle { - .fa-user { - margin-right: 5px; - } - } - } - &.link a { - font-weight: 500; - font-size: 0.75em; - color: @cp-link; - - &:hover { - color: @cp-link-hover; - text-decoration: underline; - } - &:visited { - color: @cp-link-visited; - } - } - - &.link { - @media screen and (max-width: @media-not-big) { - display: none; - } - } - } -} diff --git a/customize.dist/src/less/variables.less b/customize.dist/src/less/variables.less deleted file mode 100644 index 8469ec518..000000000 --- a/customize.dist/src/less/variables.less +++ /dev/null @@ -1,118 +0,0 @@ -@import (once) '../less2/include/colortheme.less'; -@import (once) '../less2/include/browser.less'; - -@base: @colortheme_base; -@dark-base: darken(@base, 20%); -@less-dark-base: darken(@base, 10%); -@light-base: @colortheme_light-base; -@less-light-base: lighten(@base, 10%); -@fore: #555; -@old-base: @colortheme_old-base; -@old-fore: @colortheme_old-fore; - -@main-font-size: 16px; - -@cp-green: @colortheme_cp-green; -@cp-accent: lighten(@cp-green, 20%); - -@cp-red: @colortheme_cp-red; -@cp-outline: #444; - -@cp-orange: #FE9A2E; - -@cp-blue: #00CFC1; -@cp-blue: #00ADEE; -@cp-light-blue: #41b7d8; // lighten(@cp-blue, 20%); - -@cp-purple: #558; - -@page-white: #fafafa; - -// links -@cp-link: @cp-light-blue; -@cp-link-visited: @cp-light-blue; -@cp-link-hover: darken(@cp-light-blue, 10%); - - -@slide-default-bg: #000; - -@bg-loading: #222; -@color-loading: @old-fore; - -@media-not-big: @browser_media-not-big; -@media-not-small: @browser_media-not-small; - -@media-short-screen: @browser_media-short-screen; -@media-narrow-screen: @browser_media-narrow-screen; -@media-medium-screen: @browser_media-medium-screen; - - -// Dropdown - -@dropdown-font: @main-font-size @colortheme_font; -@dropdown-bg: #f9f9f9; -@dropdown-color: black; -@dropdown-bg-hover: #f1f1f1; -@dropdown-bg-active: #e8e8e8; - -// Toolbar - -@toolbar-button-font: @dropdown-font; - -@toolbar-pad-bg: @colortheme_pad-bg; -@toolbar-pad-color: @colortheme_pad-color; -@toolbar-slide-bg: @colortheme_slide-bg; -@toolbar-slide-color: @colortheme_slide-color; -@toolbar-code-bg: @colortheme_code-bg; -@toolbar-code-color: @colortheme_code-color; -@toolbar-poll-bg: @colortheme_poll-bg; -@toolbar-poll-color: @colortheme_poll-color; -@toolbar-whiteboard-bg: @colortheme_whiteboard-bg; -@toolbar-whiteboard-color: @colortheme_whiteboard-color; -@toolbar-drive-bg: @colortheme_drive-bg; -@toolbar-drive-color: @colortheme_drive-color; -@toolbar-file-bg: @colortheme_file-bg; -@toolbar-file-color: @colortheme_file-color; -@toolbar-friends-bg: @colortheme_friends-bg; -@toolbar-friends-color: @colortheme_friends-color; -@toolbar-default-bg: @colortheme_default-bg; -@toolbar-default-color: @colortheme_default-color; -@toolbar-settings-bg: @colortheme_settings-bg; -@toolbar-settings-color: @colortheme_settings-color; -@toolbar-profile-bg: @colortheme_profile-bg; -@toolbar-profile-color: @colortheme_profile-color; -@toolbar-todo-bg: #7bccd1; -@toolbar-todo-color: #000; - -@topbar-back: #fff; -@topbar-color: #000; -@topbar-button-bg: #2E9AFE; -@topbar-button-color: #fff; -@topbar-height: 50px; - -@toolbar-top-height: 64px; - -@main-border-width: 15vw; -@cp-darkblue: #3333ff; -@cp-accent2: darken(@cp-darkblue, 20%); - -@main-block-bg: rgba(200, 200, 200, 0.3); -@main-color: #fff; -@main-bg: url('/customize/bg4.jpg') no-repeat center center; - -@category-bg: #f4f4f4; - -@button-bg: #3066e5; -@button-alt-bg: #fff; -@button-red-bg: #e54e4e; - -.unselectable () { - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -@toolbar-line-height: 32px; diff --git a/customize.dist/src/less2/404.less b/customize.dist/src/less2/404.less new file mode 100644 index 000000000..33a852cd0 --- /dev/null +++ b/customize.dist/src/less2/404.less @@ -0,0 +1,40 @@ +@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 ff4a2abec..b845a5d6f 100644 --- a/customize.dist/src/less2/include/alertify.less +++ b/customize.dist/src/less2/include/alertify.less @@ -1,7 +1,9 @@ -@import (once) "./colortheme.less"; +@import (once) "./colortheme-all.less"; @import (once) "./browser.less"; +@import (once) "./variables.less"; .alertify_main () { + @max-z-index: 2147483647; @alertify-fore: @colortheme_modal-fg; @alertify-base: @colortheme_modal-bg; @@ -19,13 +21,14 @@ @alertify-input-bg: @colortheme_modal-input; @alertify-input-fg: @colortheme_modal-fg; - @alertify_padding-base: @colortheme_modal-padding; - @alertify_box-shadow: @colortheme_modal-shadow; + @alertify_padding-base: @variables_padding; + @alertify_box-shadow: @variables_shadow; // Logs to show that something has happened // These show only once .alertify-logs { + z-index: 10000; // alertify logs @media print { visibility: hidden; } @@ -61,7 +64,16 @@ bottom: 0; width: 100%; height: 100%; - z-index: 99999; + z-index: 100000; // alertify container + &.forefront { + z-index: @max-z-index; // alertify max forefront + } + + .message { + h1, h2, h3, h4, h5, h6 { + color: @alertify-fg; + } + } h1, h2, h3 { margin-top: 0; @@ -83,6 +95,20 @@ .dialog { padding: 12px; +/* + div.tokenfield { + .token { + //border: 1px solid red; + //color: red; + } + + color: @colortheme_light-base; + background-color: @alertify-dialog-bg; + + input[id$="tokenfield"][type="text"].token-input { + background-color: @alertify-dialog-bg !important; + } + }*/ } .dialog, .alert { @@ -99,6 +125,8 @@ width: 100%; } } + display: flex; + flex-flow: column; } width: 100%; @@ -106,10 +134,12 @@ position: relative; top: 50%; transform: translateY(-50%); + max-height: 100%; + display: flex; > * { width: 100%; - min-width: 300px; + min-width: 260px; max-width: 500px; margin: 0 auto; text-align: left; @@ -122,26 +152,73 @@ padding: @alertify_padding-base; margin-bottom: @alertify_padding-base; margin: 0; + overflow: auto; + } + .alertify-tabs { + max-height: 100%; + display: flex; + flex-flow: column; + .alertify-tabs-titles { + height: 30px; + display: flex; + border-bottom: 1px solid @alertify-fore; + margin-bottom: 20px; + box-sizing: content-box; + span { + font-size: 20px; + height: 30px; + line-height: 30px; + box-sizing: border-box; + padding: 0 15px; + border-left: 1px solid lighten(@alertify-base, 10%); + border-right: 1px solid lighten(@alertify-base, 10%); + cursor: pointer; + } + span.alertify-tabs-active { + background-color: @alertify-fore; + border-left: 1px solid @alertify-fore; + border-right: 1px solid @alertify-fore; + color: @alertify-base; + font-weight: bold; + cursor: default; + } + } + .alertify-tabs-contents { + flex: 1 1 auto; + min-height: 0; + & > div { + max-height: 100%; + display: none; + overflow: auto; + } + & > div.alertify-tabs-content-active { + display: block; + } + } } input:not(.form-control), textarea { background-color: @alertify-input-bg; - color: @alertify-input-fg; + color: @alertify-input-fg; border: 0px; margin-bottom: 15px; width: 100%; font-size: 100%; padding: @alertify_padding-base; - &:focus { - //outline-offset: -2px; - } } - input[type="checkbox"] { + input[type="checkbox"], input[type="radio"] { + width: auto; padding: 0; - margin: 0; margin-right: 0.5em; + margin-top: 1px; + margin-bottom: 5px; + vertical-align: middle; + & + label { + margin-bottom: 0; + margin-right: 2em; + } } nav { @@ -154,7 +231,6 @@ box-sizing: border-box; position: relative; outline: 0; - border: 0; display: inline-block; align-items: center; padding: 0 6px; @@ -192,6 +268,14 @@ } } + &.primary { + background-color: @colortheme_alertify-primary; + color: @colortheme_alertify-primary-text; + &:hover, &:active { + background-color: darken(@colortheme_alertify-primary, 10%); + } + } + &:hover, &:active { background-color: @alertify-btn-bg-hover; } @@ -200,7 +284,7 @@ border: 1px dotted @alertify-base; } &::-moz-focus-inner { - border:0; + border: 0; } } @@ -214,11 +298,11 @@ .alertify-logs { position: fixed; - z-index: 99999; + z-index: 99999; // alertify logs &.bottom, &:not(.top) { bottom: 16px; - // Bottom left placement. Default. Use for transitions. + /* // Bottom left placement. Default. Use for transitions. &.left, &:not(.right) { > * { @@ -229,7 +313,7 @@ > * { } - } + }*/ } // All left positions. @@ -267,7 +351,7 @@ &.top { top: 0; - // Top left placement, use for transitions. + /* // Top left placement, use for transitions. &.left, &:not(.right) { > * { @@ -278,7 +362,7 @@ > * { } - } + }*/ } > * { @@ -306,6 +390,5 @@ pointer-events: auto; } } - } } diff --git a/customize.dist/src/less2/include/app-noscroll.less b/customize.dist/src/less2/include/app-noscroll.less new file mode 100644 index 000000000..c66019d61 --- /dev/null +++ b/customize.dist/src/less2/include/app-noscroll.less @@ -0,0 +1,20 @@ +// html +.app-noscroll_main () { + height: 100%; + width: 100%; + padding: 0px; + margin: 0px; + overflow: hidden; + box-sizing: border-box; + position: relative; + body { + height: 100%; + width: 100%; + padding: 0px; + margin: 0px; + overflow: hidden; + box-sizing: border-box; + position: relative; + } +} + diff --git a/customize.dist/src/less2/include/app-print.less b/customize.dist/src/less2/include/app-print.less new file mode 100644 index 000000000..957170044 --- /dev/null +++ b/customize.dist/src/less2/include/app-print.less @@ -0,0 +1,46 @@ +.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 { + display: block; + .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; + } + } + } +} diff --git a/customize.dist/src/less2/include/avatar.less b/customize.dist/src/less2/include/avatar.less new file mode 100644 index 000000000..b935db786 --- /dev/null +++ b/customize.dist/src/less2/include/avatar.less @@ -0,0 +1,40 @@ +@import (once) "./tools.less"; + +.avatar_main (@width) { + &.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 + } + } + } +} + diff --git a/customize.dist/src/less2/include/checkmark.less b/customize.dist/src/less2/include/checkmark.less new file mode 100644 index 000000000..30c6e7d4a --- /dev/null +++ b/customize.dist/src/less2/include/checkmark.less @@ -0,0 +1,67 @@ +@import (once) "./colortheme-all.less"; + +.checkmark_main(@size) { + + @width: round(@size / 8); + @dim1: round(@size / 3); + @dim2: round(2 * @size / 3); + @top: round(@size / 12); + // Text + .cp-checkmark { + 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-checkmark-secondary { + .cp-checkmark-mark { + &:after { + border-color: @colortheme_checkmark-col2; + } + } + input { + &:checked ~ .cp-checkmark-mark { + background-color: @colortheme_checkmark-back2; + } + } + } + &:hover .cp-checkmark-mark { + background-color: @colortheme_checkmark-back0-active; + } + + input { + display: none; + &:checked ~ .cp-checkmark-mark { + background-color: @colortheme_checkmark-back1; + &:after { + display: block; + } + } + } + + .cp-checkmark-mark { + margin-right: 10px; + position: relative; + height: @size; + width: @size; + background-color: @colortheme_checkmark-back0; + display: flex; + justify-content: center; + &:after { + content: ""; + display: none; + margin-top: @top; + width: @dim1; + height: @dim2; + transform: rotate(45deg); + border: solid @colortheme_checkmark-col1; + border-width: 0 @width @width 0; + } + } + + } +} diff --git a/customize.dist/src/less2/include/ckeditor-fix.less b/customize.dist/src/less2/include/ckeditor-fix.less new file mode 100644 index 000000000..98672bd86 --- /dev/null +++ b/customize.dist/src/less2/include/ckeditor-fix.less @@ -0,0 +1,15 @@ +.ckeditor_fix () { + + .cke_reset_all * { + color: inherit; + } + .cke_toolbox_main { + display: inline-block; + } + .cke_toolbox .cp-toolbar-history { + input.gotoInput { // TODO + padding: 3px 3px; + } + } + +} diff --git a/customize.dist/src/less2/include/colortheme-all.less b/customize.dist/src/less2/include/colortheme-all.less new file mode 100644 index 000000000..4544da76e --- /dev/null +++ b/customize.dist/src/less2/include/colortheme-all.less @@ -0,0 +1,6 @@ +// Don't override/edit this file directly, you can create +// 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"; diff --git a/customize.dist/src/less2/include/colortheme.less b/customize.dist/src/less2/include/colortheme.less index 4ecafb27a..1bdb69291 100644 --- a/customize.dist/src/less2/include/colortheme.less +++ b/customize.dist/src/less2/include/colortheme.less @@ -1,4 +1,6 @@ @colortheme_font: 'Open Sans', 'Helvetica Neue', sans-serif; +@colortheme_app-font-size: 16px; +@colortheme_app-font: @colortheme_app-font-size @colortheme_font; @colortheme_link-color: #0275D8; @colortheme_link-color-visited: #005999; @@ -18,53 +20,99 @@ @colortheme_modal-link: #eee; @colortheme_modal-link-visited: lighten(@colortheme_modal-link, 10%); @colortheme_modal-dim: rgba(0, 0, 0, 0.4); -@colortheme_modal-padding: 12px; -@colortheme_modal-shadow: 0 8px 32px 0 rgba(0,0,0,.4); + +@colortheme_loading-bg: #222; +@colortheme_loading-color: @colortheme_old-fore; @colortheme_modal-input: #111; @colortheme_alertify-red: #E55236; @colortheme_alertify-green: #77C825; +@colortheme_alertify-primary: #fff; +@colortheme_alertify-primary-text: #000; @colortheme_notification-log: rgba(0, 0, 0, 0.8); @colortheme_notification-warn: rgba(205, 37, 50, 0.8); -// Apps +@colortheme_dropdown-bg: #f9f9f9; +@colortheme_dropdown-color: black; +@colortheme_dropdown-bg-hover: #f1f1f1; +@colortheme_dropdown-bg-active: #e8e8e8; + +// Apps, these colors are used for customizing the toolbar for the apps. @colortheme_pad-bg: #1c4fa0; @colortheme_pad-color: #fff; +@colortheme_pad-toolbar-bg: #c1e7ff; +@colortheme_pad-warn: #ffae00; @colortheme_slide-bg: #e57614; @colortheme_slide-color: #fff; +@colortheme_slide-warn: #005868; @colortheme_code-bg: #ffae00; @colortheme_code-color: #000; +@colortheme_code-warn: #9A37F7; @colortheme_poll-bg: #006304; @colortheme_poll-color: #fff; +@colortheme_poll-help-bg: #bbffbb; +@colortheme_poll-th-bg: #005bef; +@colortheme_poll-th-fg: #fff; +@colortheme_poll-warn: #ffade3; @colortheme_whiteboard-bg: #800080; @colortheme_whiteboard-color: #fff; +@colortheme_whiteboard-warn: #ffae00; @colortheme_drive-bg: #0087ff; @colortheme_drive-color: #fff; +@colortheme_drive-warn: #cd2532; @colortheme_file-bg: #cd2532; @colortheme_file-color: #fff; +@colortheme_file-warn: #ffae00; -@colortheme_friends-bg: #607B8D; +@colortheme_friends-bg: #607b8d; @colortheme_friends-color: #fff; +@colortheme_friends-warn: #cd2532; @colortheme_default-bg: #ddd; @colortheme_default-color: #000; +@colortheme_default-warn: #cd2532; @colortheme_settings-bg: #0087ff; @colortheme_settings-color: #fff; +@colortheme_settings-warn: #cd2532; @colortheme_profile-bg: #0087ff; @colortheme_profile-color: #fff; - -@cryptpad_color_blue: #4591C4; +@colortheme_profile-warn: #cd2532; + +@colortheme_todo-bg: #7bccd1; +@colortheme_todo-color: #000; +@colortheme_todo-warn: #cd2532; + +// Sidebar layout (profile / settings) +@colortheme_sidebar-active: #fff; +@colortheme_sidebar-left-bg: #eee; +@colortheme_sidebar-left-fg: #000; +@colortheme_sidebar-left-branch: #888; +@colortheme_sidebar-right-bg: #fff; +@colortheme_sidebar-right-fg: #000; +@colortheme_sidebar-description: #777; +@colortheme_sidebar-button-bg: #3066e5; +@colortheme_sidebar-button-red-bg: #e54e4e; +@colortheme_sidebar-button-alt-bg: #fff; + +@cryptpad_color_blue: #4591C4; @cryptpad_color_grey: #999999; @cryptpad_header_col: #1E1F1F; -@cryptpad_text_col: #3F4141; \ No newline at end of file +@cryptpad_text_col: #3F4141; + +@colortheme_checkmark-back0: #ffffff; +@colortheme_checkmark-back0-active: #bbbbbb; +@colortheme_checkmark-back1: #FF0073; +@colortheme_checkmark-col1: #ffffff; +@colortheme_checkmark-back2: #FFFFFF; +@colortheme_checkmark-col2: #000000; diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less new file mode 100644 index 000000000..4fd0a5a1d --- /dev/null +++ b/customize.dist/src/less2/include/creation.less @@ -0,0 +1,261 @@ +@import (once) "./colortheme-all.less"; +@import (once) "./tools.less"; +@import (once) "./checkmark.less"; +@import (once) './icon-colors.less'; + +.creation_main() { + .tippy-popper { + z-index: 100000001 !important; + } + #cp-creation-container { + position: absolute; + z-index: 100000000; // #loading * 10 + top: 0px; + background: @colortheme_loading-bg; + color: @colortheme_loading-color; + display: flex; + flex-flow: column; /* we need column so that the child can shrink vertically */ + justify-content: center; + width: 100%; + height: 100%; + overflow: auto; + } + #cp-creation { + flex: 0 1 auto; /* allows shrink */ + min-height: 0; + overflow: auto; + text-align: center; + font: @colortheme_app-font; + width: 100%; + outline: none; + & > div { + width: 60vw; + max-width: 100%; + margin: 40px auto; + text-align: left; + } + + .cp-creation-create, .cp-creation-settings { + margin-top: 0px; + @creation-button: #30B239; + button { + .tools_unselectable(); + padding: 15px; + background: @creation-button; + color: #FFF; + font-weight: bold; + margin: 3px 10px; + border: none; + cursor: pointer; + outline: none; + width: 100%; + &:hover { + //background: darken(@creation-button, 5%); + background: lighten(@creation-button, 5%); + } + } + } + .cp-creation-create { + text-align: center; + margin: auto; + margin-top: 20px; + width: 400px; + max-width: 100%; + button { + margin: 0; + } + } + + #cp-creation-form { + display: flex; + flex-flow: column; + align-items: center; + & > div { + width: 400px; + max-width: 100%; + display: flex; + align-items: center; + flex-wrap: wrap; + font-size: 16px; + margin: 10px 0; + label { + flex: 1; + } + input[type="checkbox"] { + &+ label { + margin-bottom: 0; + flex: 1; + padding: 0 10px; + } + } + .cp-creation-help { + font-size: 18px; + color: white; + &:hover { + color: #AAA; + 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; + &.active { + max-height: 40px; + } + } + .cp-creation-expire { + .cp-creation-expire-picker { + text-align: center; + input { + width: 100px; + } + } + } + .cp-creation-settings { + button { + margin: 0; + padding: 0; + } + .cp-filler { flex: 1; } + } + + div.cp-creation-remember { + margin-top: 30px; + .cp-creation-remember-help { + font-style: italic; + } + } + 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; + } + } + .cp-creation-template-container { + width: 100%; + display: flex; + flex-wrap: wrap; + justify-content: center; + overflow-y: auto; + align-items: center; + .cp-creation-template-element { + @darker: darken(@colortheme_modal-fg, 30%); + + width: 135px; + padding: 5px; + margin: 5px; + display: inline-flex; + flex-flow: column; + + box-sizing: content-box; + + text-align: left; + line-height: 1em; + cursor: pointer; + + background-color: #111; + color: @darker; + border: 1px solid transparent; + + &.cp-creation-template-selected { + border: 1px solid white; + background-color: #222; + } + + transition: all 0.1s; + + &:hover { + color: @colortheme_modal-fg; + } + + align-items: center; + + img { + max-width: 100px; + max-height: 100px; + background: #fff; + } + + .cp-creation-template-element-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + height: 20px; + line-height: 20px; + margin-top: 5px; + max-width: 100%; + } + .fa { + cursor: pointer; + width: 100px; + height: 100px; + font-size: 70px; + text-align: center; + line-height: 100px; + } + } + } + } + + .cp-creation-deleted-container { + text-align: center; + .cp-creation-deleted { + background: #111; + 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-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; + } + .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 new file mode 100644 index 000000000..0d3552450 --- /dev/null +++ b/customize.dist/src/less2/include/dropdown.less @@ -0,0 +1,149 @@ +@import (once) "./colortheme-all.less"; +@import (once) "./tools.less"; + +/* The container
- needed to position the dropdown content */ +.dropdown_main () { + .cp-dropdown-container { + @dropdown_font: @colortheme_app-font-size @colortheme_font; + position: relative; + display: inline-block; + + .fa { + font-family: FontAwesome; + } + + button { + .fa-caret-down { + margin-right: 0px; + margin-left: 5px; + } + * { + .tools_unselectable(); + cursor: default; + } + } + + .cp-dropdown-content { + display: none; + position: absolute; + background-color: @colortheme_dropdown-bg; + min-width: 250px; + box-shadow: 0px 1px 5px 0px rgba(0, 0, 0, 0.2); + z-index: 1000; //Z dropdown content + max-height: 300px; + overflow-y: auto; + font: @dropdown_font; + line-height: 1em; + + &.cp-dropdown-left { + right: 0; + } + + &:hover { + display: block; + } + + & > a, & > span { + color: @colortheme_dropdown-color; + padding: 5px 16px; + text-decoration: none; + display: flex; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + float: none; + text-align: left; + 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; + text-align: center; + margin-right: 5px !important; + } + + &:hover { + background-color: @colortheme_dropdown-bg-hover; + color: @colortheme_dropdown-color; + } + + &.cp-dropdown-element-active { + background-color: @colortheme_dropdown-bg-active; + color: @colortheme_dropdown-color; + } + } + &> span { + box-sizing: border-box; + height: 26px; + border-radius: 0; + border: 0; + padding: 0 16px; + .cp-dropdown-content { + margin-top: 26px; + left: 0; + } + button { + padding: 0; + text-align: left; + margin: 0; + border-radius: 0; + border: 0; + width: 100%; + line-height: 1em; + .cp-toolbar-drawer-element { + margin-left: 10px; + display: inline; + vertical-align: top; + } + } + } + + hr { + margin: 5px 0px; + height: 1px; + background: #bbb; + } + + p { + min-width: 160px; + padding: 5px; + margin: 0; + white-space: normal; + text-align: left; + color: black; + font-size: 14px; + * { + font-size: 14px; + } + h2 { + color: black; + font-weight: bold; + text-align: center; + background-color: #EEEEEE; + padding: 5px 0px; + margin: 5px 0px; + font-size: 16px; + white-space: normal; + } + } + } + } +} + diff --git a/customize.dist/src/less2/include/fileupload.less b/customize.dist/src/less2/include/fileupload.less new file mode 100644 index 000000000..3ed3e793e --- /dev/null +++ b/customize.dist/src/less2/include/fileupload.less @@ -0,0 +1,57 @@ +@import (once) './colortheme-all.less'; +@import (once) './modal.less'; + +.fileupload_main () { + /* Upload status table */ + #cp-fileupload { + .modal_base(); + position: absolute; + left: 10vw; right: 10vw; + bottom: 10vh; + opacity: 0.9; + box-sizing: border-box; + z-index: 1000000; //Z file upload table container + display: none; + #cp-fileupload-table { + width: 80vw; + tr:nth-child(1) { + background-color: darken(@colortheme_modal-bg, 20%); + td { + text-align: center; + font-weight: bold; + padding: 0.25em; + } + } + @upload_pad_h: 0.25em; + @upload_pad_v: 0.5em; + + td { + padding: @upload_pad_h @upload_pad_v; + } + .cp-fileupload-table-link { + .fa { + margin-right: 5px; + } + } + .cp-fileupload-table-progress { + width: 200px; + position: relative; + text-align: center; + box-sizing: border-box; + } + .cp-fileupload-table-progress-container { + position: absolute; + width: 0px; + left: @upload_pad_v; + top: @upload_pad_h; bottom: @upload_pad_h; + background-color: rgba(0,0,255,0.3); + z-index: -1; //Z file upload progress container + } + .cp-fileupload-table-cancel { text-align: center; } + .fa.cancel { + color: rgb(255, 0, 115); + } + } + } +} + diff --git a/customize.dist/src/less2/include/font.less b/customize.dist/src/less2/include/font.less index b1fdd0b19..ec8e77f0f 100644 --- a/customize.dist/src/less2/include/font.less +++ b/customize.dist/src/less2/include/font.less @@ -1,7 +1,7 @@ .font_neuropolitical () { @font-face { font-family: Neuropolitical; - src: url(./customize/fonts/neuropolitical.ttf) + src: url("/customize/fonts/neuropolitical.ttf"); } } .font_open-sans () { diff --git a/customize.dist/src/less2/include/framework.less b/customize.dist/src/less2/include/framework.less new file mode 100644 index 000000000..4bdb2b0b8 --- /dev/null +++ b/customize.dist/src/less2/include/framework.less @@ -0,0 +1,18 @@ +@import (once) "./toolbar.less"; +@import (once) './fileupload.less'; +@import (once) './alertify.less'; +@import (once) './tokenfield.less'; +@import (once) './creation.less'; + +.framework_main(@bg-color, @warn-color, @color) { + .toolbar_main( + @bg-color: @bg-color, + @warn-color: @warn-color, + @color: @color + ); + .fileupload_main(); + .alertify_main(); + .tokenfield_main(); + .creation_main(); +} + diff --git a/customize.dist/src/less2/include/help.less b/customize.dist/src/less2/include/help.less new file mode 100644 index 000000000..90f23119b --- /dev/null +++ b/customize.dist/src/less2/include/help.less @@ -0,0 +1,38 @@ +@import (once) "./colortheme-all.less"; + +.help_main (@color, @bg-color) { + .cp-help-container { + position: relative; + background-color: lighten(@bg-color, 15%); + &.cp-help-hidden { + display: none; + } + + .cp-help-close { + position: absolute; + top: 5px; + right: 5px; + } + .cp-help-text { + color: @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%); + } + h1 { + font-size: 20px; + } + h2 { + font-size: 18px; + } + h3 { + font-size: 16px; + } + ul, ol, p { margin: 0; } + } + } +} diff --git a/customize.dist/src/less2/include/icon-colors.less b/customize.dist/src/less2/include/icon-colors.less new file mode 100644 index 000000000..b787fee1c --- /dev/null +++ b/customize.dist/src/less2/include/icon-colors.less @@ -0,0 +1,30 @@ +@import (once) "./colortheme-all.less"; +.iconColors_main () { + // Classes used in common-interface.js + .cp-icon-color-pad { color: @colortheme_pad-bg; } + .cp-icon-color-code { color: @colortheme_code-bg; } + .cp-icon-color-slide { color: @colortheme_slide-bg; } + .cp-icon-color-poll { color: @colortheme_poll-bg; } + .cp-icon-color-file { color: @colortheme_file-bg; } + .cp-icon-color-contacts { color: @colortheme_friends-bg; } + .cp-icon-color-whiteboard { color: @colortheme_whiteboard-bg; } + .cp-icon-color-drive { color: @colortheme_drive-bg; } + .cp-icon-color-settings { color: @colortheme_settings-bg; } + .cp-icon-color-profile { color: @colortheme_settings-bg; } + .cp-icon-color-default { color: @colortheme_default-bg; } + .cp-icon-color-todo { color: @colortheme_todo-bg; } + + .cp-border-color-pad { border-color: @colortheme_pad-bg !important; } + .cp-border-color-code { border-color: @colortheme_code-bg !important; } + .cp-border-color-slide { border-color: @colortheme_slide-bg !important; } + .cp-border-color-poll { border-color: @colortheme_poll-bg !important; } + .cp-border-color-file { border-color: @colortheme_file-bg !important; } + .cp-border-color-contacts { border-color: @colortheme_friends-bg !important; } + .cp-border-color-whiteboard { border-color: @colortheme_whiteboard-bg !important; } + .cp-border-color-drive { border-color: @colortheme_drive-bg !important; } + .cp-border-color-settings { border-color: @colortheme_settings-bg !important; } + .cp-border-color-profile { border-color: @colortheme_settings-bg !important; } + .cp-border-color-default { border-color: @colortheme_default-bg !important; } + .cp-border-color-todo { border-color: @colortheme_todo-bg !important; } +} + diff --git a/customize.dist/src/less2/include/icons.less b/customize.dist/src/less2/include/icons.less new file mode 100644 index 000000000..03a6af5ab --- /dev/null +++ b/customize.dist/src/less2/include/icons.less @@ -0,0 +1,43 @@ +.icons_main() { + li { + display: inline-block; + margin: 10px 10px; + width: 140px; + height: 140px; + text-align: center; + vertical-align: top; + overflow: hidden; + text-overflow: ellipsis; + padding-top: 5px; + padding-bottom: 5px; + border: 1px solid white; + + .cp-icons-name { + width: 100%; + height: 24px; + margin: 0; + display: inline-block; + font-size: 14px; + //align-items: center; + //justify-content: center; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + word-wrap: break-word; + } + &.cp-icons-element-selected { + background-color: white; + color: #666; + } + .fa { + display: block; + font-size: 64px; + margin: 18px 0; + text-align: center; + &.listonly { + display: none; + } + } + } +} + diff --git a/customize.dist/src/less2/include/infopages.less b/customize.dist/src/less2/include/infopages.less index 6d9b380ed..b07b90aa8 100644 --- a/customize.dist/src/less2/include/infopages.less +++ b/customize.dist/src/less2/include/infopages.less @@ -1,4 +1,4 @@ -@import (once) "./colortheme.less"; +@import (once) "./colortheme-all.less"; @infopages_infobar-height: 64px; @infopages_padding: 32px; @@ -89,7 +89,6 @@ color: #fff; padding-top: 30%; margin-bottom: 0; - } } @@ -117,7 +116,7 @@ .infopages_topbar () { .cp-topbar { background: #fff; - z-index: 9001; + z-index: 10000; //Z infopage toolbar position: fixed; top: 0; display: flex; @@ -152,8 +151,8 @@ } } -// navigation top bar -.navbar { +// navigation top bar +.navbar { background: #fff; .navbar-brand { display: block; @@ -165,6 +164,7 @@ } a { border: 2px solid transparent; + white-space: nowrap; } .nav-link { padding: 0.5em 0.7em; @@ -173,7 +173,8 @@ }; } .cp-register-btn { - border: 2px solid #4591C4; + border: 2px solid #4591C4; + display: inline-block; } button:focus { outline: none; @@ -192,21 +193,9 @@ } .cp-register-btn { margin-right: 13px; - margin-left: 83vw; text-align: center; } } -@media (max-width: 687px) { - .cp-register-btn { - margin-left: 75vw; - } -} -@media (max-width: 467px) { - .cp-register-btn { - margin-left: 63vw; - } -} - //footer general styles diff --git a/customize.dist/src/less2/include/leftside-menu.less b/customize.dist/src/less2/include/leftside-menu.less new file mode 100644 index 000000000..28ae57af4 --- /dev/null +++ b/customize.dist/src/less2/include/leftside-menu.less @@ -0,0 +1,23 @@ +@import (once) "./unselectable.less"; +@import (once) "./variables.less"; +@import (once) "./colortheme-all.less"; + +.leftside-menu_main() { +} +.leftside-menu-category_main() { + .unselectable_make(); + padding: 5px 20px; + margin: 15px 0; + cursor: pointer; + height: @variables_bar-height; + line-height: @variables_bar-height - 10px; + .fa { + width: 25px; + } + &:hover { + background: rgba(0,0,0,0.05); + } + &.cp-leftside-active { + background: @colortheme_sidebar-active; + } +} diff --git a/customize.dist/src/less2/include/limit-bar.less b/customize.dist/src/less2/include/limit-bar.less new file mode 100644 index 000000000..b2ea5f230 --- /dev/null +++ b/customize.dist/src/less2/include/limit-bar.less @@ -0,0 +1,61 @@ +@import (once) "./colortheme-all.less"; + +.limit-bar_main () { + .cp-limit-container { + @colortheme_green: #5cb85c; + display: inline-flex; + flex-flow: column-reverse; + width: 100%; + margin-top: 20px; + .cp-limit-bar { + display: inline-flex; + justify-content: center; + align-items: center; + + max-width: 100%; + margin: 3px; + box-sizing: border-box; + border-top: 1px solid #999; + background: white; + position: relative; + text-align: center; + width: ~"calc(100% - 6px)"; + height: 35px; + line-height: 25px; + overflow: hidden; + .cp-limit-usage { + height: 100%; + display: inline-block; + background: blue; + position: absolute; + left: 0; + top: 0; + z-index: 1; // .usage + &.cp-limit-usage-normal { + background: @colortheme_green; + } + &.cp-limit-usage-warning { + background: orange; + } + &.cp-limit-usage-above { + background: red; + } + } + .cp-limit-usage-text { + position: relative; + color: grey; + text-shadow: 1px 0 2px white, 0 1px 2px white, -1px 0 2px white, 0 -1px 2px white; + z-index: 2; // .usageText + font-size: @colortheme_app-font-size; + font-weight: bold; + } + } + .cp-limit-upgrade { + padding: 0; + line-height: 25px; + height: 25px; + margin: 0 3px; + border-radius: 0; + } + } +} diff --git a/customize.dist/src/less2/include/markdown-toolbar.less b/customize.dist/src/less2/include/markdown-toolbar.less new file mode 100644 index 000000000..4fb466525 --- /dev/null +++ b/customize.dist/src/less2/include/markdown-toolbar.less @@ -0,0 +1,20 @@ +@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 new file mode 100644 index 000000000..470f81a82 --- /dev/null +++ b/customize.dist/src/less2/include/markdown.less @@ -0,0 +1,37 @@ +.markdown_preformatted-code (@color: #333) { + pre > code { + display: block; + position: relative; + border: 1px solid @color; + width: 90%; + margin: auto; + padding-left: .25vw; + overflow-x: auto; + overflow-y: hidden; + } +} + +.markdown_gfm-table (@color: black) { + table { + border-collapse: collapse; + tr { + th { + border: 3px solid @color; + padding: 15px; + } + } + } +} + +.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 ed63ea6fd..3bd674527 100644 --- a/customize.dist/src/less2/include/modal.less +++ b/customize.dist/src/less2/include/modal.less @@ -1,11 +1,12 @@ -@import (once) "./colortheme.less"; +@import (once) "./colortheme-all.less"; +@import (once) "./variables.less"; .modal_base() { font-family: @colortheme_font; background-color: @colortheme_modal-bg; color: @colortheme_modal-fg; - box-shadow: @colortheme_modal-shadow; + box-shadow: @variables_shadow; a { color: @colortheme_modal-link; @@ -19,7 +20,7 @@ .cp-modal-container { display: none; - z-index: 100000; + z-index: 100000; //Z modal container position: absolute; top: 0; bottom: 0; @@ -30,9 +31,9 @@ .cp-modal { background-color: @colortheme_modal-bg; color: @colortheme_modal-fg; - box-shadow: @colortheme_modal-shadow; + box-shadow: @variables_shadow; - padding: @colortheme_modal-padding; + padding: @variables_padding; position: absolute; top: 15vh; bottom: 15vh; @@ -70,7 +71,7 @@ position: absolute; top: 0; right: 0; - margin: @colortheme_modal-padding; + margin: @variables_padding; cursor: pointer; } } diff --git a/customize.dist/src/less2/include/sidebar-layout.less b/customize.dist/src/less2/include/sidebar-layout.less new file mode 100644 index 000000000..ede03942e --- /dev/null +++ b/customize.dist/src/less2/include/sidebar-layout.less @@ -0,0 +1,102 @@ +@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; + +@sidebar_button-width: 400px; + + +.sidebar-layout_main() { + input[type="text"] { + padding-left: 10px; + } + #cp-sidebarlayout-container { + font-size: 16px; + display: flex; + flex: 1; + min-height: 0; + #cp-sidebarlayout-leftside { + color: @leftside-color; + width: 250px; + background: @leftside-bg; + display: flex; + flex-flow: column; + .cp-sidebarlayout-categories { + flex: 1; + .cp-sidebarlayout-category { + .leftside-menu-category_main(); + } + } + } + #cp-sidebarlayout-rightside { + flex: 1; + padding: 5px 20px; + color: @rightside-color; + overflow: auto; + + // Following rules are only in settings + .cp-sidebarlayout-element { + label:not(.noTitle), .label { + display: block; + font-weight: bold; + margin-bottom: 0; + } + .cp-sidebarlayout-description { + display: block; + color: @description-color; + margin-bottom: 5px; + p { + margin-bottom: 0; + } + } + margin-bottom: 20px; + } + [type="text"], button { + vertical-align: middle; + height: 40px; + box-sizing: border-box; + } + .cp-sidebarlayout-input-block { + display: inline-flex; + width: @sidebar_button-width; + input { + flex: 1; + border-radius: 0.25em 0 0 0.25em; + border: 1px solid #adadad; + border-right: 0px; + } + button { + border-radius: 0 0.25em 0.25em 0; + //border: 1px solid #adadad; + border-left: 0px; + } + } + &>div { + margin: 10px 0; + } + button.btn { + @button-bg: @colortheme_sidebar-button-bg; + @button-red-bg: @colortheme_sidebar-button-red-bg; + background-color: @button-bg; + border-color: darken(@button-bg, 10%); + color: white; + &:hover { + background-color: darken(@button-bg, 10%); + } + &.btn-danger { + background-color: @button-red-bg; + border-color: darken(@button-red-bg, 10%); + color: white; + &:hover { + background-color: darken(@button-red-bg, 10%); + } + } + } + } + } +} + + diff --git a/customize.dist/src/less2/include/tokenfield.less b/customize.dist/src/less2/include/tokenfield.less new file mode 100644 index 000000000..6604b4481 --- /dev/null +++ b/customize.dist/src/less2/include/tokenfield.less @@ -0,0 +1,83 @@ +@import (once) "./tools.less"; + +.tokenfield_main () { + .tokenfield { + .tools_unselectable(); + height: auto; + min-height: 34px; + padding-bottom: 0px; + background-color: unset; + border: none; + display: flex; + flex-wrap: wrap; + align-items: center; + padding: 0 10px; + .token { + box-sizing: border-box; + border-radius: 3px; + display: inline-block; + border: 1px solid #d9d9d9; + background-color: #ededed; + white-space: nowrap; + margin: 10px 5px; + height: 24px; + vertical-align: middle; + cursor: default; + + color: #222; + + &:hover { + border-color: #b9b9b9; + } + &.invalid { + background: none; + border: 1px solid transparent; + border-radius: 0; + border-bottom: 1px dotted #d9534f; + } + &.invalid.active { + background: #ededed; + border: 1px solid #ededed; + border-radius: 3px; + } + .token-label { + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + padding-left: 4px; + vertical-align: middle; + } + .close { + font-family: Arial; + display: inline-block; + line-height: 24px; + font-size: 1.1em; + margin-left: 5px; + float: none; + height: 100%; + vertical-align: middle; + padding-right: 4px; + } + &.active { + border-color: rgba(82, 168, 236, 0.8); + } + &.duplicate { + border-color: #ebccd1; + } + } + .token-input { + background: none; + flex: 1; + border: 0; + padding: 0; + margin: 0 !important; // Override alertify + box-shadow: none; + max-width: 100%; + &:focus { + border-color: transparent; + outline: 0; + box-shadow: none; + } + } + } +} diff --git a/customize.dist/src/less2/include/toolbar-history.less b/customize.dist/src/less2/include/toolbar-history.less new file mode 100644 index 000000000..abf14f3ac --- /dev/null +++ b/customize.dist/src/less2/include/toolbar-history.less @@ -0,0 +1,52 @@ +@import (once) "./colortheme-all.less"; + +.history_main () { + .cp-toolbar-history { + display: none; + text-align: center; + width: 100%; + * { + font: @colortheme_app-font; + } + .cp-toolbar-history-next { + display: inline-block; + vertical-align: middle; + margin: 20px; + } + .cp-toolbar-history-previous { + display: inline-block; + vertical-align: middle; + margin: 20px; + } + .cp-toolbar-history-goto { + display: inline-block; + vertical-align: middle; + text-align: center; + input { width: 75px; } + } + .cp-toolbar-history-goto-input { + padding-left: 5px; + margin-left: 5px; + vertical-align: middle; + } + button { + color: inherit; + background-color: rgba(0,0,0,0.2); + &:hover { + 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 new file mode 100644 index 000000000..50c51f611 --- /dev/null +++ b/customize.dist/src/less2/include/toolbar.less @@ -0,0 +1,999 @@ +@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"; + +.toolbar_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 + @barWidth: 600px // width of the toolbar +) { + + @toolbar_line-height: 32px; + @toolbar_top-height: 64px; + @toolbar_button-font: @colortheme_app-font; + + .dropdown_main(); + .ckeditor_fix(); + .history_main(); + .iconColors_main(); + .markdownToolbar_main(@color, @bg-color); + .help_main(@color, @bg-color); + + .cp-toolbar-container { + display: flex; + } + + .toolbar_button { + height: @toolbar_line-height; + box-sizing: border-box; + padding: 3px 10px; + margin: 0; + transition: all 0.15s; + .tools_unselectable(); + &.cp-toolbar-hidden { + display: none; + } + .cp-toolbar-drawer-element { + display: none; + } + // Bootstrap 4 colors (btn-secondary) + border: 1px solid transparent; + color: inherit; + font: @toolbar_button-font; + * { + color: inherit; + font: @toolbar_button-font; + } + background: transparent; + &:hover { + background-color: rgba(50,50,50,0.3); + } + } + + .cp-toolbar-userlist-drawer { + background-color: @bg-color; + font: @colortheme_app-font-size @colortheme_font; + min-width: 175px; + width: 175px; + display: block; + overflow-y: auto; + overflow-x: hidden; + padding: 10px; + box-sizing: border-box; + .cp-toolbar-userlist-drawer-close { + position: absolute; + margin-top: -10px; + margin-left: 149px; + font-size: 15px; + opacity: 0.5; + cursor: pointer; + text-shadow: unset; + &:hover { + opacity: 1; + } + } + h2 { + color: inherit; + text-align: center; + padding: 5px 0px; + margin: 5px 0px; + font: inherit; + font-weight: bold; + white-space: normal; + line-height: auto; + } + text-align: baseline; + .cp-toolbar-userlist-viewer { + font-style: italic; + padding: 5px; + background: rgba(0,0,0,0.1); + margin: 2px 0; + } + + & > p { + font: @colortheme_app-font-size @colortheme_font; + margin: 0; + padding: 0; + display: block; + } + + .cp-toolbar-userlist-others { + display: flex; + flex-flow: column; + margin: 10px 0; + margin-bottom: 20px; + &>span { + height: 48px; + padding: 5px; + margin: 2px 0; + background: rgba(0,0,0,0.1); + .avatar_main(30px); + .cp-avatar-default, media-tag { + margin-right: 5px; + } + &.cp-userlist-clickable { + cursor: pointer; + &:hover { + background-color: rgba(0,0,0,0.3); + } + } + .cp-toolbar-userlist-rightcol { + order: 10; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: flex; + flex-flow: column; + height: 100%; + .cp-toolbar-userlist-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: flex; + justify-content: space-between; + align-items: center; + } + .cp-toolbar-userlist-name-input { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: none; + border: none; + } + .cp-toolbar-userlist-name-value { + overflow: hidden; + flex: 1; + min-width: 0; + min-height: 0; + text-overflow: ellipsis; + } + .cp-toolbar-userlist-name-edit { + width: 20px; + font-size: 16px; + padding: 0; + border: none; + height: 20px; + cursor: pointer; + } + .cp-toolbar-userlist-friend { + padding: 0; + } + } + } + } + .cp-toolbar-userlist-friend { + display: inline-block; + width: 20px; + } + } + + #cp-app-toolbar-creation-dialog.cp-modal-container { + .icons_main(); + + li:hover { + border: 1px solid white; + } + .cp-modal { + display: flex; + flex-flow: column; + li, li .fa { + cursor: pointer; + } + &> p { + margin: 50px; + } + &> div { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-content: center; + overflow-y: auto; + } + } + + .cp-creation-icons-name { + white-space: nowrap; + } + + #cp-app-toolbar-creation-advanced { + width: auto; + margin: 0; + padding: 0; + outline: none; + } + label[for="cp-app-toolbar-creation-advanced"] { + margin: 0; + margin-left: 5px; + } + + @media screen and (max-height: @browser_media-not-big) { + .cp-modal { + & > p { + display: none; + } + & > div { + align-content: unset; + li { + height: 40px; + width: 200px; + display: flex; + align-items: center; + .fa { + font-size: 32px; + } + .cp-icons-name { + height: auto; + } + } + } + } + } + } + + .cp-toolbar-userlist-drawer { + background-color: @bg-color; + color: @color; + .cp-toolbar-userlist-drawer-close { + color: @color; + } + h2 { + background-color: darken(@bg-color, 10%); + color: @color; + } + .cp-toolbar-userlist-name-input { + background-color: darken(@bg-color, 10%); + color: @color; + } + .cp-toolbar-userlist-name-edit { + color: contrast(@color, + lighten(@color, 20%), + darken(@color, 20%)); + background: transparent; + &:hover { + color: @color; + } + } + .cp-toolbar-userlist-friend { + &:hover { + color: darken(@color, 15%); + } + } + } + + .cp-toolbar { + * { + outline-width: 0; + &:focus { + outline-width: 0; + } + } + + @toolbar-green: #5cb85c; + + box-sizing: border-box; + padding: 0px; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + background-color: @bg-color; + color: @color; + + .fa { + font: normal normal normal 14px/1 FontAwesome; + font-family: FontAwesome; + } + + .tools_unselectable(); + + font: @toolbar_button-font; + width: 100%; + z-index: 10000; // cp-toolbar + + .cp-dropdown-container { + //height: 100%; + //display: inline-block; + button { + height: 100%; + border-radius: 0; + margin: 0; + background: transparent; + } + } + + button { + .toolbar_button; + } + + .cp-toolbar-limit { + box-sizing: border-box; + height: 26px; + width: 26px; + display: inline-block; + padding: 3px; + margin: 0px 3px 0 6px; + vertical-align: middle; + line-height: @toolbar_top-height; + span { + cursor: pointer; + margin: auto; + font-size: 20px; + } + } + + div { + white-space: normal; + } + + /*button, select { + height: @toolbar_line-height; + box-sizing: border-box; + padding: 3px 10px; + margin: 0; + }*/ + + select { + margin-left: 5px; + margin-right: 5px; + padding-left: 5px; + border: 1px solid #A6A6A6; + border-bottom-color: #979797; + vertical-align: top; + box-sizing: content-box; + option { + height: 24px; + } + } + + &.cp-toolbar-notitle { + .cp-toolbar-top-filler { + flex: 1; + } + } + .cp-toolbar-top { + @media screen and (max-width: @browser_media-medium-screen) { + flex-wrap: wrap; + height: @toolbar_line-height; + .cp-pad-not-pinned { + line-height: 32px; + flex: unset; + padding: 0; + align-self: auto; + margin: 0 5px; + } + .cp-toolbar-top-filler { + height: 32px; + } + .cp-toolbar-title { + height: @toolbar_line-height; + line-height: initial; + margin: 0; + .cp-toolbar-title-hoverable { + width: 100%; + } + .cp-toolbar-title-value-page { + padding: 5px; + line-height: unset; + border: 0; + } + .cp-toolbar-title-editable, .cp-toolbar-title-value-page { + max-width: ~"calc(100vw - 26px)"; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + font-size: @colortheme_app-font-size; + height: @toolbar_line-height; + box-sizing: border-box; + line-height: 20px; + } + .cp-toolbar-title-edit, .cp-toolbar-title-save { + box-sizing: border-box; + height: @toolbar_line-height; + line-height: @colortheme_app-font-size; + display: inline-block; + + .fa { + font-size: @colortheme_app-font-size; + } + } + input { + height: @toolbar_line-height; + font-size: @colortheme_app-font-size; + flex: 1; + max-width: none; + line-height: calc(@toolbar_line-height - 12px); // padding + border + } + } + .cp-toolbar-link { + height: @toolbar_line-height; + width: @toolbar_line-height; + .cp-toolbar-link-logo { + padding: 5px; + } + } + .cp-toolbar-user { + height: @toolbar_line-height; + .cp-toolbar-new { + height: @toolbar_line-height; + width: @toolbar_line-height; + margin-left: 0; + button { + height: @toolbar_line-height; + width: @toolbar_line-height; + font-size: 20px; + margin-top: -1px; + } + } + .cp-toolbar-user-dropdown { + height: @toolbar_line-height; + width: @toolbar_line-height; + &> button { + height: @toolbar_line-height; + width: @toolbar_line-height; + span { font-size: unset; } + } + &> button.cp-avatar.cp-avatar { + media-tag { + margin: 4px; + max-width: 24px; + min-width: 24px; + max-height: 24px; + min-height: 24px; + } + } + } + .cp-toolbar-limit { + line-height: 32px; + margin: 0; + } + } + /* + .cp-toolbar-top-filler { + flex: 1; + } + .cp-toolbar-title { + flex: auto; + width: 100%; + order: 10; + height: @toolbar_line-height; + line-height: initial; + margin: 0; + .cp-toolbar-title-hoverable { + width: 100%; + } + .cp-toolbar-title-editable { + max-width: ~"calc(100vw - 26px)"; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + font-size: @colortheme_app-font-size; + height: @toolbar_line-height; + box-sizing: border-box; + line-height: 20px; + } + .cp-toolbar-title-edit, .cp-toolbar-title-save { + box-sizing: border-box; + height: @toolbar_line-height; + line-height: @colortheme_app-font-size; + display: inline-block; + + .fa { + font-size: @colortheme_app-font-size; + } + } + input { + height: @toolbar_line-height; + font-size: @colortheme_app-font-size; + flex: 1; + max-width: none; + line-height: calc(@toolbar_line-height - 12px); // padding + border + } + } + */ + } + } + + .cp-toolbar-spinner { + font-size: @colortheme_app-font-size; + color: @color; + } + .cp-toolbar-limit { + text-shadow: -1px 0 @color, 0 1px @color, 1px 0 @color, 0 -1px @color; + color: @warn-color; + } + .cp-toolbar-leftside, .cp-toolbar-rightside { + background-color: lighten(@bg-color, 8%); + button:hover, button.cp-toolbar-button-active { + background-color: @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%); + transition: all 0.15s; + color: @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; + &:hover { + background: darken(@bg-color, 5%); + } + } + input { + border: 1px solid darken(@bg-color, 15%); + background: darken(@bg-color, 10%); + color: @color; + } + .cp-dropdown-content.cp-dropdown-left a { + color: black; + } + } + + .cp-toolbar-top { + display: flex; + flex-flow: row; + height: @toolbar_top-height; + position: relative; + width: 100%; + + .cp-pad-not-pinned { + order: 4; + flex: 1; + + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + + line-height: @toolbar_top-height; + padding: 0; + margin: 0 5px; + font-size: @colortheme_app-font-size; + color: @warn-color; + .cp-pnp-msg { + padding-left: 5px; + font-family: @colortheme_font; + font-size: @colortheme_app-font-size; + a { + font-size: @colortheme_app-font-size; + font-family: @colortheme_font; + font-weight: bold; + color: @warn-color; + &:hover { + text-decoration: underline; + } + } + @media screen and (max-width: (@browser_media-not-big)) { + display: none; + } + } + @media screen and (max-width: (@browser_media-not-big)) { + overflow: visible; + max-width: 20px; + } + } + .cp-toolbar-top-filler { + height: @toolbar_top-height; + display: inline-block; + order: 5; + //flex: 1; + } + .cp-toolbar-title { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + order: 3; + height: 100%; + display: inline-flex; + align-items: center; + line-height: @toolbar_top-height; + margin: 0 10px; + .cp-toolbar-title-value { + border: 1px solid transparent; + padding: 5px; + font-size: 25px; + vertical-align: middle; + line-height: 25px; + white-space: nowrap; + } + .cp-toolbar-title-value-page { + border: 1px solid transparent; + padding: 0 5px; + line-height: 48px; + } + .cp-toolbar-title-edit, .cp-toolbar-title-save { + display: flex; + align-items: center; + font-size: 20px; + vertical-align: middle; + line-height: 20px; + .fa { + font-size: 20px; + } + } + .cp-toolbar-title-readonly { + margin-left: 10px; + font-size: 25px; + font-style: italic; + white-space: nowrap; + } + .cp-toolbar-title-hoverable { + display: inline-flex; + overflow: hidden; + } + .cp-toolbar-title-edit { + cursor: pointer; + border: 1px solid transparent; + padding: 5px; + border-collapse: collapse; + span { + cursor: pointer; + } + } + .cp-toolbar-title-save { + cursor: pointer; + padding: 5px; + border-collapse: collapse; + span { + cursor: pointer; + } + } + .cp-toolbar-title-editable { + overflow: hidden; + text-overflow: ellipsis; + border-collapse: collapse; + } + input { + max-width: ~"calc(100% - 40px)"; + //flex: 1; + vertical-align: middle; + box-sizing: border-box; + cursor: auto; + width: 300px; + font-size: 20px; + padding: 5px 5px; + height: 40px; + line-height: 28px; // padding + border + } + } + .cp-toolbar-link, .cp-toolbar-new { + font-size: 48px; + line-height: 64px; + width: @toolbar_top-height; + height: @toolbar_top-height; + padding: 0; + box-sizing: border-box; + display: inline-block; + + color: white; + a { + color: white; + } + transition: all 0.15s; + } + .cp-toolbar-new { + background-color: rgba(0,0,0,0.2); + &:hover { + background-color: rgba(0,0,0,0.3); + } + text-align: center; + font-size: 32px; + margin-left: 10px; + &> button { + display: flex; + align-items: center; + justify-content: center; + width: 64px; + font-size: 1em; + color: inherit; + height: 64px; + padding: 0px; + margin: 0; + &::before { + width: 100%; + text-align: center; + padding-top: 4px; + } + &:hover { + background-color: initial; + border-color: transparent; + } + span { + vertical-align: top; + font-size: 1em; + text-decoration: none; + color: inherit; + } + } + } + .cp-toolbar-link { + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + background-color: rgba(0,0,0,0.4); + &:hover { + background-color: rgba(0,0,0,0.5); + } + order: 1; + .fa { + margin: 0; + } + a.cp-toolbar-link-logo { + cursor: pointer; + display: inline-flex; + text-decoration: none; + height: auto; + padding: 10px; + + img { + cursor: pointer; + height: 100%; + width: 100%; + } + } + } + .cp-toolbar-user { + height: @toolbar_top-height; + display: inline-flex; + order: 6; + line-height: @toolbar_top-height; + color: white; + .cp-toolbar-new { order: 2; } + .cp-toolbar-user-dropdown { order: 3; } + .cp-toolbar-backup { order: 4; } // TODO drive migration to secure iframe + &> * { + display: inline-block; + height: 100%; + vertical-align: top; + } + .cp-toolbar-user-dropdown { + z-index: 10000; //Z cp-toolbar-user-dropdown + //margin-left: 20px; + height: 64px; + width: 64px; + padding: 0px; + box-sizing: border-box; + text-align: center; + background-color: rgba(0,0,0,0.3); + transition: all 0.15s; + &:hover { + background-color: rgba(0,0,0,0.4); + } + .cp-dropdown-content { + margin: 0; + overflow: visible; + } + & > button { + display: flex; + justify-content: center; + align-items: center; + height: 64px; + width: 64px; + padding: 0; + span { + text-align: center; + width: 100%; + cursor: default; + font-size: 32px; + } + &.cp-avatar { + .avatar_main(48px); + media-tag { + margin: 8px; + } + border: 0; + } + } + } + p.cp-toolbar-account { + &> span { + font-weight: bold; + span { + font-weight: normal; + } + } + } + .cp-toolbar-backup { + margin: 0; + border-radius: 0; + background: transparent; + &:hover { + background-color: rgba(0,0,0,0.2); + } + } + } + } + + .cp-toolbar-leftside { + //height: @toolbar_line-height; + &:empty { + height: 0; + } + display: inline-flex; + align-items: center; + max-width: 100%; + flex: 1 1 auto; + //margin-bottom: -1px; + .cp-toolbar-users { + pre { + /* needed for ckeditor */ + white-space: pre; + margin: 5px 0px; + } + } + button { + margin: 0px; + border-radius: 0; + height: 100%; + } + .cp-dropdown-content { + margin-top: -1px; + } + + & > span { + height: @toolbar_line-height; + } + + #cp-toolbar-userlist-drawer-open { order: 1; } + .cp-toolbar-share-button { order: 2; } + .cp-toolbar-spinner { order: 3; } + + #cp-toolbar-userlist-drawer-open button { + width: 125px; + text-align: center; + } + .cp-toolbar-share-button { + width: 50px; + text-align: center; + } + } + .cp-toolbar-rightside { + 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; + } + + .cp-toolbar-rightside-button { + cursor: pointer; + // UI actions + &.cp-toolbar-icon-toggle { order: 1; } + &.cp-toolbar-icon-preview { order: 2; } + &.cp-toolbar-icon-present { order: 3; } + // Content actions + &.cp-toolbar-icon-mediatag { order: 10; } + order: 11; + // Storage actions + &.cp-toolbar-icon-hashtag { order: 20; } + &.cp-toolbar-icon-template { order: 21; } + &.cp-toolbar-icon-forget { order: 22; } + // Drawer + &.cp-toolbar-drawer-button { order: 30; } + + } + + .cp-toolbar-drawer-content:empty ~ .cp-toolbar-drawer-button { + display: none; + } + .cp-toolbar-drawer-content { + box-shadow: 0px 1px 5px 0px rgba(0, 0, 0, 0.2); + position: absolute; + right: 0px; + margin-top: @toolbar_line-height; + min-width: 50px; + background: @colortheme_dropdown-bg; + display: flex; + flex-flow: column; + z-index: 10000; //Z cp-toolbar-drawer-content + color: black; + .fa { + font-size: 17px; + } + &> span { + order: 8; + box-sizing: border-box; + min-width: 150px; + height: @toolbar_line-height; + border-radius: 0; + border: 0; + } + button { + padding: 5px 16px; + text-align: left; + margin: 0; + border-radius: 0; + border: 0; + width: 100%; + line-height: 1em; + &.cp-toolbar-button-active { + background-color: inherit; + } + .cp-toolbar-drawer-element { + margin-left: 10px; + display: inline; + vertical-align: baseline; + } + &.fa-info-circle, &.fa-history, &.fa-cog { + .cp-toolbar-drawer-element { + margin-left: 11px; + } + } + &.fa-question { + .cp-toolbar-drawer-element { + margin-left: 16px; + } + } + &:hover { + background-color: @colortheme_dropdown-bg-hover !important; + color: @colortheme_dropdown-color; + } + order: 8; + &.fa-history { order: 1; } + &.fa-download { order: 2; } + &.fa-upload { order: 3; } + &.fa-print { order: 4; } + &.fa-cog { order: 5; } + &.fa-info-circle { order: 6; } + &.fa-help { order: 7; } + } + } + } + .cp-toolbar-spinner { + line-height: @toolbar_line-height; + padding: 0 20px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 200px; + box-sizing: border-box; + &> span.fa { + height: 20px; + width: 20px; + //margin: 8px; + line-height: 20px; + font-size: 20px; + text-align: center; + } + } + .cp-toolbar-readonly { + margin-right: 5px; + font-weight: bold; + text-transform: uppercase; + } + .cp-toolbar-share { + a { + .fa { + margin-right: 5px; + } + } + } + +} + diff --git a/customize.dist/src/less2/include/tools.less b/customize.dist/src/less2/include/tools.less new file mode 100644 index 000000000..9fd2df5bc --- /dev/null +++ b/customize.dist/src/less2/include/tools.less @@ -0,0 +1,27 @@ +.tools_placeholder-color (@color) { + &::-webkit-input-placeholder { /* WebKit, Blink, Edge */ + color: @color;; + } + &:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ + color: @color; + opacity: 1; + } + &::-moz-placeholder { /* Mozilla Firefox 19+ */ + color: @color; + opacity: 1; + } + &:-ms-input-placeholder { /* Internet Explorer 10-11 */ + color: @color; + } + &::-ms-input-placeholder { /* Microsoft Edge */ + color: @color; + } +} + +.tools_unselectable () { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} diff --git a/customize.dist/src/less2/include/unselectable.less b/customize.dist/src/less2/include/unselectable.less new file mode 100644 index 000000000..30223128b --- /dev/null +++ b/customize.dist/src/less2/include/unselectable.less @@ -0,0 +1,13 @@ +.unselectable_make() { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +.unselectable_main() { + .cp-unselectable { + .unselectable_make(); + } +} diff --git a/customize.dist/src/less2/include/variables.less b/customize.dist/src/less2/include/variables.less new file mode 100644 index 000000000..ba6c642e2 --- /dev/null +++ b/customize.dist/src/less2/include/variables.less @@ -0,0 +1,9 @@ +// This is a file for generic constants which we didn't want to hardcode everywhere. +// However, unlike colortheme, customizing these variables will cause breakage. + +// Elements size +@variables_bar-height: 32px; + +// Used in modal.less and alertify.less +@variables_padding: 12px; +@variables_shadow: 0 8px 32px 0 rgba(0,0,0,.4); diff --git a/customize.dist/src/less2/loading.less b/customize.dist/src/less2/loading.less new file mode 100644 index 000000000..8c7e8f5e4 --- /dev/null +++ b/customize.dist/src/less2/loading.less @@ -0,0 +1,73 @@ +/* +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 index 3cab13fbb..dadbef539 100644 --- a/customize.dist/src/less2/main.less +++ b/customize.dist/src/less2/main.less @@ -9,4 +9,35 @@ 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"; } + diff --git a/customize.dist/src/less2/pages/page-404.less b/customize.dist/src/less2/pages/page-404.less new file mode 100644 index 000000000..c6afae5b0 --- /dev/null +++ b/customize.dist/src/less2/pages/page-404.less @@ -0,0 +1,37 @@ +@import (once) "../include/colortheme-all.less"; +@import (once) "../include/font.less"; +.font_neuropolitical(); +.font_open-sans(); + +html, body { + margin: 0px; + padding: 0px; + #cp-main { + + height: 100vh; + margin: 0px; + width: 100%; + padding-top: 5%; + text-align: center; + #cp-logo { + display: block; + max-width: 15%; + margin: auto; + } + #cp-brand { + font-family: neuropolitical; + font-size: 40px; + } + #cp-title { + font-size: 30px; + } + #cp-scramble, #cp-link { + font-size: 20px; + } + #cp-title, #cp-scramble, #cp-link { + //font-family: 'Open Sans'; + font-family: monospace; + } + } +} + diff --git a/customize.dist/src/less2/pages/page-about.less b/customize.dist/src/less2/pages/page-about.less index d356fecd0..83354abd7 100644 --- a/customize.dist/src/less2/pages/page-about.less +++ b/customize.dist/src/less2/pages/page-about.less @@ -1,5 +1,5 @@ @import (once) "../include/infopages.less"; -@import (once) "../include/colortheme.less"; +@import (once) "../include/colortheme-all.less"; .infopages_main(); .infopages_topbar(); @@ -44,7 +44,7 @@ } } } - .cp-bio-avatar-right { + .cp-bio-avatar-right { padding-right: 15px; padding-left: 0; @media (max-width: 991px) { @@ -112,4 +112,4 @@ } .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 56a60022c..4d2f9ad0e 100644 --- a/customize.dist/src/less2/pages/page-contact.less +++ b/customize.dist/src/less2/pages/page-contact.less @@ -1,5 +1,5 @@ @import (once) "../include/infopages.less"; -@import (once) "../include/colortheme.less"; +@import (once) "../include/colortheme-all.less"; .infopages_main(); .infopages_topbar(); @@ -7,3 +7,84 @@ .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 new file mode 100644 index 000000000..99df97f9f --- /dev/null +++ b/customize.dist/src/less2/pages/page-faq.less @@ -0,0 +1,37 @@ +@import (once) "../include/infopages.less"; +@import (once) "../include/colortheme-all.less"; + +.infopages_main(); +.infopages_topbar(); + +.cp-faq-header { + padding: 0; + font-size: 1.2em; + a { + padding: 0; + } +} +.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-questions-q:hover { + color: #2e688f; + text-decoration: underline; + } + .cp-faq-questions-a { + display: none; + padding: 0; + } +} + diff --git a/customize.dist/src/less2/pages/page-features.less b/customize.dist/src/less2/pages/page-features.less new file mode 100644 index 000000000..37f2be245 --- /dev/null +++ b/customize.dist/src/less2/pages/page-features.less @@ -0,0 +1,67 @@ +@import (once) "../include/infopages.less"; +@import (once) "../include/colortheme-all.less"; + +.infopages_main(); +.infopages_topbar(); + +@features_th-bg: #555; +@features_th-fg: #fff; +@features_tr-bg-alt: #ddd; +@features_notes: #333; +@features_yes: #c4ffca; +@features_no: #ffc4bc; +@features_part: #faffd3; + +table#cp-features-table { + width: 100%; + th { + background-color: @features_th-bg; + color: @features_th-fg; + padding: 10px; + border: 1px solid @features_th-bg; + } + tbody { + td { + height: 32px; + line-height: 32px; + padding: 5px; + border: 1px solid @features_th-bg; + } + tr:nth-child(odd) { + background-color: @features_tr-bg-alt; + } + } + td:nth-child(4) { + color: @features_notes; + font-size: 14px; + font-style: italic; + } + td:first-child { + font-weight: bold; + } + .yes, .no, .part { + text-align: center; + } + .yes { background-color: @features_yes; } + .no { background-color: @features_no; } + .part { background-color: @features_part; } + .left { + text-align: left; + } +} + +#cp-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); + } +} + diff --git a/customize.dist/src/less2/pages/page-index.less b/customize.dist/src/less2/pages/page-index.less index 90a765a4a..55671f913 100644 --- a/customize.dist/src/less2/pages/page-index.less +++ b/customize.dist/src/less2/pages/page-index.less @@ -1,8 +1,5 @@ -//@import (once) "./variables.less"; - @import (once) "../include/infopages.less"; -@import (once) "../include/colortheme.less"; - +@import (once) "../include/colortheme-all.less"; .infopages_main(); .infopages_topbar(); @@ -96,13 +93,13 @@ body { } @callout-padding: 15px; a:hover { - text-decoration: none + text-decoration: none; } .bs-callout { display: flex; align-items: stretch; margin: 25px 0; - background:rgba(255,255,255,0.6); + background: rgba(255,255,255,0.6); color: black; transition: all .1s ease-in-out; box-sizing: border-box; @@ -139,10 +136,7 @@ h4 { .bs-callout:hover { //color: white; transform: scale(1.05); - cursor: pointer; -} -.bs-callout:hover .fa { - //width: 100%; + cursor: pointer; } .bs-callout:hover.cp-callout-more { transform: none !important; @@ -164,7 +158,6 @@ h4 { .cp-callout-recent .fa { background-color: @colortheme_drive-bg; } .cp-hidden { display: none !important; } .cp-callout-more { - width: auto; display: inline-block; align-content: center; height: 2em; diff --git a/customize.dist/src/less2/pages/page-login.less b/customize.dist/src/less2/pages/page-login.less index 050064348..77dead84c 100644 --- a/customize.dist/src/less2/pages/page-login.less +++ b/customize.dist/src/less2/pages/page-login.less @@ -1,6 +1,7 @@ @import (once) "../include/infopages.less"; -@import (once) "../include/colortheme.less"; +@import (once) "../include/colortheme-all.less"; @import (once) "../include/alertify.less"; +@import (once) "../loading.less"; .infopages_main(); .infopages_topbar(); @@ -11,15 +12,12 @@ display: flex; align-items: center; justify-content: space-between; - width: 100%; - .login { - } } } .cp-container { #data { - background: #4591C4; + background: #4591C4; padding-top: 3em; padding-bottom: 7em; padding-left: 30px; @@ -53,26 +51,18 @@ } .extra { margin-top: 1em; - .cp-login-register { - color: @cryptpad_color_blue; - background: #fff; - border: 2px solid @cryptpad_color_blue; - border-radius: 0; - &:hover { - transform: scale(1.05); - } - } .login { - background: transparent; - color: @cryptpad_color_blue; - padding: 0; + background: @cryptpad_color_blue; + color: #fff; + padding: 10px; + border-radius: 0; &:hover { transform: scale(1.05); - } + } } } } .cp-container { padding-top: 3em; min-height: 66vh; -} \ No newline at end of file +} diff --git a/customize.dist/src/less2/pages/page-privacy.less b/customize.dist/src/less2/pages/page-privacy.less index 59fa4b1fd..a1f8b2955 100644 --- a/customize.dist/src/less2/pages/page-privacy.less +++ b/customize.dist/src/less2/pages/page-privacy.less @@ -1,5 +1,5 @@ @import (once) "../include/infopages.less"; -@import (once) "../include/colortheme.less"; +@import (once) "../include/colortheme-all.less"; .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 82c02edcd..6336c6aa3 100644 --- a/customize.dist/src/less2/pages/page-register.less +++ b/customize.dist/src/less2/pages/page-register.less @@ -1,6 +1,7 @@ @import (once) "../include/infopages.less"; -@import (once) "../include/colortheme.less"; +@import (once) "../include/colortheme-all.less"; @import (once) "../include/alertify.less"; +@import (once) "../loading.less"; .infopages_main(); .infopages_topbar(); @@ -17,6 +18,9 @@ } } #register { + &.btn { + padding: .5rem .5rem; + } margin-top: 16px; font-size: 1.25em; min-width: 30%; // conflict? @@ -48,14 +52,22 @@ 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 { - background: #4591C4; /* fallback for old browsers */ - background: -webkit-linear-gradient(to right, #FF7C4F, #4592C4); /* Chrome 10-25, Safari 5.1-6 */ - background: linear-gradient(to right, #FF7C4F, #4592C4); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ + // 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; diff --git a/customize.dist/src/less2/pages/page-terms.less b/customize.dist/src/less2/pages/page-terms.less index 59fa4b1fd..a1f8b2955 100644 --- a/customize.dist/src/less2/pages/page-terms.less +++ b/customize.dist/src/less2/pages/page-terms.less @@ -1,5 +1,5 @@ @import (once) "../include/infopages.less"; -@import (once) "../include/colortheme.less"; +@import (once) "../include/colortheme-all.less"; .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 cf127e1ec..0e02ce41a 100644 --- a/customize.dist/src/less2/pages/page-what-is-cryptpad.less +++ b/customize.dist/src/less2/pages/page-what-is-cryptpad.less @@ -1,5 +1,5 @@ @import (once) "../include/infopages.less"; -@import (once) "../include/colortheme.less"; +@import (once) "../include/colortheme-all.less"; .infopages_main(); .infopages_topbar(); @@ -28,7 +28,7 @@ color: @cryptpad_header_col; } p { - color: @cryptpad_text_col + color: @cryptpad_text_col; } #zeroknowledge { width: 65%; @@ -40,4 +40,4 @@ display: block; margin: 0 auto; } -} \ No newline at end of file +} diff --git a/customize.dist/store.js b/customize.dist/store.js deleted file mode 100644 index 5b82921ed..000000000 --- a/customize.dist/store.js +++ /dev/null @@ -1,97 +0,0 @@ -define(function () { - /* - This module uses localStorage, which is synchronous, but exposes an - asyncronous API. This is so that we can substitute other storage - methods. - - To override these methods, create another file at: - /customize/storage.js - */ - - var Store = {}; - - // Store uses nodebacks... - Store.set = function (key, val, cb) { - localStorage.setItem(key, JSON.stringify(val)); - cb(); - }; - - // implement in alternative store - Store.setBatch = function (map, cb) { - Object.keys(map).forEach(function (key) { - localStorage.setItem(key, JSON.stringify(map[key])); - }); - cb(void 0, map); - }; - - var safeGet = window.safeGet = function (key) { - var val = localStorage.getItem(key); - try { - return JSON.parse(val); - } catch (err) { - console.log(val); - console.error(err); - return val; - } - }; - - Store.get = function (key, cb) { - cb(void 0, safeGet(key)); - }; - - // implement in alternative store - Store.getBatch = function (keys, cb) { - var res = {}; - keys.forEach(function (key) { - res[key] = safeGet(key); - }); - cb(void 0, res); - }; - - Store.remove = function (key, cb) { - localStorage.removeItem(key); - cb(); - }; - - // implement in alternative store - Store.removeBatch = function (keys, cb) { - keys.forEach(function (key) { - localStorage.removeItem(key); - }); - cb(); - }; - - Store.keys = function (cb) { - cb(void 0, Object.keys(localStorage)); - }; - - Store.ready = function (f) { - if (typeof(f) === 'function') { - f(void 0, Store); - } - }; - - var changeHandlers = Store.changeHandlers = []; - - Store.change = function (f) { - if (typeof(f) !== 'function') { - throw new Error('[Store.change] callback must be a function'); - } - changeHandlers.push(f); - - if (changeHandlers.length === 1) { - // start listening for changes - window.addEventListener('storage', function (e) { - changeHandlers.forEach(function (f) { - f({ - key: e.key, - oldValue: e.oldValue, - newValue: e.newValue, - }); - }); - }); - } - }; - - return Store; -}); diff --git a/customize.dist/template.js b/customize.dist/template.js index 062be4308..0ee34d407 100644 --- a/customize.dist/template.js +++ b/customize.dist/template.js @@ -1,16 +1,12 @@ define([ 'jquery', '/common/hyperscript.js', - '/common/cryptpad-common.js', '/customize/pages.js', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', -], function ($, h, Cryptpad, Pages) { +], function ($, h, Pages) { $(function () { var $body = $('body'); - var isMainApp = function () { - return /^\/(pad|code|slide|poll|whiteboard|file|media|contacts|drive|settings|profile|todo)\/$/.test(location.pathname); - }; var infoPage = function () { return h('div#mainBlock.hidden', typeof(Pages[location.pathname]) === 'function'? @@ -21,79 +17,17 @@ $(function () { var pathname = location.pathname; - if (isMainApp()) { - if (typeof(Pages[pathname]) === 'function') { - var $flash = $('body, #iframe-container, #pad-iframe, textarea'); - $flash.css({ - display: 'none', - opacity: 0, - overflow: 'hidden', - }); - var ready = function () { - $flash.css({ - display: '', - opacity: '', - overflow: '', - }); - }; - - require([ - 'less!/customize/src/less/loading.less' - ], function () { - if (/whiteboard/.test(pathname)) { - $('body').html(h('body', Pages[pathname]()).innerHTML); - require(['/whiteboard/main.js'], ready); - } else if (/poll/.test(pathname)) { - $('body').html(h('body', Pages[pathname]()).innerHTML); - require(['/poll/main.js'], ready); - } else if (/drive/.test(pathname)) { - $('body').append(h('body', Pages[pathname]()).innerHTML); - require(['/drive/main.js'], ready); - } else if (/\/file\//.test(pathname)) { - $('body').append(h('body', Pages[pathname]()).innerHTML); - require([ '/file/main.js' ], ready); - } else if (/^\/contacts\/$/.test(pathname)) { - $('body').append(h('body', Pages[pathname]()).innerHTML); - require([ '/contacts/main.js' ], ready); - } else if (/pad/.test(pathname)) { - $('body').append(h('body', Pages[pathname]()).innerHTML); - require([ '/pad/main.js' ], ready); - } else if (/code/.test(pathname)) { - $('body').append(h('body', Pages[pathname]()).innerHTML); - require([ '/code/main.js' ], ready); - } else if (/slide/.test(pathname)) { - $('body').append(h('body', Pages[pathname]()).innerHTML); - require([ '/slide/main.js' ], ready); - } else if (/^\/settings\//.test(pathname)) { - $('body').append(h('body', Pages[pathname]()).innerHTML); - require([ '/settings/main.js', ], ready); - } else if (/^\/profile\//.test(pathname)) { - $('body').append(h('body', Pages[pathname]()).innerHTML); - require([ '/profile/main.js', ], ready); - } else if (/^\/todo\//.test(pathname)) { - $('body').append(h('body', Pages[pathname]()).innerHTML); - require([ '/todo/main.js', ], ready); - } - }); - - return; - } - } else { - // add class on info-pages - var css = location.pathname.replace(/(index)?\.html$/gi, "") // .html - .replace(/[^a-zA-Z]+/gi, '-') // any non-alpha character - .replace(/^-|-$/g, ''); // starting/trailing dashes - if (css === '') - { - css = 'index'; - } - $('body').addClass('cp-page-' + css); - } + // add class on info-pages + var css = location.pathname.replace(/(index)?\.html$/gi, "") // .html + .replace(/[^a-zA-Z]+/gi, '-') // any non-alpha character + .replace(/^-|-$/g, ''); // starting/trailing dashes + if (css === '') { css = 'index'; } + $('body').addClass('cp-page-' + css); + 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.min.js' + 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css' ], function () { $body.append($main); @@ -108,6 +42,8 @@ $(function () { require([ '/customize/main.js', ], function () {}); } else if (/invite/.test(pathname)) { require([ '/invite/main.js'], function () {}); + } else if (/faq/.test(pathname)) { + window.location.hash = window.location.hash; } else { require([ '/customize/main.js', ], function () {}); } diff --git a/customize.dist/translations/README.md b/customize.dist/translations/README.md index e7323cade..422c9707b 100644 --- a/customize.dist/translations/README.md +++ b/customize.dist/translations/README.md @@ -111,3 +111,9 @@ These tests will check to make sure that your translation has an entry for every If you have any issues, reach out via any of the methods listed in the readme under **Contacting Us**. We're happy to help. +## Deleting a translation +When a key is nolonger used (such as presentSuccess) you can delete it using this bash one-liner. + +```shell +( export KEY=presentSuccess && grep -nr "$KEY" ./customize.dist/translations/ | sed 's/:.*$//' | while read x; do sed -i -e "/out\.$KEY =/d" $x; done ) +``` \ No newline at end of file diff --git a/customize.dist/translations/messages.de.js b/customize.dist/translations/messages.de.js index ec4d092a5..57e94ac16 100644 --- a/customize.dist/translations/messages.de.js +++ b/customize.dist/translations/messages.de.js @@ -53,7 +53,6 @@ out.shareSuccess = 'Die URL wurde in die Zwischenablage kopiert'; out.presentButtonTitle = "Präsentationsmodus starten"; - out.presentSuccess = 'Drücke ESC um den Präsentationsmodus zu verlassen!'; out.backgroundButtonTitle = 'Die Hintergrundfarbe der Präsentation ändern'; out.colorButtonTitle = 'Die Textfarbe im Präsentationsmodus ändern'; diff --git a/customize.dist/translations/messages.el.js b/customize.dist/translations/messages.el.js new file mode 100644 index 000000000..493be541d --- /dev/null +++ b/customize.dist/translations/messages.el.js @@ -0,0 +1,778 @@ +define(function () { + var out = {}; + + + + out.main_title = "CryptPad: Zero Knowledge, συνεργατική επεξεργασία σε πραγματικό χρόνο"; + out.main_slogan = "Ισχύς εν τη ενώσει - Η συνεργασία είναι η λύση"; // TODO remove? + + out.type = {}; + out.type.pad = 'Εμπλουτισμένο κείμενο'; + out.type.code = 'Κώδικας'; + out.type.poll = 'Δημοσκόπηση'; + out.type.slide = 'Παρουσίαση'; + out.type.drive = 'Αποθηκευτικός χώρος'; + out.type.whiteboard = 'Πίνακας σχεδιασμού'; + out.type.file = 'Αρχείο'; + out.type.media = 'Πολυμέσα'; + out.type.todo = "Εργασίες"; + out.type.contacts = 'Επαφές'; + + out.button_newpad = 'Νέο pad εμπλουτισμένου κειμένου'; + out.button_newcode = 'Νέο pad κώδικα'; + out.button_newpoll = 'Νέα δημοσκόπηση'; + out.button_newslide = 'Νέα παρουσίαση'; + out.button_newwhiteboard = 'Νέος πίνακας'; + + // NOTE: We want to update the 'common_connectionLost' key. + // Please do not add a new 'updated_common_connectionLostAndInfo' but change directly the value of 'common_connectionLost' + out.updated_0_common_connectionLost = "Η σύνδεση με τον διακομιστή χάθηκε
Βρίσκεστε σε λειτουργία ανάγνωσης μόνο μέχρι να επανέλθει η σύνδεση."; + out.common_connectionLost = out.updated_0_common_connectionLost; + + out.websocketError = 'Αδυναμία σύνδεσης στον διακομιστή...'; + out.typeError = "Αυτό το pad δεν είναι συμβατό με την επιλεγμένη εφαρμογή"; + out.onLogout = 'Έχετε αποσυνδεθεί, {0}κάντε "κλικ" εδώ{1} για να συνδεθείτε
ή πατήστε Escape για να προσπελάσετε το έγγραφο σε λειτουργία ανάγνωσης μόνο.'; + out.wrongApp = "Αδυναμία προβολής του περιεχομένου αυτής της συνεδρίας στον περιηγητή σας. Παρακαλώ δοκιμάστε επαναφόρτωση της σελίδας."; + + out.loading = "Φόρτωση..."; + out.error = "Σφάλμα"; + out.saved = "Αποθηκεύτηκε"; + out.synced = "Όλα έχουν αποθηκευτεί"; + out.deleted = "Το έγγραφο διαγράφηκε από τον αποθηκευτικό σας χώρο"; + + out.realtime_unrecoverableError = "Η μηχανή πραγματικού χρόνου αντιμετώπισε κάποιο ανεπανόρθωτο σφάλμα. Πατήστε OK για επαναφόρτωση."; + + out.disconnected = 'Έγινε αποσύνδεση'; + out.synchronizing = 'Γίνεται συγχρονισμός'; + out.reconnecting = 'Γίνεται επανασύνδεση...'; + out.typing = "Γίνεται επεξεργασία"; + out.initializing = "Γίνεται προετοιμασία..."; + out.forgotten = 'Μετακινήθηκε στον κάδο ανακύκλωσης'; + out.errorState = 'Κρίσιμο σφάλμα: {0}'; + out.lag = 'Αργή σύνδεση'; + out.readonly = 'Λειτουργία ανάγνωσης μόνο'; + out.anonymous = "Ανώνυμος/η"; + out.yourself = "Ο εαυτός σας"; + out.anonymousUsers = "Ανώνυμοι συντάκτες"; + out.anonymousUser = "Ανώνυμος συντάκτης"; + out.users = "Χρήστες"; + out.and = "Και"; + out.viewer = "Θεατής"; + out.viewers = "Θεατές"; + out.editor = "Συντάκτης"; + out.editors = "Συντάκτες"; + out.userlist_offline = "Είσαστε προς το παρόν εκτός σύνδεσης, η λίστα χρηστών δεν είναι διαθέσιμη."; + + out.language = "Γλώσσα"; + + out.comingSoon = "Έρχεται σύντομα..."; + + out.newVersion = 'To CryptPad αναβαθμίστηκε!
' + + 'Δείτε τι καινούριο υπάρχει στην πιο πρόσφατη έκδοση:
'+ + 'Σημειώσεις κυκλοφορίας του CryptPad {0}'; + + out.upgrade = "Αναβάθμιση"; + out.upgradeTitle = "Αναβαθμίστε τον λογαριασμό σας για να αυξήσετε το όριο αποθηκευτικού χώρου"; + + out.upgradeAccount = "Αναβάθμιση λογαριασμού"; + out.MB = "MB"; + out.GB = "GB"; + out.KB = "KB"; + + out.supportCryptpad = "Υποστηρίξτε το CryptPad"; + + out.formattedMB = "{0} MB"; + out.formattedGB = "{0} GB"; + out.formattedKB = "{0} KB"; + + out.greenLight = "Όλα λειτουργούν σωστά"; + out.orangeLight = "Η αργή σύνδεση ίσως έχει αντίκτυπο στην διάδραση"; + out.redLight = "Έχετε αποσυνδεθεί από τη συνεδρία"; + + out.pinLimitReached = "Έχετε φτάσει το όριο αποθηκευτικού χώρου"; + out.updated_0_pinLimitReachedAlert = "Έχετε φτάσει το όριο αποθηκευτικού χώρου. Τα νέα pads δεν θα αποθηκευτούν στο CryptDrive σας.
" + + 'Μπορείτε είτε να διαγράψετε αρχεία από το CryptDrive σας, είτε να αναβαθμισετε τον λογαριασμό σας για να αυξήσετε το όριο αποθήκευσης.'; + out.pinLimitReachedAlert = out.updated_0_pinLimitReachedAlert; + out.pinLimitReachedAlertNoAccounts = out.pinLimitReached; + out.pinLimitNotPinned = "Έχετε φτάσει το όριο αποθηκευτικού χώρου.
"+ + "Αυτό το pad δεν θα αποθηκευτεί στο CryptDrive σας."; + out.pinLimitDrive = "Έχετε φτάσει το όριο αποθηκευτικού χώρου.
" + + "Δεν μπορείτε να δημιουργήσετε νέα pads."; + + out.moreActions = "Περισσότερες επιλογές"; + + out.importButton = "Εισαγωγή"; + out.importButtonTitle = 'Εισάγετε ένα pad από τοπικό αρχείο'; + + out.exportButton = "Εξαγωγή"; + out.exportButtonTitle = 'Εξάγετε αυτό το pad σε τοπικό αρχείο'; + out.exportPrompt = 'Πως θα θέλατε να ονομάσετε το αρχείο σας;'; + + out.changeNamePrompt = 'Αλλάξτε το όνομα σας (αφήστε το κενό για ανωνυμία): '; + out.user_rename = "Αλλαγή εμφανιζόμενου ονόματος"; + out.user_displayName = "Εμφανιζόμενο όνομα"; + out.user_accountName = "Όνομα χρήστη"; + + out.clickToEdit = "Κάντε \"κλικ\" για επεξεργασία"; + out.saveTitle = "Αποθήκευση τίτλου (enter)"; + + out.forgetButton = "Διαγραφή"; + out.forgetButtonTitle = 'Μετακίνηση αυτού του pad στον κάδο'; + out.forgetPrompt = 'Πατώντας OK θα μετακινηθεί αυτό το pad στον κάδο ανακύκλωσης. Είστε σίγουρος;'; + out.movedToTrash = 'Το pad μετακινήθηκε στον κάδο.
Μεταφερθείτε στο CryptDrive σας'; + + out.shareButton = 'Διαμοιρασμός'; + out.shareSuccess = 'Ο σύνδεσμος αντιγράφηκε στην προσωρινή μνήμη'; + + out.userListButton = "Λίστα χρηστών"; + + out.userAccountButton = "Ο λογαριασμός σας"; + + out.newButton = 'Νέο'; + out.newButtonTitle = 'Δημιουργία νέου pad'; + out.uploadButton = 'Μεταφόρτωση αρχείου'; + out.uploadButtonTitle = 'Μεταφόρτωση νέου αρχείου στον τρέχοντα φάκελο'; + + out.saveTemplateButton = "Αποθήκευση ως πρότυπο"; + out.saveTemplatePrompt = "Επιλέξτε τίτλο για αυτό το πρότυπο"; + out.templateSaved = "Το πρότυπο αποθηκεύτηκε!"; + out.selectTemplate = "Επιλέξτε ένα πρότυπο ή πατήστε escape"; + out.useTemplate = "Έχετε διαθέσιμα πρότυπα για αυτό το είδος pad. Θα θέλετε να χρησιμοποιήσετε κάποιο;"; //Would you like to "You have available templates for this type of pad. Do you want to use one?"; + out.useTemplateOK = 'Επιλέξτε ένα πρότυπο (Enter)'; + out.useTemplateCancel = 'Ξεκινήστε από το μηδέν (Esc)'; + + out.previewButtonTitle = "Προβολή ή απόκρυψη προεπισκόπησης της μορφοποίησης Markdown"; + + out.presentButtonTitle = "Είσοδος σε λειτουργία παρουσίασης"; + + out.backgroundButtonTitle = 'Αλλάξτε το χρώμα παρασκηνίου στην παρουσίαση'; + out.colorButtonTitle = 'Αλλάξτε το χρώμα κειμένου στην λειτουργία παρουσίασης'; + + out.printText = "Εκτύπωση"; + out.printButton = "Εκτύπωση (enter)"; + out.printButtonTitle = "Εκτυπώστε τις διαφάνειές σας ή εξάγετε τες ως αρχείο PDF"; + out.printOptions = "Επιλογές διάταξης"; + out.printSlideNumber = "Εμφάνιση του αριθμού διαφάνειας"; + out.printDate = "Εμφάνιση της ημερομηνίας"; + out.printTitle = "Εμφάνιση του τίτλου του pad"; + out.printCSS = "Προσαρμοσμένες ρυθμίσεις εμφάνισης (CSS):"; + out.printTransition = "Ενεργοποίηση κινούμενων μεταβάσεων"; + + out.filePickerButton = "Ενσωμάτωση αρχείου από το CryptDrive σας"; + out.filePicker_close = "Κλείσιμο"; + out.filePicker_description = "Επιλέξτε ένα αρχείο από το CryptDrive σας για ενσωμάτωση ή μεταφορτώστε ένα καινούριο"; + out.filePicker_filter = "Προβολή αρχείων κατά όνομα"; + out.or = 'ή'; + + out.tags_title = "Ετικέτες (για εσάς μόνο)"; + out.tags_add = "Ενημερώστε τις ετικέτες αυτής της σελίδας"; + out.tags_searchHint = "Βρείτε αρχεία από τις ετικέτες τους ψάχνωντας στο CryptDrive σας"; + out.tags_searchHint = "Ξεκινήστε μια αναζήτηση με το σύμβολο # στο CryptDrive σας για να βρείτε pads με ετικέτες."; + out.tags_notShared = "Οι ετικέτες σας δεν μοιράζονται με άλλους χρήστες"; + out.tags_duplicate = "Διπλή ετικέτα: {0}"; + out.tags_noentry = "Δεν μπορείτε να βάλετε ετικέτα σε διεγραμένο pad!"; + + out.slideOptionsText = "Επιλογές"; + out.slideOptionsTitle = "Προσαρμόστε τις διαφάνειες σας"; + out.slideOptionsButton = "Αποθήκευση (enter)"; + out.slide_invalidLess = "Μη έγκυρη προσαρμογή"; + + out.languageButton = "Γλώσσα"; + out.languageButtonTitle = "Επιλέξτε τη γλώσσα που θα χρησιμοποιήσετε για την επισήμανση σύνταξης"; + out.themeButton = "Θέμα"; + out.themeButtonTitle = "Επιλέξτε το θέμα που θα χρησιμοποιήσετε για την επεξεργασία κώδικα και διαφανειών"; + + out.editShare = "Σύνδεσμος επεξεργασίας"; + out.editShareTitle = "Αντιγραφή του συνδέσμου επεξεργασίας στην προσωρινή μνήμη"; + out.editOpen = "Άνοιγμα του συνδέσμου επεξεργασίας σε νέα καρτέλα"; + out.editOpenTitle = "Άνοιγμα αυτού του pad για επεξεργασία σε νέα καρτέλα"; + out.viewShare = "Σύνδεσμος μόνο για ανάγνωση"; + out.viewShareTitle = "Αντιγραφή του συνδέσμου μόνο για ανάγνωση στην προσωρινή μνήμη"; + out.viewOpen = "Άνοιγμα του συνδέσμου μόνο για ανάγνωση σε νέα καρτέλα"; + out.viewOpenTitle = "Άνοιγμα αυτού του pad μόνο για ανάγνωση σε νέα καρτέλα"; + out.fileShare = "Αντιγραφή συνδέσμου"; + out.getEmbedCode = "Κώδικας ενσωμάτωσης"; + out.viewEmbedTitle = "Ενσωματώστε αυτό το pad σε μία εξωτερική σελίδα"; + out.viewEmbedTag = "Για να ενσωματώσετε αυτό το pad, συμπεριλάβετε αυτό το iframe στη σελίδα σας, στο σημείο που θέλετε. Μπορείτε να το διαμορφώσετε χρησιμοποιώντας CSS η HTML παραμέτρους."; + out.fileEmbedTitle = "Ενσωματώστε το αρχείο σε μια εξωτερική σελίδα"; + out.fileEmbedScript = "Για να ενσωματώσετε αυτό το αρχείο, συμπεριλάβετε αυτό το script στη σελίδα σας για να φορτωθεί το Media Tag:"; + out.fileEmbedTag = "Έπειτα τοποθετήστε αυτό το Media Tag στο σημείο της σελίδας που επιθυμείτε να γίνει ενσωμάτωση:"; + + out.notifyJoined = "Ο/Η {0} εισήλθε στη συνεργατική συνεδρία"; + out.notifyRenamed = "Ο/Η {0} είναι τώρα γνωστός/η ως {1}"; + out.notifyLeft = "Ο/Η {0} αποχώρησε από τη συνεργατική συνεδρία"; + + out.okButton = 'OK (enter)'; + + out.cancel = "Ακύρωση"; + out.cancelButton = 'Ακύρωση (esc)'; + out.doNotAskAgain = "Να μην ρωτηθώ ξανά (Esc)"; + + out.historyText = "Ιστορικό"; + out.historyButton = "Εμφάνιση ιστορικού του εγγράφου"; + out.history_next = "Μετάβαση στην επόμενη έκδοση"; + out.history_prev = "Μετάβαση στην προηγούμενη έκδοση"; + out.history_goTo = "Μετάβαση στην επιλεγμένη έκδοση"; + out.history_close = "Επιστροφή"; + out.history_closeTitle = "Κλείσιμο ιστορικού"; + out.history_restore = "Επαναφορά"; + out.history_restoreTitle = "Επαναφορά της επιλεγμένης έκδοσης του εγγράφου"; + out.history_restorePrompt = "Είστε σίγουροι πως θέλετε να αντικαταστήσετε την τρέχουσα έκδοση του εγγράφου με την επιλεγμένη;"; + out.history_restoreDone = "Έγινε επαναφορά του εγγράφου"; + out.history_version = "Έκδοση:"; + + // Ckeditor + out.openLinkInNewTab = "Άνοιγμα συνδέσμου σε νέα καρτέλα"; + out.pad_mediatagTitle = "Ρυθμίσεις Media-Tag"; + out.pad_mediatagWidth = "Πλάτος (px)"; + out.pad_mediatagHeight = "Ύψος (px)"; + + // Polls + + out.poll_title = "Zero Knowledge επιλογή ημερομηνίας"; + out.poll_subtitle = "Zero Knowledge, πραγματικού χρόνου οργάνωση"; + + out.poll_p_save = "Οι ρυθμίσεις σας ενημερώνονται άμεσα, έτσι δεν χρειάζεται ποτέ να αποθηκεύσετε."; + out.poll_p_encryption = "Όλο το περιεχόμενο είναι κρυπτογραφημένο και έτσι μόνο τα άτομα που έχουν τον σύνδεσμο μπορούν να έχουν πρόσβαση σε αυτό. Ούτε ο διακομιστής δεν μπορεί να δει τι γράφετε."; + + out.wizardLog = "Πατήστε το κουμπί πάνω αριστερά για να επιστρέψετε στη δημοσκόπηση σας"; + out.wizardTitle = "Χρησιμοποιήστε τον οδηγό για να δημιουργήσετε τη δημοσκόπηση σας"; + out.wizardConfirm = "Είσαστε έτοιμοι να προσθέσετε αυτές τις επιλογές στη δημοσκόπηση σας;"; + + out.poll_publish_button = "Δημοσίευση"; + out.poll_admin_button = "Διαχείριση"; + out.poll_create_user = "Προσθέστε έναν νέο χρήστη"; + out.poll_create_option = "Προσθέστε μια νέα επιλογή"; + out.poll_commit = "Υποβολή"; + + out.poll_closeWizardButton = "Κλείσιμο οδηγού"; + out.poll_closeWizardButtonTitle = "Κλείσιμο οδηγού"; + out.poll_wizardComputeButton = "Υπολογισμός επιλογών"; + out.poll_wizardClearButton = "Εκκαθάριση πεδίων"; + out.poll_wizardDescription = "Αυτόματα δημιουργήστε έναν αριθμό επιλογών εισάγοντας όσες ημερομηνίες και χρόνους θέλετε"; + out.poll_wizardAddDateButton = "+ Ημερομηνίες"; + out.poll_wizardAddTimeButton = "+ Χρόνους"; + + out.poll_optionPlaceholder = "Επιλογή"; + out.poll_userPlaceholder = "Το όνομα σας"; + out.poll_removeOption = "Είστε σίγουροι πως θέλετε να αφαιρέσετε αυτή την επιλογή;"; + out.poll_removeUser = "Είστε σίγουροι πως θέλετε να αφαιρέσετε αυτόν τον χρήστη;"; + + out.poll_titleHint = "Τίτλος"; + out.poll_descriptionHint = "Περιγράψτε τη δημοσκόπηση σας και χρησιμοποιήστε το κουμπί ✓ (δημοσίευση) όταν έχετε τελειώσει.\n" + + "Η περιγραφή μπορεί να γραφτεί χρησιμοποιώντας μορφοποίηση markdown και μπορείτε να ενσωματώσετε γραφικά στοιχεία από το CryptDrive σας.\n" + + "Οποιοσδήποτε με τον σύνδεσμο της δημοσκόπησης μπορεί να αλλάξει την περιγραφή, αλλά αυτό δεν συνίσταται."; + + out.poll_remove = "Αφαίρεση"; + out.poll_edit = "Επεξεργασία"; + out.poll_locked = "Κλείδωμα"; + out.poll_unlocked = "Ξεκλείδωμα"; + + out.poll_show_help_button = "Εμφάνιση βοήθειας"; + out.poll_hide_help_button = "Απόκρυψη βοήθειας"; + + out.poll_bookmark_col = 'Αποθηκεύστε αυτή τη στήλη ώστε να είναι πάντα ξεκλείδωτη και εμφανής κατά την εκκίνηση για εσάς'; + out.poll_bookmarked_col = 'Αυτή είναι η στήλη σελιδοδεικτών σας. Θα είναι πάντα ξεκλείδωτη και εμφανής κατά την εκκίνηση για εσάς.'; + out.poll_total = 'Σύνολο'; + + out.poll_comment_list = "Σχόλια"; + out.poll_comment_add = "Κάντε ένα σχόλιο"; + out.poll_comment_submit = "Αποστολή"; + out.poll_comment_remove = "Διαγράψτε αυτό το σχόλιο"; + out.poll_comment_placeholder = "Το σχόλιό σας"; + + out.poll_comment_disabled = "Δημοσιεύστε αυτή τη δημοσκόπηση χρησημοποιώντας το κουμπί ✓ για να ενεργοποιηθεί ο σχολιασμός."; + + // Canvas + out.canvas_clear = "Εκκαθάριση"; + out.canvas_delete = "Διαγραφή επιλογής"; + out.canvas_disable = "Απενεργοποίηση σχεδιασμού"; + out.canvas_enable = "Ενεργοποίηση σχεδιασμού"; + out.canvas_width = "Πλάτος"; + out.canvas_opacity = "Αδιαφάνεια"; + out.canvas_opacityLabel = "Αδιαφάνεια: {0}"; + out.canvas_widthLabel = "Πλάτος: {0}"; + out.canvas_saveToDrive = "Αποθηκεύστε αυτή την εικόνα ως αρχείο στο CryptDrive σας"; + out.canvas_currentBrush = "Τρέχων πινέλο"; + out.canvas_chooseColor = "Επιλογή χρώματος"; + out.canvas_imageEmbed = "Εισάγετε μια εικόνα από τον υπολογιστή σας"; + + // Profile + out.profileButton = "Προφίλ"; // dropdown menu + out.profile_urlPlaceholder = 'Διεύθυνση'; + out.profile_namePlaceholder = 'Το όνομα που θα εμφανίζετε στο προφίλ σας'; + out.profile_avatar = "Αβατάρ"; + out.profile_upload = " Μεταφορτώστε ένα νέο αβατάρ"; + out.profile_uploadSizeError = "Σφάλμα: το αβατάρ σας πρέπει να είναι μικρότερο από {0}"; + out.profile_uploadTypeError = "Σφάλμα: αυτό το είδος αρχείου δεν επιτρέπεται. Επιτρεπόμενα αρχεία: {0}"; + out.profile_error = "Σφάλμα κατά τη δημιουργία του προφίλ σας: {0}"; + out.profile_register = "Πρέπει να εγγραφείτε για να δημιουργήσετε προφίλ!"; + out.profile_create = "Δημιουργήστε προφίλ"; + out.profile_description = "Περιγραφή"; + out.profile_fieldSaved = 'Η καινούρια καταχώρηση αποθηκεύτηκε: {0}'; + + out.profile_inviteButton = "Σύνδεση"; + out.profile_inviteButtonTitle ='Δημιουργήστε έναν σύνδεσμο για να προσκαλέσετε αυτόν το χρήστη να συνδεθεί μαζί σας.'; + out.profile_inviteExplanation = "Πατώντας OK θα δημιουργηθεί ένας σύνδεσμος προς μια ασφαλή συνεδρία επικοινωνίας όπου μόνο ο/η {0} θα μπορεί να ανοίξει.

Ο σύνδεσμος θα αντιγραφεί στην προσωρινή μνήμη και μπορεί να διαμοιραστεί δημόσια."; + out.profile_viewMyProfile = "Προβολή του προφίλ μου"; + + // contacts/userlist + out.userlist_addAsFriendTitle = 'Προσθήκη του/της "{0}" ως επαφή'; + out.userlist_thisIsYou = 'Αυτός είστε εσείς ("{0}")'; + out.userlist_pending = "Εκρεμμεί..."; + out.contacts_title = "Επαφές"; + out.contacts_addError = 'Σφάλμα κατά την προσθήκη αυτής της επαφής στη λίστα'; + out.contacts_added = 'Η επαφή αποδέχτηκε την πρόσκληση.'; + out.contacts_rejected = 'Η επαφή απέρριψε την πρόσκληση'; + out.contacts_request = 'Ο/Η {0} Θα ήθελε να σας προσθέσει ως επαφή. Αποδοχή;'; + out.contacts_send = 'Αποστολή'; + out.contacts_remove = 'Αφαίρεση αυτής της επαφής'; + out.contacts_confirmRemove = 'Είσαστε σίγουροι πως θέλετε να αφαιρέσετε τον/την {0} από τις επαφές σας;'; + out.contacts_typeHere = "Πληκτρολογήστε ένα μήνυμα εδώ..."; + + out.contacts_info1 = "Αυτές είναι οι επαφές σας. Από εδώ, μπορείτε να:"; + out.contacts_info2 = "Πατήσετε στο εικονίδιο της επαφής για να συνομιλήσετε μαζί τους"; + out.contacts_info3 = "Κάνετε \"διπλό κλικ\" στο εικονίδιο για να δείτε το προφίλ τους"; + out.contacts_info4 = "Ο κάθε συμμετέχων μπορεί να διαγράψει μόνιμα το ιστορικό μιας συνομιλίας"; + + out.contacts_removeHistoryTitle = 'Εκκαθάριση του ιστορικού συνομιλίας'; + out.contacts_confirmRemoveHistory = 'Είστε σίγουροι πως θέλετε να διαγράψετε μόνιμα το ιστορικό; Τα δεδομένα δεν μπορούν να επαναφερθούν'; + out.contacts_removeHistoryServerError = 'Προέκυψε ένα σφάλμα κατά της εκκαθάριση του ιστορικού. Δοκιμάστε ξανά αργότερα'; + out.contacts_fetchHistory = "Ανάκτηση παλαιότερου ιστορικού"; + + // File manager + + out.fm_rootName = "Έγγραφα"; + out.fm_trashName = "Σκουπίδια"; + out.fm_unsortedName = "Αταξινόμητα"; + out.fm_filesDataName = "Όλα τα αρχεία"; + out.fm_templateName = "Πρότυπα"; + out.fm_searchName = "Αναζήτηση"; + out.fm_recentPadsName = "Πρόσφατα pads"; + out.fm_searchPlaceholder = "Αναζήτηση..."; + out.fm_newButton = "Νέο"; + out.fm_newButtonTitle = "Δημιουργήστε ένα νέο pad ή φάκελο, εισάγετε ένα αρχείο στον τρέχοντα φάκελο"; + out.fm_newFolder = "Νέος φάκελος"; + out.fm_newFile = "Νέο pad"; + out.fm_folder = "Φάκελος"; + out.fm_folderName = "Όνομα φακέλου"; + out.fm_numberOfFolders = "# φακέλων"; + out.fm_numberOfFiles = "# αρχείων"; + out.fm_fileName = "Όνομα αρχείου"; + out.fm_title = "Τίτλος"; + out.fm_type = "Τύπος"; + out.fm_lastAccess = "Τελευταία προσπέλαση"; + out.fm_creation = "Δημιουργία"; + out.fm_forbidden = "Απαγορευμένη ενέργεια"; + out.fm_originalPath = "Πρωτότυπη διαδρομή"; + out.fm_openParent = "Προβολή στον φάκελο"; + out.fm_noname = "Έγγραφο χωρίς τίτλο"; + out.fm_emptyTrashDialog = "Θέλετε σίγουρα να αδειάσετε τον κάδο;"; + out.fm_removeSeveralPermanentlyDialog = "Θέλετε σίγουρα να αφαιρέσετε αυτά τα {0} αντικείμενα από το CryptDrive σας μόνιμα;"; + out.fm_removePermanentlyDialog = "Θέλετε σίγουρα να αφαιρέσετε αυτό το αντικείμενο από το CryptDrive σας μόνιμα;"; + out.fm_removeSeveralDialog = "Θέλετε σίγουρα να μετακινήσετε αυτά τα {0} αντικείμενα στον κάδο;"; + out.fm_removeDialog = "Θέλετε σίγουρα να μετακινήσετε το {0} στον κάδο;"; + out.fm_restoreDialog = "Θέλετε σίγουρα να επαναφέρετε το {0} στην προηγούμενη τοποθεσία του;"; + out.fm_unknownFolderError = "Η επιλεγμένη ή πιο πρόσφατη τοποθεσία δεν υπάρχει πλέον. Γίνεται άνοιγμα του τρέχοντα φακέλου..."; + out.fm_contextMenuError = "Αδυναμία ανοίγματος μενού για αυτό το αντικείμενο. Αν το πρόβλημα επιμείνει, δοκιμάστε να επαναφορτώσετε τη σελίδα."; + out.fm_selectError = "Αδυναμία επιλογής του συγκεκριμένου αντικειμένου. Αν το πρόβλημα επιμείνει, δοκιμάστε να επαναφορτώσετε τη σελίδα."; + out.fm_categoryError = "Αδυναμία ανοίγματος της επιλεγμένης κατηγορίας, γίνεται προβολή του γονικού φακέλου."; + out.fm_info_root = "Δημιουργήστε εδώ όσους υποφακέλους θέλετε για να ταξινομήσετε τα αρχεία σας."; + out.fm_info_unsorted = 'Περιέχει όλα τα αρχεία που έχετε επισκεφτεί αλλά δεν έχουν ταξινομηθεί στα "Έγγραφα", ούτε έχουν μετακινηθεί στα "Σκουπίδια".'; // "My Documents" should match with the "out.fm_rootName" key, and "Trash" with "out.fm_trashName" + out.fm_info_template = 'Περιέχει όλα τα pads που έχουν αποθηκευτεί ως πρότυπα και μπορείτε να ξαναχρησιμοποιήσετε όταν δημιουργείτε ένα νέο pad.'; + out.fm_info_recent = "Λίστα των πρόσφατα τροποποιημένων ή ανοιγμένων pads."; + out.updated_0_fm_info_trash = 'Αδειάστε τον κάδο σας για να απελευθερώσετε χώρο στο CryptDrive σας.'; + out.fm_info_trash = out.updated_0_fm_info_trash; + out.fm_info_allFiles = 'Περιέχει όλα τα αρχεία από τα "Έγγραφα", "Αταξινόμητα" και "Σκουπίδια". Δεν μπορείτε να μετακινήσετε ή να αφαιρέσετε αρχεία από εδώ.'; // Same here + out.fm_info_anonymous = 'Δεν έχετε συνδεθεί, οπότε τα pads σας θα διαγραφούν μετά από 3 μήνες (μάθετε περισσότερα). ' + + 'Εγγραφείτε ή Συνδεθείτε για να τα κρατήσετε επ\' αόριστον.'; + out.fm_alert_backupUrl = "Σύνδεσμος ασφαλείας για αυτόν τον αποθηκευτικό χώρο.
" + + "Συνίσταται ιδιαιτέρως να τον κρατήσετε μυστικό.
" + + "Μπορείτε να τον χρησιμοποιήσετε για να ανακτήσετε όλα σας τα αρχεία σε περίπτωση που διαγραφεί η μνήμη του περιηγητή σας.
" + + "Οποιοσδήποτε με αυτόν τον σύνδεσμο μπορεί να επεξεργαστεί ή να αφαιρέσει όλα τα αρχεία σας στον διαχειριστή αρχείων.
"; + out.fm_alert_anonymous = "Γεια σας! Αυτή τη στιγμή χρησιμοποιείτε το CryptPad ανώνυμα, αυτό είναι ok αλλά τα pads σας ίσως διαγραφούν μετά από ένα διάστημα " + + "αδράνειας. Έχουμε απενεργοποιήσει προηγμένες λειτουργίες του αποθηκευτικού χώρου για τους ανώνυμους χρήστες επειδή θέλουμε να καταστήσουμε ξεκάθαρο πως " + + 'δεν είναι ένα ασφαλές μέρος για να αποθηκεύετε πράγματα. Μπορείτε να διαβάσετε περισσότερα σχετικά ' + + 'με το γιατί το κάνουμε αυτό και γιατί θα έπρεπε να Εγγραφείτε ή να Συνδεθείτε.'; + out.fm_backup_title = 'Σύνδεσμος ασφαλείας'; + out.fm_nameFile = 'Πως θα θέλατε να ονομάσετε αυτό το αρχείο;'; + out.fm_error_cantPin = "Εσωτερικό σφάλμα διακομιστή. Παρακαλούμε επαναφορτώστε τη σελίδα και προσπαθήστε ξανά."; + out.fm_viewListButton = "Προβολή λίστας"; + out.fm_viewGridButton = "Προβολή πλέγματος"; + out.fm_renamedPad = "Έχετε ορίσει ένα προσαρμοσμένο όνομα για αυτό το pad. Ο διαμοιραζόμενος τίτλος του είναι:
{0}"; + out.fm_prop_tagsList = "Ετικέτες"; + out.fm_burnThisDriveButton = "Διαγραφή όλων των πληροφοριών που έχουν αποθηκευτεί από το CryptPad στον περιηγητή σας"; + out.fm_burnThisDrive = "Είστε σίγουροι πως θέλετε να διαγράψετε όλα όσα έχουν αποθηκευτεί από το CryptPad στον περιηγητή σας;
" + + "Αυτό θα αφαιρέσει το CryptDrive σας και το ιστορικό του από τον περιηγητή σας, αλλά τα pads σας θα εξακολουθήσουν να υπάρχουν (κρυπτογραφημένα) στον διακομιστή μας."; + // File - Context menu + out.fc_newfolder = "Νέος φάκελος"; + out.fc_rename = "Μετονομασία"; + out.fc_open = "Άνοιγμα"; + out.fc_open_ro = "Άνοιγμα για προβολή μόνο"; + out.fc_delete = "Μετακίνηση στον κάδο"; + out.fc_restore = "Επαναφορά"; + out.fc_remove = "Αφαίρεση από το CryptDrive σας"; + out.fc_empty = "Άδειασμα του κάδου"; + out.fc_prop = "Ιδιότητες"; + out.fc_hashtag = "Ετικέτες"; + out.fc_sizeInKilobytes = "Μέγεθος σε Kilobytes"; + // fileObject.js (logs) + out.fo_moveUnsortedError = "Δεν μπορείτε να μετακινήσετε έναν φάκελο στη λίστα των αταξινόμητων pads"; + out.fo_existingNameError = "Το όνομα χρησμοποιείται ήδη σε αυτή την τοποθεσία. Παρακαλώ επιλέξτε ένα άλλο."; + out.fo_moveFolderToChildError = "Δεν μπορείτε να μετακινήσετε έναν φάκελο μέσα σε κάποιο από τα περιεχόμενα του"; + out.fo_unableToRestore = "Αδυναμία επαναφοράς αυτού του αρχείο στην αρχική τοποθεσία του. Μπορείτε να δοκιμάσετε να το μετακινήσετε σε μια νέα τοποθεσία."; + out.fo_unavailableName = "Ένα αρχείο ή ένας φάκελος με το ίδιο όνομα υπάρχει ήδη στη νέα τοποθεσία. Μετονομάστε το αρχείο και προσπαθήστε ξανά."; + + out.fs_migration = "Το CryptDrive σας αναβαθμίστηκε σε μια νεότερη έκδοση. Ως αποτέλεσμα, η τρέχουσα σελίδα θα πρέπει να επαναφορτωθεί.
Παρακαλούμε επαναφορτώστε τη σελίδα για να συνεχίσετε να την χρησιμοποιείτε."; + + // login + out.login_login = "Σύνδεση"; + out.login_makeAPad = 'Δημιουργήστε ένα pad ανώνυμα'; + out.login_nologin = "Περιηγηθείτε στα τοπικά pads"; + out.login_register = "Εγγραφή"; + out.logoutButton = "Αποσύνδεση"; + out.settingsButton = "Ρυθμίσεις"; + + out.login_username = "Όνομα χρήστη"; + out.login_password = "Κωδικός"; + out.login_confirm = "Επιβεβαίωση κωδικού"; + out.login_remember = "Απομνημόνευση"; + + out.login_hashing = "Κρυπτογραφούμε τον κωδικό σας, αυτό μπορεί να πάρει λίγη ώρα."; + + out.login_hello = 'Καλησπέρα {0},'; // {0} is the username + out.login_helloNoName = 'Καλησπέρα,'; + out.login_accessDrive = 'Περιηγήθείτε στον αποθηκευτικό σας χώρο'; + out.login_orNoLogin = 'ή'; + + out.login_noSuchUser = 'Μη έγκυρο όνομα χρήστη ή λάθος κωδικός. Προσπαθήστε ξανά, ή εγγραφείτε'; + out.login_invalUser = 'Απαιτείται όνομα χρήστη'; + out.login_invalPass = 'Απαιτείται κωδικός'; + out.login_unhandledError = 'Προέκυψε ένα μη αναμενόμενο σφάλμα :('; + + out.register_importRecent = "Εισαγωγή ιστορικού (Συνίσταται)"; + out.register_acceptTerms = "Αποδέχομαι τους όρους χρήσης της υπηρεσίας"; + out.register_passwordsDontMatch = "Οι κωδικοί δεν ταιριάζουν!"; + out.register_passwordTooShort = "Οι κωδικοί πρέπει να αποτελούνται από τουλάχιστον {0} χαρακτήρες."; + + out.register_mustAcceptTerms = "Πρέπει να αποδεχτείτε τους όρους της υπηρεσίας."; + out.register_mustRememberPass = "Δεν μπορούμε να επαναφέρουμε τον κωδικό σας αν τον ξεχάσετε. Είναι πολύ σημαντικό να τον θυμάστε! Παρακαλούμε πατήστε στο κουτάκι για επιβεβαίωση."; + + out.register_header = "Καλώς ήρθατε στο CryptPad"; + out.register_explanation = [ + "

Ας δούμε κάνα-δυο πράγματα πρώτα:

", + "
    ", + "
  • Ο κωδικός σας είναι το μυστικό κλειδί που κρυπτογραφεί όλα τα pads σας. Αν το χάσετε, δεν υπάρχει τρόπος να επαναφέρουμε τα δεδομένα σας.
  • ", + "
  • Μπορείτε να εισάγετε τα pads που ανοίξατε πρόσφατα στον περιηγητή σας ώστε να τα έχετε στον λογαριασμό σας.
  • ", + "
  • Αν χρησιμοποιείτε έναν κοινόχρηστο υπολογιστή, θα πρέπει να αποσυνδεθείτε όταν τελειώσετε, το να κλείσετε την καρτέλα δεν είναι αρκετό.
  • ", + "
" + ].join(''); + + out.register_writtenPassword = "Έχω σημειώσει το όνομα χρήστη και τον κωδικό μου, συνέχεια"; + out.register_cancel = "Επιστροφή"; + + out.register_warning = "Zero Knowledge σημαίνει πως δεν μπορούμε να επαναφέρουμε τον λογαριασμό σας αν χάσετε τον κωδικό σας."; + + out.register_alreadyRegistered = "Αυτός ο χρήστης υπάρχει ήδη, μήπως θέλετε να συνδεθείτε;"; + + // Settings + out.settings_cat_account = "Λογαριασμός"; + out.settings_cat_drive = "CryptDrive"; + out.settings_cat_code = "Κώδικας"; + out.settings_title = "Ρυθμίσεις"; + out.settings_save = "Αποθήκευση"; + + out.settings_backupCategory = "Αντίγραφο ασφαλείας"; + out.settings_backupTitle = "Αποθηκεύστε ή επαναφέρετε όλα σας τα δεδομένα"; + out.settings_backup = "Δημιουργία αντιγράφου ασφαλείας"; + out.settings_restore = "Επαναφορά από αντίγραφο ασφαλείας"; + + out.settings_resetNewTitle = "Εκκαθάριση του CryptDrive"; + out.settings_resetButton = "Αφαίρεση"; + out.settings_reset = "Αφαίρεση όλων των αρχείων και φακέλων από το CryptDrive σας"; + out.settings_resetPrompt = "Αυτή η ενέργεια θα αφαιρέσει όλα τα pads από τον αποθηκευτικό σας χώρο.
"+ + "Θέλετε σίγουρα να συνεχίσετε;
" + + "Πληκτρολογήστε “I love CryptPad” για επιβεβαίωση."; + out.settings_resetDone = "Ο αποθηκευτικός σας χώρος είναι πλέον άδειος!"; + out.settings_resetError = "Λάθος κείμενο επιβεβαίωσης. Το CryptDrive σας δεν έχει αλλαχθεί."; + + out.settings_resetTipsAction = "Επαναφορά"; + out.settings_resetTips = "Συμβουλές"; + out.settings_resetTipsButton = "Επαναφέρετε όλες τις διαθέσιμες συμβουλές για το CryptDrive"; + out.settings_resetTipsDone = "Όλες οι συμβουλές είναι πάλι ορατές."; + + out.settings_thumbnails = "Μικρογραφίες"; + out.settings_disableThumbnailsAction = "Απενεργοποίηση μικρογραφιών στο CryptDrive σας"; + out.settings_disableThumbnailsDescription = "Οι μικρογραφίες δημιουργούνται αυτόματα και αποθηκεύονται στον περιηγητή σας όταν επισκέπτεστε ένα νέο pad. Μπορείτε να απενεργοποιήσετε αυτό το χαρακτηριστικό εδώ."; + out.settings_resetThumbnailsAction = "Εκκαθάριση"; + out.settings_resetThumbnailsDescription = "Εκκαθάριση όλων των μικρογραφιών που έχουν αποθηκευτεί στον περιηγητή σας."; + out.settings_resetThumbnailsDone = "Όλες οι μικρογραφίες έχουν διαγραφεί."; + + out.settings_importTitle = "Εισάγετε τα πρόσφατα pads αυτού του περιηγητή στο CryptDrive σας"; + out.settings_import = "Εισαγωγή"; + out.settings_importConfirm = "Είσαστε σίγουρος ότι θέλετε να εισάγετε τα πρόσφατα pads από αυτόν τον περιηγητή στον λογαριασμό χρήστη σας στο CryptDrive?"; + out.settings_importDone = "Εισαγωγή ολοκληρώθηκε"; + + out.settings_userFeedbackTitle = "Αναπληροφόρηση"; + out.settings_userFeedbackHint1 = "Το CryptPad αποστέλλει κάποιες πολύ βασικές πληροφορίες σ' εμάς, ώστε να μας ενημερώσει για το πως μπορούμε να βελτιώσουμε την εμπειρία σας."; + out.settings_userFeedbackHint2 = "Το περιεχόμενο των pads σας δεν διαμοιράζεται ποτέ μαζί μας."; + out.settings_userFeedback = "Ενεργοποίηση αναπληροφόρησης χρήστη"; + + out.settings_anonymous = "Δεν είσαστε συνδεδεμένος. Οι τρέχουσες ρυθμίσεις ισχύουν μόνο για τον συγκεκριμένο περιηγητή."; + out.settings_publicSigningKey = "Δημόσιο κλειδί κρυπτογράφησης"; + + out.settings_usage = "Χρήση"; + out.settings_usageTitle = "Δείτε ολόκληρο το μέγεθος των καρφιτσωμένων pads σας σε MB"; + out.settings_pinningNotAvailable = "Τα καρφιτσωμένα pads είναι διαθέσιμα μόνο σε εγγεγραμένους χρήστες."; + out.settings_pinningError = "Κάτι πήγε στραβά"; + out.settings_usageAmount = "Τα καρφιτσωμένα pads σας καταναλώνουν σε χώρο {0}MB"; + + out.settings_logoutEverywhereButton = "Αποσύνδεση"; + out.settings_logoutEverywhereTitle = "Αποσύνδεση παντού"; + out.settings_logoutEverywhere = "Εξαναγκασμός αποσύνδεσης όλων των άλλων διαδικτυακών συνεδριών."; + out.settings_logoutEverywhereConfirm = "Είσαστε σίγουροι; Θα χρειαστεί να επανασυνδεθείτε σε όλες σας τις συσκευές."; + + out.settings_codeIndentation = 'Εσοχές στον επεξεργαστή κώδικα (κενά)'; + out.settings_codeUseTabs = "Εισαγωγή εσoχών με χρήση του πλήκτρου tab, αντί κενών"; + + out.upload_title = "Μεταφόρτωση αρχείου"; + out.upload_rename = "Θέλετε να μετονομάσετε το {0} πριν το μεταφορτώσετε στον διακομιστή;
" + + "Η κατάληξη του αρχείου ({1}) θα προστεθεί αυτόματα. "+ + "Αυτό το όνομα θα είναι μόνιμο και ορατό σε άλλους χρήστες."; + out.upload_serverError = "Λάθος Διακομιστή: δεν μπορούμε να μεταφορτώσουμε το αρχείο σας αυτή την στιγμή."; + out.upload_uploadPending = "Προσπαθείτε ήδη να μεταφορτώσετε κάτι αυτή την στιγμή. Ακύρωση και μεταφόρτωση του κανούριου σας αρχείου;"; + out.upload_success = "Το αρχείο σας ({0}) έχει μεταφορτωθεί επιτυχώς κι έχει προστεθεί στον αποθηκευτικό σας χώρο."; + out.upload_notEnoughSpace = "Δεν υπάρχει αρκετός αποθηκευτικός χώρος γι' αυτό το αρχείο στο CryptDrive σας."; + out.upload_tooLarge = "Αυτό το αρχείο ξεπερνάει το μέγιστο μέγεθος μεταφόρτωσης."; + out.upload_choose = "Επιλέξτε ένα αρχείο"; + out.upload_pending = "Εκρεμμεί"; + out.upload_cancelled = "Ακυρώθηκε"; + out.upload_name = "Όνομα αρχείου"; + out.upload_size = "Μέγεθος"; + out.upload_progress = "Εξέλιξη"; + out.upload_mustLogin = "Πρέπει να είσαστε συνδεδεμένος για να μεταφορτώσετε ένα αρχείο"; + out.download_button = " Αποκρυπτογράφηση & Κατέβασμα"; + out.download_mt_button = "Λήψη"; + + out.todo_title = "CryptTodo"; + out.todo_newTodoNamePlaceholder = "Περιγράψτε την εργασία σας..."; + out.todo_newTodoNameTitle = "Προσθέστε την εργασία σας στη λίστα εργασιών"; + out.todo_markAsCompleteTitle = "Σημειώστε αυτή την εργασία ως ολοκληρωμένη"; + out.todo_markAsIncompleteTitle = "Σημειώστε αυτή την εργασία ως ανολοκλήρωτη"; + out.todo_removeTaskTitle = "Αφαιρέστε αυτή την εργασία από την λίστα εργασιών σας"; + + // pad + out.pad_showToolbar = "Εμφάνιση γραμμής εργαλείων"; + out.pad_hideToolbar = "Απόκρυψη γραμμής εργαλείων"; + + // general warnings + out.warn_notPinned = "Αυτό το pad δεν είναι αποθηκευμένο σε κάποιο CryptDrive. Θα διαγραφεί σε 3 μήνες. Μάθετε περισσότερα..."; + + // markdown toolbar + out.mdToolbar_button = "Εμφάνιση ή απόκρυψη της γραμμής εργαλείων Markdown"; + out.mdToolbar_defaultText = "Το κείμενο σας εδώ"; + out.mdToolbar_help = "Βοήθεια"; + out.mdToolbar_tutorial = "http://www.markdowntutorial.com/"; + out.mdToolbar_bold = "Έντονα"; + out.mdToolbar_italic = "Πλάγια"; + out.mdToolbar_strikethrough = "Διεγραμμένα"; + out.mdToolbar_heading = "Επικεφαλίδα"; + out.mdToolbar_link = "Σύνδεσμος"; + out.mdToolbar_quote = "Παράθεση"; + out.mdToolbar_nlist = "Λίστα με αριθμούς"; + out.mdToolbar_list = "Λίστα με σημεία"; + out.mdToolbar_check = "Λίστα εργασιών"; + out.mdToolbar_code = "Κώδικας"; + + // index.html + + + //about.html + out.main_p2 = 'Αυτό το εγχείρημα χρησιμοποιεί τον γραφικό επεξεργαστή CKEditor, CodeMirror, και την μηχανή πραγματικού χρόνου ChainPad.'; + out.main_howitworks_p1 = 'Το CryptPad χρησιμοποιεί μια παραλλαγή του αλγόριθμου Operational transformation με τον οποίο καταφέρνει να πετύχει κατανεμημένη συναίνεση χρησιμοποιώντας Blockchain, μια δομή που έγινε δημοφιλής μέσω του Bitcoin. Με αυτό τον τρόπο ο αλγόριθμος αποφεύγει την ανάγκη ύπαρξης ενός κεντρικού διακομιστή για να επιλύσει συγκρούσεις ταυτόχρονης επεξεργασίας και χωρίς την ανάγκη επίλυσης αυτών των συγκρούσεων, ο διακομιστής δεν χρειάζεται να έχει γνώση του περιεχομένου που υπάρχει στο pad.'; + + // contact.html + out.main_about_p2 = 'Αν έχετε απορίες ή σχόλια, επικοινωνήστε μαζί μας!
Μπορείτε να στείλετε ένα tweet, να δημιουργήσετε ένα θέμα στο GitHub. Ελάτε να πείτε "γεια" στο Matrix κανάλι μας ή στο IRC (#cryptpad on irc.freenode.net), ή στείλτε μας ένα email.'; + out.main_about_p22 = 'Στείλτε μας ένα tweet'; + out.main_about_p23 = 'Δημιουργήστε ένα θέμα στο GitHub'; + out.main_about_p24 = 'Πείτε "γεια" στο Matrix'; + out.main_about_p25 = 'Στείλτε μας ένα email'; + out.main_about_p26 = 'Αν έχετε απορίες ή σχόλια, επικοινωνήστε μαζί μας!'; + + out.main_info = "

Συνεργαστείτε με ασφάλεια

Αναπτύξτε τις ιδέες σας μαζί με κοινά αρχεία όσο η τεχνολογία Zero Knowledge εξασφαλίζει την ιδιωτικότητά σας; ακόμη κι από εμάς."; + out.main_catch_phrase = "Το Zero Knowledge σύννεφο"; + + out.main_howitworks = 'Πως Λειτουργεί'; + out.main_zeroKnowledge = 'Πρωτόκολλο Zero Knowledge'; + out.main_zeroKnowledge_p = "Δεν χρειάζεται να μας εμπιστευθείτε όταν σας λέμε πως δεν θα κοιτάξουμε τα pads σας, διότι με την επαναστατική τεχνολογία Zero Knowledge του CryptPad δεν μπορούμε να τα κοιτάξουμε. Μάθετε περισσότερα για το πως προστατεύουμε την Ασφάλεια και Ιδιωτικότητά σας."; + out.main_writeItDown = 'Σημειώστε το'; + + out.main_writeItDown_p = "Τα μεγαλύτερα έργα προέρχονται από τις μικρότερες ιδέες. Καταγράψτε τις στιγμές έμπνευσης και τις απροσδόκητες ιδέες σας διότι ποτέ δεν ξέρετε ποια από αυτές μπορεί να είναι η επόμενη μεγάλη ανακάλυψη."; + out.main_share = 'Μοιραστείτε τον σύνδεσμο, μοιραστείτε το pad'; + out.main_share_p = "Αναπτύξτε τις ιδέες σας μαζί: πραγματοποιήστε αποτελεσματικές συναντήσεις, συνεργαστείτε στις λίστες εργασιών και κάντε γρήγορες παρουσιάσεις με όλους τους φίλους σας και από όλες τις συσκευές σας."; + out.main_organize = 'Οργανωθείτε'; + out.main_organize_p = "Με το CryptPad Drive, μπορείτε να συγκεντρωθείτε στο τι είναι σημαντικό. Οι φάκελοι σας επιτρέπουν να ελέγχετε τα έργα σας και να έχετε μία συνολική εικόνα για το πως προχωράνε τα πράγματα."; + out.tryIt = 'Δοκιμάστε το!'; + out.main_richText = 'Επεξεργαστής Εμπλουτισμένου Κειμένου'; + out.main_richText_p = 'Επεξεργαστείτε pads εμπλουτισμένου κειμένου συνεργατικά με την πραγματικού χρόνου Zero Knowledge εφαρμογή μας CkEditor.'; + out.main_code = 'Επεξεργαστής κώδικα'; + out.main_code_p = 'Επεξεργαστείτε κώδικα συνεργατικά με την πραγματικού χρόνου Zero Knowledge εφαρμογή μας CodeMirror.'; + out.main_slide = 'Επεξεργαστής Slide'; + out.main_slide_p = 'Δημιουργείστε τις παρουσιάσεις σας χρησιμοποιώντας μορφοποίηση Markdown και προβάλλετέ τις στον περιηγητή σας.'; + out.main_poll = 'Δημοσκοπήσεις'; + out.main_poll_p = 'Προγραμματίστε την συνάντησή σας ή την δραστηριότητά σας, ή ψηφίστε την καλύτερη λύση σχετικά με το πρόβλημά σας.'; + out.main_drive = 'CryptDrive'; + + out.main_richTextPad = 'Pad εμπλουτισμένου κειμένου'; + out.main_codePad = 'Pad κώδικα'; + out.main_slidePad = 'Markdown παρουσίαση'; + out.main_pollPad = 'Δημοσκόπηση ή Χρονοδιάγραμμα'; + out.main_whiteboardPad = 'Πίνακας σχεδιασμού'; + out.main_localPads = 'Τοπικά pads'; + out.main_yourCryptDrive = 'Το CryptDrive σας'; + out.main_footerText = "Με το CryptPad, μπορείτε να δημιουργήσετε γρήγορα συνεργατικά έγγραφα για κοινόχρηστες σημειώσεις και καταγραφή ιδεών."; + + out.footer_applications = "Εφαρμογές"; + out.footer_contact = "Επικοινωνία"; + out.footer_aboutUs = "Σχετικά με εμάς"; + + out.about = "Σχετικά"; + out.privacy = "Ιδιωτικότητα"; + out.contact = "Επικοινωνία"; + out.terms = "Όροι χρήσης"; + out.blog = "Ιστολόγιο"; + + out.topbar_whatIsCryptpad = "Τι είναι το CryptPad"; + + // what-is-cryptpad.html + + out.whatis_title = 'Τι είναι το CryptPad'; + out.whatis_collaboration = 'Γρήγορη, εύκολη συνεργασία'; + out.whatis_collaboration_p1 = 'Με το CryptPad, μπορείτε να δημιουργείτε όλοι μαζί γρήγορα συνεργατικά έγγραφα για τις σημειώσεις σας και τις ιδέες που καταγράφετε. Όταν εγγραφείτε και συνδεθείτε, σας δίνεται άμεσα η δυνατότητα \'ανεβάσματος\' κι έναν \'αποθηκευτικό χώρο\' CryptDrive όπου μπορείτε να οργανώσετε όλα σας τα pads. Ως εγγεγραμένος χρήστης παίρνετε 50MB δωρεάν.'; + out.whatis_collaboration_p2 = 'Μπορείτε να μοιραστείτε την πρόσβαση σε ένα έγγραφο του CryptPad απλά δίνοντας τον σύνδεσμο σε κάποιον άλλο. Μπορείτε επίσης να μοιραστείτε ένα σύνδεσμο ο οποίος παρέχει πρόσβαση μόνο για ανάγνωση σε ένα pad, επιτρέποντάς σας να κοινοποιήσετε την συλλογική σας δουλειά ενώ ταυτόχρονα έχετε ακόμα τη δυνατότητα να το επεξεργαστείτε.'; + out.whatis_collaboration_p3 = 'Μπορείτε να δημιουργήσετε απλά εμπλουτισμένα κείμενα με το CKEditor όπως επίσης κείμενα με γλώσσα προγραμματισμού Markdown τα οποία τροποποιούνται σε πραγματικό χρόνο καθώς πληκτρολογείτε. Μπορείτε επίσης να χρησιμοποιήσετε την εφαρμογή δημοσκόπησης για να προγραμματίσετε δραστηριότητες με πολλαπλούς συμμετέχοντες.'; + out.whatis_zeroknowledge = 'Zero Knowledge'; + out.whatis_zeroknowledge_p1 = "Δεν θέλουμε να ξέρουμε τι πληκτρολογείτε και με τον σύγχρονο τρόπο κρυπτογράφησης μπορείτε να είσαστε σίγουροι ότι δεν μπορούμε να ξέρουμε. Το CryptPad χρησιμοποιεί 100% κρυπτογράφηση client side για να προστατεύσει το περιεχόμενο που πληκτρολογείτε από εμάς, τους ανθρώπους που φιλοξενούν τον διακομιστή."; + out.whatis_zeroknowledge_p2 = 'Όταν κάνετε εγγραφή και συνδέεστε, το όνομα χρήστη σας κι ο κωδικός σας μετατρέπονται σε ένα κρυπτογραφημένο κλειδί χρησιμοποιώντας το scrypt key derivation function. Το συγκεκριμένο κλειδί, το όνομα χρήστη κι ο κωδικός χρήστη δεν στέλνονται καν στον διακομιστή. Αντιθέτως χρησιμοποιούνται από το client side για να αποκρυπτογραφήσουν το περιεχόμενο του CryptDrive σας, το οποίο περιέχει όλα τα κλειδιά για όλα τα pads στα οποία μπορείτε να έχετε πρόσβαση.'; + out.whatis_zeroknowledge_p3 = 'Όταν μοιράζεστε έναν σύνδεσμο προς ένα έγγραφο, μοιράζεστε το κρυπτογραφημένο κλειδί για το συγκεκριμένο έγγραφο αλλά εφόσον το κλειδί είναι στο fragment identifier, δεν στέλνεται ποτέ απευθείας στον διακομιστή. Επισκεφθείτε το privacy blog post για να μάθετε περισσότερα σχετικά με το σε ποια μεταδεδομένα έχουμε πρόσβαση και σε ποια όχι.'; + out.whatis_drive = 'Οργάνωση με το CryptDrive'; + out.whatis_drive_p1 = 'Κάθε φορά που επισκέπτεσθε ένα pad στο CryptPad, το pad προστίθεται αυτόματα στο CryptDrive στον κυρίως φάκελο. Αργότερα μπορείτε να οργανώσετε αυτά τα pad σε φακέλους ή μπορείτε να τα μετακινήσετε στον κάδο ανακύκλωσης. Το CryptDrive σας επιτρέπει να περιηγηθείτε ανάμεσα στα pads σας και να τα οργανώνετε όποτε κι όπως θέλετε.'; + out.whatis_drive_p2 = 'Με το κλασικό drag-and-drop, μπορείτε να μεταφέρετε pads μέσα στον αποθηκευτικό σας χώρο και ο σύνδεσμος αυτών των pads θα παραμείνει ο ίδιος ώστε οι συνεργάτες σας να μην σταματήσουν ποτέ να έχουν πρόσβαση.'; + out.whatis_drive_p3 = 'Μπορείτε επίσης να ανεβάσετε αρχεία στο CryptDrive σας και να τα μοιραστείτε με συνεργάτες. Τα ανεβασμένα αρχεία μπορούν να οργανωθούν ακριβώς όπως τα συνεργατικά pads.'; + out.whatis_business = 'Το CryptPad για επιχειρήσεις'; + out.whatis_business_p1 = 'Το πρωτόκολλο κρυπτογράφησης Zero Knowledge του CryptPad είναι ιδανικό για να πολλαπλασιαστεί η αποτελεσματικότητα των ήδη υπάρχοντων πρωτοκόλλων ασφαλείας προστατεύοντας τα εταιρικά στοιχεία πρόσβασης με ισχυρή κρυπτογράφηση. Επειδή τα ευαίσθητα δεδομένα μπορούν να αποκρυπτογραφηθούν μόνο με την χρήση των στοιχείων των υπαλλήλων, το CryptPad εξαλείφει τον παράγοντα hacker ο οποίος ενυπάρχει σε παραδοσιακούς εταιρικούς διακομιστές. Διαβάστε το CryptPad Whitepaper για να μάθετε περισσότερα σχετικά με το πως μπορεί να βοηθήσει την επιχείρησή σας.'; + out.whatis_business_p2 = 'To CryptPad μπορεί να εγκατασταθεί τοπικά και οι προγραμματιστές του στην XWiki SAS είναι σε θέση να προσφέρουν εμπορική υποστήριξη, τροποποιήσεις και περαιτέρω ανάπτυξη. Επικοινωνήστε στο sales@cryptpad.fr για περισσότερες πληροφορίες.'; + + // privacy.html + + out.policy_title = 'Πολιτική απορρήτου του CryptPad'; + out.policy_whatweknow = 'Τι γνωρίζουμε για εσάς'; + out.policy_whatweknow_p1 = 'Ως εφαρμογή η οποία φιλοξενείται στο διαδίκτυο, το CryptPad έχει πρόσβαση στα μεταδεδομένα που είναι εκτεθειμένα από το πρωτόκολλο HTTP. Αυτό συμπεριλαμβάνει την διεύθυνση IP σας και ποικίλες HTTP κεφαλίδες που μπορούν να χρησιμοποιηθούν για να ταυτοποιήσουν τον συγκεκριμένο περιηγητή. Μπορείτε να δείτε τι πληροφορίες μοιράζεται ο περιηγητής σας με το να επισκεφθείτε WhatIsMyBrowser.com.'; + out.policy_whatweknow_p2 = 'Χρησιμοποιούμε το Kibana, μια πλατφόρμα ανάλυσης ανοιχτού κώδικα, για να μάθουμε περισσότερα για τους χρήστες μας. Το Κibana μας ενημερώνει για το πως βρήκατε το CryptPad, μέσω απευθείας σύνδεσης, μέσω μηχανής αναζήτησης, ή μέσω αναφοράς από άλλη διαδυκτιακή υπηρεσία όπως το Reddit ή το Twitter.'; + out.policy_howweuse = 'Πώς χρησιμοποιούμε αυτά που μαθαίνουμε'; + out.policy_howweuse_p1 = 'Χρησιμοποιούμε αυτές τις πληροφορίες για να παίρνουμε καλύτερες αποφάσεις σχετικά με την προώθηση του CryptPad, εξετάζοντας ποιες από τις προηγούμενες προσπάθειές μας υπήρξαν επιτυχείς. Οι πληροφορίες σχετικά με την τοποθεσία σας μας βοηθούν στο να σκεφτούμε αν θα έπρεπε να παρέχουμε καλύτερη υποστήριξη για γλώσσες εκτός των Αγγλικών.'; + out.policy_howweuse_p2 = "Οι πληροφορίες σχετικά με τον περιηγητή σας (είτε είναι επιτραπέζιου είτε φορητού λειτουργικού συστήματος) μας βοηθάνε να παίρνουμε αποφάσεις στο θέμα προτεραιοτήτων βελτίωσης χαρακτηριστικών. Η ομάδα προγραμματισμού μας είναι μικρή και προσπαθούμε να κάνουμε επιλογές οι οποίες θα βελτιώσουν την εμπειρία όσων το δυνατό περισσότερων χρηστών."; + out.policy_whatwetell = 'Τι λέμε σε άλλους για εσάς'; + out.policy_whatwetell_p1 = 'Δεν παρέχουμε σε τρίτους τις πληροφορίες που συλλέγουμε ή τις πληροφορίες που μας δίνετε εκτός κι αν είμαστε υποχρεωμένοι νομικά.'; + out.policy_links = 'Σύνδεσμοι σε άλλες σελίδες'; + out.policy_links_p1 = 'Αυτή η ιστοσελίδα περιέχει συνδέσμους προς άλλες σελίδες, συμπεριλαμβανομένων αυτών που δημιουργήθηκαν από άλλους οργανισμούς. Δεν είμαστε υπεύθυνοι για την πολιτική απορρήτου ή το περιεχόμενο μιας εξωτερικής σελίδας. Ως γενικό κανόνα έχουμε πως οι σύνδεσμοι σε διαφορετικές σελίδες ανοίγουν σε καινούριο παράθυρο για να είναι ξεκάθαρο ότι φεύγετε από το CryptPad.fr.'; + out.policy_ads_p1 = 'Δεν προβάλουμε διαφημίσεις εντός της υπηρεσίας, όμως μπορεί να παρέχουμε συνδέσμους στους ανθρώπους που ενισχύουν οικονομικά την έρευνά μας.'; + out.policy_choices = 'Οι επιλογές που έχετε'; + out.policy_choices_open = 'Ο κώδικάς μας διατίθεται ελεύθερα, οπότε έχετε πάντα την επιλογή να φιλοξενήσετε το Cryptpad σε δικό σας διακομιστή.'; + out.policy_choices_vpn = 'Εάν θέλετε να χρησιμοποιήσετε τη δική μας εκδοχή του Cryptpad, αλλά δεν θέλετε να φαίνεται η IP διεύθυνσή σας, μπορείτε να προστατέψετε την IP σας χρησιμοποιώντας το Tor browser bundle, ή ένα VPN.'; + out.policy_choices_ads = 'Εάν θα θέλατε απλά να εμποδίσετε την πλατφόρμα ανάλυσής μας, μπορείτε να χρησιμοποιήσετε εργαλεία απόκρυψης διαφημίσεων όπως το Privacy Badger.'; + + // terms.html + + out.tos_title = " Όροι και Προϋποθέσεις του CryptPad"; + out.tos_legal = "Παρακαλούμε μην κάνετε κακή χρήση ή/και κατάχρηση της υπηρεσίας ή οτιδήποτε παράνομο."; + out.tos_availability = "Ελπίζουμε να βρείτε χρήσιμη αυτή την υπηρεσία, αλλά η προσβασιμότητα κι η απόδοση δεν μπορούν να εγγυηθούν. Παρακαλούμε κάνετε εξαγωγή των δεδομένων σας συχνά."; + out.tos_e2ee = "Τα περιεχόμενα του CryptPad μπορούν να διαβαστούν ή να αλλαχθούν από οποιονδήποτε μπορεί να μαντέψει ή να αποκτήσει την ηλεκτρονική διεύθυνση του pad. Προτείνουμε να χρησιμοποιείτε τεχνολογία κρυπτογραφημένων μηνυμάτων από άκρη σε άκρη (e2ee) για να μοιράζεστε συνδέσμους και να μην αναλάβετε καμία ευθύνη σε περίπτωση που διαρρέυσει κάποιος τέτοιος σύνδεσμος."; + out.tos_logs = "Τα μεταδεδομένα που παρέχονται από τον περιηγητή σας στον διακομιστή μπορεί να καταγράφονται με σκοπό τη συντήρηση της υπηρεσίας."; + out.tos_3rdparties = "Δεν παρέχουμε προσωπικά δεδομένα σε τρίτους παρά μόνο εάν ζητηθεί από το νόμο."; + + // 404 page + out.four04_pageNotFound = "Η σελίδα που ψάχνετε, δεν βρέθηκε!"; + + // BottomBar.html + + //out.bottom_france = 'Δημιουργήθηκε με love στην Γαλλία'; + //out.bottom_support = 'Ένα XWiki SAS Labs Project με την υποστήριξη του OpenPaaS-ng'; + + // Header.html + + out.header_france = 'Με love στην Γαλλία από την XWiki SAS'; + + out.header_support = ' OpenPaaS-ng'; + out.updated_0_header_logoTitle = 'Μετάβαση στο CryptDrive σας'; + out.header_logoTitle = out.updated_0_header_logoTitle; + out.header_homeTitle = 'Μετάβαση στην αρχική σελίδα του CryptPad'; + + // Initial states + + out.initialState = [ + '

', + 'Αυτό είναι CryptPad, ο συνεργατικός επεξεργαστής πραγματικού χρόνου Zero Knowledge. Τα πάντα αποθηκεύονται καθώς πληκτρολογείτε.', + '
', + 'Μοιραστείτε τον σύνδεσμο σε αυτό το pad για να το επεξεργαστείτε με φίλους ή χρησιμοποιήστε το κουμπί για να μοιραστείτε ένα κείμενο με δικαιώματα read-only link το οποίο επιτρέπει να το αναγνώσει κάποιος αλλά όχι να το επεξεργαστεί.', + '

', + ].join(''); + + out.codeInitialState = [ + '# Ο συνεργατικός επεξεργαστής Zero Knowledge του CryptPad\n', + '\n', + '* Ό,τι πληκτρολογείτε εδώ είναι κρυπτογραφημένο έτσι ώστε μόνο οι άνθρωποι που έχουν τον σύνδεσμο να μπορούν να έχουν πρόσβαση.\n', + '* Μπορείτε να επιλέξετε την γλώσσα προγραμματισμού για να υπογραμμίζετε και το χρώμα του θέματος UI πάνω δεξιά.' + ].join(''); + + out.slideInitialState = [ + '# CryptSlide\n', + '1. Γράψτε τα περιεχόμενα των slides σας χρησιμοποιώντας σύνταξη markdown\n', + ' - Μάθετε περισσότερα για την σύνταξη markdown [εδώ](http://www.markdowntutorial.com/)\n', + '2. Διαχωρίστε τα slides σας με ---\n', + '3. Πατήστε το κουμπάκι "Play" για να δείτε το αποτέλεσμα', + ' - Τα slides σας ενημερώνονται σε πραγματικό χρόνο' + ].join(''); + + // Readme + + out.driveReadmeTitle = "Τι είναι το CryptPad;"; + out.readme_welcome = "Καλωσήρθατε στο CryptPad!"; + out.readme_p1 = "Καλωσήρθατε στο CryptPad, όπου μπορείτε να έχετε τις σημειώσεις σας μόνοι σας ή με φίλους."; + out.readme_p2 = "Αυτό το pad έχει έναν γρήγορο οδηγό χρήσης του πως να χρησιμοποιήσετε το CryptPad για να κρατάτε σημειώσεις, να τις έχετε οργανωμένες και να δουλέψετε πάνω τους συνεργατικά."; + out.readme_cat1 = "Μάθετε το CryptDrive σας"; + out.readme_cat1_l1 = "Δημιούργηστε ένα pad: Στο CryptDrive σας, κάντε \"κλικ\" στο {0} και έπειτα στο {1} και μπορείτε να δημιουργήσετε ένα pad."; // 0: New, 1: Rich Text + out.readme_cat1_l2 = "Ανοίξτε pads από το CryptDrive σας: κάντε διπλό \"κλικ\" σε ένα εικονίδιο pad για να το ανοίξετε."; + out.readme_cat1_l3 = "Οργάνωστε τα pads σας: Όταν είσαστε συνδεδεμένοι, κάθε pad στο οποίο έχετε πρόσβαση θα εμφανίζεται ως {0} στο τμήμα του δίσκου σας."; // 0: Unsorted files + out.readme_cat1_l3_l1 = "Μπορείτε να κάνετε \"κλικ\" και να σύρετε αρχεία μέσα σε φακέλους στον τομέα {0} του δίσκου σας και να δημιουργήσετε καινούρια αρχεία."; // 0: Documents + out.readme_cat1_l3_l2 = "Θυμηθείτε να δοκιμάζετε το δεξί \"κλικ\" στα εικονίδια διότι συχνά υπάρχουν επιπρόσθετα μενού."; + out.readme_cat1_l4 = "Πετάξτε τα παλιά pads στα σκουπίδια: Μπορείτε να κάνετε \"κλικ\" και να σύρετε τα pads μέσα στα {0} με τον ίδιο τρόπο που τα σύρετε μέσα στους φακέλους."; // 0: Trash + out.readme_cat2 = "Δημιουργείστε pads σαν επαγγελματίας"; + out.edit = "επεξεργασία"; + out.view = "προβολή"; + out.readme_cat2_l1 = "Το κουμπί {0} στο pad σας επιτρέπει να δίνετε πρόσβαση στους συνεργάτες σας είτε να κάνουν {1} είτε να κάνουν {2} το pad."; // 0: Share, 1: edit, 2: view + out.readme_cat2_l2 = "Αλλάξτε τον τίτλο του pad κάνοντας \"κλικ\" στο μολύβι"; + out.readme_cat3 = "Ανακαλύψτε CryptPad εφαρμογές"; + out.readme_cat3_l1 = "Με το CryptPad code editor, μπορείτε να συνεργαστείτε σε κώδικα όπως οι γλώσσες προγραμματισμού Javascript και markdown ή HTML και Markdown"; + out.readme_cat3_l2 = "Με το CryptPad slide editor, μπορείτε να κάνετε γρήγορες παρουσιάσεις χρησιμοποιώντας γλώσσα Markdown"; + out.readme_cat3_l3 = "Με το CryptPoll μπορείτε να ψηφίζετε γρήγορα, ειδικά για να ορίζετε συναντήσεις σε ημερομηνίες που ταιριάζουν με το πρόγραμμα όλων"; + + // Tips + out.tips = {}; + out.tips.shortcuts = "`ctrl+b`, `ctrl+i` και `ctrl+u` είναι γρήγορες συντομεύσεις για έντονα, πλάγια και υπογραμμισμένα γράμματα."; + out.tips.indent = "Σε αριθμημένες λίστες όπως και λίστες με τελείες, μπορείτε να χρησιμοποιήσετε tab ή shift+tab για να αυξήσετε ή να μειώσετε τις εσοχές με γρήγορο τρόπο."; + out.tips.store = "Κάθε φορά που επισκέπτεστε ένα pad, εάν είσαστε συνδεδεμένοι, θα σώζεται αυτόματα στο CryptDrive σας."; + out.tips.marker = "Μπορείτε να υπογραμμίσετε κείμενο σε ένα pad χρησιμοποιώντας τον \"μαρκαδόρο\" από το μενού μορφoποίησης."; + out.tips.driveUpload = "Οι εγγεγραμένοι χρήστες μπορούν να ανεβάσουν κρυπτογραφημένα αρχεία σύροντάς τα και πετώντας τα στο CryptDrive τους."; + out.tips.filenames = "Μπορείτε να μετονομάσετε αρχεία στο CryptDrive σας. Το όνομα που θα δώσετε είναι μόνο για εσάς."; + out.tips.drive = "Οι συνδεδεμένοι χρήστες μπορούν να οργανώσουν τα αρχεία τους στο CryptDrive τους, τα οποία είναι προσβάσιμα από το εικονίδιο CryptPad που είναι πάνω αριστερά σε όλα τα pads."; + out.tips.profile = "Οι εγγεγραμένοι χρήστες μπορούν να δημιουργήσουν ένα προφίλ από το μενού χρήστη πάνω δεξιά."; + out.tips.avatars = "Μπορείτε να ανεβάσετε ένα άβαταρ στο προφίλ σας. Θα το βλέπουν οι άλλοι όταν συνεργάζεστε σε ένα pad."; + out.tips.tags = "Βάλτε ετικέτες στα pads σας και ψάξτε με # στο CryptDrive σας για να τα βρείτε"; + + out.feedback_about = "Εάν το διαβάζετε αυτό, πιθανότατα ήσασταν περίεργοι για ποιο λόγο το CryptPad ζητά ιστοσελίδες όταν κάνετε συγκεκριμένες ενέργειες"; + out.feedback_privacy = "Ενδιαφερόμαστε για την ιδιωτικότητά σας και ταυτόχρονα θέλουμε το CryptPad να είναι πολύ εύκολο στην χρήση. Χρησιμοποιούμε αυτό το αρχείο για να καταλάβουμε ποια χαρακτηριστικά του περιβάλλοντος διάδρασης ενδιαφέρουν τους χρήστες μας, με το να το ζητήσουμε σε συνδυασμό με μια παράμετρο η οποία μας δείχνει συγκεκριμένα ποια ενέργεια έγινε."; + out.feedback_optout = "Εάν θα θέλατε να απέχετε, επισκεφθείτε τη σελίδα ρυθμίσεων του λογαριασμού σας, όπου θα βρείτε ένα κουτί στο οποίο μπορείτε να ενεργοποιήσετε ή να απενεργοποιήσετε την αναπληροφόρηση"; + + return out; +}); diff --git a/customize.dist/translations/messages.es.js b/customize.dist/translations/messages.es.js index 82e7fc0a9..fc905870a 100644 --- a/customize.dist/translations/messages.es.js +++ b/customize.dist/translations/messages.es.js @@ -50,7 +50,6 @@ define(function () { out.shareSuccess = 'URL copiada al portapapeles'; out.presentButtonTitle = "Entrar en el modo presentación"; - out.presentSuccess = 'ESC para salir del modo presentación'; out.backgroundButtonTitle = 'Cambiar el color de fondo en el modo presentación'; out.colorButtonTitle = 'Cambiar el color de texto en el modo presentación'; @@ -153,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"; @@ -235,7 +234,8 @@ define(function () { out.login_invalUser = "Nombre de usuario requerido"; out.login_invalPass = "Contraseña requerida"; out.login_unhandledError = "Ha ocurrido un error inesperado :("; - out.register_importRecent = "Importar historial (recomendado)"; + out.register_importRecent = "Importe el historial de tu sesión anónima"; + out.register_acceptTerms = "Acepto los términos de servicio"; out.register_passwordsDontMatch = "Las contraseñas no corresponden"; out.register_mustAcceptTerms = "Tienes que aceptar los términos de servicio"; @@ -294,17 +294,12 @@ define(function () { '

', 'Esto es CryptPad, el editor colaborativo en tiempo real Zero Knowledge. Todo está guardado cuando escribes.', '
', - 'Comparte el enlace a este pad para editar con amigos o utiliza el botón  Compartir  para obtener un enlace sólo lectura que permite leer pero no escribir.', + 'Comparte el enlace a este pad para editar con amigos o utiliza el botón para obtener un enlace sólo lectura que permite leer pero no escribir.', '

', - - '

', - 'Vamos, empieza a escribir...', - '

', - '

 

' ].join(''); out.codeInitialState = "/*\n Esto es CryptPad, el editor colaborativo en tiempo real zero knowledge.\n Lo que escribes aquí está cifrado de manera que sólo las personas con el enlace pueden acceder a ello.\n Incluso el servidor no puede ver lo que escribes.\n Lo que ves aquí, lo que escuchas aquí, cuando sales, se queda aquí\n*/"; - out.slideInitialState = "# CryptSlide\n* Esto es CryptPad, el editor colaborativo en tiempo real zero knowledge.\n* Lo que escribes aquí está cifrado de manera que sólo las personas con el enlace pueden acceder a ello.\n* Incluso el servidor no puede ver lo que escribes.\n* Lo que ves aquí, lo que escuchas aquí, cuando sales, se queda aquí\n\n---\n# Cómo utilizarlo\n1. Escribe tu contenido en Markdown\n - Puedes aprender más sobre Markdown [aquí](http://www.markdowntutorial.com/)\n2. Separa tus diapositivas con ---\n3. Haz clic en \"Presentar\" para ver el resultado - Tus diapositivas se actualizan en tiempo real"; + out.slideInitialState = "# CryptSlide\n1. Escribe tu contenido en Markdown\n - Puedes aprender más sobre Markdown [aquí](http://www.markdowntutorial.com/)\n2. Separa tus diapositivas con ---\n3. Haz clic en \"Presentar\" para ver el resultado - Tus diapositivas se actualizan en tiempo real"; out.driveReadmeTitle = "¿Qué es CryptPad?"; out.readme_welcome = "¡Bienvenido a CryptPad!"; out.readme_p1 = "Bienvenido a CryptPad, aquí podrás anotar cosas solo o con otra gente."; diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index dfeb46371..05845a83c 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -1,7 +1,7 @@ define(function () { var out = {}; - out.main_title = "CryptPad: Éditeur collaboratif en temps réel, zero knowledge"; + 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 = {}; @@ -27,21 +27,33 @@ define(function () { out.websocketError = 'Impossible de se connecter au serveur WebSocket...'; out.typeError = "Ce pad n'est pas compatible avec l'application sélectionnée"; - out.onLogout = 'Vous êtes déconnecté de votre compte utilisateur, cliquez ici pour vous authentifier
ou appuyez sur Échap pour accéder au pad en mode lecture seule.'; + out.onLogout = 'Vous êtes déconnecté de votre compte utilisateur, {0}cliquez ici{1} pour vous authentifier
ou appuyez sur Échap pour accéder au pad en mode lecture seule.'; out.wrongApp = "Impossible d'afficher le contenu de ce document temps-réel dans votre navigateur. Vous pouvez essayer de recharger la page."; + out.padNotPinned = 'Ce pad va expirer après 3 mois d\'inactivité, {0}connectez-vous{1} ou {2}enregistrez-vous{3} pour le préserver.'; + out.anonymousStoreDisabled = "L'administrateur de cette instance de CryptPad a désactivé le drive pour les utilisateurs non enregistrés. Vous devez vous connecter pour pouvoir utiliser CryptDrive."; + out.expiredError = "Ce pad a atteint sa date d'expiration est n'est donc plus disponible."; + out.deletedError = 'Ce pad a été supprimé par son propriétaire et n\'est donc plus disponible.'; + out.inactiveError = 'Ce pad a été supprimé en raison de son inactivité. Appuyez sur Échap pour créer un nouveau pad.'; + 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.loading = "Chargement..."; out.error = "Erreur"; out.saved = "Enregistré"; out.synced = "Tout est enregistré"; 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.disconnected = 'Déconnecté'; out.synchronizing = 'Synchronisation'; - out.reconnecting = 'Reconnexion...'; + out.reconnecting = 'Reconnexion'; out.typing = "Édition"; + out.initializing = "Initialisation..."; + out.forgotten = 'Déplacé vers la corbeille'; + out.errorState = 'Erreur critique : {0}'; out.lag = 'Latence'; out.readonly = 'Lecture seule'; out.anonymous = "Anonyme"; @@ -54,6 +66,7 @@ define(function () { out.viewers = "lecteurs"; out.editor = "éditeur"; out.editors = "éditeurs"; + out.userlist_offline = "Vous êtes actuellement hors-ligne, la liste des utilisateurs n'est pas disponible."; out.language = "Langue"; @@ -73,9 +86,9 @@ define(function () { out.supportCryptpad = "Soutenir CryptPad"; - out.formattedMB = "{0} Mo"; - out.formattedGB = "{0} Go"; - out.formattedKB = "{0} Ko"; + out.formattedMB = "{0} Mo"; + out.formattedGB = "{0} Go"; + out.formattedKB = "{0} Ko"; out.greenLight = "Tout fonctionne bien"; out.orangeLight = "Votre connexion est lente, ce qui réduit la qualité de l'éditeur"; @@ -129,15 +142,22 @@ define(function () { out.saveTemplatePrompt = "Choisir un titre pour ce modèle"; out.templateSaved = "Modèle enregistré !"; out.selectTemplate = "Sélectionner un modèle ou appuyer sur Échap"; + 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"; out.presentButtonTitle = "Entrer en mode présentation"; - out.presentSuccess = 'Appuyer sur Échap pour quitter le mode présentation'; out.backgroundButtonTitle = 'Changer la couleur de fond de la présentation'; out.colorButtonTitle = 'Changer la couleur du texte en mode présentation'; + out.propertiesButton = "Propriétés"; + out.propertiesButtonTitle = 'Voir les propriétés de ce pad'; + out.printText = "Imprimer"; out.printButton = "Imprimer (Entrée)"; out.printButtonTitle = "Imprimer votre présentation ou l'enregistrer au format PDF"; @@ -147,16 +167,29 @@ define(function () { out.printTitle = "Afficher le titre du pad"; out.printCSS = "Personnaliser l'apparence (CSS):"; out.printTransition = "Activer les animations de transition"; + out.printBackground = "Utiliser une image d'arrière-plan"; + out.printBackgroundButton = "Choisir une image"; + out.printBackgroundValue = "Arrière-plan actuel: {0}"; + out.printBackgroundNoValue = "Aucun arrière-plan affiché"; + out.printBackgroundRemove = "Supprimer cet arrière-plan"; - out.filePickerButton = "Intégrer un fichier"; + out.filePickerButton = "Intégrer un fichier stocké dans CryptDrive"; out.filePicker_close = "Fermer"; - out.filePicker_description = "Choisissez un fichier de votre CryptDrive pour l'intégrer ou uploadez-en un nouveau"; + out.filePicker_description = "Choisissez un fichier de votre CryptDrive pour l'intégrer ou importez-en un nouveau"; out.filePicker_filter = "Filtrez les fichiers par leur nom"; out.or = 'ou'; + out.tags_title = "Mots-clés du pad (pour vous uniquement)"; + out.tags_add = "Modifier les mots-clés du pad"; + out.tags_searchHint = "Commencez une recherche par # dans votre CryptDrive pour retrouver vos pads par mot-clé."; + out.tags_notShared = "Vos mots-clés ne sont pas partagés avec les autres utilisateurs."; + out.tags_duplicate = "Mot-clé déjà présent : {0}"; + out.tags_noentry = "Vous ne pouvez pas ajouter de mots-clés à un pad supprimé!"; + out.slideOptionsText = "Options"; out.slideOptionsTitle = "Personnaliser la présentation"; out.slideOptionsButton = "Enregistrer (Entrée)"; + out.slide_invalidLess = "Feuille de style non valide"; out.languageButton = "Langage"; out.languageButtonTitle = "Sélectionner le langage à utiliser pour la coloration syntaxique"; @@ -171,6 +204,13 @@ define(function () { out.viewShareTitle = "Copier lien d'accès en lecture seule dans le presse-papiers"; out.viewOpen = "Voir dans un nouvel onglet"; out.viewOpenTitle = "Ouvrir le lien en lecture seule dans un nouvel onglet"; + out.fileShare = "Copier le lien"; + out.getEmbedCode = "Obtenir le code d'intégration"; + out.viewEmbedTitle = "Intégrer le pad dans une page web"; + out.viewEmbedTag = "Pour intégrer ce pad, veuillez inclure l'iframe suivant dans votre page là où vous souhaitez l'afficher. Vous pouvez changer sa taille en utilisant du code CSS ou des attributs HTML."; + out.fileEmbedTitle = "Intégrer le fichier dans une page web"; + out.fileEmbedScript = "Pour intégrer un fichier, veuillez inclure le script suivant une fois dans votre page afin de pouvoir charger le Media Tag :"; + out.fileEmbedTag = "Ensuite vous pouvez placer ce Media Tag où vous souhaitez dans votre page pour l'intégrer :"; out.notifyJoined = "{0} a rejoint la session collaborative"; out.notifyRenamed = "{0} a changé son nom en {1}"; @@ -179,7 +219,12 @@ define(function () { out.okButton = 'OK (Entrée)'; out.cancel = "Annuler"; - out.cancelButton = 'Annuler (Echap)'; + out.cancelButton = 'Annuler (Échap)'; + out.doNotAskAgain = "Ne plus demander (Échap)"; + + out.show_help_button = "Afficher l'aide"; + out.hide_help_button = "Cacher l'aide"; + out.help_button = "Aide"; out.historyText = "Historique"; out.historyButton = "Afficher l'historique du document"; @@ -194,8 +239,11 @@ define(function () { out.history_restoreDone = "Document restauré"; out.history_version = "Version :"; - // Ckeditor links + // Ckeditor out.openLinkInNewTab = "Ouvrir le lien dans un nouvel onglet"; + out.pad_mediatagTitle = "Options du Media-Tag"; + out.pad_mediatagWidth = "Largeur (px)"; + out.pad_mediatagHeight = "Hauteur (px)"; // Polls @@ -213,7 +261,7 @@ define(function () { out.poll_admin_button = "Administrer"; out.poll_create_user = "Ajouter un utilisateur"; out.poll_create_option = "Ajouter une option"; - out.poll_commit = "Valider"; + out.poll_commit = "Ajouter"; out.poll_closeWizardButton = "Fermer l'assistant"; out.poll_closeWizardButtonTitle = "Fermer l'assistant"; @@ -229,15 +277,26 @@ define(function () { out.poll_removeUser = "Êtes-vous sûr de vouloir supprimer cet utilisateur ?"; out.poll_titleHint = "Titre"; - out.poll_descriptionHint = "Description"; + out.poll_descriptionHint = "Décrivez votre sondage puis cliquer sur le bouton ✓ (Publier).\n" + + "La description peut contenir de la syntaxe markdown, et vous pouvez y ajouter des images stockées dans votre CryptDrive.\n" + + "Toutes les personnes possédant le lien d'édition de ce sondage peuvent modifier la description, bien que ce soit déconseillé."; out.poll_remove = "Supprimer"; out.poll_edit = "Modifier"; out.poll_locked = "Verrouillé"; out.poll_unlocked = "Déverrouillé"; - out.poll_show_help_button = "Afficher l'aide"; - out.poll_hide_help_button = "Cacher l'aide"; + out.poll_bookmark_col = "Marquer cette colonne comme favorite pour qu'elle soit toujours déverouillée et affichée en première position."; + out.poll_bookmarked_col = "Voici votre colonne favorite ; elle sera toujours dévérouillée et affichée en première position."; + out.poll_total = 'TOTAL'; + + out.poll_comment_list = "Commentaires"; + out.poll_comment_add = "Ajouter un commentaire"; + out.poll_comment_submit = "Envoyer"; + out.poll_comment_remove = "Supprimer ce commentaire"; + out.poll_comment_placeholder = "Votre commentaire"; + + out.poll_comment_disabled = "Publiez ce sondage en utilisant le bouton ✓ afin d'activer les commentaires."; // Canvas out.canvas_clear = "Nettoyer"; @@ -246,11 +305,12 @@ define(function () { out.canvas_enable = "Activer le dessin"; out.canvas_width = "Taille"; out.canvas_opacity = "Opacité"; - out.canvas_opacityLabel = "Opacité: {0}"; - out.canvas_widthLabel = "Taille: {0}"; + out.canvas_opacityLabel = "Opacité : {0}"; + out.canvas_widthLabel = "Taille : {0}"; out.canvas_saveToDrive = "Sauvegarder cette image en tant que fichier dans CryptDrive"; out.canvas_currentBrush = "Pinceau actuel"; out.canvas_chooseColor = "Choisir une couleur"; + out.canvas_imageEmbed = "Intégrer une image de votre ordinateur"; // Profile out.profileButton = "Profil"; // dropdown menu @@ -258,11 +318,13 @@ define(function () { out.profile_namePlaceholder = 'Nom ou pseudo pour le profil'; out.profile_avatar = "Avatar"; out.profile_upload = " Importer un nouvel avatar"; - out.profile_error = "Erreur lors de la création du profil : {0}"; + out.profile_uploadSizeError = "Erreur : votre avatar doit avoir une taille inférieure à {0}"; + out.profile_uploadTypeError = "Erreur : le format de votre avatar est invalide. Les formats autorisés sont : {0}"; + out.profile_error = "Erreur lors de la création du profil : {0}"; out.profile_register = "Vous devez vous inscrire pour pouvoir créer un profil !"; out.profile_create = "Créer un profil"; out.profile_description = "Description"; - out.profile_fieldSaved = 'Nouvelle valeur enregistrée: {0}'; + out.profile_fieldSaved = 'Nouvelle valeur enregistrée : {0}'; out.profile_inviteButton = "Inviter"; out.profile_inviteButtonTitle = 'Créer un lien pour inviter cet utilisateur à se connecter avec vous.'; @@ -270,8 +332,8 @@ define(function () { out.profile_viewMyProfile = "Voir mon profil"; // contacts/userlist - out.userlist_addAsFriendTitle = 'Ajouter "{0}" comme contact'; - out.userlist_thisIsYou = 'Vous ("{0}")'; + out.userlist_addAsFriendTitle = 'Ajouter « {0} » comme contact'; + out.userlist_thisIsYou = 'Vous (« {0} »)'; out.userlist_pending = "En attente..."; out.contacts_title = "Contacts"; out.contacts_addError = "Erreur lors de l'ajout de ce contact dans votre liste"; @@ -280,7 +342,7 @@ define(function () { out.contacts_request = '{0} souhaite vous ajouter en tant que contact. Accepter ?'; out.contacts_send = 'Envoyer'; out.contacts_remove = 'Supprimer ce contact'; - out.contacts_confirmRemove = 'Êtes-vous sûr de voulour supprimer {0} de vos contacts ?'; + out.contacts_confirmRemove = 'Êtes-vous sûr de vouloir supprimer {0} de vos contacts ?'; out.contacts_typeHere = "Entrez un message ici..."; @@ -292,6 +354,7 @@ define(function () { out.contacts_removeHistoryTitle = "Supprimer l'historique du chat"; out.contacts_confirmRemoveHistory = 'Êtes-vous sûr de vouloir supprimer définitivement l\'historique de votre chat ? Les messages ne pourront pas être restaurés.'; out.contacts_removeHistoryServerError = 'Une erreur est survenue lors de la supprimer de l\'historique du chat. Veuillez réessayer plus tard.'; + out.contacts_fetchHistory = "Récupérer l'historique plus ancien"; // File manager @@ -302,6 +365,7 @@ define(function () { out.fm_templateName = "Modèles"; out.fm_searchName = "Recherche"; out.fm_recentPadsName = "Pads récents"; + out.fm_ownedPadsName = "Pads en votre possession"; 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"; @@ -321,8 +385,10 @@ define(function () { out.fm_openParent = "Montrer dans le dossier"; out.fm_noname = "Document sans titre"; out.fm_emptyTrashDialog = "Êtes-vous sûr de vouloir vider la corbeille ?"; - out.fm_removeSeveralPermanentlyDialog = "Êtes-vous sûr de vouloir supprimer ces {0} éléments de manière permanente ?"; - out.fm_removePermanentlyDialog = "Êtes-vous sûr de vouloir supprimer cet élément de manière permanente ?"; + out.fm_removeSeveralPermanentlyDialog = "Êtes-vous sûr de vouloir supprimer ces {0} éléments de votre CryptDrive de manière permanente ?"; + out.fm_removePermanentlyDialog = "Êtes-vous sûr de vouloir supprimer cet élément de votre CryptDrive de manière permanente ?"; + out.fm_deleteOwnedPad = "Êtes-vous sûr de vouloir supprimer définitivement ce pad du serveur ?"; + out.fm_deleteOwnedPads = "Êtes-vous sûr de vouloir supprimer définitivement ces pads du serveur ?"; out.fm_restoreDialog = "Êtes-vous sûr de vouloir restaurer {0} à son emplacement précédent ?"; out.fm_removeSeveralDialog = "Êtes-vous sûr de vouloir déplacer ces {0} éléments vers la corbeille ?"; out.fm_removeDialog = "Êtes-vous sûr de vouloir déplacer {0} vers la corbeille ?"; @@ -337,8 +403,10 @@ define(function () { out.updated_0_fm_info_trash = "Vider la corbeille permet de libérer de l'espace dans votre CryptDrive"; out.fm_info_trash = out.updated_0_fm_info_trash; out.fm_info_allFiles = 'Contient tous les fichiers de "Documents", "Fichiers non triés" et "Corbeille". Vous ne pouvez pas supprimer ou déplacer des fichiers depuis cet endroit.'; // Same here - out.fm_info_anonymous = 'Vous n\'êtes pas connecté, ces pads risquent donc d\'être supprimés (découvrez pourquoi). ' + + out.fm_info_anonymous = 'Vous n\'êtes pas connecté, ces pads seront donc supprimés après 3 mois d\'inactivité (découvrez pourquoi). ' + + 'Ils sont stockés dans votre navigateur donc nettoyer votre historique peut les faire disparaître.
' + 'Inscrivez-vous ou connectez-vous pour les maintenir en vie.'; + out.fm_info_owned = "Vous êtes propriétaire des pads affichés dans cette catégorie. Cela signifie que vous pouvez choisir de les supprimer définitivement du serveur à n'importe quel moment. Ils seront alors inaccessibles pour tous les autres utilisateurs."; out.fm_alert_backupUrl = "Lien de secours pour ce CryptDrive.
" + "Il est fortement recommandé de garder ce lien pour vous-même.
" + "Il vous servira en cas de perte des données de votre navigateur afin de retrouver vos fichiers.
" + @@ -352,16 +420,26 @@ define(function () { out.fm_error_cantPin = "Erreur interne du serveur. Veuillez recharger la page et essayer de nouveau."; 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_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 ?
" + + "Cette action supprimera votre CryptDrive et son historique de votre navigateur, mais les pads existeront toujours (de manière chiffrée) sur notre serveur."; + 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}"; // File - Context menu out.fc_newfolder = "Nouveau dossier"; out.fc_rename = "Renommer"; out.fc_open = "Ouvrir"; out.fc_open_ro = "Ouvrir (lecture seule)"; - out.fc_delete = "Supprimer"; + out.fc_delete = "Déplacer vers la corbeille"; + out.fc_delete_owned = "Supprimer du serveur"; out.fc_restore = "Restaurer"; - out.fc_remove = "Supprimer définitivement"; + out.fc_remove = "Supprimer de votre CryptDrive"; 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."; @@ -397,9 +475,11 @@ define(function () { out.login_invalPass = 'Mot de passe requis'; out.login_unhandledError = "Une erreur inattendue s'est produite :("; - out.register_importRecent = "Importer l'historique (Recommendé)"; + out.register_importRecent = "Importer les pads de votre session anonyme"; out.register_acceptTerms = "J'accepte les conditions d'utilisation"; out.register_passwordsDontMatch = "Les mots de passe doivent être identiques!"; + out.register_passwordTooShort = "Les mots de passe doivent contenir au moins {0} caractères."; + out.register_mustAcceptTerms = "Vous devez accepter les conditions d'utilisation."; out.register_mustRememberPass = "Nous ne pouvons pas réinitialiser votre mot de passe si vous l'oubliez. C'est important que vous vous en souveniez! Veuillez cocher la case pour confirmer."; out.register_writtenPassword = "J'ai bien noté mon nom d'utilisateur et mon mot de passe, continuer"; @@ -407,6 +487,7 @@ define(function () { out.register_warning = "Zero Knowledge signifie que nous ne pouvons pas récupérer vos données si vous perdez vos identifiants."; out.register_alreadyRegistered = "Cet utilisateur existe déjà, souhaitez-vous vous connecter ?"; + out.register_whyRegister = "Pourquoi s'inscrire ?"; out.register_header = "Bienvenue dans CryptPad"; out.register_explanation = [ "

Faisons d'abord le point sur certaines choses

", @@ -421,6 +502,9 @@ define(function () { out.settings_cat_account = "Compte"; out.settings_cat_drive = "CryptDrive"; out.settings_cat_code = "Code"; + out.settings_cat_pad = "Documents texte"; + out.settings_cat_creation = "Nouveau pad"; + out.settings_cat_subscription = "Abonnement"; out.settings_title = "Préférences"; out.settings_save = "Sauver"; @@ -433,8 +517,8 @@ define(function () { out.settings_resetButton = "Supprimer"; out.settings_reset = "Supprimer tous les fichiers et dossiers de votre CryptDrive"; out.settings_resetPrompt = "Cette action va supprimer tous les pads de votre drive.
"+ - "Êtes-vous sûr de vouloir continuer ?
" + - "Tapez “I love CryptPad” pour confirmer."; + "Êtes-vous sûr de vouloir continuer ?
" + + "Tapez « I love CryptPad » pour confirmer."; out.settings_resetDone = "Votre drive est désormais vide!"; out.settings_resetError = "Texte de vérification incorrect. Votre CryptDrive n'a pas été modifié."; @@ -443,6 +527,13 @@ define(function () { out.settings_resetTipsButton = "Réinitialiser les astuces visibles dans CryptDrive"; out.settings_resetTipsDone = "Toutes les astuces sont de nouveau visibles."; + out.settings_thumbnails = "Miniatures"; + out.settings_disableThumbnailsAction = "Désactiver la création de miniatures dans CryptDrive"; + out.settings_disableThumbnailsDescription = "Des miniatures de vos pads sont automatiquement créées et stockées dans votre navigateur. Vous pouvez désactiver cette fonctionnalité."; + out.settings_resetThumbnailsAction = "Nettoyer"; + out.settings_resetThumbnailsDescription = "Nettoyer toutes les miniatures stockées dans votre navigateur."; + out.settings_resetThumbnailsDone = "Toutes les miniatures ont été effacées."; + out.settings_importTitle = "Importer les pads récents de ce navigateur dans votre CryptDrive"; out.settings_import = "Importer"; out.settings_importConfirm = "Êtes-vous sûr de vouloir importer les pads récents de ce navigateur dans le CryptDrive de votre compte utilisateur ?"; @@ -453,7 +544,14 @@ define(function () { out.settings_userFeedbackHint2 = "Le contenu de vos pads et les clés de déchiffrement ne seront jamais partagés avec le serveur."; out.settings_userFeedback = "Activer l'envoi de retours d'expérience"; - out.settings_anonymous = "Vous n'êtes pas connectés. Ces préférences seront utilisées pour ce navigateur."; + out.settings_deleteTitle = "Suppression du compte"; + 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"; out.settings_usage = "Utilisation"; @@ -470,12 +568,29 @@ define(function () { out.settings_codeIndentation = "Indentation dans l'éditeur de code (nombre d'espaces)"; out.settings_codeUseTabs = "Utiliser des tabulations au lieu d'espaces"; + out.settings_padWidth = "Largeur de l'éditeur de texte"; + out.settings_padWidthHint = "L'éditeur de documents texte occupe toute la largeur de l'écran disponible par défaut, ce qui peut rendre le texte difficile à lire. Vous pouvez ici réduire la largeur de l'éditeur."; + out.settings_padWidthLabel = "Réduire la largeur de l'éditeur"; + + out.settings_creationSkip = "Passer l'écran de création de pad"; + out.settings_creationSkipHint = "L'écran de création de pad offre de nouvelles options pour créer un pad, permettant d'avoir plus de contrôle et de sécurité concernant vos données. Toutefois, il peut ralentir votre travail en ajoutant une étape supplémentaire et donc, ici, vous avez la possibilité de choisir de passer cet écran et d'utiliser les paramètres par défaut choisis au-dessus."; + out.settings_creationSkipTrue = "Passer"; + out.settings_creationSkipFalse = "Afficher"; + + 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.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_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."; out.upload_notEnoughSpace = "Il n'y a pas assez d'espace libre dans votre CryptDrive pour ce fichier."; + out.upload_notEnoughSpaceBrief = "Pas assez d'espace"; out.upload_tooLarge = "Ce fichier dépasse la taille maximale autorisée."; + out.upload_tooLargeBrief = 'Fichier trop volumineux'; out.upload_choose = "Choisir un fichier"; out.upload_pending = "En attente"; out.upload_cancelled = "Annulé"; @@ -485,6 +600,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.todo_title = "CryptTodo"; out.todo_newTodoNamePlaceholder = "Décrivez votre tâche..."; @@ -497,8 +613,21 @@ 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"; + out.mdToolbar_help = "Aide"; + out.mdToolbar_tutorial = "https://blog.wax-o.com/2014/04/tutoriel-un-guide-pour-bien-commencer-avec-markdown/"; + out.mdToolbar_bold = "Gras"; + out.mdToolbar_italic = "Italique"; + out.mdToolbar_strikethrough = "Barré"; + out.mdToolbar_heading = "Titre"; + out.mdToolbar_link = "Lien"; + out.mdToolbar_quote = "Citation"; + out.mdToolbar_nlist = "Liste ordonnée"; + out.mdToolbar_list = "Liste à puces"; + out.mdToolbar_check = "Liste de tâches"; + out.mdToolbar_code = "Code"; // index.html @@ -507,6 +636,12 @@ define(function () { 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.'; //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"; @@ -528,7 +663,7 @@ define(function () { 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 = 'Plannifiez vos réunions ou évènements, ou votez pour la meilleure solution concernant votre problème.'; + 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'; @@ -545,13 +680,32 @@ define(function () { out.footer_aboutUs = "À propos"; out.about = "À propos"; - out.privacy = "Vie privée"; + out.privacy = "Confidentialité"; out.contact = "Contact"; out.terms = "Conditions"; out.blog = "Blog"; out.topbar_whatIsCryptpad = "Qu'est-ce que CryptPad"; + // what-is-cryptpad.html + + out.whatis_title = "Qu'est-ce que CryptPad"; + out.whatis_collaboration = 'Collaboration rapide, facile'; + out.whatis_collaboration_p1 = "Avec CryptPad, vous pouvez créer rapidement des documents collaboratifs pour prendre des notes à plusieurs. Quand vous vous enregistrez et vous vous connectez, vous obtenez la possibilité d'importer des fichiers dans un CryptDrive où vous pouvez organiser tous vos pads (documents). En tant qu'utilisateur enregistré, vous possédez 50 Mo de stockage gratuit."; + out.whatis_collaboration_p2 = "Vous pouvez partager l'accès à un document simplement en partageant le lien. Vous pouvez aussi partager un lien spécial fournissant un accès en lecture seule au pad, permettant de publier des travaux collaboratifs tout en restant maître de l'édition."; + out.whatis_collaboration_p3 = "Vous pouvez créer des documents de texte avec CKEditor tout comme des documents Markdown qui sont rendus en temps-réel pendant que vous tapez. Vous pouvez aussi utiliser l'application de sondage pour planifier des évènements avec plusieurs participants."; + out.whatis_zeroknowledge = 'Zero Knowledge'; + out.whatis_zeroknowledge_p1 = "Nous ne souhaitons pas connaître ce que vous tapez et grâce à la cryptographie moderne, vous pouvez être assuré que nous ne le pouvons pas. CryptPad utilise un chiffrement à 100% côté client pour protéger le contenu que vous tapez de nous, les personnes contrôlant le serveur."; + out.whatis_zeroknowledge_p2 = "Quand vous vous enregistrez et vous vous connectez, votre nom d'utilisateur et votre mot de passe sont transformés en une clé secrète grâce à la fonction de dérivation de clé Scrypt. Ni cette clé, ni le nom d'utilisateur ou le mot de passe, ne sont envoyés au serveur. À la place, elle est utilisée côté client pour chiffrer et déchiffrer le contenu de votre CryptDrive, qui contient toutes les clés permettant d'accéder à vos pads."; + out.whatis_zeroknowledge_p3 = "Quand vous partagez le lien vers un document, vous partagez la clé cryptographique permettant de déchiffrer le document, mais puisque cette clé se trouve dans l'identificateur de fragment, elle n'est jamais envoyée au serveur. Venez lire notre article de blog sur la vie privée pour en apprendre davantage sur le type de métadonnées auxquelles nous avons ou n'avons pas accès."; + out.whatis_drive = "Organisation avec CryptDrive"; + out.whatis_drive_p1 = "Dés que vous accédez à un pad dans CryptPad, celui-ci est automatiquement ajouté à votre CryptDrive, dans le dossier principal. Vous pouvez alors ranger ce pad dans un dossier ou le déplacer vers la corbeille. CryptDrive vous permet de rechercher parmi vos pads et de les organiser quand vous le souaitez, comme vous le souhaitez."; + 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_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 out.policy_title = 'Politique de confidentialité de CryptPad'; @@ -572,41 +726,287 @@ define(function () { out.policy_choices_vpn = 'Si vous souhaitez utiliser notre instance hébergée (cryptpad.fr) mais que vous ne souhaitez pas exposer votre adresse IP, vous pouvez la protéger en utilisant le navigateur Tor, ou un VPN.'; out.policy_choices_ads = 'Si vous souhaitez uniquement bloquer notre plateforme d\'analytique, vous pouvez utiliser un bloqueur de publicités tel que Privacy Badger.'; + // features.html + + out.features = "Fonctionnalités"; + out.features_title = "Tableau des fonctionnalités"; + out.features_feature = "Fonctionnalité"; + out.features_anon = "Utilisateur anonyme"; + out.features_registered = "Utilisateur enregistré"; + out.features_notes = "Notes"; + out.features_f_pad = "Créer/modifier/voir un pad"; + out.features_f_pad_notes = "Texte, Code, Présentation, Sondage et Tableau blanc"; + 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_export = "Export/Import"; + out.features_f_export_notes = "Pour les pads et CryptDrive"; + out.features_f_viewFiles = "Voir des fichiers"; + out.features_f_uploadFiles = "Importer des fichiers"; + out.features_f_embedFiles = "Intégrer des fichiers"; + out.features_f_embedFiles_notes = "Intégrer un fichier de CryptDrive dans un pad"; + out.features_f_multiple = "Appareils multiples"; + 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_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"; + out.features_f_tags = "Utiliser les tags"; + out.features_f_tags_notes = "Permet la recherche de documents par tags dans CryptDrive"; + out.features_f_contacts = "Application Contacts"; + out.features_f_contacts_notes = "Ajouter des contacts et discuter avec eux de manière sécurisée"; + 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"; + + // faq.html + + out.faq_link = "FAQ"; + out.faq_title = "Foire Aux Questions"; + out.faq_whatis = "Qu'est-ce que CryptPad ?"; + out.faq = {}; + out.faq.keywords = { + title: 'Termes spéciaux', + pad: { + q: "Qu'est-ce qu'un pad ?", + a: 'Pad est un terme popularisé par Etherpad un éditeur collaboratif en temps-réel. ' + + 'Il désigne un document que vous pouvez modifier dans votre navigateur et, en général, vous pouvez voir les modifications effectuées par les autres utilisateurs de manière quasiment instantanée.' + }, + owned: { + q: "Qu'est-ce qu'un pad avec propriétaire ?", + a: "Être propriétaire d'un pad signifie que vous êtes identifié comme tel par le serveur avec à votre clé de signature publique.
" + + "Le propriétaire d'un pad peut décider de supprimer ce pad du serveur de manière permanente, afin de le rendre inaccessible aux autres collaborateurs même s'ils possédent le lien dans leur CryptDrive." + }, + expiring: { + q: "Qu'est-ce qu'un pad à durée de vie ?", + a: "Un pad à durée de vie est un pad créé avec une date définie à partir de laquelle il sera supprimé automatiquement du serveur. Ils peuvent être configurés pour avoir une durée de vie comprise entre une heure et cent mois. Le pad et tout son historique sera alors inaccessible, de manière permanente, même s'il est en cours d'édition à sa date d'expiration.
" + + "Si un pad possède une date d'expiration, vous pouvez la vérifier en regardant les propriétés du pad, soit avec un clic-droit sur le pad dans votre CryptDrive, ou soit en cliquant sur Propriétés dans le sous-menu de la barre d'outils de l'application." + }, + tag: { + q: "Comment utiliser les mots-clés ?", + a: "Vous pouvez ajouter des mots-clés aux pads ou aux fichiers depuis votre CryptDrive et depuis le document en utilisant le bouton (Mots-clés) de la barre d'outils des éditeurs.
" + + "Il est ensuite possible de rechercher des pads et des fichiers dans votre CryptDrive en tapant un mot-clé, précédé de #, dans la barre de recherche (exemple: #crypto)." + }, + template: { + q: "Qu'est-ce qu'un modèle ?", + a: "Un modèle est un pad qui peut être utilisé pour définir le contenu initial d'un nouveau pad du même type quand vous le créez.
" + + "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." + }, + }; + out.faq.privacy = { + title: 'Confidentialité', + different: { + q: "Comment Cryptpad est-il différent des autres services de pads ?", + a: "CryptPad chiffre les changements effectués dans vos pads avant de les envoyer au serveur pour qu'il soient stockés, nous ne pouvons donc pas lire le contenu que vous avez tapé." + }, + me: { + q: "Quelles informations le serveur possède-t-il sur moi ?", + a: "Les administrateurs du serveur peuvent voir les adresses IP des utilisateurs de CryptPad.
" + + "Nous n'enregistrons pas les pads visités par chaque adresse IP mais nous le pouvons, bien que nous n'aurions pas accès au contenu déchiffré de ces pads.
" + + "Si vous avez des inquiétudes à ce sujet, il est préférable de considérer que nous collectons ces informations puisque nous n'avons aucun moyen de prouver que ce n'est pas le cas.

" + + "Nous collectons toutefois certaines données de télémétrie concernant la façon dont les gens utilisent CryptPad, par exemple la résolution de l'écran utilisé ou l'utilisation des boutons de la barre d'outils. Ces données nous aident à améliorer le produit, mais il est possible de désactiver l'envoi de telles informations au serveur en décochant la case Activer l'envoi de retours d'expérience dans vos Préférences.

" + + "Enfin, nous gardons une trace des pads stockés dans le CryptDrive des utilisateurs afin de pouvoir imposer les limites de stockage, mais nous n'avons, encore une fois, pas accès au contenu ou au type de ces pads. Ces limites sont toutefois associées à la clé publique des utilisateurs, nous ne pouvons donc pas les relier à un nom ou une adresse email.

" + + "Nous avons écrit un article de blog (en anglais) à ce sujet si vous souhaitez en apprendre davantage." + }, + register: { + q: "Qu'est-ce que le serveur apprend à mon sujet si je m'inscrit ?", + a: "Nous ne demandons pas aux utilisateurs d'entrer une adresse email pour s'enregistrer, et le serveur ne connaît pas votre nom d'utilisateur ni votre mot de passe.
" + + "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." + }, + other: { + q: "Que peuvent apprendre les autres collaborateurs à mon sujet ?", + a: "Quand vous éditez un pad avec quelqu'un d'autre, vous communiquez en passant par notre serveur, nous sommes donc les seuls à connaître votre adresse IP.
" + + "Les autres utilisateurs ont accès à votre pseudonyme, votre avatar, le lien vers votre profil (si vous en avez un) et votre clé publique (qui est utilisée pour le chiffrement des communications entre utilisateurs)." + }, + anonymous: { + q: "CryptPad me rend-il anonyme ?", + a: "Bien que CryptPad soit conçu pour en savoir le moins possible à votre sujet, il ne fournit pas un anonymat complet.
" + + "Nos serveurs ont accès à votre adresse IP, mais vous pouvez la cacher en utilisant, par exemple, Tor pour accéder à CryptPad.
" + + "Utiliser Tor sans changer votre comportement ne garantira toutefois pas votre anonymat, puisque notre serveur est en mesure d'identifier des utilisateurs avec leur identifiant cryptoraphique unique. Si vous utilisez le même compte utilisateur avec et sans Tor, il serait donc possible de désanonymiser votre session.

" + + "Pour les utilisateurs qui n'ont pas besoin d'un niveau de confidentialité aussi élevé, Tor n'est pas nécessaire puisque CryptPad ne nécessite pas la saisie d'un nom réel, d'un numéro de téléphone ou même d'une adresse email comme de nombreux autres services." + }, + policy: { + q: "Avez-vous une politique de confidentialité des données ?", + a: 'Oui ! Elle est disponible ici.' + }, + }; + out.faq.security = { + title: 'Sécurité', + proof: { + q: "Comment utilisez-vous les preuves à divulgation nulle de connaissance (Zero Knowledge proofs) ?", + a: "Quand nous utilisons le terme Zero Knowledge, ce n'est pas une référence aux Zero Knowledge proofs, mais aux Services Web Zero Knowledge.
" + + "Les Services Web Zero Knowledge chiffrent les données des utilisateurs dans le navigateur, de manière à ce que le serveur n'aie pas accès au contenu déchiffré ni aux clés de chiffrement.

" + + "Nous avons établi une courte liste de Services Zero Knowledge sur notre blog." + }, + why: { + q: "Pourquoi devrais-je utiliser CryptPad ?", + a: "Notre position est que les services cloud ne devraient pas nécessiter l'accès à vos données afin que vous puissiez les partager avec vos amis ou vos collègues. Si vous utilisez un autre service pour le travail collaboratif et qu'il n'indique pas clairement que le serveur n'a pas accès aux informations, il est très probable que vos données soient utilisées pour faire du profit." + }, + compromised: { + q: "CryptPad me protège-t-il si mon ordinateur est compromis ?", + a: "Dans le cas où votre ordinateur ou téléphone serait volé, CryptPad vous permet de déclencher une déconnexion à distance de votre compte CryptPad sur tous les appareils, excepté celui sur lequel vous vous trouvez. Pour ce faire, vous pouvez cliquer sur Se déconnecter partout dans votre page de Préférences.
" + + "Tous les appareils qui sont actuellement connectés sur CryptPad à votre compte seront déconnectés. Tous les appareils qui se sont connectés au compte et ne vous demandent plus vos identifiants vous forceront à vous identifier de nouveau lorsque vous visiterez CryptPad.
" + + "Actuellement, la déconnexion à distance est implémentée dans votre navigateur et non avec le serveur. Cela signifie que cette fonction devrait être suffisante pour protéger vos données si vous oubliez de vous déconnecter après l'utilisation sur un ordinateur partagé, mais elle ne vous protègera pas des agences gouvernementales." + }, + crypto: { + q: "Quelle cryptographie utilisez-vous ?", + a: 'CryptPad est basé sur deux librairies open-source de cryptographie : tweetnacl.js et scrypt-async.js.
' + + 'Scrypt est une fonction de dérivation de clé basée sur un mot de passe. Nous l\'utilisons pour transformer votre nom d\'utilisateur et votre mot de passe en un unique ensemble de clés qui sécurise l\'accès à votre CryptDrive afin que vous seul puissiez accéder à votre liste de pads.
' + + 'Nous utilisons les outils de chiffrement xsalsa20-poly1305 et x25519-xsalsa20-poly1305 fournis par tweetnacl pour chiffrer vos pads et l\'historique du chat respectivement.' + } + }; + out.faq.usability = { + title: 'Utilisation', + register: { + q: "Qu'est-ce que je gagne en créant un compte utilisateur ?", + a: 'Les utilisateurs enregistrés ont accès à un certain nombre de nouvelles fonctionnalités inaccessibles aux utilisateurs non connectés. Un tableau récapitulatif est disponible ici.' + }, + share: { + q: "Comment partager des pads chiffrés avec mes amis ?", + a: "CryptPad stocke la clé secrète de chiffrement des pads après le symbole `#` dans l'URL. " + + "Tout ce qui se trouve après ce symbole n'est jamais envoyé au serveur, ainsi nous n'avons pas accès à vos clés de chiffrement. " + + "Partager le lien d'un pad revient donc à permettre la lecture ou la modification du contenu." + }, + remove: { + q: "J'ai supprimé un pad ou un fichier de mon CryptDrive, mais le contenu est encore disponible. Comment le supprimer ?", + a: "Seuls les pads avec propriétaire (introduits en février 2018) peuvent être supprimés du serveur. Ils ne peuvent d'ailleurs être supprimés du serveur que par leur propriétaire (l'utilisateur ayant créé le pad).
" + + "Si vous n'êtes pas le créateur du pad, vous devrez demander au propriétaire de le supprimer pour vous.
" + + "Pour les pads dont vous êtes le propriétaire, vous pouvez effectuer un clic-droit sur le pad dans votre CryptDrive, et sélectionner Supprimer du serveur." + }, + forget: { + q: "Que faire si j'oublie mon mot de passe ?", + a: "Malheureusement, si nous avions la possibilité de retrouver ou de modifier votre mot de passe, cela signifierait que nous avons accès à vos pads, ce n'est donc pas le cas.
" + + "Si vous n'avez pas noté votre nom d'utilisateur et votre mot de passe, et que vous ne vous en souvenez pas, il est peut-être possible de retrouver l'accès à certains pads grâce à l'historique de votre navigateur." + }, + change: { + q: "Que faire si je souhaite changer de mot de passe ?", + a: "Il n'est actuellement pas possible de changer votre mot de passe sur CryptPad, mais nous comptons développer cette fonctionnalité très bientôt." + }, + devices: { + q: "Je suis connecté sur deux appareils mais avec deux CryptDrive différents, comment est-ce possible ?", + a: "Il est possible que vous ayez enregistré le même nom d'utilisateur 2 fois, mais avec des mots de passe différents.
" + + "Puisque le serveur de CryptPad vous identifie avec une clé cryptographique et non avec votre nom d'utilisateur, il ne peut pas empêcher la création d'autres comptes avec le même nom. Ainsi, chaque utilisateur possède une combinaison nom d'utilisateur / mot de passe unique.
" + + "Les utilisateurs enregistrés peuvent voir leur nom de compte en haut de la page de préférences." + }, + folder: { + q: "Puis-je partager des dossiers complets de mon CryptDrive ?", + a: "Nous travaillons sur l'ajout d'une fonctionnalité workgroups (ou groupes de travail), qui permettrait aux collaborateurs de partager une structure de type dossier, avec tous les pads contenus dans cette structure." + }, + feature: { + q: "Pouvez-vous ajouter une fonctionnalité particulière dont j'ai besoin ?", + a: 'Beaucoup de fonctionnalités de CryptPad existent parce que des utilisateurs les ont demandées.
' + + 'Notre page de contact liste les différentes manières de nous joindre.

' + + 'Malheureusement, nous ne pouvons pas garantir que nous allons implémenter toutes les fonctionnalités demandées.
' + + 'Si une fonctionnalité particulière est nécessaire pour votre organisation, vous pouvez sponsoriser son développement pour s\'assurer de sa réalisation. Veuillez contacter sales@cryptpad.fr pour plus d\'informations.

' + + "Si vous n'avez pas la possibilité de sponsoriser du développement, nous sommes toujours intéressés par de nouvelles idées et des retours d'expérience qui peuvent nous aider à améliorer CryptPad. N'hésitez pas à nous contacter, avec les méthodes données précédemment, à n'importe quel moment." + }, + }; + out.faq.other = { + title: "Autres questions", + pay: { + q: "Pourquoi payer alors que toutes les fonctionnalités sont gratuites ?", + a: "Un compte premium permet d'augmenter la limite de stockage dans le CryptDrive, ainsi que celle de ses amis (en savoir plus).
" + + "En plus des ces avantages directs, l'abonnement premium permet aussi de financer le développement actif et de manière continue de CryptPad. Cela comprend la correction de bugs, l'ajout de nouvelles fonctionnalités et rendre plus facile l'hébergement de CryptPad par d'autres personnes.
" + + "Avec un abonnement, vous aidez aussi à prouver aux autres fournisseurs de services que les gens sont prêts à supporter les technologies améliorant le respect de leur vie privée. Nous espérons qu'un jour, les entreprises ayant pour revenu principal la revente de données des utilisateurs soient de l'histoire ancienne.
" + + "Enfin, nous offrons la plupart des fonctionnalités gratuitement parce que nous croyons que tout le monde mérite le respect de la vie privée. En souscrivant à un compte premium, vous nous aider à maintenir ces fonctionnalités basiques accessibles aux populations défavorisées." + }, + goal: { + q: "Quel est votre objectif ?", + a: "En développant une technologie de collaboration qui respecte la vie privée, nous espérons augmenter les attentes des utilisateurs en ce qui concerne les plateformes de services \"cloud\" et leur politique de confidentialité. Nous souhaitons que notre travail conduise les autres fournisseurs de services, quel que soit leur domaine, à égaler voire dépasser nos efforts.
" + + "Malgré notre optimisme, nous savons que la plupart du Web est financé par les publicités ciblées. Il y a encore beaucoup de travail à effectuer que l'on peut faire de nous-mêmes, et nous apprécions le support, la promotion et les contributions de notre communauté envers cet objectif." + }, + jobs: { + q: "Est-ce que vous embauchez ?", + a: 'Oui ! Vous pouvez envoyer un email à jobs@xwiki.com.' + }, + host: { + q: "Pouvez-vous m'aider à installer ma propre instance de CryptPad ?", + a: 'Nous serions heureux de fournir du support pour l\'installation de CryptPad au sein de votre organisation. Veuillez contacter sales@cryptpad.fr pour plus d\'informations.' + }, + revenue: { + q: "Comment participer au système de partage des revenus ?", + a: "Si vous possédez votre propre instance de CryptPad et que vous souhaitez activer les comptes payant et partager les revenus avec les développeurs, votre serveur devra être configuré comme un service partenaire.
" + + 'Dans votre répertoire CryptPad, le fichier config.example.js devrait contenir des explications concernant les étapes à suivre pour configurer votre serveur. Vous devrez aussi contacter sales@cryptpad.fr pour vérifier que votre serveur est configuré correctement et pour discuter des méthodes de paiement.' + }, + }; // terms.html out.tos_title = "Conditions d'utilisation de CryptPad"; out.tos_legal = "Veuillez ne pas être malveillant, abusif, ou faire quoi que ce soit d'illégal."; - out.tos_availability = "Nous espérons que vous trouvez ce service utile, mais nous ne pouvons garantir ses performances et disponibilités. Nous vous recommandons d'exporter vos données régurlièrement."; + out.tos_availability = "Nous espérons que vous trouvez ce service utile, mais nous ne pouvons garantir ses performances et disponibilités. Nous vous recommandons d'exporter vos données régulièrement."; out.tos_e2ee = "Le contenu sur CryptPad peuvent être lus et modifiés par quiconque est en mesure de deviner ou d'obtenir de quelque manière que ce soit l'identificateur de fragment du pad. Nous vous recommandons d'utiliser des technologies de messagerie chiffrées de bout à bout (end-to-end encryption ou e2ee) pour partager les liens, et déclinons toute responsabilité dans le cas ou un tel lien serait divulgué."; out.tos_logs = "Les meta-données fournies par votre navigateur au serveur peuvent être enregistrées dans le but de maintenir le service."; out.tos_3rdparties = "Nous ne fournissons aucune donnée individuelle à des tierces parties à moins d'y être contraints par la loi."; - // BottomBar.html - - out.bottom_france = 'Fait avec amour en France'; - out.bottom_support = 'Un projet XWiki SAS Labs avec le soutien de OpenPaaS-ng'; + // 404 page + out.four04_pageNotFound = "Nous n'avons pas trouvé la page que vous cherchez."; // 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"; // Initial states + out.help = {}; + + out.help.title = "Pour bien démarrer"; + out.help.generic = { + more: 'Apprenez-en davantage sur le fonctionnement de CryptPad en lisant notre FAQ', + share: 'Utilisez le menu partage () pour générer un lien d\'accès ou d\'édition pad', + stored: 'Chaque pad que vous visitez est stocké automatiquement dans votre CryptDrive' + }; + + out.help.text = { + formatting: 'Vous pouvez afficher ou cacher la barre d\'outils de texte en cliquant sur les boutons ou ', + embed: 'Les utilisateurs enregistrés peuvent intégrer un fichier de leur CryptDrive en utilisant le bouton ', + history: 'Vous pouvez utiliser l\'historique () pour voir ou restaurer les versions précédentes du pad' + }; + + out.help.pad = { + export: 'Vous pouvez exporter le contenu en tant que PDF avec le bouton de la barre d\'outils de mise en forme du texte' + }; + + out.help.code = { + modes: 'Utilisez le sous-menu pour changer le mode de coloration syntaxique ou le thème de couleur' + }; + + out.help.slide = { + markdown: 'Rédigez vos slides en Markdown et séparez les avec une ligne contenant ---', + present: 'Démarrez la présentation en utilisant le bouton ', + settings: 'Modifiez les préférences de la présentation (image de fond, transitions, numéro de pages, ...) avec le bouton dans le sous-menu ', + colors: 'Modifiez la couleur du texte ou du fond en utilisant les boutons et ' + }; + + out.help.poll = { + decisions: 'Prenez des décisions en privé avec des personnes de confiance', + options: 'Proposez des options et exprimez vos préférences', + choices: 'Cliquez sur les cellules de votre colonne pour modifier leur valeur entre oui (), peut-être (~) ou non ()', + submit: 'Cliquez sur Ajouter pour rendre vos choix visibles aux autres' + }; + + out.help.whiteboard = { + colors: 'Double-cliquez sur les couleurs pour changer la palette', + mode: 'Vous pouvez désactiver le mode dessin pour déplacer, redimensionner, ou supprimer des éléments du dessin', + 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.initialState = [ '

', 'Voici CryptPad, l\'éditeur collaboratif en temps-réel Zero Knowledge. Tout est sauvegardé dés que vous le tapez.', '
', - 'Partagez le lien vers ce pad avec des amis ou utilisez le bouton  Partager  pour obtenir le lien de lecture-seule, qui permet la lecture mais non la modification.', + 'Partagez le lien vers ce pad avec des amis ou utilisez le bouton pour obtenir le lien de lecture-seule, qui permet la lecture mais non la modification.', '

', - '

', - '', - 'Lancez-vous, commencez à taper...', - '

', - '

 

' ].join(''); out.codeInitialState = [ @@ -618,14 +1018,6 @@ define(function () { out.slideInitialState = [ '# CryptSlide\n', - '* Voici CryptPad, l\'éditeur collaboratif en temps-réel Zero Knowledge.\n', - '* Ce que vous tapez ici est chiffré de manière que seules les personnes avec le lien peuvent y accéder.\n', - '* Même le serveur est incapable de voir ce que vous tapez.\n', - '* Ce que vous voyez ici, ce que vous entendez, quand vous partez, ça reste ici.\n', - '\n', - '---', - '\n', - '# Comment l\'utiliser\n', '1. Écrivez le contenu de votre présentation avec la syntaxe Markdown\n', ' - Apprenez à utiliser markdown en cliquant [ici](http://www.markdowntutorial.com/)\n', '2. Séparez vos slides avec ---\n', @@ -640,7 +1032,7 @@ define(function () { out.readme_cat1 = "Découvrez votre CryptDrive"; out.readme_cat1_l1 = "Créer un pad : Dans votre CryptDrive, cliquez sur {0} puis {1} et vous obtenez un nouveau pad."; // 0: New, 1: Rich Text out.readme_cat1_l2 = "Ouvrir des pads depuis votre CryptDrive : Double-cliquez sur l'icone d'un pad pour l'ouvrir."; - out.readme_cat1_l3 = "Organiser vos pads : Quand vous êtes connectés, tous les pads auquel vous accédez sont ajoutés dans la section {0} de votre CryptDrive."; // 0: Unsorted files + out.readme_cat1_l3 = "Organiser vos pads : Quand vous êtes connecté, tous les pads auquel vous accédez sont ajoutés dans la section {0} de votre CryptDrive."; // 0: Unsorted files out.readme_cat1_l3_l1 = "Vous pouvez cliquer et faire glisser des fichiers dans des dossiers dans la section {0} de votre CryptDrive, et créer de nouveaux dossiers."; // 0: Documents out.readme_cat1_l3_l2 = "N'hésitez pas à utiliser le clic droit sur les icones puisque des menus sont souvent disponibles."; out.readme_cat1_l4 = "Déplacer des pads vers la corbeille : Vous pouvez cliquer et faire glisser vos pads dans la {0} de la même manière que vous pouvez les déposer dans des dossiers."; // 0: Trash @@ -656,17 +1048,67 @@ define(function () { // Tips out.tips = {}; - out.tips.lag = "L'icône verte dans le coin supérieur droit montre la qualité de votre connexion Internet vers le serveur CryptPad."; 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.title = "Vous pouvez changer le titre de votre pad en cliquant au centre en haut de la page."; - 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és."; 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."; + out.tips.drive = "Les utilisateurs enregistrés peuvent organiser leurs fichiers dans leur CryptDrive, accessible depuis l'icône CryptPad dans le coin supérieur gauche des pads."; + out.tips.profile = "Les utilisateurs enregistrés peuvent créer un profil depuis le menu utilisateur, dans le coin supérieur droit."; + out.tips.avatars = "Vous pouvez uploader un avatar dans votre profil. Les autres personnes le verront dans la liste d'utilisateurs des pads."; + out.tips.tags = "Ajoutez des mots-clés aux pads et effectuer une recherche commençant par # dans votre CryptDrive pour les retrouver."; out.feedback_about = "Si vous lisez ceci, vous vous demandez probablement pourquoi CryptPad envoie des requêtes vers des pages web quand vous realisez certaines actions."; out.feedback_privacy = "Nous prenons au sérieux le respect de votre vie privée, et en même temps nous souhaitons rendre CryptPad très simple à utiliser. Nous utilisons cette page pour comprendre quelles fonctionnalités dans l'interface comptent le plus pour les utilisateurs, en l'appelant avec un paramètre spécifiant quelle action a été réalisée."; out.feedback_optout = "Si vous le souhaitez, vous pouvez désactiver ces requêtes en vous rendant dans votre page de préférences, où vous trouverez une case à cocher pour désactiver le retour d'expérience."; + // Creation page + out.creation_404 = "Ce pad n'existe plus. Vous pouvez créer un nouveau pad en utilisant le formulaire suivant."; + out.creation_ownedTitle = "Type de pad"; + out.creation_owned = "Être propriétaire de ce pad"; + out.creation_ownedTrue = "Être propriétaire"; + out.creation_ownedFalse = "Pas de propriétaire"; + out.creation_owned1 = "Être propriétaire d'un pad signifie que vous pouvez le supprimer du serveur à tout moment. Une fois supprimé, il disparaît du CryptDrive des autres utilisateurs."; + out.creation_owned2 = "Un pad sans propriétaire ne peut pas être supprimé du serveur à moins d'avoir dépassé son éventuelle date d'expiration."; + out.creation_expireTitle = "Durée de vie"; + out.creation_expire = "Ajouter une durée de vie"; + out.creation_expireTrue = "Ajouter durée de vie"; + out.creation_expireFalse = "Illimité"; + out.creation_expireHours = "Heure(s)"; + out.creation_expireDays = "Jour(s)"; + 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_noTemplate = "Pas de modèle"; + out.creation_newTemplate = "Nouveau modèle"; + out.creation_create = "Créer"; + out.creation_saveSettings = "Ne plus me demander"; + out.creation_settings = "Voir davantage de préférences"; + out.creation_rememberHelp = "Ouvrez votre page de Préférences pour voir ce formulaire à nouveau."; + // Properties about creation data + out.creation_owners = "Propriétaires"; + out.creation_ownedByOther = "Appartient à un autre utilisateur"; + out.creation_noOwner = "Pas de propriétaire"; + out.creation_expiration = "Date d'expiration"; + 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"; + + // New share modal + out.share_linkCategory = "Partage"; + out.share_linkAccess = "Droits d'accès"; + out.share_linkEdit = "Édition"; + out.share_linkView = "Lecture-seule"; + out.share_linkOptions = "Options du lien"; + out.share_linkEmbed = "Mode intégration (barre d'outils cachée)"; + out.share_linkPresent = "Mode présentation (sections d'édition cachées)"; + out.share_linkOpen = "Ouvrir le lien"; + out.share_linkCopy = "Copier le lien"; + out.share_embedCategory = "Intégration"; + out.share_mediatagCopy = "Copier le mediatag"; + return out; }); diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 43cbc9b22..ce2868783 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -22,28 +22,39 @@ define(function () { out.button_newslide = 'New Presentation'; out.button_newwhiteboard = 'New Whiteboard'; - // NOTE: We want to update the 'common_connectionLost' key. - // Please do not add a new 'updated_common_connectionLostAndInfo' but change directly the value of 'common_connectionLost' + // 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."; out.common_connectionLost = out.updated_0_common_connectionLost; 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."; + out.expiredError = 'This pad has reached its expiration time and is no longer available.'; + out.deletedError = 'This pad has been deleted by its owner and is no longer available.'; + out.inactiveError = 'This pad has been deleted due to inactivity. Press Esc to create a new pad.'; + 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.loading = "Loading..."; out.error = "Error"; out.saved = "Saved"; out.synced = "Everything is saved"; 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.disconnected = 'Disconnected'; out.synchronizing = 'Synchronizing'; - out.reconnecting = 'Reconnecting...'; - out.typing = "Typing"; + out.reconnecting = 'Reconnecting'; + out.typing = "Editing"; + out.initializing = "Initializing..."; + out.forgotten = 'Moved to the trash'; + out.errorState = 'Critical error: {0}'; out.lag = 'Lag'; out.readonly = 'Read only'; out.anonymous = "Anonymous"; @@ -56,6 +67,7 @@ define(function () { out.viewers = "viewers"; out.editor = "editor"; out.editors = "editors"; + out.userlist_offline = "You're currently offline, the user list is not available."; out.language = "Language"; @@ -131,15 +143,22 @@ define(function () { out.saveTemplatePrompt = "Choose a title for the template"; out.templateSaved = "Template saved!"; out.selectTemplate = "Select a template or press escape"; + 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"; out.presentButtonTitle = "Enter presentation mode"; - out.presentSuccess = 'Hit ESC to exit presentation mode'; out.backgroundButtonTitle = 'Change the background color in the presentation'; out.colorButtonTitle = 'Change the text color in presentation mode'; + out.propertiesButton = "Properties"; + out.propertiesButtonTitle = 'Get pad properties'; + out.printText = "Print"; out.printButton = "Print (enter)"; out.printButtonTitle = "Print your slides or export them as a PDF file"; @@ -149,16 +168,30 @@ define(function () { out.printTitle = "Display the pad title"; out.printCSS = "Custom style rules (CSS):"; out.printTransition = "Enable transition animations"; + out.printBackground = "Use a background image"; + out.printBackgroundButton = "Pick an image"; + out.printBackgroundValue = "Current background: {0}"; + out.printBackgroundNoValue = "No background image displayed"; + out.printBackgroundRemove = "Remove this background image"; - out.filePickerButton = "Embed a file"; + out.filePickerButton = "Embed a file stored in CryptDrive"; out.filePicker_close = "Close"; out.filePicker_description = "Choose a file from your CryptDrive to embed it or upload a new one"; out.filePicker_filter = "Filter files by name"; out.or = 'or'; + out.tags_title = "Tags (for you only)"; + out.tags_add = "Update this page's tags"; + out.tags_searchHint = "Find files by their tags by searching in your CryptDrive"; + out.tags_searchHint = "Start a search with # in your CryptDrive to find your tagged pads."; + out.tags_notShared = "Your tags are not shared with other users"; + out.tags_duplicate = "Duplicate tag: {0}"; + out.tags_noentry = "You can't tag a deleted pad!"; + out.slideOptionsText = "Options"; out.slideOptionsTitle = "Customize your slides"; out.slideOptionsButton = "Save (enter)"; + out.slide_invalidLess = "Invalid custom style"; out.languageButton = "Language"; out.languageButtonTitle = "Select the language to use for the syntax highlighting"; @@ -173,6 +206,13 @@ define(function () { out.viewShareTitle = "Copy the read-only link to clipboard"; out.viewOpen = "Open read-only link in a new tab"; out.viewOpenTitle = "Open this pad in read-only mode in a new tab"; + out.fileShare = "Copy link"; + out.getEmbedCode = "Get embed code"; + out.viewEmbedTitle = "Embed the pad in an external page"; + out.viewEmbedTag = "To embed this pad, include this iframe in your page wherever you want. You can style it using CSS or HTML attributes."; + out.fileEmbedTitle = "Embed the file in an external page"; + out.fileEmbedScript = "To embed this file, include this script once in your page to load the Media Tag:"; + out.fileEmbedTag = "Then place this Media Tag wherever in your page you would like to embed:"; out.notifyJoined = "{0} has joined the collaborative session"; out.notifyRenamed = "{0} is now known as {1}"; @@ -182,6 +222,11 @@ define(function () { out.cancel = "Cancel"; out.cancelButton = 'Cancel (esc)'; + out.doNotAskAgain = "Don't ask me again (Esc)"; + + out.show_help_button = "Show help"; + out.hide_help_button = "Hide help"; + out.help_button = "Help"; out.historyText = "History"; out.historyButton = "Display the document history"; @@ -196,8 +241,11 @@ define(function () { out.history_restoreDone = "Document restored"; out.history_version = "Version:"; - // Ckeditor links + // Ckeditor out.openLinkInNewTab = "Open Link in New Tab"; + out.pad_mediatagTitle = "Media-Tag settings"; + out.pad_mediatagWidth = "Width (px)"; + out.pad_mediatagHeight = "Height (px)"; // Polls @@ -215,7 +263,7 @@ define(function () { out.poll_admin_button = "Admin"; out.poll_create_user = "Add a new user"; out.poll_create_option = "Add a new option"; - out.poll_commit = "Commit"; + out.poll_commit = "Submit"; out.poll_closeWizardButton = "Close wizard"; out.poll_closeWizardButtonTitle = "Close wizard"; @@ -231,15 +279,26 @@ define(function () { out.poll_removeUser = "Are you sure you'd like to remove this user?"; out.poll_titleHint = "Title"; - out.poll_descriptionHint = "Describe your poll, and use the 'publish' button when you're done. Anyone with the link can change the description, but this is discouraged."; + out.poll_descriptionHint = "Describe your poll, and use the ✓ (publish) button when you're done.\n" + + "The description can be written using markdown syntax and you can embed media elements from your CryptDrive.\n" + + "Anyone with the link can change the description, but this is discouraged."; out.poll_remove = "Remove"; out.poll_edit = "Edit"; out.poll_locked = "Locked"; out.poll_unlocked = "Unlocked"; - out.poll_show_help_button = "Show help"; - out.poll_hide_help_button = "Hide help"; + out.poll_bookmark_col = 'Bookmark this column so that it is always unlocked and displayed at the beginning for you'; + out.poll_bookmarked_col = 'This is your bookmarked column. It will always be unlocked and displayed at the beginning for you.'; + out.poll_total = 'TOTAL'; + + out.poll_comment_list = "Comments"; + out.poll_comment_add = "Add a comment"; + out.poll_comment_submit = "Send"; + out.poll_comment_remove = "Delete this comment"; + out.poll_comment_placeholder = "Your comment"; + + out.poll_comment_disabled = "Publish this poll using the ✓ button to enable the comments."; // Canvas out.canvas_clear = "Clear"; @@ -253,6 +312,7 @@ define(function () { out.canvas_saveToDrive = "Save this image as a file in your CryptDrive"; out.canvas_currentBrush = "Current brush"; out.canvas_chooseColor = "Choose a color"; + out.canvas_imageEmbed = "Embed an image from your computer"; // Profile out.profileButton = "Profile"; // dropdown menu @@ -260,6 +320,8 @@ define(function () { out.profile_namePlaceholder = 'Name displayed in your profile'; out.profile_avatar = "Avatar"; out.profile_upload = " Upload a new avatar"; + out.profile_uploadSizeError = "Error: your avatar must be smaller than {0}"; + out.profile_uploadTypeError = "Error: your avatar type is not allowed. Allowed types are: {0}"; out.profile_error = "Error while creating your profile: {0}"; out.profile_register = "You have to sign up to create a profile!"; out.profile_create = "Create a profile"; @@ -304,6 +366,7 @@ define(function () { out.fm_templateName = "Templates"; out.fm_searchName = "Search"; out.fm_recentPadsName = "Recent pads"; + out.fm_ownedPadsName = "Owned"; out.fm_searchPlaceholder = "Search..."; out.fm_newButton = "New"; out.fm_newButtonTitle = "Create a new pad or folder, import a file in the current folder"; @@ -323,10 +386,12 @@ define(function () { out.fm_openParent = "Show in folder"; out.fm_noname = "Untitled Document"; out.fm_emptyTrashDialog = "Are you sure you want to empty the trash?"; - out.fm_removeSeveralPermanentlyDialog = "Are you sure you want to remove these {0} elements from the trash permanently?"; - out.fm_removePermanentlyDialog = "Are you sure you want to remove that element permanently?"; + out.fm_removeSeveralPermanentlyDialog = "Are you sure you want to remove these {0} elements from your CryptDrive permanently?"; + out.fm_removePermanentlyDialog = "Are you sure you want to remove that element from your CryptDrive permanently?"; out.fm_removeSeveralDialog = "Are you sure you want to move these {0} elements to the trash?"; out.fm_removeDialog = "Are you sure you want to move {0} to the trash?"; + out.fm_deleteOwnedPad = "Are you sure you want to remove permanently this pad from the server?"; + out.fm_deleteOwnedPads = "Are you sure you want to remove permanently these pads from the server?"; out.fm_restoreDialog = "Are you sure you want to restore {0} to its previous location?"; out.fm_unknownFolderError = "The selected or last visited directory no longer exist. Opening the parent folder..."; out.fm_contextMenuError = "Unable to open the context menu for that element. If the problem persist, try to reload the page."; @@ -340,7 +405,9 @@ define(function () { out.fm_info_trash = out.updated_0_fm_info_trash; out.fm_info_allFiles = 'Contains all the files from "Documents", "Unsorted" and "Trash". You can\'t move or remove files from here.'; // Same here out.fm_info_anonymous = 'You are not logged in so your pads will expire after 3 months (find out more). ' + - 'Sign up or Log in to keep them alive.'; + 'They are stored in your browser so clearing history may make them disappear.
' + + 'Sign up or Log in to keep them alive.
'; + out.fm_info_owned = "You are the owner of the pads displayed here. This means you can remove them permanently from the server whenever you want. If you do so, other users won't be able to access them anymore."; out.fm_alert_backupUrl = "Backup link for this drive.
" + "It is highly recommended that you keep it secret.
" + "You can use it to retrieve all your files in case your browser memory got erased.
" + @@ -354,16 +421,26 @@ define(function () { out.fm_error_cantPin = "Internal server error. Please reload the page and try again."; 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_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?
" + + "This will remove your CryptDrive and its history from your browser, but your pads will still exist (encrypted) on our server."; + 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}"; // File - Context menu out.fc_newfolder = "New folder"; out.fc_rename = "Rename"; out.fc_open = "Open"; out.fc_open_ro = "Open (read-only)"; - out.fc_delete = "Delete"; + out.fc_delete = "Move to trash"; + out.fc_delete_owned = "Delete from the server"; out.fc_restore = "Restore"; - out.fc_remove = "Delete permanently"; + out.fc_remove = "Remove from your CryptDrive"; 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"; @@ -399,12 +476,15 @@ define(function () { out.login_invalPass = 'Password required'; out.login_unhandledError = 'An unexpected error occurred :('; - out.register_importRecent = "Import pad history (Recommended)"; + out.register_importRecent = "Import pads from your anonymous session"; out.register_acceptTerms = "I accept the terms of service"; out.register_passwordsDontMatch = "Passwords do not match!"; + out.register_passwordTooShort = "Passwords must be at least {0} characters long."; + out.register_mustAcceptTerms = "You must accept the terms of service."; out.register_mustRememberPass = "We cannot reset your password if you forget it. It's very important that you remember it! Please check the checkbox to confirm."; + out.register_whyRegister = "Why sign up?"; out.register_header = "Welcome to CryptPad"; out.register_explanation = [ "

Lets go over a couple things first:

", @@ -414,8 +494,6 @@ define(function () { "
  • If you are using a shared computer, you need to log out when you are done, closing the tab is not enough.
  • ", "" ].join(''); - out.register_testimonial =" \"Tools like Etherpad and Google Docs [...] all share a weakness, which is that whomever owns the document server can see everything you're typing. Cryptpad is a free/open project that uses some of the ideas behind blockchain to implement a \"zero-knowledge\" version of a collaborative document editor, ensuring that only the people working on a document can see it.\" "; - out.register_testimonial_name = "Cory Doctorow"; out.register_writtenPassword = "I have written down my username and password, proceed"; out.register_cancel = "Go back"; @@ -428,6 +506,9 @@ define(function () { out.settings_cat_account = "Account"; out.settings_cat_drive = "CryptDrive"; out.settings_cat_code = "Code"; + out.settings_cat_pad = "Rich text"; + out.settings_cat_creation = "New pad"; + out.settings_cat_subscription = "Subscription"; out.settings_title = "Settings"; out.settings_save = "Save"; @@ -450,6 +531,13 @@ define(function () { out.settings_resetTipsButton = "Reset the available tips in CryptDrive"; out.settings_resetTipsDone = "All the tips are now visible again."; + out.settings_thumbnails = "Thumbnails"; + out.settings_disableThumbnailsAction = "Disable thumbnails creation in your CryptDrive"; + out.settings_disableThumbnailsDescription = "Thumbnails are automatically created and stored in your browser when you visit a new pad. You can disable this feature here."; + out.settings_resetThumbnailsAction = "Clean"; + out.settings_resetThumbnailsDescription = "Clean all the pads thumbnails stored in your browser."; + out.settings_resetThumbnailsDone = "All the thumbnails have been erased."; + out.settings_importTitle = "Import this browser's recent pads in your CryptDrive"; out.settings_import = "Import"; out.settings_importConfirm = "Are you sure you want to import recent pads from this browser to your user account's CryptDrive?"; @@ -460,6 +548,13 @@ define(function () { out.settings_userFeedbackHint2 = "Your pad's content will never be shared with the server."; out.settings_userFeedback = "Enable user feedback"; + out.settings_deleteTitle = "Account deletion"; + 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"; @@ -477,12 +572,29 @@ define(function () { out.settings_codeIndentation = 'Code editor indentation (spaces)'; out.settings_codeUseTabs = "Indent using tabs (instead of spaces)"; + out.settings_padWidth = "Editor's maximum width"; + out.settings_padWidthHint = "Rich text pads use by default the maximum available width on your screen and it can be difficult to read. You can reduce the editor's width here."; + out.settings_padWidthLabel = "Reduce the editor's width"; + + out.settings_creationSkip = "Skip the pad creation screen"; + out.settings_creationSkipHint = "The pad creation screen offers new options to create a pad, providing you more control and security over your data. However, it may slow down your workflow by adding one additionnal step so, here, you have the option to skip this screen and use the default settings selected above."; + out.settings_creationSkipTrue = "Skip"; + out.settings_creationSkipFalse = "Display"; + + 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.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_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."; out.upload_notEnoughSpace = "There is not enough space for this file in your CryptDrive."; + out.upload_notEnoughSpaceBrief = "Not enough space"; out.upload_tooLarge = "This file exceeds the maximum upload size."; + out.upload_tooLargeBrief = 'File too large'; out.upload_choose = "Choose a file"; out.upload_pending = "Pending"; out.upload_cancelled = "Cancelled"; @@ -492,6 +604,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.todo_title = "CryptTodo"; out.todo_newTodoNamePlaceholder = "Describe your task..."; @@ -504,8 +617,21 @@ define(function () { out.pad_showToolbar = "Show toolbar"; out.pad_hideToolbar = "Hide toolbar"; - // general warnings - out.warn_notPinned = "This pad is not in anyone's CryptDrive. It will expire after 3 months. Learn more..."; + // markdown toolbar + out.mdToolbar_button = "Show or hide the Markdown toolbar"; + out.mdToolbar_defaultText = "Your text here"; + out.mdToolbar_help = "Help"; + out.mdToolbar_tutorial = "http://www.markdowntutorial.com/"; + out.mdToolbar_bold = "Bold"; + out.mdToolbar_italic = "Italic"; + out.mdToolbar_strikethrough = "Strikethrough"; + out.mdToolbar_heading = "Heading"; + out.mdToolbar_link = "Link"; + out.mdToolbar_quote = "Quote"; + out.mdToolbar_nlist = "Ordered list"; + out.mdToolbar_list = "Bullet list"; + out.mdToolbar_check = "Task list"; + out.mdToolbar_code = "Code"; // index.html @@ -515,7 +641,12 @@ define(function () { 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.'; // 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_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)'; + out.main_about_p25 = 'send us an email'; + out.main_about_p26 = 'If you have any questions or comments, feel free to reach out!'; 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"; @@ -601,6 +732,250 @@ define(function () { out.policy_choices_vpn = 'If you want to use our hosted instance, but don\'t want to expose your IP address, you can protect your IP using the Tor browser bundle, or a VPN.'; out.policy_choices_ads = 'If you just want to block our analytics platform, you can use adblocking tools like Privacy Badger.'; + // features.html + + out.features = "Features"; + out.features_title = "Features table"; + out.features_feature = "Feature"; + out.features_anon = "Anonymous user"; + out.features_registered = "Registered user"; + out.features_notes = "Notes"; + out.features_f_pad = "Create/edit/view a pad"; + out.features_f_pad_notes = "Rich Text, Code, Slide, Poll and Whiteboard applications"; + 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_export = "Export/Import"; + out.features_f_export_notes = "For pads and CryptDrive"; + out.features_f_viewFiles = "View files"; + out.features_f_uploadFiles = "Upload files"; + out.features_f_embedFiles = "Embed files"; + out.features_f_embedFiles_notes = "Embed a file stored in CryptDrive in a pad"; + out.features_f_multiple = "Use on multiple devices"; + out.features_f_multiple_notes = "Easy way to access your pads from any device"; + out.features_f_logoutEverywhere = "Log out from other devices"; + out.features_f_logoutEverywhere_notes = ""; // Used in the French translation to explain + out.features_f_templates = "Use templates"; + out.features_f_templates_notes = "Create templates and create new pads from your templates"; + out.features_f_profile = "Create a profile"; + out.features_f_profile_notes = "Personal page including an avatar and a description"; + out.features_f_tags = "Use tags"; + out.features_f_tags_notes = "Allow users to search by tags in CryptDrive"; + 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_registered = "Free: 50MB
    Premium: 5GB/20GB/50GB"; + + // faq.html + + out.faq_link = "FAQ"; + out.faq_title = "Frequently Asked Questions"; + out.faq_whatis = "What is CryptPad?"; + out.faq = {}; + out.faq.keywords = { + title: 'Keywords', + pad: { + q: "What is a pad?", + a: "Pad is a term popularized by Etherpad, a real-time collaborative editor.\n" + + "It refers to a document that you can edit in your browser, generally with other people's changes visible nearly instantly." + }, + owned: { + q: "What is an owned Pad?", + a: "An owned pad is a pad created with an explicit owner, identified to the server by their public signing key." + + " A pad's owner may choose to delete their pads from the server, making it unavailable to other collaborators in the future, whether they had it in their CryptDrive or not." + }, + expiring: { + q: "What is an expiring Pad?", + a: "An expiring pad is a pad created with a set time at which it will be automatically removed from the server." + + " Expiring pads can be configured to last anywhere from one hour to one hundred months." + + " The pad and all of its history will become permanently unavailable even if it is being edited at the time that it expires.

    " + + "If a pad is set to expire, you can check its expiration time by viewing its properties, either by right-clicking the pad in your CryptDrive, or by using the properties sub-menu from an application's toolbar." + }, + tag: { + q: "How can I use tags?", + a: "You can tag pads and uploaded files via your CryptDrive, or using the tag button () in any editor's toolbar." + + " Search for pads and files in your CryptDrive using the search bar with a term beginning with hashtag, like #crypto." + }, + template: { + q: "What is a template?", + a: "A template is a pad which can be used to define to initial content for another pad of the same type when you create it." + + " 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." + }, + }; + out.faq.privacy = { + title: 'Privacy', + different: { + q: "How is CryptPad different from other Pad services?", + a: "CryptPad encrypts changes to your pads before sending that information to be stored on the server, so we can't read what you're typing." + }, + me: { + q: "What information does the server know about me?", + a: "Server administrators are able to see the IP addresses of people who visit the CryptPad." + + " We don't record which addresses visit which pads, but we could, even though we don't have access to the decrypted content of those pads." + + " If you are worried about us analyzing that information, it's safest to assume that we do collect it, since we can't prove that we don't.

    " + + + "We collect some basic telemetry about how people use CryptPad, such as the size of the screen on their device, and which buttons they click the most." + + "This helps us improve the software, but if you'd prefer not to send such information to the server, you can opt out by unchecking the Enable user feedback checkbox.

    " + + + "We do keep track of which pads are in a user's CryptDrive so that we can impose storage limits, but we don't know the content or type of those pads." + + " Storage quotas are associated with a user's public key, but we don't associate names or emails with those public keys.

    " + + + " For more information, you can read this blog post which we wrote about the topic." + }, + register: { + q: "Does the server know more about me if I register?", + a: "We don't require users to verify their email address, and the server does not even learn your username or password when you register." + + " Instead, the register and login forms generate a unique keyring from your input, and the server only learns your cryptographic signature." + + " We use this information to track details like how much data you are using, which allows us to restrict each user to a quota.

    " + + + "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." + }, + other: { + q: "What can other collaborators learn about me?", + a: "When you edit a pad with someone else, you communicate through the server, so only we learn your IP address." + + " Other users can see your display name, avatar, the link to your profile (if you have one), and your public key (which is used for encrypting communications between each other)." + }, + anonymous: { + q: "Does CryptPad make me anonymous?", + a: "Even though CryptPad is designed to know as little about you as possible, it does not provide strong anonymity." + + " Our servers have access to your IP address, however, you can hide this information by using Tor to access CryptPad." + + " Using Tor without changing your behaviour will not guarantee you anonymity, as the server is also able to identify users by their unique cryptographic identifier." + + " If you use the same account when you're not using Tor, it will be possible to deanonymize your session.

    " + + + "For users who require a lesser degree of privacy, CryptPad does not require users to identify themselves by name, phone number, or email address like many other services." + }, + policy: { + q: "Do you have a data privacy policy?", + a: "Yes! It is available here." + } + }; + out.faq.security = { + title: 'Security', + proof: { + q: "How do you use Zero Knowledge Proofs?", + a: "When we use the term Zero Knowledge, we are not referring to Zero Knowledge proofs, but to Zero Knowledge Web Services." + + " Zero Knowledge Web Services encrypt user data in the user's browser, without the server ever having access to the unencrypted data, or the encryption keys.

    " + + "We've compiled a short list of Zero Knowledge services here." + }, + why: { + q: "Why should I use CryptPad?", + a: "Our position is that cloud services should not require access to your data in order for you to share it with your friends and colleagues." + + " If you are using another service to collaborate, and they do not explicitly say that they can't access your information, it is very likely that they are leveraging it for profit." + }, + compromised: { + q: "Does CryptPad protect me if my device is compromised?", + a: "In the event that your device is stolen, CryptPad allows you to trigger a remote logout of all devices except the one you are currently using." + + " To do so, go to your settings page and click Log out everywhere." + + " All other devices which are currently connected to the account will log out." + + " Any previously connected devices which visit CryptPad will log out as soon as they load the page.

    " + + + "Currently, remote logout is implemented in the browser, not in conjunction with the server." + + " As such, it may not protect you from government agencies, but it should be sufficient if you forgot to log out after using CryptPad from a shared computer." + }, + crypto: { + q: "What cryptography do you use?", + a: "CryptPad is based upon two open-source cryptography libraries: tweetnacl.js and scrypt-async.js.

    " + + + "Scrypt is a password-based key derivation algorithm. We use it to turn your username and password into a unique keyring which secures access to your CryptDrive such that only you can access your list of pads.

    " + + + "We use the xsalsa20-poly1305 and x25519-xsalsa20-poly1305 cyphers provided by tweetnacl to encrypt pads and chat history, respectively." + } + }; + out.faq.usability = { + title: 'Usability', + register: { + q: "What do I get by registering?", + a: "Registered users have access to a number of features unavailable to unregistered users. There's a chart here." + }, + share: { + q: "How can I share encrypted pads with my friends?", + a: "CryptPad puts the secret encryption key to your pad after the # character in the URL." + + " Anything after this character is not sent to the server, so we never have access to your encryption keys." + + " By sharing the link to a pad, you share the ability to read and access it." + }, + remove: { + q: "I removed a pad or file from my CryptDrive, but the content is still available. How can I remove it?", + a: "Only owned pads (introduced in February 2018) can be deleted. Additionally, these pads can only be deleted by their owners (the person that originally created the pad)." + + " If you are not the creator of the pad, you will have to ask its owner to delete it for you." + + " For pads you do own, you can right-click the pad in your CryptDrive, and choose Delete from the server." + }, + forget: { + q: "What if I forget my password?", + a: "Unfortunately, if we could recover access to your encrypted pads for you, we'd be able to access them ourselves." + + " If you did not record your username and password anywhere, and cannot remember either, you may be able to recover your pads by filtering your browser's history." + }, + change: { + q: "What if I want to change my password?", + a: "It is not currently possible to change your CryptPad password, though we are planning to develop this functionality very soon." + }, + devices: { + q: "I am logged in on two devices, and see two different CryptDrives, how is this possible?", + a: "It's probable that you registered the same name twice, using different passwords." + + " Because the CryptPad server identifies you by your cryptographic signature and not your name, it cannot prevent others from registering with the same name." + + " As such, each user account has a unique username and password combination." + + " Logged in users can see their username at the top of the settings page." + }, + folder: { + q: "Can I share entire folders from my CryptDrive?", + a: "We're working on adding support for \"workgroups\", which would allow collaborators to share a folder structure, and all the pads contained within that structure." + }, + feature: { + q: "Can you add a very special feature which I need?", + a: "Many of the features in CryptPad exist because users asked for them." + + " Our contacts page lists the ways that you can reach us.

    " + + + "Unfortunately, we cannot guarantee that we will implement everything that people ask for." + + " If a particular feature is critical for your organization, you can sponsor development time to ensure its completion." + + " Please contact sales@cryptpad.fr for more information.

    " + + + "Even if you cannot afford to sponsor development, we're interested in feedback that can help us improve CryptPad." + + " Feel free to contact us with via the above methods any time." + }, + }; + out.faq.other = { + title: "Other questions", + pay: { + q: "Why should I pay when so many features are free?", + a: "We give supporters additional storage and the ability to increase their friends' quotas (learn more).

    " + + + "Beyond these short term benefits, by subscribing with a premium account you help to fund continued, active development of CryptPad. That includes fixing bugs, adding new features, and making it easier for others to help host CryptPad themselves." + + " Additionally, you help to prove to other service providers that people are willing to support privacy enhancing technologies. It is our hope that eventually business models based on selling user data will become a thing of the past.

    " + + + "Finally, we offer most of CryptPad's functionality for free because we believe everyone deserves personal privacy, not just those with disposable income." + + " By supporting us, you help us continue to make it possible for underprivileged populations to access these basic features without a price tag attached." + }, + goal: { + q: "What is your goal?", + a: "By developing privacy-respecting collaboration technology, we wish to raise users' expectations of privacy from cloud-computing platforms." + + " We hope that our work drives other service providers in all domains to match or exceed our efforts." + + " Despite our optimism, we know that much of the web is funded by revenue from targeted advertising." + + " There is much more work to be done than we can manage ourselves, and we appreciate the promotion, support, and contributions of our community towards this goal." + }, + jobs: { + q: "Are you hiring?", + a: "Yes! Please introduce yourself with an email to jobs@xwiki.com." + }, + host: { + q: "Can you help me set up my own instance of CryptPad?", + a: "We are happy to provide support for your organization's internal CryptPad installation. Please contact sales@cryptpad.fr for more information." + }, + revenue: { + q: "How can I participate in the revenue sharing?", + a: " If you are running your own instance of CryptPad, and would like to enable paid accounts and split the revenue with the developers, your server will need to be configured as a partner service.

    " + + + "In your CryptPad directory, config.example.js should contain an explanation of what you need to configure on your server." + + " You will need to contact sales@cryptpad.fr to verify that your server is configured to use HTTPS correctly, and discuss payment methods." + }, + }; + // terms.html out.tos_title = "CryptPad Terms of Service"; @@ -610,33 +985,72 @@ define(function () { out.tos_logs = "Metadata provided by your browser to the server may be logged for the purpose of maintaining the service."; out.tos_3rdparties = "We do not provide individualized data to third parties unless required to by law."; + // 404 page + out.four04_pageNotFound = "We couldn't find the page you were looking for."; + // BottomBar.html - out.bottom_france = 'Made with love in France'; - out.bottom_support = 'An XWiki SAS Labs Project with the support of OpenPaaS-ng'; + //out.bottom_france = 'Made with love in France'; + //out.bottom_support = 'An XWiki SAS Labs Project with the support of OpenPaaS-ng'; // 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'; // Initial states + out.help = {}; + + out.help.title = "Getting started"; + out.help.generic = { + more: 'Learn more about how CryptPad can work for you by reading our FAQ', + share: 'Use the share menu () to generate a link so collaborators can view or edit this pad', + stored: 'Every pad you visit is automatically stored in your CryptDrive', + }; + + out.help.text = { + formatting: 'You can display or hide the text formatting toolbar by clicking the or buttons', + embed: 'Registered users can embed an image or a file stored in their CryptDrive using ', + history: 'You can use history to view or restore previous versions', + }; + + out.help.pad = { + export: 'You can export the content as PDF using the button in the text formatting toolbar', + }; + + out.help.code = { + modes: 'Use the dropdown menus in the submenu to change syntax highlighting modes or color themes', + }; + + out.help.slide = { + markdown: 'Write slides in Markdown and separate them with a line containing ---', + present: 'Start the presentation using the button', + settings: 'Change the slide settings (background, transitions, page numbers, etc.) with the button in the submenu', + colors: 'Change the text and background colors using the and buttons', + }; + + out.help.poll = { + decisions: 'Make decisions in private among trusted friends', + options: 'Propose options, and express your preferences', + choices: 'Click cells in your column to cycle through yes (), maybe (~), or no ()', + submit: 'Click submit to make your choices visible to others', + }; + + out.help.whiteboard = { + colors: 'Double-click colors to modify your palette', + mode: 'Disable draw mode to drag and stretch strokes', + embed: 'Embed images from your disk or your CryptDrive and export them as PNG to your disk or your CryptDrive ' + }; + + out.initialState = [ '

    ', 'This is CryptPad, the Zero Knowledge realtime collaborative editor. Everything is saved as you type.', '
    ', - 'Share the link to this pad to edit with friends or use the  Share  button to share a read-only link which allows viewing but not editing.', + 'Share the link to this pad to edit with friends or use the button to share a read-only link which allows viewing but not editing.', '

    ', - - '

    ', - 'Go ahead, just start typing...', - '

    ', - '

     

    ' ].join(''); out.codeInitialState = [ @@ -648,14 +1062,6 @@ define(function () { out.slideInitialState = [ '# CryptSlide\n', - '* This is a zero knowledge realtime collaborative editor.\n', - '* What you type here is encrypted so only people who have the link can access it.\n', - '* Even the server cannot see what you type.\n', - '* What you see here, what you hear here, when you leave here, let it stay here.\n', - '\n', - '---', - '\n', - '# How to use\n', '1. Write your slides content using markdown syntax\n', ' - Learn more about markdown syntax [here](http://www.markdowntutorial.com/)\n', '2. Separate your slides with ---\n', @@ -697,10 +1103,59 @@ define(function () { out.tips.drive = "Logged in users can organize their files in their CryptDrive, accessible from the CryptPad icon at the top left of all pads."; out.tips.profile = "Registered users can create a profile from the user menu in the top right."; out.tips.avatars = "You can upload an avatar in your profile. People will see it when you collaborate in a pad."; + out.tips.tags = "Tag your pads and start a search with # in your CryptDrive to find them"; out.feedback_about = "If you're reading this, you were probably curious why CryptPad is requesting web pages when you perform certain actions"; out.feedback_privacy = "We care about your privacy, and at the same time we want CryptPad to be very easy to use. We use this file to figure out which UI features matter to our users, by requesting it along with a parameter specifying which action was taken."; out.feedback_optout = "If you would like to opt out, visit your user settings page, where you'll find a checkbox to enable or disable user feedback"; + // Creation page + out.creation_404 = "This pad not longer exists. Use the following form to create a new pad."; + out.creation_ownedTitle = "Type of pad"; + out.creation_owned = "Owned pad"; // Creation page + out.creation_ownedTrue = "Owned pad"; // Settings + out.creation_ownedFalse = "Open pad"; + out.creation_owned1 = "An owned pad can be deleted from the server whenever the owner wants. Deleting an owned pad removes it from other users' CryptDrives."; + out.creation_owned2 = "An open pad doesn't have any owner and thus, it can't be deleted from the server unless it has reached its expiration time."; + out.creation_expireTitle = "Life time"; + out.creation_expire = "Expiring pad"; + out.creation_expireTrue = "Add a life time"; + out.creation_expireFalse = "Unlimited"; + out.creation_expireHours = "Hour(s)"; + out.creation_expireDays = "Day(s)"; + 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_noTemplate = "No template"; + out.creation_newTemplate = "New template"; + out.creation_create = "Create"; + out.creation_saveSettings = "Don't show this again"; + out.creation_settings = "View more settings"; + out.creation_rememberHelp = "Visit your Settings page to reset this preference"; + // Properties about creation data + out.creation_owners = "Owners"; + out.creation_ownedByOther = "Owned by another user"; + out.creation_noOwner = "No owner"; + out.creation_expiration = "Expiration time"; + 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"; + + // New share modal + out.share_linkCategory = "Share link"; + out.share_linkAccess = "Access rights"; + out.share_linkEdit = "Edit"; + out.share_linkView = "View"; + out.share_linkOptions = "Link options"; + out.share_linkEmbed = "Embed mode (toolbar and userlist hidden)"; + out.share_linkPresent = "Present mode (editable sections hidden)"; + out.share_linkOpen = "Open in new tab"; + out.share_linkCopy = "Copy to clipboard"; + out.share_embedCategory = "Embed"; + out.share_mediatagCopy = "Copy mediatag to clipboard"; + + return out; }); diff --git a/customize.dist/translations/messages.pl.js b/customize.dist/translations/messages.pl.js index 339600ac0..9c24da181 100644 --- a/customize.dist/translations/messages.pl.js +++ b/customize.dist/translations/messages.pl.js @@ -52,7 +52,6 @@ define(function () { out.shareSuccess = 'Pomyślnie skopiowano URL'; out.presentButtonTitle = "Otwórz tryb prezentacji"; - out.presentSuccess = 'Naciśnij ESC aby wyjść z trybu prezentacji'; out.backgroundButtonTitle = 'Zmień kolor tła dla tej prezentacji'; out.colorButtonTitle = 'Zmień kolor tekstu dla tej prezentacji'; diff --git a/customize.dist/translations/messages.pt-br.js b/customize.dist/translations/messages.pt-br.js index e2c29fae1..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..."; @@ -128,7 +128,6 @@ define(function () { out.previewButtonTitle = "Mostrar ou esconder o modo de visualização markdown"; out.presentButtonTitle = "Entrar no modo apresentação"; - out.presentSuccess = 'Pressione ESC para sair do modo de apresentação'; out.backgroundButtonTitle = 'Mudar cor do fundo da apresentação'; out.colorButtonTitle = 'Mudar a cor do texto no modo apresentação'; @@ -487,13 +486,8 @@ define(function () { '

    ', 'This is CryptPad, the Zero Knowledge realtime collaborative editor. Everything is saved as you type.', '
    ', - 'Share the link to this pad to edit with friends or use the  Share  button to share a read-only link which allows viewing but not editing.', + 'Share the link to this pad to edit with friends or use the button to share a read-only link which allows viewing but not editing.', '

    ', - - '

    ', - 'Go ahead, just start typing...', - '

    ', - '

     

    ' ].join(''); out.codeInitialState = [ @@ -505,14 +499,6 @@ define(function () { out.slideInitialState = [ '# CryptSlide\n', - '* This is a zero knowledge realtime collaborative editor.\n', - '* What you type here is encrypted so only people who have the link can access it.\n', - '* Even the server cannot see what you type.\n', - '* What you see here, what you hear here, when you leave here, let it stay here.\n', - '\n', - '---', - '\n', - '# How to use\n', '1. Write your slides content using markdown syntax\n', ' - Learn more about markdown syntax [here](http://www.markdowntutorial.com/)\n', '2. Separate your slides with ---\n', diff --git a/customize.dist/translations/messages.ro.js b/customize.dist/translations/messages.ro.js index 61881eb36..fe24c7057 100644 --- a/customize.dist/translations/messages.ro.js +++ b/customize.dist/translations/messages.ro.js @@ -4,16 +4,6 @@ define(function () { out.main_title = "CryptPad: Zero Knowledge, Colaborare în timp real"; out.main_slogan = "Puterea stă în cooperare - Colaborarea este cheia"; - out.type = {}; - out.pad = "Rich text"; - out.code = "Code"; - out.poll = "Poll"; - out.slide = "Presentation"; - out.drive = "Drive"; - out.whiteboard = "Whiteboard"; - out.file = "File"; - out.media = "Media"; - out.button_newpad = "Filă Text Nouă"; out.button_newcode = "Filă Cod Nouă"; out.button_newpoll = "Sondaj Nou"; @@ -23,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"; @@ -77,7 +67,6 @@ define(function () { out.templateSaved = "Șablon salvat!"; out.selectTemplate = "Selectează un șablon sau apasă escape"; out.presentButtonTitle = "Intră în modul de prezentare"; - out.presentSuccess = "Apasă ESC pentru a ieși din modul de prezentare"; out.backgroundButtonTitle = "Schimbă culoarea de fundal din prezentare"; out.colorButtonTitle = "Schimbă culoarea textului în modul de prezentare"; out.printButton = "Printează (enter)"; @@ -331,9 +320,9 @@ define(function () { out.header_france = "With \"love\" from \"Franța\"/ by \"XWiki"; out.header_support = " \"OpenPaaS-ng\""; out.header_logoTitle = "Mergi la pagina principală"; - out.initialState = "

    Acesta este CryptPad, editorul colaborativ bazat pe tehnologia Zero Knowledge în timp real. Totul este salvat pe măsură ce scrii.
    Partajează link-ul către acest pad pentru a edita cu prieteni sau folosește  Share  butonul pentru a partaja read-only link permițând vizualizarea dar nu și editarea.

    Îndrăznește, începe să scrii...

     

    "; + out.initialState = "

    Acesta este CryptPad, editorul colaborativ bazat pe tehnologia Zero Knowledge în timp real. Totul este salvat pe măsură ce scrii.
    Partajează link-ul către acest pad pentru a edita cu prieteni sau folosește butonul pentru a partaja read-only link permițând vizualizarea dar nu și editarea.

    "; out.codeInitialState = "/*\n Acesta este editorul colaborativ de cod bazat pe tehnologia Zero Knowledge CryptPad.\n Ce scrii aici este criptat, așa că doar oamenii care au link-ul pot să-l acceseze.\n Poți să alegi ce limbaj de programare pus n evidență și schema de culori UI n dreapta sus.\n*/"; - out.slideInitialState = "# CryptSlide\n* Acesta este un editor colaborativ bazat pe tehnologia Zero Knowledge.\n* Ce scrii aici este criptat, așa că doar oamenii care au link-ul pot să-l acceseze.\n* Nici măcar serverele nu au acces la ce scrii tu.\n* Ce vezi aici, ce auzi aici, atunci când pleci, lași aici.\n\n-\n# Cum se folosește\n1. Scrie-ți conținutul slide-urilor folosind sintaxa markdown\n - Află mai multe despre sintaxa markdown [aici](http://www.markdowntutorial.com/)\n2. Separă-ți slide-urile cu -\n3. Click pe butonul \"Play\" pentru a vedea rezultatele - Slide-urile tale sunt actualizate în timp real."; + out.slideInitialState = "# CryptSlide\n1. Scrie-ți conținutul slide-urilor folosind sintaxa markdown\n - Află mai multe despre sintaxa markdown [aici](http://www.markdowntutorial.com/)\n2. Separă-ți slide-urile cu ---\n3. Click pe butonul \"Play\" pentru a vedea rezultatele - Slide-urile tale sunt actualizate în timp real."; out.driveReadmeTitle = "Ce este CryptPad?"; out.readme_welcome = "Bine ai venit n CryptPad !"; out.readme_p1 = "Bine ai venit în CryptPad, acesta este locul unde îți poți lua notițe, singur sau cu prietenii."; diff --git a/customize.dist/translations/messages.zh.js b/customize.dist/translations/messages.zh.js index d835f928e..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 = "載入中..."; @@ -116,7 +116,6 @@ define(function () { out.previewButtonTitle = "顯示或隱藏 Markdown 預覽模式"; out.presentButtonTitle = "輸入簡報模式"; - out.presentSuccess = '按 ESC 以退出簡報模式'; out.backgroundButtonTitle = '改變簡報的顏色背景'; out.colorButtonTitle = '在簡報模式下改變文字顏色'; @@ -470,13 +469,8 @@ define(function () { '

    ', '這是 CryptPad, 零知識即時協作編輯平台,當你輸入時一切已即存好。', '
    ', - '分享這個工作檔案的網址連結給友人或是使用、  分享  按鈕分享唯讀的連結 其只能看不能編寫。', - '

    ', - - '

    ', - '來吧, 開始打字輸入吧...', - '

    ', - '

     

    ' + '分享這個工作檔案的網址連結給友人或是使用、 按鈕分享唯讀的連結 其只能看不能編寫。', + '

    ' ].join(''); out.codeInitialState = [ @@ -488,14 +482,6 @@ define(function () { out.slideInitialState = [ '# CryptSlide\n', - '* 它是零知識即時協作編輯平台。\n', - '* 你所輸入的東西會予以加密,僅有知道此網頁連結者可以接取這份文件。\n', - '* 即便是本站伺服器也不知道你輸入了什麼內容。\n', - '* 你在這裏看到的、你在這裏聽到的、當你離開本站時,讓它就留在這裏吧。\n', - '\n', - '---', - '\n', - '# 如何使用\n', '1. 使用 markdown 語法來寫下你的投影片內容\n', ' - 進一步學習 markdown 語法 [here](http://www.markdowntutorial.com/)\n', '2. 利用 --- 來區隔不同的投影片\n', diff --git a/delete-inactive.js b/delete-inactive.js new file mode 100644 index 000000000..16f41da45 --- /dev/null +++ b/delete-inactive.js @@ -0,0 +1,40 @@ +/* jshint esversion: 6, node: true */ +const Fs = require("fs"); +const nThen = require("nthen"); +const Saferphore = require("saferphore"); +const PinnedData = require('./pinneddata'); +let config; +try { + config = require('./config'); +} catch (e) { + config = require('./config.example'); +} + +if (!config.inactiveTime || typeof(config.inactiveTime) !== "number") { return; } + +let inactiveTime = +new Date() - (config.inactiveTime * 24 * 3600 * 1000); +let inactiveConfig = { + unpinned: true, + olderthan: inactiveTime, + blobsolderthan: inactiveTime +}; +let toDelete; +nThen(function (waitFor) { + PinnedData.load(inactiveConfig, waitFor(function (err, data) { + if (err) { + waitFor.abort(); + throw new Error(err); + } + toDelete = data; + })); +}).nThen(function () { + var sem = Saferphore.create(10); + toDelete.forEach(function (f) { + 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())); + })); + }); + }); +}); diff --git a/docs/example.nginx.conf b/docs/example.nginx.conf index 37cb0da60..d56920bf5 100644 --- a/docs/example.nginx.conf +++ b/docs/example.nginx.conf @@ -66,11 +66,14 @@ server { rewrite ^/customize/(.*)$ $1 break; try_files /customize/$uri /customize.dist/$uri; } - location = /api/config { - default_type text/javascript; - rewrite ^.*$ /customize/api/config break; - } + location = /api/config { + 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; + } + location ^~ /blob/ { try_files $uri =404; } diff --git a/expire-channels.js b/expire-channels.js new file mode 100644 index 000000000..a42a3af58 --- /dev/null +++ b/expire-channels.js @@ -0,0 +1,114 @@ +var Fs = require("fs"); +var Path = require("path"); + +var nThen = require("nthen"); + +var config; +try { + config = require('./config'); +} catch (e) { + config = require('./config.example'); +} + +var FileStorage = require(config.storage || './storage/file'); +var root = Path.resolve(config.taskPath || './tasks'); + +var dirs; +var nt; +var store; + +var queue = function (f) { + nt = nt.nThen(f); +}; + +var tryParse = function (s) { + try { return JSON.parse(s); } + catch (e) { return null; } +}; + +var CURRENT = +new Date(); + +var handleTask = function (str, path, cb) { + var task = tryParse(str); + if (!Array.isArray(task)) { + console.error('invalid task: not array'); + return cb(); + } + if (task.length < 2) { + console.error('invalid task: too small'); + return cb(); + } + + var time = task[0]; + var command = task[1]; + var args = task.slice(2); + + if (time > CURRENT) { + // not time for this task yet + console.log('not yet time'); + return cb(); + } + + nThen(function (waitFor) { + switch (command) { + case 'EXPIRE': + console.log("expiring: %s", args[0]); + store.removeChannel(args[0], waitFor()); + break; + default: + console.log("unknown command", command); + } + }).nThen(function () { + // remove the task file... + Fs.unlink(path, function (err) { + if (err) { console.error(err); } + cb(); + }); + }); +}; + +nt = nThen(function (w) { + Fs.readdir(root, w(function (e, list) { + if (e) { throw e; } + dirs = list; + if (dirs.length === 0) { + w.abort(); + return; + } + })); +}).nThen(function (waitFor) { + FileStorage.create(config, waitFor(function (_store) { + store = _store; + })); +}).nThen(function () { + dirs.forEach(function (dir, dIdx) { + queue(function (w) { + console.log('recursing into %s', dir); + Fs.readdir(Path.join(root, dir), w(function (e, list) { + list.forEach(function (fn) { + queue(function (w) { + var filePath = Path.join(root, dir, fn); + var cb = w(); + + console.log("processing file at %s", filePath); + Fs.readFile(filePath, 'utf8', function (e, str) { + if (e) { + console.error(e); + return void cb(); + } + + handleTask(str, filePath, cb); + }); + }); + }); + if (dIdx === (dirs.length - 1)) { + queue(function () { + store.shutdown(); + }); + } + })); + }); + }); +}); + + diff --git a/package.json b/package.json index 2dd774c27..f400db4c4 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,38 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "1.14.0", + "version": "1.29.0", + "license": "AGPL-3.0-or-later", "dependencies": { - "chainpad-server": "^1.0.1", - "express": "~4.10.1", + "chainpad-server": "^2.0.0", + "express": "~4.16.0", + "mkdirp": "^0.5.1", "nthen": "~0.1.0", + "pull-stream": "^3.6.1", + "replify": "^1.2.0", "saferphore": "0.0.1", + "sortify": "^1.0.4", + "stream-to-pull-stream": "^1.7.2", "tweetnacl": "~0.12.2", "ws": "^1.0.1" }, "devDependencies": { + "flow-bin": "^0.59.0", + "heapdump": "^0.3.9", "jshint": "~2.9.1", - "selenium-webdriver": "^2.53.1", - "less": "2.7.1" + "less": "2.7.1", + "lesshint": "^4.5.0", + "selenium-webdriver": "^3.6.0" }, "scripts": { "start": "node server.js", "dev": "DEV=1 node server.js", - "lint": "jshint --config .jshintrc --exclude-path .jshintignore .", + "fresh": "FRESH=1 node server.js", + "lint": "jshint --config .jshintrc --exclude-path .jshintignore . && ./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/", + "lint:js": "jshint --config .jshintrc --exclude-path .jshintignore .", + "lint:less": "./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/", + "flow": "./node_modules/.bin/flow", "test": "node TestSelenium.js", - "template": "cd customize.dist/src && for page in ../index.html ../privacy.html ../terms.html ../about.html ../contact.html ../what-is-cryptpad.html ../../www/login/index.html ../../www/register/index.html ../../www/settings/index.html ../../www/user/index.html;do echo $page; cp template.html $page; done;" + "template": "cd customize.dist/src && for page in ../index.html ../privacy.html ../terms.html ../about.html ../contact.html ../what-is-cryptpad.html ../features.html ../../www/login/index.html ../../www/register/index.html ../../www/user/index.html;do echo $page; cp template.html $page; done;" } } diff --git a/pinned.js b/pinned.js new file mode 100644 index 000000000..d5df9373a --- /dev/null +++ b/pinned.js @@ -0,0 +1,83 @@ +/* jshint esversion: 6, node: true */ +const Fs = require('fs'); +const Semaphore = require('saferphore'); +const nThen = require('nthen'); + +const sema = Semaphore.create(20); + +let dirList; +const fileList = []; +const pinned = {}; + +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); +}; + +module.exports.load = function (cb, config) { + nThen((waitFor) => { + Fs.readdir('./pins', waitFor((err, list) => { + if (err) { + if (err.code === 'ENOENT') { + dirList = []; + return; + } + throw err; + } + dirList = list; + })); + }).nThen((waitFor) => { + dirList.forEach((f) => { + sema.take((returnAfter) => { + Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => { + if (err) { throw err; } + list2.forEach((ff) => { + if (config && config.exclude && config.exclude.indexOf(ff) > -1) { return; } + fileList.push('./pins/' + f + '/' + ff); + }); + }))); + }); + }); + }).nThen((waitFor) => { + fileList.forEach((f) => { + sema.take((returnAfter) => { + Fs.readFile(f, waitFor(returnAfter((err, content) => { + if (err) { throw err; } + const hashes = hashesFromPinFile(content.toString('utf8'), f); + hashes.forEach((x) => { + (pinned[x] = pinned[x] || {})[f.replace(/.*\/([^/]*).ndjson$/, (x, y)=>y)] = 1; + }); + }))); + }); + }); + }).nThen(() => { + cb(pinned); + }); +}; + +if (!module.parent) { + module.exports.load(function (data) { + Object.keys(data).forEach(function (x) { + console.log(x + ' ' + JSON.stringify(data[x])); + }); + }); +} diff --git a/pinneddata.js b/pinneddata.js index 2ecf8605d..f9053b9b6 100644 --- a/pinneddata.js +++ b/pinneddata.js @@ -9,6 +9,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 } @@ -47,84 +48,126 @@ const dsFileStats = {}; const out = []; const pinned = {}; -nThen((waitFor) => { - Fs.readdir('./datastore', waitFor((err, list) => { - if (err) { throw err; } - dirList = list; - })); -}).nThen((waitFor) => { - dirList.forEach((f) => { - sema.take((returnAfter) => { - Fs.readdir('./datastore/' + f, waitFor(returnAfter((err, list2) => { - if (err) { throw err; } - list2.forEach((ff) => { fileList.push('./datastore/' + f + '/' + ff); }); - }))); +module.exports.load = function (config, cb) { + nThen((waitFor) => { + Fs.readdir('./datastore', waitFor((err, list) => { + if (err) { throw err; } + dirList = list; + })); + }).nThen((waitFor) => { + dirList.forEach((f) => { + sema.take((returnAfter) => { + Fs.readdir('./datastore/' + f, waitFor(returnAfter((err, list2) => { + if (err) { throw err; } + list2.forEach((ff) => { fileList.push('./datastore/' + f + '/' + ff); }); + }))); + }); }); - }); -}).nThen((waitFor) => { + }).nThen((waitFor) => { - Fs.readdir('./blob', waitFor((err, list) => { - if (err) { throw err; } - dirList = list; - })); -}).nThen((waitFor) => { - dirList.forEach((f) => { - sema.take((returnAfter) => { - Fs.readdir('./blob/' + f, waitFor(returnAfter((err, list2) => { - if (err) { throw err; } - list2.forEach((ff) => { fileList.push('./blob/' + f + '/' + ff); }); - }))); + Fs.readdir('./blob', waitFor((err, list) => { + if (err) { throw err; } + dirList = list; + })); + }).nThen((waitFor) => { + dirList.forEach((f) => { + sema.take((returnAfter) => { + Fs.readdir('./blob/' + f, waitFor(returnAfter((err, list2) => { + if (err) { throw err; } + list2.forEach((ff) => { fileList.push('./blob/' + f + '/' + ff); }); + }))); + }); }); - }); -}).nThen((waitFor) => { - fileList.forEach((f) => { - sema.take((returnAfter) => { - Fs.stat(f, waitFor(returnAfter((err, st) => { - if (err) { throw err; } - dsFileStats[f.replace(/^.*\/([^\/\.]*)(\.ndjson)?$/, (all, a) => (a))] = st; - }))); + }).nThen((waitFor) => { + fileList.forEach((f) => { + sema.take((returnAfter) => { + Fs.stat(f, waitFor(returnAfter((err, st) => { + if (err) { throw err; } + st.filename = f; + dsFileStats[f.replace(/^.*\/([^\/\.]*)(\.ndjson)?$/, (all, a) => (a))] = st; + }))); + }); }); - }); -}).nThen((waitFor) => { - Fs.readdir('./pins', waitFor((err, list) => { - if (err) { throw err; } - dirList = list; - })); -}).nThen((waitFor) => { - fileList.splice(0, fileList.length); - dirList.forEach((f) => { - sema.take((returnAfter) => { - Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => { - if (err) { throw err; } - list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); }); - }))); + }).nThen((waitFor) => { + Fs.readdir('./pins', waitFor((err, list) => { + if (err) { throw err; } + dirList = list; + })); + }).nThen((waitFor) => { + fileList.splice(0, fileList.length); + dirList.forEach((f) => { + sema.take((returnAfter) => { + Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => { + if (err) { throw err; } + list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); }); + }))); + }); }); - }); -}).nThen((waitFor) => { - fileList.forEach((f) => { - sema.take((returnAfter) => { - Fs.readFile(f, waitFor(returnAfter((err, content) => { - if (err) { throw err; } - const hashes = hashesFromPinFile(content.toString('utf8'), f); - const size = sizeForHashes(hashes, dsFileStats); - if (process.argv.indexOf('--unpinned') > -1) { - hashes.forEach((x) => { pinned[x] = 1; }); - } else { - out.push([f, Math.floor(size / (1024 * 1024))]); - } - }))); + }).nThen((waitFor) => { + fileList.forEach((f) => { + sema.take((returnAfter) => { + Fs.readFile(f, waitFor(returnAfter((err, content) => { + if (err) { throw err; } + const hashes = hashesFromPinFile(content.toString('utf8'), f); + const size = sizeForHashes(hashes, dsFileStats); + if (config.unpinned) { + hashes.forEach((x) => { pinned[x] = 1; }); + } else { + out.push([f, Math.floor(size / (1024 * 1024))]); + } + }))); + }); }); - }); -}).nThen(() => { - if (process.argv.indexOf('--unpinned') > -1) { - Object.keys(dsFileStats).forEach((f) => { - if (!(f in pinned)) { - console.log("./datastore/" + f.slice(0,2) + "/" + f + ".ndjson " + - dsFileStats[f].size + " " + (+dsFileStats[f].mtime)); + }).nThen(() => { + if (config.unpinned) { + let before = Infinity; + if (config.olderthan) { + before = config.olderthan; + if (isNaN(before)) { + return void cb('--olderthan error [' + config.olderthan + '] not a valid date'); + } } - }); - } else { - out.sort((a,b) => (a[1] - b[1])); - out.forEach((x) => { console.log(x[0] + ' ' + x[1] + ' MB'); }); - } -}); + let blobsbefore = before; + if (config.blobsolderthan) { + blobsbefore = config.blobsolderthan; + if (isNaN(blobsbefore)) { + return void cb('--blobsolderthan error [' + config.blobsolderthan + '] not a valid date'); + } + } + let files = []; + Object.keys(dsFileStats).forEach((f) => { + if (!(f in pinned)) { + const isBlob = dsFileStats[f].filename.indexOf('.ndjson') === -1; + if ((+dsFileStats[f].atime) >= ((isBlob) ? blobsbefore : before)) { return; } + files.push({ + filename: dsFileStats[f].filename, + size: dsFileStats[f].size, + atime: dsFileStats[f].atime + }); + } + }); + cb(null, files); + } else { + out.sort((a,b) => (a[1] - b[1])); + cb(null, out.slice()); + } + }); +}; + +if (!module.parent) { + let config = {}; + if (process.argv.indexOf('--unpinned') > -1) { config.unpinned = true; } + const ot = process.argv.indexOf('--olderthan'); + config.olderthan = ot > -1 && new Date(process.argv[ot+1]); + const bot = process.argv.indexOf('--blobsolderthan'); + config.blobsolderthan = bot > -1 && new Date(process.argv[bot+1]); + module.exports.load(config, function (err, data) { + if (err) { throw new Error(err); } + if (!Array.isArray(data)) { return; } + if (config.unpinned) { + data.forEach((f) => { console.log(f.filename + " " + f.size + " " + (+f.atime)); }); + } else { + data.forEach((x) => { console.log(x[0] + ' ' + x[1] + ' MB'); }); + } + }); +} diff --git a/readme.md b/readme.md index 956077d15..150d21bba 100644 --- a/readme.md +++ b/readme.md @@ -16,13 +16,17 @@ Installing CryptPad is pretty straightforward. You can read all about it in the It also contains information on keeping your instance of CryptPad up to date. +## Current version + +The most recent version and all past release notes can be found [here](https://github.com/xwiki-labs/cryptpad/releases/). + ## Setup using Docker -See [Cryptpad-Docker](docs/cryptpad-docker.md) +See [Cryptpad-Docker](docs/cryptpad-docker.md) and the community wiki's [Docker](https://github.com/xwiki-labs/cryptpad/wiki/Docker-(with-Nginx-and-Traefik)) page for details on how to get up-and-running with Cryptpad in Docker. ## Setup using Ansible -See [Ansible Role for Cryptpad](https://github.com/systemli/ansible-role-cryptpad) +See [Ansible Role for Cryptpad](https://github.com/systemli/ansible-role-cryptpad). # Security @@ -75,7 +79,7 @@ If you have any questions or comments, or if you're interested in contributing t This software is and will always be available under the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. If you wish to use this technology in a proprietary product, please contact -sales@xwiki.com +sales@xwiki.com. [ChainPad]: https://github.com/xwiki-contrib/chainpad [active attack]: https://en.wikipedia.org/wiki/Attack_(computing)#Types_of_attacks diff --git a/rpc.js b/rpc.js index 73fb43736..48c25b8ef 100644 --- a/rpc.js +++ b/rpc.js @@ -10,6 +10,10 @@ var Fs = require("fs"); 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; @@ -28,7 +32,7 @@ var WARN = function (e, output) { }; var isValidId = function (chan) { - return chan && chan.length && /^[a-fA-F0-9]/.test(chan) && + return chan && chan.length && /^[a-zA-Z0-9=+-]*$/.test(chan) && [32, 48].indexOf(chan.length) > -1; }; @@ -94,8 +98,7 @@ var unescapeKeyCharacters = function (key) { return key.replace(/\-/g, '/'); }; -// TODO Rename to getSession ? -var beginSession = function (Sessions, key) { +var getSession = function (Sessions, key) { var safeKey = escapeKeyCharacters(key); if (Sessions[safeKey]) { Sessions[safeKey].atime = +new Date(); @@ -135,7 +138,7 @@ var expireSessions = function (Sessions) { var addTokenForKey = function (Sessions, publicKey, token) { if (!Sessions[publicKey]) { throw new Error('undefined user'); } - var user = beginSession(Sessions, publicKey); + var user = getSession(Sessions, publicKey); user.tokens.push(token); user.atime = +new Date(); if (user.tokens.length > 2) { user.tokens.shift(); } @@ -157,7 +160,7 @@ var isValidCookie = function (Sessions, publicKey, cookie) { return false; } - var user = beginSession(Sessions, publicKey); + var user = getSession(Sessions, publicKey); if (!user) { return false; } var idx = user.tokens.indexOf(parsed.seq); @@ -212,8 +215,7 @@ var checkSignature = function (signedMsg, signature, publicKey) { }; var loadUserPins = function (Env, publicKey, cb) { - var pinStore = Env.pinStore; - var session = beginSession(Env.Sessions, publicKey); + var session = getSession(Env.Sessions, publicKey); if (session.channels) { return cb(session.channels); @@ -230,7 +232,7 @@ var loadUserPins = function (Env, publicKey, cb) { pins[channel] = false; }; - pinStore.getMessages(publicKey, function (msg) { + Env.pinStore.getMessages(publicKey, function (msg) { // handle messages... var parsed; try { @@ -287,14 +289,14 @@ var getUploadSize = function (Env, channel, cb) { var paths = Env.paths; var path = makeFilePath(paths.blob, channel); if (!path) { - return cb('INVALID_UPLOAD_ID', path); + return cb('INVALID_UPLOAD_ID'); } Fs.stat(path, function (err, stats) { if (err) { // if a file was deleted, its size is 0 bytes if (err.code === 'ENOENT') { return cb(void 0, 0); } - return void cb(err); + return void cb(err.code); } cb(void 0, stats.size); }); @@ -308,26 +310,43 @@ var getFileSize = function (Env, channel, cb) { return cb('GET_CHANNEL_SIZE_UNSUPPORTED'); } - return void Env.msgStore.getChannelSize(channel, function (e, size) { + return void Env.msgStore.getChannelSize(channel, function (e, size /*:number*/) { if (e) { - if (e === 'ENOENT') { return void cb(void 0, 0); } + if (e.code === 'ENOENT') { return void cb(void 0, 0); } return void cb(e.code); } cb(void 0, size); }); } - // 'channel' refers to a file, so you need anoter API + // 'channel' refers to a file, so you need another API getUploadSize(Env, channel, function (e, size) { - if (e) { return void cb(e); } + if (typeof(size) === 'undefined') { return void cb(e); } cb(void 0, size); }); }; +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 /*:object*/) { + 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) { - var msgStore = Env.msgStore; if (!Array.isArray(channels)) { return cb('INVALID_PIN_LIST'); } - if (typeof(msgStore.getChannelSize) !== 'function') { + if (typeof(Env.msgStore.getChannelSize) !== 'function') { return cb('GET_CHANNEL_SIZE_UNSUPPORTED'); } @@ -357,6 +376,37 @@ var getMultipleFileSize = function (Env, channels, cb) { }); }; +/* accepts a list, and returns a sublist of channel or file ids which seem + to have been deleted from the server (file size 0) + + we might consider that we should only say a file is gone if fs.stat returns + ENOENT, but for now it's simplest to just rely on getFileSize... +*/ +var getDeletedPads = function (Env, channels, cb) { + if (!Array.isArray(channels)) { return cb('INVALID_LIST'); } + var L = channels.length; + + var sem = Saferphore.create(10); + var absentees = []; + + var job = function (channel, wait) { + return function (give) { + getFileSize(Env, channel, wait(give(function (e, size) { + if (e) { return; } + if (size === 0) { absentees.push(channel); } + }))); + }; + }; + + nThen(function (w) { + for (var i = 0; i < L; i++) { + sem.take(job(channels[i], w)); + } + }).nThen(function () { + cb(void 0, absentees); + }); +}; + var getTotalSize = function (Env, publicKey, cb) { var bytes = 0; return void getChannelList(Env, publicKey, function (channels) { @@ -397,8 +447,7 @@ var getHash = function (Env, publicKey, cb) { // The limits object contains storage limits for all the publicKey that have paid // To each key is associated an object containing the 'limit' value and a 'note' explaining that limit -var limits = {}; -var updateLimits = function (config, publicKey, cb) { +var updateLimits = function (Env, config, publicKey, cb /*:(?string, ?any[])=>void*/) { if (config.adminEmail === false) { if (config.allowSubscriptions === false) { return; } throw new Error("allowSubscriptions must be false if adminEmail is false"); @@ -415,7 +464,7 @@ var updateLimits = function (config, publicKey, cb) { var body = JSON.stringify({ domain: config.myDomain, - subdomain: config.mySubdomain, + subdomain: config.mySubdomain || null, adminEmail: config.adminEmail, version: Package.version }); @@ -428,6 +477,28 @@ var updateLimits = function (config, publicKey, cb) { "Content-Length": Buffer.byteLength(body) } }; + + // read custom limits from the config + var customLimits = (function (custom) { + var limits = {}; + Object.keys(custom).forEach(function (k) { + k.replace(/\/([^\/]+)$/, function (all, safeKey) { + var id = unescapeKeyCharacters(safeKey || ''); + limits[id] = custom[k]; + return ''; + }); + }); + return limits; + }(config.customLimits || {})); + + var isLimit = function (o) { + var valid = o && typeof(o) === 'object' && + typeof(o.limit) === 'number' && + typeof(o.plan) === 'string' && + typeof(o.note) === 'string'; + return valid; + }; + var req = Https.request(options, function (response) { if (!('' + response.statusCode).match(/^2\d\d$/)) { return void cb('SERVER ERROR ' + response.statusCode); @@ -441,10 +512,15 @@ var updateLimits = function (config, publicKey, cb) { response.on('end', function () { try { var json = JSON.parse(str); - limits = json; + Env.limits = json; + Object.keys(customLimits).forEach(function (k) { + if (!isLimit(customLimits[k])) { return; } + Env.limits[k] = customLimits[k]; + }); + var l; if (userId) { - var limit = limits[userId]; + var limit = Env.limits[userId]; l = limit && typeof limit.limit === "number" ? [limit.limit, limit.plan, limit.note] : [defaultLimit, '', '']; } @@ -465,7 +541,7 @@ var updateLimits = function (config, publicKey, cb) { var getLimit = function (Env, publicKey, cb) { var unescapedKey = unescapeKeyCharacters(publicKey); - var limit = limits[unescapedKey]; + var limit = Env.limits[unescapedKey]; var defaultLimit = typeof(Env.defaultStorageLimit) === 'number'? Env.defaultStorageLimit: DEFAULT_LIMIT; @@ -479,7 +555,7 @@ var getFreeSpace = function (Env, publicKey, cb) { getLimit(Env, publicKey, function (e, limit) { if (e) { return void cb(e); } getTotalSize(Env, publicKey, function (e, size) { - if (e) { return void cb(e); } + if (typeof(size) === 'undefined') { return void cb(e); } var rem = limit[0] - size; if (typeof(rem) !== 'number') { @@ -499,6 +575,53 @@ var sumChannelSizes = function (sizes) { .reduce(function (a, b) { return a + b; }, 0); }; +// inform that the +var loadChannelPins = function (Env) { + Pinned.load(function (data) { + Env.pinnedPads = data; + Env.evPinnedPadsReady.fire(); + }); +}; +var addPinned = function ( + Env, + publicKey /*:string*/, + channelList /*Array*/, + cb /*:()=>void*/) +{ + Env.evPinnedPadsReady.reg(() => { + channelList.forEach((c) => { + const x = Env.pinnedPads[c] = Env.pinnedPads[c] || {}; + x[publicKey] = 1; + }); + cb(); + }); +}; +var removePinned = function ( + Env, + publicKey /*:string*/, + channelList /*Array*/, + cb /*:()=>void*/) +{ + Env.evPinnedPadsReady.reg(() => { + channelList.forEach((c) => { + const x = Env.pinnedPads[c]; + if (!x) { return; } + delete x[publicKey]; + }); + cb(); + }); +}; +var isChannelPinned = function (Env, channel, cb) { + Env.evPinnedPadsReady.reg(() => { + if (Env.pinnedPads[channel] && Object.keys(Env.pinnedPads[channel]).length) { + cb(true); + } else { + delete Env.pinnedPads[channel]; + cb(false); + } + }); +}; + var pinChannel = function (Env, publicKey, channels, cb) { if (!channels && channels.filter) { return void cb('INVALID_PIN_LIST'); @@ -506,7 +629,7 @@ var pinChannel = function (Env, publicKey, channels, cb) { // get channel list ensures your session has a cached channel list getChannelList(Env, publicKey, function (pinned) { - var session = beginSession(Env.Sessions, publicKey); + var session = getSession(Env.Sessions, publicKey); // only pin channels which are not already pinned var toStore = channels.filter(function (channel) { @@ -518,11 +641,11 @@ var pinChannel = function (Env, publicKey, channels, cb) { } getMultipleFileSize(Env, toStore, function (e, sizes) { - if (e) { return void cb(e); } + if (typeof(sizes) === 'undefined') { return void cb(e); } var pinSize = sumChannelSizes(sizes); getFreeSpace(Env, publicKey, function (e, free) { - if (e) { + if (typeof(free) === 'undefined') { WARN('getFreeSpace', e); return void cb(e); } @@ -534,6 +657,7 @@ var pinChannel = function (Env, publicKey, channels, cb) { toStore.forEach(function (channel) { session.channels[channel] = true; }); + addPinned(Env, publicKey, toStore, () => {}); getHash(Env, publicKey, cb); }); }); @@ -542,14 +666,13 @@ var pinChannel = function (Env, publicKey, channels, cb) { }; var unpinChannel = function (Env, publicKey, channels, cb) { - var pinStore = Env.pinStore; if (!channels && channels.filter) { // expected array return void cb('INVALID_PIN_LIST'); } getChannelList(Env, publicKey, function (pinned) { - var session = beginSession(Env.Sessions, publicKey); + var session = getSession(Env.Sessions, publicKey); // only unpin channels which are pinned var toStore = channels.filter(function (channel) { @@ -560,13 +683,13 @@ var unpinChannel = function (Env, publicKey, channels, cb) { return void getHash(Env, publicKey, cb); } - pinStore.message(publicKey, JSON.stringify(['UNPIN', toStore]), + Env.pinStore.message(publicKey, JSON.stringify(['UNPIN', toStore]), function (e) { if (e) { return void cb(e); } toStore.forEach(function (channel) { delete session.channels[channel]; }); - + removePinned(Env, publicKey, toStore, () => {}); getHash(Env, publicKey, cb); }); }); @@ -574,8 +697,7 @@ var unpinChannel = function (Env, publicKey, channels, cb) { var resetUserPins = function (Env, publicKey, channelList, cb) { if (!Array.isArray(channelList)) { return void cb('INVALID_PIN_LIST'); } - var pinStore = Env.pinStore; - var session = beginSession(Env.Sessions, publicKey); + var session = getSession(Env.Sessions, publicKey); if (!channelList.length) { return void getHash(Env, publicKey, function (e, hash) { @@ -586,7 +708,7 @@ var resetUserPins = function (Env, publicKey, channelList, cb) { var pins = {}; getMultipleFileSize(Env, channelList, function (e, sizes) { - if (e) { return void cb(e); } + if (typeof(sizes) === 'undefined') { return void cb(e); } var pinSize = sumChannelSizes(sizes); @@ -604,14 +726,19 @@ var resetUserPins = function (Env, publicKey, channelList, cb) { They will not be able to pin additional pads until they upgrade or delete enough files to go back under their limit. */ - if (pinSize > limit && session.hasPinned) { return void(cb('E_OVER_LIMIT')); } - pinStore.message(publicKey, JSON.stringify(['RESET', channelList]), + if (pinSize > limit[0] && session.hasPinned) { return void(cb('E_OVER_LIMIT')); } + Env.pinStore.message(publicKey, JSON.stringify(['RESET', channelList]), function (e) { if (e) { return void cb(e); } channelList.forEach(function (channel) { pins[channel] = true; }); + var oldChannels = Object.keys(session.channels); + removePinned(Env, publicKey, oldChannels, () => { + addPinned(Env, publicKey, channelList, ()=>{}); + }); + // update in-memory cache IFF the reset was allowed. session.channels = pins; getHash(Env, publicKey, function (e, hash) { @@ -646,7 +773,8 @@ var isPrivilegedUser = function (publicKey, cb) { }); }; var safeMkdir = function (path, cb) { - Fs.mkdir(path, function (e) { + // flow wants the mkdir call w/ 3 args, 0o777 is default for a directory. + Fs.mkdir(path, 0o777, function (e) { if (!e || e.code === 'EEXIST') { return void cb(); } cb(e); }); @@ -655,8 +783,15 @@ var safeMkdir = function (path, cb) { var makeFileStream = function (root, id, cb) { var stub = id.slice(0, 2); var full = makeFilePath(root, id); + if (!full) { + WARN('makeFileStream', 'invalid id ' + id); + return void cb('BAD_ID'); + } safeMkdir(Path.join(root, stub), function (e) { - if (e) { return void cb(e); } + if (e || !full) { // !full for pleasing flow, it's already checked + WARN('makeFileStream', e); + return void cb(e ? e.message : 'INTERNAL_ERROR'); + } try { var stream = Fs.createWriteStream(full, { @@ -688,7 +823,7 @@ var clearOwnedChannel = function (Env, channelId, unsafeKey, cb) { Env.msgStore.getChannelMetadata(channelId, function (e, metadata) { if (e) { return cb(e); } if (!(metadata && Array.isArray(metadata.owners))) { return void cb('E_NO_OWNERS'); } - // Confirm that the channel is owned by the user is question + // Confirm that the channel is owned by the user in question if (metadata.owners.indexOf(unsafeKey) === -1) { return void cb('INSUFFICIENT_PERMISSIONS'); } @@ -699,14 +834,46 @@ var clearOwnedChannel = function (Env, channelId, unsafeKey, cb) { }); }; +var removeOwnedChannel = function (Env, channelId, unsafeKey, cb) { + if (typeof(channelId) !== 'string' || channelId.length !== 32) { + return cb('INVALID_ARGUMENTS'); + } + + if (!(Env.msgStore && Env.msgStore.removeChannel && Env.msgStore.getChannelMetadata)) { + return cb("E_NOT_IMPLEMENTED"); + } + + Env.msgStore.getChannelMetadata(channelId, function (e, metadata) { + if (e) { return cb(e); } + if (!(metadata && Array.isArray(metadata.owners))) { return void cb('E_NO_OWNERS'); } + if (metadata.owners.indexOf(unsafeKey) === -1) { + return void cb('INSUFFICIENT_PERMISSIONS'); + } + return void Env.msgStore.removeChannel(channelId, function (e) { + cb(e); + }); + }); +}; + +/* Users should be able to clear their own pin log with an authenticated RPC +*/ +var removePins = function (Env, safeKey, cb) { + if (typeof(Env.pinStore.removeChannel) !== 'function') { + return void cb("E_NOT_IMPLEMENTED"); + } + Env.pinStore.removeChannel(safeKey, function (err) { + cb(err); + }); +}; + var upload = function (Env, publicKey, content, cb) { var paths = Env.paths; var dec; try { dec = Buffer.from(content, 'base64'); } - catch (e) { return void cb(e); } + catch (e) { return void cb('DECODE_BUFFER'); } var len = dec.length; - var session = beginSession(Env.Sessions, publicKey); + var session = getSession(Env.Sessions, publicKey); if (typeof(session.currentUploadSize) !== 'number' || typeof(session.currentUploadSize) !== 'number') { @@ -721,7 +888,7 @@ var upload = function (Env, publicKey, content, cb) { if (!session.blobstage) { makeFileStream(paths.staging, publicKey, function (e, stream) { - if (e) { return void cb(e); } + if (!stream) { return void cb(e); } var blobstage = session.blobstage = stream; blobstage.write(dec); @@ -738,7 +905,7 @@ var upload = function (Env, publicKey, content, cb) { var upload_cancel = function (Env, publicKey, cb) { var paths = Env.paths; - var session = beginSession(Env.Sessions, publicKey); + var session = getSession(Env.Sessions, publicKey); delete session.currentUploadSize; delete session.pendingUploadSize; if (session.blobstage) { session.blobstage.close(); } @@ -768,7 +935,7 @@ var isFile = function (filePath, cb) { var upload_complete = function (Env, publicKey, cb) { var paths = Env.paths; - var session = beginSession(Env.Sessions, publicKey); + var session = getSession(Env.Sessions, publicKey); if (session.blobstage && session.blobstage.close) { session.blobstage.close(); @@ -776,14 +943,22 @@ var upload_complete = function (Env, publicKey, cb) { } 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 prefix = id.slice(0, 2); var newPath = makeFilePath(paths.blob, id); + if (typeof(newPath) !== 'string') { + WARN('safeMkdir', "newPath is null"); + return void cb('RENAME_ERR'); + } safeMkdir(Path.join(paths.blob, prefix), function (e) { - if (e) { + if (e || !newPath) { WARN('safeMkdir', e); return void cb('RENAME_ERR'); } @@ -804,12 +979,15 @@ var upload_complete = function (Env, publicKey, cb) { var retries = 3; var handleMove = function (e, newPath, id) { - if (e) { + if (e || !oldPath || !newPath) { if (retries--) { setTimeout(function () { return tryRandomLocation(handleMove); }, 750); + } else { + cb(e); } + return; } // lol wut handle ur errors @@ -818,12 +996,12 @@ var upload_complete = function (Env, publicKey, cb) { WARN('rename', e); if (retries--) { - return setTimeout(function () { + return void setTimeout(function () { tryRandomLocation(handleMove); }, 750); } - return cb(e); + return void cb('RENAME_ERR'); } cb(void 0, id); }); @@ -832,6 +1010,99 @@ var upload_complete = function (Env, publicKey, cb) { tryRandomLocation(handleMove); }; +var owned_upload_complete = function (Env, safeKey, 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; + } + + var oldPath = makeFilePath(Env.paths.staging, safeKey); + if (typeof(oldPath) !== 'string') { + return void cb('EINVAL_CONFIG'); + } + + // construct relevant paths + var root = Env.paths.staging; + + //var safeKey = escapeKeyCharacters(safeKey); + var safeKeyPrefix = safeKey.slice(0, 2); + + var blobId = createFileId(); + var blobIdPrefix = blobId.slice(0, 2); + + var plannedPath = Path.join(root, safeKeyPrefix, safeKey, blobIdPrefix); + + var tries = 0; + + var chooseSafeId = function (cb) { + if (tries >= 3) { + // you've already failed three times in a row + // give up and return an error + cb('E_REPEATED_FAILURE'); + } + + var path = Path.join(plannedPath, blobId); + 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 + blobId = blobIdPrefix + createFileId().slice(2); + return void chooseSafeId(cb); + } else if (e.code === 'ENOENT') { + // no entry, so it's safe for us to proceed + return void cb(void 0, path); + } else { + // it failed in an unexpected way. log it + // try again, but no more than a fixed number of times... + tries++; + chooseSafeId(cb); + } + }); + }; + + // the user wants to move it into their own space + // /blob/safeKeyPrefix/safeKey/blobPrefix/blobID + + var finalPath; + nThen(function (w) { + // make the requisite directory structure using Mkdirp + Mkdirp(plannedPath, w(function (e /*, path */) { + if (e) { // does not throw error if the directory already existed + w.abort(); + return void cb(e); + } + })); + }).nThen(function (w) { + // produce an id which confirmably does not collide with another + chooseSafeId(w(function (e, path) { + if (e) { + w.abort(); + return void cb(e); + } + finalPath = path; // this is where you'll put the new file + })); + }).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'); } */ + Fs.rename(oldPath /* XXX */, finalPath, w(function (e) { + if (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, blobId); + }); +}; + var upload_status = function (Env, publicKey, filesize, cb) { var paths = Env.paths; @@ -839,12 +1110,13 @@ var upload_status = function (Env, publicKey, filesize, cb) { if (typeof(filesize) !== 'number' && filesize >= 0) { return void cb('E_INVALID_SIZE'); } + if (filesize >= Env.maxUploadSize) { return cb('TOO_LARGE'); } // validate that the provided path is not junk var filePath = makeFilePath(paths.staging, publicKey); if (!filePath) { return void cb('E_INVALID_PATH'); } getFreeSpace(Env, publicKey, function (e, free) { - if (e) { return void cb(e); } + if (e || !filePath) { return void cb(e); } // !filePath for pleasing flow if (filesize >= free) { return cb('NOT_ENOUGH_SPACE'); } isFile(filePath, function (e, yes) { if (e) { @@ -856,10 +1128,41 @@ var upload_status = function (Env, publicKey, filesize, cb) { }); }; +var isNewChannel = function (Env, channel, cb) { + if (!isValidId(channel)) { return void cb('INVALID_CHAN'); } + if (channel.length !== 32) { return void cb('INVALID_CHAN'); } + + var count = 0; + var done = false; + Env.msgStore.getMessages(channel, function (msg) { + if (done) { return; } + var parsed; + try { + parsed = JSON.parse(msg); + if (parsed && typeof(parsed) === 'object') { count++; } + if (count >= 2) { + done = true; + cb(void 0, false); // it is not a new file + } + } catch (e) { + WARN('invalid message read from store', e); + } + }, function () { + if (done) { return; } + // no more messages... + cb(void 0, true); + }); +}; + var isUnauthenticatedCall = function (call) { return [ 'GET_FILE_SIZE', + 'GET_METADATA', 'GET_MULTIPLE_FILE_SIZE', + 'IS_CHANNEL_PINNED', + 'IS_NEW_CHANNEL', + 'GET_HISTORY_OFFSET', + 'GET_DELETED_PADS', ].indexOf(call) !== -1; }; @@ -873,14 +1176,57 @@ var isAuthenticatedCall = function (call) { 'GET_TOTAL_SIZE', 'UPDATE_LIMITS', 'GET_LIMIT', + 'UPLOAD_STATUS', 'UPLOAD_COMPLETE', + 'OWNED_UPLOAD_COMPLETE', 'UPLOAD_CANCEL', 'EXPIRE_SESSION', + 'CLEAR_OWNED_CHANNEL', + 'REMOVE_OWNED_CHANNEL', + 'REMOVE_PINS', ].indexOf(call) !== -1; }; -/*::const ConfigType = require('./config.example.js');*/ -RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)=>void*/) { +const mkEvent = function (once) { + var handlers = []; + var fired = false; + return { + reg: function (cb) { + if (once && fired) { return void setTimeout(cb); } + handlers.push(cb); + }, + unreg: function (cb) { + if (handlers.indexOf(cb) === -1) { throw new Error("Not registered"); } + handlers.splice(handlers.indexOf(cb), 1); + }, + fire: function () { + if (once && fired) { return; } + fired = true; + var args = Array.prototype.slice.call(arguments); + handlers.forEach(function (h) { h.apply(null, args); }); + } + }; +}; + +/*:: +const flow_Config = require('./config.example.js'); +type Config_t = typeof(flow_Config); +import type { ChainPadServer_Storage_t } from './storage/file.js' +type NetfluxWebsocketSrvContext_t = { + store: ChainPadServer_Storage_t, + getHistoryOffset: ( + ctx: NetfluxWebsocketSrvContext_t, + channelName: string, + lastKnownHash: ?string, + cb: (err: ?Error, offset: ?number)=>void + )=>void +}; +*/ +RPC.create = function ( + config /*:Config_t*/, + debuggable /*:(string, T)=>T*/, + cb /*:(?Error, ?Function)=>void*/ +) { // load pin-store... console.log('loading rpc module...'); @@ -890,14 +1236,21 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) return typeof(config[key]) === 'string'? config[key]: def; }; - var Env = {}; - Env.defaultStorageLimit = config.defaultStorageLimit; - - Env.maxUploadSize = config.maxUploadSize || (20 * 1024 * 1024); - - var Sessions = Env.Sessions = {}; + var Env = { + defaultStorageLimit: config.defaultStorageLimit, + maxUploadSize: config.maxUploadSize || (20 * 1024 * 1024), + Sessions: {}, + paths: {}, + msgStore: (undefined /*:any*/), + pinStore: (undefined /*:any*/), + pinnedPads: {}, + evPinnedPadsReady: mkEvent(true), + limits: {} + }; + debuggable('rpc_env', Env); - var paths = Env.paths = {}; + var Sessions = Env.Sessions; + var paths = Env.paths; var pinPath = paths.pin = keyOrDefaultString('pinPath', './pins'); var blobPath = paths.blob = keyOrDefaultString('blobPath', './blob'); var blobStagingPath = paths.staging = keyOrDefaultString('blobStagingPath', './blobstage'); @@ -906,16 +1259,34 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) return msg && msg.length === 2 && isUnauthenticatedCall(msg[0]); }; - var handleUnauthenticatedMessage = function (msg, respond) { + var handleUnauthenticatedMessage = function (msg, respond, nfwssCtx) { switch (msg[0]) { - case 'GET_FILE_SIZE': - return void getFileSize(Env, msg[1], function (e, size) { + case 'GET_HISTORY_OFFSET': { + if (typeof(msg[1]) !== 'object' || typeof(msg[1].channelName) !== 'string') { + return respond('INVALID_ARG_FORMAT', msg); + } + const msgHash = typeof(msg[1].msgHash) === 'string' ? msg[1].msgHash : undefined; + nfwssCtx.getHistoryOffset(nfwssCtx, msg[1].channelName, msgHash, (e, ret) => { if (e) { - console.error(e); + if (e.code !== 'ENOENT') { + WARN(e.stack, msg); + } + return respond(e.message); } + respond(e, [null, ret, null]); + }); + break; + } + case 'GET_FILE_SIZE': + return void getFileSize(Env, msg[1], function (e, size) { 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) { @@ -924,17 +1295,31 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) } respond(e, [null, dict, null]); }); + case 'GET_DELETED_PADS': + return void getDeletedPads(Env, msg[1], function (e, list) { + if (e) { + WARN(e, msg[1]); + return respond(e); + } + respond(e, [null, list, null]); + }); + case 'IS_CHANNEL_PINNED': + return void isChannelPinned(Env, msg[1], function (isPinned) { + respond(null, [null, isPinned, null]); + }); + case 'IS_NEW_CHANNEL': + return void isNewChannel(Env, msg[1], function (e, isNew) { + respond(e, [null, isNew, null]); + }); default: console.error("unsupported!"); return respond('UNSUPPORTED_RPC_CALL', msg); } }; - var rpc = function ( - ctx /*:{ store: Object }*/, - data /*:Array>*/, - respond /*:(?string, ?Array)=>void*/) - { + var rpc0 = function (ctx, data, respond) { + if (!Env.msgStore) { Env.msgStore = ctx.store; } + if (!Array.isArray(data)) { return void respond('INVALID_ARG_FORMAT'); } @@ -952,7 +1337,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) } if (isUnauthenticateMessage(msg)) { - return handleUnauthenticatedMessage(msg, respond); + return handleUnauthenticatedMessage(msg, respond, ctx); } var signature = msg.shift(); @@ -960,7 +1345,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) // make sure a user object is initialized in the cookie jar if (publicKey) { - beginSession(Sessions, publicKey); + getSession(Sessions, publicKey); } else { console.log("No public key"); } @@ -983,6 +1368,9 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) if (checkSignature(serialized, signature, publicKey) !== true) { return void respond("INVALID_SIGNATURE_OR_PUBLIC_KEY"); } + } else if (msg[1] !== 'UPLOAD') { + console.error("INVALID_RPC CALL:", msg[1]); + return void respond("INVALID_RPC_CALL"); } var safeKey = escapeKeyCharacters(publicKey); @@ -1013,8 +1401,6 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) Respond('E_ACCESS_DENIED'); }; - if (!Env.msgStore) { Env.msgStore = ctx.store; } - var handleMessage = function (privileged) { if (config.logRPC) { console.log(msg[0]); } switch (msg[0]) { @@ -1053,7 +1439,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) Respond(e, size); }); case 'UPDATE_LIMITS': - return void updateLimits(config, safeKey, function (e, limit) { + return void updateLimits(Env, config, safeKey, function (e, limit) { if (e) { WARN(e, limit); return void Respond(e); @@ -1086,6 +1472,17 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) if (e) { return void Respond(e); } Respond(void 0, response); }); + + case 'REMOVE_OWNED_CHANNEL': + return void removeOwnedChannel(Env, msg[1], publicKey, function (e) { + if (e) { return void Respond(e); } + Respond(void 0, "OK"); + }); + case 'REMOVE_PINS': + return void removePins(Env, safeKey, function (e) { + if (e) { return void Respond(e); } + Respond(void 0, "OK"); + }); // restricted to privileged users... case 'UPLOAD': if (!privileged) { return deny(); } @@ -1099,7 +1496,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) return void upload_status(Env, safeKey, msg[1], function (e, yes) { if (!e && !yes) { // no pending uploads, set the new size - var user = beginSession(Sessions, safeKey); + var user = getSession(Sessions, safeKey); user.pendingUploadSize = filesize; user.currentUploadSize = 0; } @@ -1111,6 +1508,12 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) WARN(e, hash); Respond(e, hash); }); + case 'OWNED_UPLOAD_COMPLETE': + if (!privileged) { return deny(); } + return void owned_upload_complete(Env, safeKey, function (e, blobId) { + WARN(e, blobId); + Respond(e, blobId); + }); case 'UPLOAD_CANCEL': if (!privileged) { return deny(); } return void upload_cancel(Env, safeKey, function (e) { @@ -1133,7 +1536,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) } // if session has not been authenticated, do so - var session = beginSession(Sessions, safeKey); + var session = getSession(Sessions, safeKey); if (typeof(session.privilege) !== 'boolean') { return void isPrivilegedUser(publicKey, function (yes) { session.privilege = yes; @@ -1145,8 +1548,21 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) handleMessage(session.privilege); }; + var rpc = function ( + ctx /*:NetfluxWebsocketSrvContext_t*/, + data /*:Array>*/, + respond /*:(?string, ?Array)=>void*/) + { + try { + return rpc0(ctx, data, respond); + } catch (e) { + console.log("Error from RPC with data " + JSON.stringify(data)); + console.log(e.stack); + } + }; + var updateLimitDaily = function () { - updateLimits(config, undefined, function (e) { + updateLimits(Env, config, undefined, function (e) { if (e) { WARN('limitUpdate', e); } @@ -1155,6 +1571,8 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) updateLimitDaily(); setInterval(updateLimitDaily, 24*3600*1000); + loadChannelPins(Env); + Store.create({ filePath: pinPath, }, function (s) { diff --git a/runtests.js b/runtests.js new file mode 100644 index 000000000..6d275d5d6 --- /dev/null +++ b/runtests.js @@ -0,0 +1,87 @@ +// jshint esversion: 6, browser: false, node: true +// This file is for automated testing, it should probably not be invoked for any other purpose. +// It will: +// 1. npm install +// 2. bower install +// 3. launch the server +// 4. run the tests on the machine +const Spawn = require('child_process').spawn; + +const processes = []; + +const killAll = (cb) => { + processes.forEach((p) => { p.kill(); }); + setTimeout(() => { + processes.forEach((p) => { + console.log("Process [" + p.command + "] did not end, using kill-9"); + p.kill('SIGKILL'); + }); + cb(); + }, 10); +}; +const error = (msg) => { + killAll(() => { + throw new Error(msg); + }); +}; + +const run = (cmd, args, cb) => { + const proc = Spawn(cmd, args); + processes.push(proc); + proc.procName = cmd + ' ' + args.join(' '); + console.log('>>' + proc.procName); + proc.stdout.on('data', (data) => { process.stdout.write(data); }); + proc.stderr.on('data', (data) => { process.stderr.write(data); }); + proc.on('close', (code) => { + const idx = processes.indexOf(proc); + if (idx === -1) { + error("process " + proc.procName + " disappeared from list"); + return; + } + processes.splice(idx, 1); + if (code) { + error("Process [" + proc.procName + "] ended with " + code); + } + cb(); + }); +}; + +run('npm', ['install'], () => { + const nThen = require('nthen'); + nThen((waitFor) => { + if (process.platform === 'darwin') { + run('bash', ['-c', + 'ps -ef | grep -v grep | grep \'Google Chrome.app/Contents/MacOS/Google Chrome\'' + + ' | awk \'{print $2}\' | while read x; do kill $x; done' + ], waitFor()); + + run('bash', ['-c', + 'ps -ef | grep -v grep | grep \'/usr/bin/safaridriver\'' + + ' | awk \'{print $2}\' | while read x; do kill $x; done' + ], waitFor()); + + run('bash', ['-c', + 'ps -ef | grep -v grep | grep \'/Applications/Firefox.app/Contents/MacOS/firefox-bin\'' + + ' | awk \'{print $2}\' | while read x; do kill $x; done' + ], waitFor()); + + run('bash', ['-c', + 'lsof | grep \'TCP .*:hbci (LISTEN)\'' + + ' | awk \'{print $2}\' | while read x; do kill $x; done' + ], waitFor()); + + run('bash', ['-c', 'rm -rf ./blob ./blobstage ./datastore'], waitFor()); + + run('bash', ['-c', 'caffeinate -u -t 2'], waitFor()); + } + }).nThen((waitFor) => { + run('bower', ['install'], waitFor()); + }).nThen((waitFor) => { + run('npm', ['run', 'fresh'], ()=>{}); + run('node', ['./TestSelenium.js'], waitFor()); + }).nThen((waitFor) => { + if (process.platform === 'darwin') { + run('bash', ['-c', 'pmset displaysleepnow'], waitFor()); + } + }).nThen(killAll); +}); diff --git a/screenshot.png b/screenshot.png index cbc837f58..cbb155697 100644 Binary files a/screenshot.png and b/screenshot.png differ diff --git a/server.js b/server.js index baf9b2e99..a7830cbaf 100644 --- a/server.js +++ b/server.js @@ -9,6 +9,7 @@ var WebSocketServer = require('ws').Server; var NetfluxSrv = require('./node_modules/chainpad-server/NetfluxWebsocketSrv'); var Package = require('./package.json'); var Path = require("path"); +var nThen = require("nthen"); var config; try { @@ -20,10 +21,25 @@ try { var websocketPort = config.websocketPort || config.httpPort; var useSecureWebsockets = config.useSecureWebsockets || false; +// This is stuff which will become available to replify +const debuggableStore = new WeakMap(); +const debuggable = function (name, x) { + if (name in debuggableStore) { + try { throw new Error(); } catch (e) { + console.error('cannot add ' + name + ' more than once [' + e.stack + ']'); + } + } else { + debuggableStore[name] = x; + } + return x; +}; +debuggable('global', global); +debuggable('config', config); + // support multiple storage back ends var Storage = require(config.storage||'./storage/file'); -var app = Express(); +var app = debuggable('app', Express()); var httpsOpts; @@ -32,6 +48,13 @@ if (DEV_MODE) { console.log("DEV MODE ENABLED"); } +var FRESH_MODE = !!process.env.FRESH; +var FRESH_KEY = ''; +if (FRESH_MODE) { + console.log("FRESH MODE ENABLED"); + FRESH_KEY = +new Date(); +} + const clone = (x) => (JSON.parse(JSON.stringify(x))); var setHeaders = (function () { @@ -94,6 +117,7 @@ Fs.exists(__dirname + "/customize", function (e) { var mainPages = config.mainPages || ['index', 'privacy', 'terms', 'about', 'contact']; var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$'); +app.get(mainPagePattern, Express.static(__dirname + '/customize')); app.get(mainPagePattern, Express.static(__dirname + '/customize.dist')); app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob')), { @@ -102,6 +126,7 @@ app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob app.use("/customize", Express.static(__dirname + '/customize')); app.use("/customize", Express.static(__dirname + '/customize.dist')); +app.use("/customize.dist", Express.static(__dirname + '/customize.dist')); app.use(/^\/[^\/]*$/, Express.static('customize')); app.use(/^\/[^\/]*$/, Express.static('customize.dist')); @@ -135,13 +160,14 @@ app.get('/api/config', function(req, res){ 'var obj = ' + JSON.stringify({ requireConf: { waitSeconds: 60, - urlArgs: 'ver=' + Package.version + (DEV_MODE? '-' + (+new Date()): ''), + urlArgs: 'ver=' + Package.version + (FRESH_KEY? '-' + FRESH_KEY: '') + (DEV_MODE? '-' + (+new Date()): ''), }, removeDonateButton: (config.removeDonateButton === true), allowSubscriptions: (config.allowSubscriptions === true), websocketPath: config.useExternalWebsocket ? undefined : config.websocketPath, websocketURL:'ws' + ((useSecureWebsockets) ? 's' : '') + '://' + host + ':' + websocketPort + '/cryptpad_websocket', + httpUnsafeOrigin: config.httpUnsafeOrigin, }, null, '\t'), 'obj.httpSafeOrigin = ' + (function () { if (config.httpSafeOrigin) { return config.httpSafeOrigin; } @@ -156,6 +182,22 @@ app.get('/api/config', function(req, res){ ].join(';\n')); }); +var four04_path = Path.resolve(__dirname + '/customize.dist/404.html'); +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) { + if (exists) { return Fs.createReadStream(path).pipe(res); } + send404(res); + }); +}; + +app.use(function (req, res, next) { + res.status(404); + send404(res, custom_four04_path); +}); + var httpServer = httpsOpts ? Https.createServer(httpsOpts, app) : Http.createServer(app); httpServer.listen(config.httpPort,config.httpAddress,function(){ @@ -173,32 +215,39 @@ if (config.httpSafePort) { var wsConfig = { server: httpServer }; -var createSocketServer = function (err, rpc) { - if(!config.useExternalWebsocket) { - if (websocketPort !== config.httpPort) { - console.log("setting up a new websocket server"); - wsConfig = { port: websocketPort}; - } - var wsSrv = new WebSocketServer(wsConfig); - Storage.create(config, function (store) { - NetfluxSrv.run(store, wsSrv, config, rpc); - }); - } -}; +var rpc; -var loadRPC = function (cb) { +var nt = nThen(function (w) { + if (!config.enableTaskScheduling) { return; } + var Tasks = require("./storage/tasks"); + console.log("loading task scheduler"); + Tasks.create(config, w(function (e, tasks) { + config.tasks = tasks; + })); +}).nThen(function (w) { config.rpc = typeof(config.rpc) === 'undefined'? './rpc.js' : config.rpc; - - if (typeof(config.rpc) === 'string') { - // load pin store... - var Rpc = require(config.rpc); - Rpc.create(config, function (e, rpc) { - if (e) { throw e; } - cb(void 0, rpc); - }); - } else { - cb(); + if (typeof(config.rpc) !== 'string') { return; } + // load pin store... + var Rpc = require(config.rpc); + Rpc.create(config, debuggable, w(function (e, _rpc) { + if (e) { + w.abort(); + throw e; + } + rpc = _rpc; + })); +}).nThen(function () { + if(config.useExternalWebsocket) { return; } + if (websocketPort !== config.httpPort) { + console.log("setting up a new websocket server"); + wsConfig = { port: websocketPort}; } -}; + var wsSrv = new WebSocketServer(wsConfig); + Storage.create(config, function (store) { + NetfluxSrv.run(store, wsSrv, config, rpc); + }); +}); -loadRPC(createSocketServer); +if (config.debugReplName) { + require('replify')({ name: config.debugReplName, app: debuggableStore }); +} \ No newline at end of file diff --git a/storage/file.js b/storage/file.js index 31b58aa94..08a9f19cd 100644 --- a/storage/file.js +++ b/storage/file.js @@ -1,6 +1,17 @@ +/*@flow*/ +/* jshint esversion: 6 */ +/* global Buffer */ var Fs = require("fs"); var Path = require("path"); var nThen = require("nthen"); +const ToPull = require('stream-to-pull-stream'); +const Pull = require('pull-stream'); + +const isValidChannelId = function (id) { + return typeof(id) === 'string' && + id.length >= 32 && id.length < 50 && + /^[a-zA-Z0-9=+-]*$/.test(id); +}; var mkPath = function (env, channelId) { return Path.join(env.root, channelId.slice(0, 2), channelId) + '.ndjson'; @@ -8,7 +19,7 @@ var mkPath = function (env, channelId) { var getMetadataAtPath = function (Env, path, cb) { var remainder = ''; - var stream = Fs.createReadStream(path, 'utf8'); + var stream = Fs.createReadStream(path, { encoding: 'utf8' }); var complete = function (err, data) { var _cb = cb; cb = undefined; @@ -25,16 +36,16 @@ var getMetadataAtPath = function (Env, path, cb) { var parsed = null; try { parsed = JSON.parse(metadata); - complete(void 0, parsed); + complete(undefined, parsed); } catch (e) { - console.log(); + console.log("getMetadataAtPath"); console.error(e); complete('INVALID_METADATA'); } }); stream.on('end', function () { - complete(null); + complete(); }); stream.on('error', function (e) { complete(e); }); }; @@ -45,7 +56,7 @@ var getChannelMetadata = function (Env, channelId, cb) { }; var closeChannel = function (env, channelName, cb) { - if (!env.channels[channelName]) { return; } + if (!env.channels[channelName]) { return void cb(); } try { env.channels[channelName].writeStream.close(); delete env.channels[channelName]; @@ -59,7 +70,7 @@ var closeChannel = function (env, channelName, cb) { var clearChannel = function (env, channelId, cb) { var path = mkPath(env, channelId); getMetadataAtPath(env, path, function (e, metadata) { - if (e) { return cb(e); } + if (e) { return cb(new Error(e)); } if (!metadata) { return void Fs.truncate(path, 0, function (err) { if (err) { @@ -87,7 +98,7 @@ var clearChannel = function (env, channelId, cb) { var readMessages = function (path, msgHandler, cb) { var remainder = ''; - var stream = Fs.createReadStream(path, 'utf8'); + var stream = Fs.createReadStream(path, { encoding: 'utf8' }); var complete = function (err) { var _cb = cb; cb = undefined; @@ -106,6 +117,65 @@ var readMessages = function (path, msgHandler, cb) { stream.on('error', function (e) { complete(e); }); }; +const NEWLINE_CHR = ('\n').charCodeAt(0); +const mkBufferSplit = () => { + let remainder = null; + return Pull((read) => { + return (abort, cb) => { + read(abort, function (end, data) { + if (end) { + if (data) { console.log("mkBufferSplit() Data at the end"); } + cb(end, remainder ? [remainder, data] : [data]); + remainder = null; + return; + } + const queue = []; + for (;;) { + const offset = data.indexOf(NEWLINE_CHR); + if (offset < 0) { + remainder = remainder ? Buffer.concat([remainder, data]) : data; + break; + } + let subArray = data.slice(0, offset); + if (remainder) { + subArray = Buffer.concat([remainder, subArray]); + remainder = null; + } + queue.push(subArray); + data = data.slice(offset + 1); + } + cb(end, queue); + }); + }; + }, Pull.flatten()); +}; + +const mkOffsetCounter = () => { + let offset = 0; + return Pull.map((buff) => { + const out = { offset: offset, buff: buff }; + // +1 for the eaten newline + offset += buff.length + 1; + return out; + }); +}; + +const readMessagesBin = (env, id, start, msgHandler, cb) => { + const stream = Fs.createReadStream(mkPath(env, id), { start: start }); + let keepReading = true; + Pull( + ToPull.read(stream), + mkBufferSplit(), + mkOffsetCounter(), + Pull.asyncMap((data, moreCb) => { + msgHandler(data, moreCb, () => { keepReading = false; moreCb(); }); + }), + Pull.drain(() => (keepReading), (err) => { + cb((keepReading) ? err : undefined); + }) + ); +}; + var checkPath = function (path, callback) { // TODO check if we actually need to use stat at all Fs.stat(path, function (err) { @@ -117,7 +187,8 @@ var checkPath = function (path, callback) { callback(err); return; } - Fs.mkdir(Path.dirname(path), function (err) { + // 511 -> octal 777 + Fs.mkdir(Path.dirname(path), 511, function (err) { if (err && err.code !== 'EEXIST') { callback(err); return; @@ -154,7 +225,28 @@ var flushUnusedChannels = function (env, cb, frame) { cb(); }; -var getChannel = function (env, id, callback) { +var channelBytes = function (env, chanName, cb) { + var path = mkPath(env, chanName); + Fs.stat(path, function (err, stats) { + if (err) { return void cb(err); } + cb(undefined, stats.size); + }); +}; + +/*:: +export type ChainPadServer_ChannelInternal_t = { + atime: number, + writeStream: typeof(process.stdout), + whenLoaded: ?Array<(err:?Error, chan:?ChainPadServer_ChannelInternal_t)=>void>, + onError: Array<(?Error)=>void>, + path: string +}; +*/ +var getChannel = function ( + env, + id, + callback /*:(err:?Error, chan:?ChainPadServer_ChannelInternal_t)=>void*/ +) { if (env.channels[id]) { var chan = env.channels[id]; chan.atime = +new Date(); @@ -177,13 +269,13 @@ var getChannel = function (env, id, callback) { }, env.channelExpirationMs / 2); }); } - - var channel = env.channels[id] = { + var path = mkPath(env, id); + var channel /*:ChainPadServer_ChannelInternal_t*/ = env.channels[id] = { atime: +new Date(), - messages: [], - writeStream: undefined, + writeStream: (undefined /*:any*/), whenLoaded: [ callback ], - onError: [ ] + onError: [ ], + path: path }; var complete = function (err) { var whenLoaded = channel.whenLoaded; @@ -193,9 +285,11 @@ var getChannel = function (env, id, callback) { if (err) { delete env.channels[id]; } + if (!channel.writeStream) { + throw new Error("getChannel() complete called without channel writeStream"); + } whenLoaded.forEach(function (wl) { wl(err, (err) ? undefined : channel); }); }; - var path = mkPath(env, id); var fileExists; var errorState; nThen(function (waitFor) { @@ -207,23 +301,12 @@ var getChannel = function (env, id, callback) { } fileExists = exists; })); - }).nThen(function (waitFor) { - if (errorState) { return; } - if (!fileExists) { return; } - readMessages(path, function (msg) { - channel.messages.push(msg); - }, waitFor(function (err) { - if (err) { - errorState = true; - complete(err); - } - })); }).nThen(function (waitFor) { if (errorState) { return; } var stream = channel.writeStream = Fs.createWriteStream(path, { flags: 'a' }); env.openFiles++; stream.on('open', waitFor()); - stream.on('error', function (err) { + stream.on('error', function (err /*:?Error*/) { env.openFiles--; // this might be called after this nThen block closes. if (channel.whenLoaded) { @@ -240,59 +323,92 @@ var getChannel = function (env, id, callback) { }); }; -var message = function (env, chanName, msg, cb) { +const messageBin = (env, chanName, msgBin, cb) => { getChannel(env, chanName, function (err, chan) { - if (err) { + if (!chan) { cb(err); return; } + let called = false; var complete = function (err) { - var _cb = cb; - cb = undefined; - if (_cb) { _cb(err); } + if (called) { return; } + called = true; + cb(err); }; chan.onError.push(complete); - chan.writeStream.write(msg + '\n', function () { - chan.onError.splice(chan.onError.indexOf(complete) - 1, 1); + chan.writeStream.write(msgBin, function () { + /*::if (!chan) { throw new Error("Flow unreachable"); }*/ + chan.onError.splice(chan.onError.indexOf(complete), 1); if (!cb) { return; } - chan.messages.push(msg); + //chan.messages.push(msg); chan.atime = +new Date(); complete(); }); }); }; +var message = function (env, chanName, msg, cb) { + messageBin(env, chanName, new Buffer(msg + '\n', 'utf8'), cb); +}; + var getMessages = function (env, chanName, handler, cb) { getChannel(env, chanName, function (err, chan) { - if (err) { + if (!chan) { cb(err); return; } - try { - chan.messages - .forEach(function (message) { - if (!message) { return; } - handler(message); - }); - } catch (err2) { - console.error(err2); - cb(err2); - return; - } - chan.atime = +new Date(); - cb(); + var errorState = false; + readMessages(chan.path, function (msg) { + if (!msg || errorState) { return; } + //console.log(msg); + try { + handler(msg); + } catch (e) { + errorState = true; + return void cb(err); + } + }, function (err) { + if (err) { + errorState = true; + return void cb(err); + } + if (!chan) { throw new Error("impossible, flow checking"); } + chan.atime = +new Date(); + cb(); + }); }); }; -var channelBytes = function (env, chanName, cb) { - var path = mkPath(env, chanName); - Fs.stat(path, function (err, stats) { - if (err) { return void cb(err); } - cb(void 0, stats.size); - }); +/*:: +export type ChainPadServer_MessageObj_t = { buff: Buffer, offset: number }; +export type ChainPadServer_Storage_t = { + readMessagesBin: ( + channelName:string, + start:number, + asyncMsgHandler:(msg:ChainPadServer_MessageObj_t, moreCb:()=>void, abortCb:()=>void)=>void, + cb:(err:?Error)=>void + )=>void, + message: (channelName:string, content:string, cb:(err:?Error)=>void)=>void, + messageBin: (channelName:string, content:Buffer, cb:(err:?Error)=>void)=>void, + getMessages: (channelName:string, msgHandler:(msg:string)=>void, cb:(err:?Error)=>void)=>void, + removeChannel: (channelName:string, cb:(err:?Error)=>void)=>void, + closeChannel: (channelName:string, cb:(err:?Error)=>void)=>void, + flushUnusedChannels: (cb:()=>void)=>void, + getChannelSize: (channelName:string, cb:(err:?Error, size:?number)=>void)=>void, + getChannelMetadata: (channelName:string, cb:(err:?Error|string, data:?any)=>void)=>void, + clearChannel: (channelName:string, (err:?Error)=>void)=>void }; - -module.exports.create = function (conf, cb) { +export type ChainPadServer_Config_t = { + verbose?: boolean, + filePath?: string, + channelExpirationMs?: number, + openFileLimit?: number +}; +*/ +module.exports.create = function ( + conf /*:ChainPadServer_Config_t*/, + cb /*:(store:ChainPadServer_Storage_t)=>void*/ +) { var env = { root: conf.filePath || './datastore', channels: { }, @@ -301,41 +417,61 @@ module.exports.create = function (conf, cb) { openFiles: 0, openFileLimit: conf.openFileLimit || 2048, }; - Fs.mkdir(env.root, function (err) { + // 0x1ff -> 777 + var it; + Fs.mkdir(env.root, 0x1ff, function (err) { if (err && err.code !== 'EEXIST') { // TODO: somehow return a nice error throw err; } cb({ + readMessagesBin: (channelName, start, asyncMsgHandler, cb) => { + if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); } + readMessagesBin(env, channelName, start, asyncMsgHandler, cb); + }, message: function (channelName, content, cb) { + if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); } message(env, channelName, content, cb); }, + messageBin: (channelName, content, cb) => { + if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); } + messageBin(env, channelName, content, cb); + }, getMessages: function (channelName, msgHandler, cb) { + if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); } getMessages(env, channelName, msgHandler, cb); }, removeChannel: function (channelName, cb) { + if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); } removeChannel(env, channelName, function (err) { cb(err); }); }, closeChannel: function (channelName, cb) { + if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); } closeChannel(env, channelName, cb); }, flushUnusedChannels: function (cb) { flushUnusedChannels(env, cb); }, - getChannelSize: function (chanName, cb) { - channelBytes(env, chanName, cb); + getChannelSize: function (channelName, cb) { + if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); } + channelBytes(env, channelName, cb); }, getChannelMetadata: function (channelName, cb) { + if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); } getChannelMetadata(env, channelName, cb); }, clearChannel: function (channelName, cb) { + if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); } clearChannel(env, channelName, cb); }, + shutdown: function () { + clearInterval(it); + } }); }); - setInterval(function () { + it = setInterval(function () { flushUnusedChannels(env, function () { }); }, 5000); }; diff --git a/storage/tasks.js b/storage/tasks.js new file mode 100644 index 000000000..85df70a8d --- /dev/null +++ b/storage/tasks.js @@ -0,0 +1,94 @@ +var Fs = require("fs"); +var Path = require("path"); +var nacl = require("tweetnacl"); +var nThen = require("nthen"); + +var Tasks = module.exports; + +var encode = function (time, command, args) { + if (typeof(time) !== 'number') { return null; } + if (typeof(command) !== 'string') { return null; } + if (!Array.isArray(args)) { return [time, command]; } + return [time, command].concat(args); +}; + +var randomId = function () { + var bytes = Array.prototype.slice.call(nacl.randomBytes(16)); + return bytes.map(function (b) { + var n = Number(b & 0xff).toString(16); + return n.length === 1? '0' + n: n; + }).join(''); +}; + +var mkPath = function (env, id) { + return Path.join(env.root, id.slice(0, 2), id) + '.ndjson'; +}; + +var getFreeId = function (env, cb, tries) { + if (tries > 5) { return void cb('ETOOMANYTRIES'); } + + // generate a unique id + var id = randomId(); + + // derive a path from that id + var path = mkPath(env, id); + + Fs.stat(path, function (err) { + if (err && err.code === "ENOENT") { + cb(void 0, id); + } else { + getFreeId(env, cb); + } + }); +}; + +var write = function (env, task, cb) { + var str = JSON.stringify(task) + '\n'; + var id = nacl.util.encodeBase64(nacl.hash(nacl.util.decodeUTF8(str))).replace(/\//g, '-'); + + var path = mkPath(env, id); + nThen(function (w) { + // check if the file already exists... + Fs.stat(path, w(function (err) { + if (err && err.code === 'ENOENT') { return; } + w.abort(); cb(); + })); + }).nThen(function (w) { + // create the parent directory if it does not exist + var dir = id.slice(0, 2); + var dirpath = Path.join(env.root, dir); + + Fs.mkdir(dirpath, 0x1ff, w(function (err) { + if (err && err.code !== 'EEXIST') { + return void cb(err); + } + })); + }).nThen(function () { + // write the file to the path + Fs.writeFile(mkPath(env, id), str, function (e) { + if (e) { return void cb(e); } + cb(); + }); + }); +}; + +Tasks.create = function (config, cb) { + var env = { + root: config.taskPath || './tasks', + }; + + // make sure the path exists... + Fs.mkdir(env.root, 0x1ff, function (err) { + if (err && err.code !== 'EEXIST') { + throw err; + } + cb(void 0, { + write: function (time, command, args, cb) { + var task = encode(time, command, args); + write(env, task, cb); + }, + }); + }); +}; + + diff --git a/www/assert/frame/frame.html b/www/assert/frame/frame.html new file mode 100644 index 000000000..8d2d63c0d --- /dev/null +++ b/www/assert/frame/frame.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/www/assert/frame/frame.js b/www/assert/frame/frame.js new file mode 100644 index 000000000..42808f6f9 --- /dev/null +++ b/www/assert/frame/frame.js @@ -0,0 +1,147 @@ +(function () { + + var Frame = {}; + + var uid = function () { + return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)) + .toString(32).replace(/\./g, ''); + }; + + // create an invisible iframe with a given source + // append it to a parent element + // execute a callback when it has loaded + Frame.create = function (parent, src, onload, timeout) { + var iframe = document.createElement('iframe'); + + timeout = timeout || 10000; + var to = window.setTimeout(function () { + onload('[timeoutError] could not load iframe at ' + src); + }, timeout); + + iframe.setAttribute('id', 'cors-store'); + + iframe.onload = function (e) { + onload(void 0, iframe, e); + window.clearTimeout(to); + }; + // We must pass a unique parameter here to avoid cache problems in Firefox with + // the NoScript plugin: if the iframe's content is taken from the cache, the JS + // is not executed with NoScript.... + iframe.setAttribute('src', src + '?t=' + new Date().getTime()); + + iframe.style.display = 'none'; + parent.appendChild(iframe); + }; + + /* given an iframe with an rpc script loaded, create a frame object + with an asynchronous 'send' method */ + Frame.open = function (e, A, timeout) { + var win = e.contentWindow; + + var frame = {}; + frame.id = uid(); + + var listeners = {}; + var timeouts = {}; + + timeout = timeout || 5000; + + frame.accepts = function (o) { + return A.some(function (e) { + switch (typeof(e)) { + case 'string': return e === o; + case 'object': return e.test(o); + } + }); + }; + + var changeHandlers = frame.changeHandlers = []; + + frame.change = function (f) { + if (typeof(f) !== 'function') { + throw new Error('[Frame.change] expected callback'); + } + changeHandlers.push(f); + }; + + var _listener = function (e) { + if (!frame.accepts(e.origin)) { + console.log("message from %s rejected!", e.origin); + return; + } + var message = JSON.parse(e.data); + var uid = message._uid; + var error = message.error; + var data = message.data; + + if (!uid) { + console.log("No uid!"); + return; + } + + if (uid === 'change' && changeHandlers.length) { + changeHandlers.forEach(function (f) { + f(data); + }); + return; + } + + if (timeouts[uid]) { + window.clearTimeout(timeouts[uid]); + } + if (listeners[uid]) { + listeners[uid](error, data, e); + delete listeners[uid]; + } + }; + window.addEventListener('message', _listener); + + frame.close = function () { + window.removeEventListener('message', _listener); + }; + + /* method (string): (set|get|remove) + key (string) + data (string) + cb (function) */ + frame.send = function (method, content, cb) { + var req = { + method: method, + //key: key, + data: content, //data, + }; + + var id = req._uid = uid(); + // uid must not equal 'change' + while(id === 'change') { + id = req._uid = uid(); + } + + if (typeof(cb) === 'function') { + //console.log("setting callback!"); + listeners[id] = cb; + //console.log("setting timeout of %sms", timeout); + timeouts[id] = window.setTimeout(function () { + // when the callback is executed it will clear this timeout + cb('[TimeoutError] request timed out after ' + timeout + 'ms'); + }, timeout); + } else { + console.log(typeof(cb)); + } + + win.postMessage(JSON.stringify(req), '*'); + }; + + return frame; + }; + + if (typeof(module) !== 'undefined' && module.exports) { + module.exports = Frame; + } else if (typeof(define) === 'function' && define.amd) { + define(['jquery'], function () { + return Frame; + }); + } else { + window.Frame = Frame; + } +}()); diff --git a/www/assert/frame/respond.js b/www/assert/frame/respond.js new file mode 100644 index 000000000..07f8e2994 --- /dev/null +++ b/www/assert/frame/respond.js @@ -0,0 +1,32 @@ +var validDomains = [ /.*/i, ]; +var isValidDomain = function (o) { + return validDomains.some(function (e) { + switch (typeof(e)) { + case 'string': return e === o; + case 'object': return e.test(o); + } + }); +}; + +window.addEventListener('message', function(e) { + if (!isValidDomain(e.origin)) { return; } + var payload = JSON.parse(e.data); + var parent = window.parent; + var respond = function (error, data) { + var res = { + _uid: payload._uid, + error: error, + data: data, + }; + parent.postMessage(JSON.stringify(res), '*'); + }; + + //console.error(payload); + switch(payload.method) { + case undefined: + return respond('No method supplied'); + default: + return respond(void 0, "EHLO"); + } +}); + diff --git a/www/assert/index.html b/www/assert/index.html index f4867629f..eb0bd659f 100644 --- a/www/assert/index.html +++ b/www/assert/index.html @@ -19,6 +19,11 @@ .error { border: 1px solid red; } + .thumb { + max-height: 150px; + width: auto; + border: 3px solid black; + } @@ -36,6 +41,7 @@

    "pewpewpew"

    +

    Test 2

    @@ -45,3 +51,6 @@

    Here is a macro


    + + + diff --git a/www/assert/main.js b/www/assert/main.js index 7124f58d9..eb470eb35 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -1,14 +1,16 @@ define([ 'jquery', '/bower_components/hyperjson/hyperjson.js', - '/bower_components/textpatcher/TextPatcher.amd.js', 'json.sortify', - '/common/cryptpad-common.js', '/drive/tests.js', - '/common/test.js' -], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad, Drive, Test) { + '/common/test.js', + '/common/common-hash.js', + '/common/common-util.js', + '/common/common-thumbnail.js', + '/common/wire.js', + '/common/flat-dom.js', +], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat) { window.Hyperjson = Hyperjson; - window.TextPatcher = TextPatcher; window.Sortify = Sortify; var assertions = 0; @@ -30,7 +32,7 @@ define([ ASSERTS.forEach(function (f, index) { f(function (err) { - console.log("test " + index); + //console.log("test " + index); done(err, index); }, index); }); @@ -92,18 +94,7 @@ define([ // turn it back into stringified Hyperjson, but apply filters var shjson2 = Sortify(Hyperjson.fromDOM(DOM, elementFilter, attributeFilter)); - var success = shjson === shjson2; - - var op = TextPatcher.diff(shjson, shjson2); - - var diff = TextPatcher.format(shjson, op); - - if (success) { - return cb(true); - } else { - return cb('

    insert: ' + diff.insert + '

    ' + - 'remove: ' + diff.remove + '

    '); - } + return cb(shjson === shjson2); }, "expected hyperjson equality"); }; @@ -114,21 +105,8 @@ define([ assert(function (cb) { var hjson = Hyperjson.fromDOM(target); var cloned = Hyperjson.toDOM(hjson); - var success = cloned.outerHTML === target.outerHTML; - - if (!success) { - var op = TextPatcher.diff(target.outerHTML, cloned.outerHTML); - window.DEBUG = { - error: "Expected equality between A and B", - A: target.outerHTML, - B: cloned.outerHTML, - diff: op - }; - console.log("DIFF:"); - TextPatcher.log(target.outerHTML, op); - } - return cb(success); + return cb(cloned.outerHTML === target.outerHTML); }, "Round trip serialization introduced artifacts."); }; @@ -156,15 +134,17 @@ define([ // check that old hashes parse correctly assert(function (cb) { - var secret = Cryptpad.parsePadUrl('/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy'); + //if (1) { return cb(true); } // TODO(cjd): This is a test failure which is a known bug + var secret = Hash.parsePadUrl('/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy'); return cb(secret.hashData.channel === "67b8385b07352be53e40746d2be6ccd7" && secret.hashData.key === "XAYSuJYYqa9NfmInyHci7LNy" && - secret.hashData.version === 0); + secret.hashData.version === 0 && + typeof(secret.getUrl) === 'function'); }, "Old hash failed to parse"); // make sure version 1 hashes parse correctly assert(function (cb) { - var secret = Cryptpad.parsePadUrl('/pad/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI'); + var secret = Hash.parsePadUrl('/pad/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI'); return cb(secret.hashData.version === 1 && secret.hashData.mode === "edit" && secret.hashData.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" && @@ -174,7 +154,7 @@ define([ // test support for present mode in hashes assert(function (cb) { - var secret = Cryptpad.parsePadUrl('/pad/#/1/edit/CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/present'); + var secret = Hash.parsePadUrl('/pad/#/1/edit/CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/present'); return cb(secret.hashData.version === 1 && secret.hashData.mode === "edit" && secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg" @@ -184,7 +164,7 @@ define([ // test support for present mode in hashes assert(function (cb) { - var secret = Cryptpad.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G//present'); + var secret = Hash.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G//present'); return cb(secret.hashData.version === 1 && secret.hashData.mode === "edit" && secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg" @@ -192,9 +172,42 @@ define([ && secret.hashData.present); }, "Couldn't handle multiple successive slashes"); + // test support for present & embed mode in hashes + assert(function (cb) { + var secret = Hash.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/embed/present/'); + return cb(secret.hashData.version === 1 + && secret.hashData.mode === "edit" + && secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg" + && secret.hashData.key === "DNZ2wcG683GscU4fyOyqA87G" + && secret.hashData.present + && secret.hashData.embed); + }, "Couldn't handle multiple successive slashes"); + + // test support for present & embed mode in hashes + assert(function (cb) { + var secret = Hash.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/present/embed'); + return cb(secret.hashData.version === 1 + && secret.hashData.mode === "edit" + && secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg" + && secret.hashData.key === "DNZ2wcG683GscU4fyOyqA87G" + && secret.hashData.present + && secret.hashData.embed); + }, "Couldn't handle multiple successive slashes"); + + // test support for embed mode in hashes + assert(function (cb) { + var secret = Hash.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G///embed//'); + return cb(secret.hashData.version === 1 + && secret.hashData.mode === "edit" + && secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg" + && secret.hashData.key === "DNZ2wcG683GscU4fyOyqA87G" + && !secret.hashData.present + && secret.hashData.embed); + }, "Couldn't handle multiple successive slashes"); + // test support for trailing slash assert(function (cb) { - var secret = Cryptpad.parsePadUrl('/pad/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/'); + var secret = Hash.parsePadUrl('/pad/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/'); return cb(secret.hashData.version === 1 && secret.hashData.mode === "edit" && secret.hashData.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" && @@ -202,14 +215,140 @@ define([ !secret.hashData.present); }, "test support for trailing slashes in version 1 hash failed to parse"); + assert(function (cb) { + var secret = Hash.parsePadUrl('/invite/#/1/ilrOtygzDVoUSRpOOJrUuQ/e8jvf36S3chzkkcaMrLSW7PPrz7VDp85lIFNI26dTmr=/'); + var hd = secret.hashData; + cb(hd.channel === "ilrOtygzDVoUSRpOOJrUuQ" && + hd.pubkey === "e8jvf36S3chzkkcaMrLSW7PPrz7VDp85lIFNI26dTmr=" && + hd.type === 'invite'); + }, "test support for invite urls"); + + 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); + + return cb(secret.hashData.version === 1 && + secret.hashData.mode === "edit" && + secret.hashData.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" && + secret.hashData.key === "usn4+9CqVja8Q7RZOGTfRgqI" && + !secret.hashData.present); + }, "test support for ugly tracking query paramaters in url"); + assert(function (cb) { // TODO return cb(true); }, "version 2 hash failed to parse correctly"); + assert(function (cb) { + Wire.create({ + constructor: function (cb) { + var service = function (type, data, cb) { + switch (type) { + case "HEY_BUDDY": + return cb(void 0, "SALUT!"); + default: + cb("ERROR"); + } + }; + + var evt = Util.mkEvent(); + var respond = function (e, out) { + evt.fire(e, out); + }; + cb(void 0, { + send: function (raw /*, cb */) { + try { + var parsed = JSON.parse(raw); + var txid = parsed.txid; + setTimeout(function () { + service(parsed.q, parsed.content, function (e, result) { + respond(JSON.stringify({ + txid: txid, + error: e, + content: result, + })); + }); + }); + } catch (e) { console.error("PEWPEW"); } + }, + receive: function (f) { + evt.reg(f); + }, + }); + }, + }, function (e, rpc) { + if (e) { return cb(false); } + rpc.send('HEY_BUDDY', null, function (e, out) { + if (e) { return void cb(false); } + if (out === 'SALUT!') { cb(true); } + }); + }); + }, "Test rpc factory"); + + assert(function (cb) { + require([ + '/assert/frame/frame.js', + ], function (Frame) { + Frame.create(document.body, '/assert/frame/frame.html', function (e, frame) { + if (e) { return cb(false); } + + var channel = Frame.open(frame, [ + /.*/i, + ], 5000); + + channel.send('HELO', null, function (e, res) { + if (res === 'EHLO') { return cb(true); } + cb(false); + }); + }); + }); + }, "PEWPEW"); + + (function () { + var guid = Wire.uid(); + + var t = Wire.tracker({ + timeout: 1000, + hook: function (txid, q, content) { + console.info(JSON.stringify({ + guid: guid, + txid: txid, + q: q, + content: content, + })); + }, + }); + + assert(function (cb) { + t.call('SHOULD_TIMEOUT', null, function (e) { + if (e === 'TIMEOUT') { return cb(true); } + cb(false); + }); + }, 'tracker should timeout'); + + assert(function (cb) { + var id = t.call('SHOULD_NOT_TIMEOUT', null, function (e, out) { + if (e) { return cb(false); } + if (out === 'YES') { return cb(true); } + cb(false); + }); + t.respond(id, void 0, 'YES'); + }, "tracker should not timeout"); + }()); Drive.test(assert); + assert(function (cb) { + // extract dom elements into a flattened JSON representation + var flat = Flat.fromDOM(document.body); + // recreate a _mostly_ equivalent DOM + var dom = Flat.toDOM(flat); + // assume we don't care about comments + var bodyText = document.body.outerHTML.replace(//g, ''); + // check for equality + cb(dom.outerHTML === bodyText); + }); + var swap = function (str, dict) { return str.replace(/\{\{(.*?)\}\}/g, function (all, key) { return typeof dict[key] !== 'undefined'? dict[key] : all; diff --git a/www/assert/translations/main.js b/www/assert/translations/main.js index 9a5397c73..db005ce0a 100644 --- a/www/assert/translations/main.js +++ b/www/assert/translations/main.js @@ -1,8 +1,9 @@ define([ 'jquery', - '/common/cryptpad-common.js', + '/common/common-util.js', + '/customize/messages.js', '/customize/translations/messages.js', -], function ($, Cryptpad, English) { +], function ($, Util, Messages, English) { var $body = $('body'); @@ -11,38 +12,40 @@ define([ }; var todo = function (missing) { - var str = ""; - var need = 1; + var currentLang = ""; + var currentState = 1; if (missing.length) { $body.append(pre(missing.map(function (msg) { var res = ""; - var code = msg[0]; - var key = msg[1]; - var needed = msg[2]; + var lang = msg[0]; + var key = msg[1]; // Array + var state = msg[2]; // 0 === toDelete, 1 === missing, 2 === updated, 3 === invalid (wrong type) var value = msg[3] || '""'; - if (str !== code) { - if (str !== "") + if (currentLang !== lang) { + if (currentLang !== "") { res += '\n'; } - str = code; - res += '/*\n *\n * ' + code + '\n *\n */\n\n'; + currentLang = lang; + res += '/*\n *\n * ' + lang + '\n *\n */\n\n'; } - if (need !== needed) { - need = needed; - if (need === 0) + if (currentState !== state) { + currentState = state; + if (currentState === 0) { - res += '\n// TODO: These keys are not needed anymore and should be removed ('+ code + ')\n\n'; + res += '\n// TODO: These keys are not needed anymore and should be removed ('+ lang + ')\n\n'; } } - res += (need ? '' : '// ') + 'out.' + key + ' = ' + value + ';'; - if (need === 1) { - res += ' // ' + JSON.stringify(English[key]); - } else if (need === 2) { - res += ' // TODO: Key updated --> make sure the updated key "'+ value +'" exists and is translated before that one.'; + res += (currentState ? '' : '// ') + 'out.' + key.join('.') + ' = ' + value + ';'; + if (currentState === 1) { + res += ' // ' + JSON.stringify(Util.find(English, key)); + } else if (currentState === 2) { + res += ' // TODO: Key updated --> make sure the updated key "'+ value +'" exists and is translated before this one.'; + } else if (currentState === 3) { + res += ' // NOTE: this key has an invalid type! Original value: ' + JSON.stringify(Util.find(English, key)); } return res; }).join('\n'))); @@ -50,5 +53,5 @@ define([ $body.text('// All keys are present in all translations'); } }; - Cryptpad.Messages._checkTranslationState(todo); + Messages._checkTranslationState(todo); }); diff --git a/www/auth/main.js b/www/auth/main.js index 577e1e966..a45751dcb 100644 --- a/www/auth/main.js +++ b/www/auth/main.js @@ -1,9 +1,12 @@ define([ 'jquery', '/common/cryptpad-common.js', + '/common/common-constants.js', + '/common/outer/local-store.js', '/common/test.js', + '/bower_components/nthen/index.js', '/bower_components/tweetnacl/nacl-fast.min.js' -], function ($, Cryptpad, Test) { +], function ($, Cryptpad, Constants, LocalStore, Test, nThen) { var Nacl = window.nacl; var signMsg = function (msg, privKey) { @@ -20,13 +23,21 @@ define([ ]; // Safari is weird about localStorage in iframes but seems to let sessionStorage slide. - localStorage.User_hash = localStorage.User_hash || sessionStorage.User_hash; + localStorage[Constants.userHashKey] = localStorage[Constants.userHashKey] || + sessionStorage[Constants.userHashKey]; - Cryptpad.ready(function () { + var proxy; + nThen(function (waitFor) { + Cryptpad.ready(waitFor()); + }).nThen(function (waitFor) { + Cryptpad.getUserObject(waitFor(function (obj) { + proxy = obj; + })); + }).nThen(function () { console.log('IFRAME READY'); Test(function () { // This is only here to maybe trigger an error. - window.drive = Cryptpad.getStore().getProxy().proxy['drive']; + window.drive = proxy['drive']; Test.passed(); }); $(window).on("message", function (jqe) { @@ -40,10 +51,9 @@ define([ } else if (data.cmd === 'SIGN') { if (!AUTHORIZED_DOMAINS.filter(function (x) { return x.test(domain); }).length) { ret.error = "UNAUTH_DOMAIN"; - } else if (!Cryptpad.isLoggedIn()) { + } else if (!LocalStore.isLoggedIn()) { ret.error = "NOT_LOGGED_IN"; } else { - var proxy = Cryptpad.getStore().getProxy().proxy; var sig = signMsg(data.data, proxy.edPrivate); ret.res = { uname: proxy.login_name, diff --git a/www/bounce/main.js b/www/bounce/main.js index b8814e943..edb4f029c 100644 --- a/www/bounce/main.js +++ b/www/bounce/main.js @@ -1,10 +1,14 @@ -define([], function () { - if (window.localStorage && window.localStorage.FS_hash) { +define(['/api/config'], function (ApiConfig) { + if (ApiConfig.httpSafeOrigin !== window.location.origin) { window.alert('The bounce application must only be used from the sandbox domain, ' + 'please report this issue on https://github.com/xwiki-labs/cryptpad'); return; } var bounceTo = decodeURIComponent(window.location.hash.slice(1)); - if (!bounceTo) { return; } + if (!bounceTo) { + window.alert('The bounce application must only be used with a valid href to visit'); + return; + } + window.opener = null; window.location.href = bounceTo; -}); \ No newline at end of file +}); diff --git a/www/code/app-code.less b/www/code/app-code.less new file mode 100644 index 000000000..f4153e95d --- /dev/null +++ b/www/code/app-code.less @@ -0,0 +1,126 @@ +@import (once) "../../customize/src/less2/include/browser.less"; +@import (once) "../../customize/src/less2/include/markdown.less"; +@import (once) "../../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 { + display: flex; + flex-flow: column; + max-height: 100%; + min-height: auto; + + #cp-app-code-container { + display: inline-flex; + flex-flow: column; + height: 100%; + min-height: 100%; + width: 50%; + min-width: 20%; + max-width: 80%; + resize: horizontal; + overflow: hidden; + &.cp-app-code-fullpage { + max-width: 100%; + resize: none; + flex: 1; + } + + } + .CodeMirror { + flex: 1; + font-size: initial; + width: 100%; + } + .CodeMirror-focused .cm-matchhighlight { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==); + background-position: bottom; + background-repeat: repeat-x; + } + #cp-app-code-editor { + flex: 1; + display: flex; + flex-flow: row; + height: 100%; + overflow: hidden; + &.cp-app-code-present { + #cp-app-code-container { display: none; } + #cp-app-code-preview { border: 0; } + } + } + #cp-app-code-preview { + flex: 1; + padding: 5px 20px; + overflow: auto; + display: inline-block; + height: 100%; + border-left: 1px solid black; + box-sizing: border-box; + font-family: Calibri,Ubuntu,sans-serif; + word-wrap: break-word; + position: relative; + media-tag { + * { + max-width:100%; + } + iframe[type="application/pdf"] { + max-height:50vh; + } + } + .markdown_main(); + .cp-app-code-preview-empty { + display: none; + } + &.cp-app-code-preview-isempty { + display: flex; + align-items: center; + justify-content: center; + #cp-app-code-preview-content { + display: none; + } + .cp-app-code-preview-empty { + //flex: 1 1 auto; + max-height: 100%; + max-width: 100%; + display: block; + opacity: 0.2; + } + } + } + + #cp-app-code-preview-content { + max-width: 40vw; + margin: 1em auto; + + .markdown_preformatted-code; + .markdown_gfm-table(black); + } + + .cp-splitter { + position: absolute; + height: 100%; + width: 8px; + top: 0; + left: 0; + + cursor: col-resize; + } + + @media (max-width: @browser_media-medium-screen) { + #cp-app-code-container { + flex: 1; + max-width: 100%; + resize: none; + } + #cp-app-code-preview { + display: none !important; + } + } +} + diff --git a/www/code/code.less b/www/code/code.less deleted file mode 100644 index 36883f377..000000000 --- a/www/code/code.less +++ /dev/null @@ -1,102 +0,0 @@ -@import "/customize/src/less/variables.less"; -@import "/customize/src/less/mixins.less"; -@import "/common/markdown.less"; -@import "/common/file-dialog.less"; - -html, body{ - height: 100%; - width: 100%; - padding: 0px; - margin: 0px; - overflow: hidden; - box-sizing: border-box; - position: relative; -} -body { - display: flex; - flex-flow: column; - max-height: 100%; - min-height: auto; -} - -@slideTime: 500ms; -.CodeMirror { - display: inline-block; - height: 100%; - width: 50%; - &.transition { - transition: width @slideTime, min-width @slideTime, max-width @slideTime; - } - min-width: 20%; - max-width: 80%; - resize: horizontal; - font-size: initial; -} -.CodeMirror.fullPage { - //min-width: 100%; - max-width: 100%; - resize: none; - flex: 1; -} -.CodeMirror-focused .cm-matchhighlight { - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==); - background-position: bottom; - background-repeat: repeat-x; -} -#editorContainer { - flex: 1; - display: flex; - flex-flow: row; - height: 100%; - overflow: hidden; -} -#previewContainer { - flex: 1; - padding: 5px 20px; - overflow: auto; - display: inline-block; - height: 100%; - border-left: 1px solid black; - box-sizing: border-box; - font-family: Calibri,Ubuntu,sans-serif; - word-wrap: break-word; - position: relative; - media-tag { - * { - max-width:100%; - } - iframe[type="application/pdf"] { - max-height:50vh; - } - } -} - -#preview { - max-width: 40vw; - margin: 1em auto; - - .markdown_preformatted-code; - .markdown_gfm-table(black); -} - -.cp-splitter { - position: absolute; - height: 100%; - width: 8px; - top: 0; - left: 0; - - cursor: col-resize; -} - -@media (max-width: @media-medium-screen) { - .CodeMirror { - flex: 1; - max-width: 100%; - resize: none; - } - #previewContainer { - display: none !important; - } -} - diff --git a/www/code/index.html b/www/code/index.html index 0f8824bd4..7ea06cdfc 100644 --- a/www/code/index.html +++ b/www/code/index.html @@ -1,41 +1,38 @@ - + CryptPad - + -
    - -
    +'; + }; + var embed = h('div.cp-share-modal', [ + h('h3', Messages.viewEmbedTitle), + h('p', Messages.viewEmbedTag), + h('br'), + UI.dialog.selectable(getEmbedValue()) + ]); + var embedButtons = [{ + name: Messages.cancel, + onClick: function () {}, + keys: [27] + }, { + className: 'primary', + name: Messages.share_linkCopy, + onClick: function () { + var v = getEmbedValue(); + var success = Clipboard.copy(v); + if (success) { UI.log(Messages.shareSuccess); } + }, + keys: [13] + }]; + var frameEmbed = UI.dialog.customModal(embed, { buttons: embedButtons}); + + // Create modal + var tabs = [{ + title: Messages.share_linkCategory, + content: frameLink + }, { + title: Messages.share_embedCategory, + content: frameEmbed + }]; + if (typeof(AppConfig.customizeShareOptions) === 'function') { + AppConfig.customizeShareOptions(hashes, tabs, { + type: 'DEFAULT', + origin: origin, + pathname: pathname + }); + } + common.getAttribute(['general', 'share'], function (err, val) { + val = val || {}; + if (val.edit === false) { + $(link).find('#cp-share-editable-false').prop('checked', true); + } + else { $(link).find('#cp-share-editable-true').prop('checked', true); } + if (val.embed) { $(link).find('#cp-share-embed').prop('checked', true); } + if (val.present) { $(link).find('#cp-share-present').prop('checked', true); } + $(link).find('#cp-share-link-preview').val(getLinkValue(val)); + }); + common.getMetadataMgr().onChange(function () { + hashes = common.getMetadataMgr().getPrivateData().availableHashes; + $(link).find('#cp-share-link-preview').val(getLinkValue()); + }); + return tabs; + }; + UIElements.createFileShareModal = function (config) { + var origin = config.origin; + var pathname = config.pathname; + var hashes = config.hashes; + var common = config.common; + + if (!hashes.fileHash) { throw new Error("You must provide a file hash"); } + var url = origin + pathname + '#' + hashes.fileHash; + + + // Share link tab + var link = h('div.cp-share-modal', [ + UI.dialog.selectable('', { id: 'cp-share-link-preview' }) + ]); + var getLinkValue = function () { return url; }; + $(link).find('#cp-share-link-preview').val(getLinkValue()); + var linkButtons = [{ + name: Messages.cancel, + onClick: function () {}, + keys: [27] + }, { + className: 'primary', + name: Messages.share_linkCopy, + onClick: function () { + var v = getLinkValue(); + var success = Clipboard.copy(v); + if (success) { UI.log(Messages.shareSuccess); } + }, + keys: [13] + }]; + var frameLink = UI.dialog.customModal(link, {buttons: linkButtons}); + + // Embed tab + var embed = h('div.cp-share-modal', [ + h('h3', Messages.fileEmbedTitle), + h('p', Messages.fileEmbedScript), + h('br'), + UI.dialog.selectable(common.getMediatagScript()), + h('p', Messages.fileEmbedTag), + h('br'), + UI.dialog.selectable(common.getMediatagFromHref(url)), + ]); + var embedButtons = [{ + name: Messages.cancel, + onClick: function () {}, + keys: [27] + }, { + className: 'primary', + name: Messages.share_mediatagCopy, + onClick: function () { + var v = common.getMediatagFromHref(url); + var success = Clipboard.copy(v); + if (success) { UI.log(Messages.shareSuccess); } + }, + keys: [13] + }]; + var frameEmbed = UI.dialog.customModal(embed, { buttons: embedButtons}); + + // Create modal + var tabs = [{ + title: Messages.share_linkCategory, + content: frameLink + }, { + title: Messages.share_embedCategory, + content: frameEmbed + }]; + if (typeof(AppConfig.customizeShareOptions) === 'function') { + AppConfig.customizeShareOptions(hashes, tabs, { + type: 'FILE', + origin: origin, + pathname: pathname + }); + } + return tabs; + }; + + UIElements.createButton = function (common, type, rightside, data, callback) { + var AppConfig = common.getAppConfig(); + var button; + var sframeChan = common.getSframeChannel(); + var appType = (common.getMetadataMgr().getMetadata().type || 'pad').toUpperCase(); + switch (type) { + case 'export': + button = $('