From c1e0316d4335aa52c5b33767aec43d49b8649f99 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 12 Apr 2017 18:54:22 +0200 Subject: [PATCH 001/306] Refactor fileObject --- www/common/cryptpad-common.js | 5 +- www/common/fsStore.js | 16 +- www/common/userObject.js | 839 ++++++++++++++++++++++++++++++++++ www/drive/main.js | 52 +-- 4 files changed, 877 insertions(+), 35 deletions(-) create mode 100644 www/common/userObject.js diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 9eedd1106..f815b8e4f 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -506,8 +506,9 @@ load pinpad dynamically only after you know that it will be needed */ }); return templates; }; - var addTemplate = common.addTemplate = function (href) { - getStore().addTemplate(href); + var addTemplate = common.addTemplate = function (data) { + getStore().pushData(data); + getStore().addPad(data.href, ['template']); }; var isTemplate = common.isTemplate = function (href) { diff --git a/www/common/fsStore.js b/www/common/fsStore.js index 1974aa27d..e2b283f4b 100644 --- a/www/common/fsStore.js +++ b/www/common/fsStore.js @@ -90,20 +90,22 @@ define([ ret.pushData = filesOp.pushData; ret.addPad = function (href, path, name) { - filesOp.addPad(href, path, name); + filesOp.add(href, path, name); }; ret.forgetPad = function (href, cb) { - filesOp.forgetPad(href); + filesOp.forget(href); cb(); }; - ret.addTemplate = function (href) { - filesOp.addTemplate(href); - }; - ret.listTemplates = function () { - return filesOp.listTemplates(); + var templateFiles = filesOp.getFiles(['template']); + var res = []; + templateFiles.forEach(function (f) { + var data = filesOp.getFileData(f); + res.push(JSON.parse(JSON.stringify(data))); + }); + return res; }; ret.getProxy = function () { diff --git a/www/common/userObject.js b/www/common/userObject.js new file mode 100644 index 000000000..7e7f7b292 --- /dev/null +++ b/www/common/userObject.js @@ -0,0 +1,839 @@ +define([ + '/bower_components/jquery/dist/jquery.min.js', +], function () { + var $ = window.jQuery; + var module = {}; + + var ROOT = module.ROOT = "root"; + var UNSORTED = module.UNSORTED = "unsorted"; + var TRASH = module.TRASH = "trash"; + var TEMPLATE = module.TEMPLATE = "template"; + + var init = module.init = function (files, config) { + var Cryptpad = config.Cryptpad; + var Messages = Cryptpad.Messages; + + var FILES_DATA = Cryptpad.storageKey; + var NEW_FOLDER_NAME = Messages.fm_newFolder; + var NEW_FILE_NAME = Messages.fm_newFile; + + // Logging + var DEBUG = config.DEBUG || false; + var logging = function () { + console.log.apply(console, arguments); + }; + var log = config.log || logging; + var logError = config.logError || logging; + var debug = config.debug || logging; + var error = exp.error = function() { + exp.fixFiles(); + console.error.apply(console, arguments); + }; + + // TODO: workgroup + var workgroup = config.workgroup; + + var exp = {}; + + + /* + * UTILS + */ + + var getStructure = exp.getStructure = function () { + var a = {}; + a[ROOT] = {}; + a[UNSORTED] = []; + a[TRASH] = {}; + a[FILES_DATA] = []; + a[TEMPLATE] = []; + return a; + }; + + var compareFiles = function (fileA, fileB) { return fileA === fileB; }; + + var isFile = exp.isFile = function (element) { + return typeof(element) === "string"; + }; + + var isReadOnlyFile = exp.isReadOnlyFile = function (element) { + if (!isFile(element)) { return false; } + var parsed = Cryptpad.parsePadUrl(element); + if (!parsed) { return false; } + var hash = parsed.hash; + var pHash = Cryptpad.parseHash(hash); + if (pHash && !pHash.mode) { return; } + return pHash && pHash.mode === 'view'; + }; + + var isFolder = exp.isFolder = function (element) { + return typeof(element) !== "string"; + }; + var isFolderEmpty = exp.isFolderEmpty = function (element) { + if (typeof(element) !== "object") { return false; } + return Object.keys(element).length === 0; + }; + + var hasSubfolder = exp.hasSubfolder = function (element, trashRoot) { + if (typeof(element) !== "object") { return false; } + var subfolder = 0; + var addSubfolder = function (el, idx) { + subfolder += isFolder(el.element) ? 1 : 0; + }; + for (var f in element) { + if (trashRoot) { + if ($.isArray(element[f])) { + element[f].forEach(addSubfolder); + } + } else { + subfolder += isFolder(element[f]) ? 1 : 0; + } + } + return subfolder; + }; + + var hasFile = exp.hasFile = function (element, trashRoot) { + if (typeof(element) !== "object") { return false; } + var file = 0; + var addFile = function (el, idx) { + file += isFile(el.element) ? 1 : 0; + }; + for (var f in element) { + if (trashRoot) { + if ($.isArray(element[f])) { + element[f].forEach(addFile); + } + } else { + file += isFile(element[f]) ? 1 : 0; + } + } + return file; + }; + + // Get data from AllFiles (Cryptpad_RECENTPADS) + var getFileData = exp.getFileData = function (file) { + if (!file) { return; } + var res; + files[FILES_DATA].some(function(arr) { + var href = arr.href; + if (href === file) { + res = arr; + return true; + } + return false; + }); + return res; + }; + + // Data from filesData + var getTitle = exp.getTitle = function (href) { + if (workgroup) { debug("No titles in workgroups"); return; } + var data = getFileData(href); + if (!href || !data) { + error("getTitle called with a non-existing href: ", href); + return; + } + return data.title; + }; + + + // PATHS + + var comparePath = exp.comparePath = function (a, b) { + if (!a || !b || !$.isArray(a) || !$.isArray(b)) { return false; } + if (a.length !== b.length) { return false; } + var result = true; + var i = a.length - 1; + while (result && i >= 0) { + result = a[i] === b[i]; + i--; + } + return result; + }; + + var isSubpath = exp.isSubpath = function (path, parentPaths) { + var pathA = parentPath.slice(); + var pathB = path.slice(0, pathA.length); + return comparePath(pathA, pathB); + }; + + var isPathIn = function (path, categories) { + return categories.some(function (c) { + return Array.isArray(path) && path[0] === c; + }); + }; + + var isInTrashRoot = exp.isInTrashRoot = function (path) { + return path[0] === TRASH && path.length === 4; + }; + + + // FIND + + var compareFiles = function (fileA, fileB) { return fileA === fileB; }; + + var findElement = function (root, pathInput) { + if (!pathInput) { + error("Invalid path:\n", pathInput, "\nin root\n", root); + return; + } + if (pathInput.length === 0) { return root; } + var path = pathInput.slice(); + var key = path.shift(); + if (typeof root[key] === "undefined") { + debug("Unable to find the key '" + key + "' in the root object provided:", root); + return; + } + return findElement(root[key], path); + }; + + var find = exp.find = function (path) { + return findElement(files, path); + }; + + + // GET FILES + + var getFilesRecursively = function (root, arr) { + for (var e in root) { + if (isFile(root[e])) { + if(arr.indexOf(root[e]) === -1) { arr.push(root[e]); } + } else { + getFilesRecursively(root[e], arr); + } + } + }; + var _getFiles = {}; + _getFiles['array'] = function (cat) { + if (!files[cat]) { + files[cat] = []; + } + return files[cat].slice(); + }; + _getFiles[UNSORTED] = function () { + return _getFiles['array'](UNSORTED); + }; + _getFiles[TEMPLATE] = function () { + return _getFiles['array'](TEMPLATE); + }; + _getFiles['hrefArray'] = function () { + var ret = []; + ret = ret.concat(_getFiles[UNSORTED]); + ret = ret.concat(_getFiles[TEMPLATE]); + return Cryptpad.deduplicateString(ret); + }; + _getFiles[ROOT] = function () { + var ret = []; + getFilesRecursively(files[ROOT], ret); + return ret; + }; + _getFiles[TRASH] = function () { + var root = files[TRASH]; + var ret = []; + var addFiles = function (el, idx) { + if (isFile(el.element)) { + if(ret.indexOf(el.element) === -1) { ret.push(el.element); } + } else { + getFilesRecursively(el.element, ret); + } + }; + for (var e in root) { + if (!$.isArray(root[e])) { + error("Trash contains a non-array element"); + return; + } + root[e].forEach(addFiles); + } + return ret; + }; + _getFiles[FILES_DATA] = function () { + var ret = []; + files[FILES_DATA].forEach(function (el) { + if (el.href && ret.indexOf(el.href) === -1) { + ret.push(el.href); + } + }); + return ret; + }; + var getFiles = function (categories) { + var ret = []; + categories.forEach(function (c) { + if (typeof _getFiles[c] === "function") { + ret = ret.concat(_getFiles[c]); + } + }); + return Cryptpad.deduplicateString(ret); + }; + + // SEARCH + var _findFileInRoot = function (path, href) { + if (!isPathIn([ROOT, TRASH])) { return []; } + var paths = []; + var root = find(path); + var addPaths = function (p) { + if (paths.indexOf(p) === -1) { + paths.push(p); + } + }; + + if (isFile(root)) { + if (compareFiles(href, root)) { + if (paths.indexOf(path) === -1) { + paths.push(path); + } + } + return paths; + } + for (var e in root) { + var nPath = path.slice(); + nPath.push(e); + _findFileInRoot(nPath, href).forEach(addPaths); + } + + return paths; + }; + var _findFileInHrefArray = function (rootName, href) { + var unsorted = files[rootName].slice(); + var ret = []; + var i = -1; + while ((i = unsorted.indexOf(href, i+1)) !== -1){ + ret.push([rootName, i]); + } + return ret; + }; + var _findFileInTrash = function (path, href) { + var root = find(path); + var paths = []; + var addPaths = function (p) { + if (paths.indexOf(p) === -1) { + paths.push(p); + } + }; + if (path.length === 1 && typeof(root) === 'object') { + Object.keys(root).forEach(function (key) { + var arr = root[key]; + if (!Array.isArray(arr)) { return; } + var nPath = path.slice(); + nPath.push(key); + _findFileInTrash(nPath, href).forEach(addPaths); + }); + } + if (path.length === 2) { + if (!Array.isArray(root)) { return []; } + root.forEach(function (el, i) { + var nPath = path.slice(); + nPath.push(i); + nPath.push('element'); + if (isFile(el.element)) { + if (compareFiles(href, el.element)) { + addPaths(nPath); + } + return; + } + _findFileInTrash(nPath, href).forEach(addPaths); + }); + } + if (path.length >= 4) { + _findFileInRoot(path, href).forEach(addPaths); + } + return paths; + }; + var findFile = function (href) { + var rootpaths = _findFileInRoot([ROOT], href); + var unsortedpaths = _findFileInHrefArray(UNSORTED, href); + var templatepaths = _findFileInHrefArray(TEMPLATE, href); + var trashpaths = _findFileInTrash([TRASH], href); + return rootpaths.concat(unsortedpaths, templatepaths, trashpaths); + }; + var search = exp.search = function (value) { + if (typeof(value) !== "string") { return []; } + var res = []; + // Search in ROOT + var findIn = function (root) { + Object.keys(root).forEach(function (k) { + if (isFile(root[k])) { + if (k.toLowerCase().indexOf(value.toLowerCase()) !== -1) { + res.push(root[k]); + } + return; + } + findIn(root[k]); + }); + }; + findIn(files[ROOT]); + // Search in TRASH + var trash = files[TRASH]; + Object.keys(trash).forEach(function (k) { + if (k.toLowerCase().indexOf(value.toLowerCase()) !== -1) { + trash[k].forEach(function (el) { + if (isFile(el.element)) { + res.push(el.element); + } + }); + } + trash[k].forEach(function (el) { + if (isFolder(el.element)) { + findIn(el.element); + } + }); + }); + + // Search title + var allFilesList = files[FILES_DATA].slice(); + allFilesList.forEach(function (t) { + if (t.title && t.title.toLowerCase().indexOf(value.toLowerCase()) !== -1) { + res.push(t.href); + } + }); + + // Search Href + var href = Cryptpad.getRelativeHref(value); + if (href) { + res.push(href); + } + + res = Cryptpad.deduplicateString(res); + + var ret = []; + res.forEach(function (l) { + var paths = findFile(l); + ret.push({ + paths: findFile(l), + data: exp.getFileData(l) + }); + }); + return ret; + }; + + /** + * OPERATIONS + */ + + var getAvailableName = function (parentEl, name) { + if (typeof(parentEl[name]) === "undefined") { return name; } + var newName = name; + var i = 1; + while (typeof(parentEl[newName]) !== "undefined") { + newName = name + "_" + i; + i++; + } + return newName; + }; + + // FILES DATA + var pushFileData = exp.pushData = function (data) { + Cryptpad.pinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e, hash) { + console.log(hash); + }); + files[FILES_DATA].push(data); + }; + var spliceFileData = exp.removeData = function (idx) { + var data = files[FILES_DATA][idx]; + if (typeof data === "object") { + Cryptpad.unpinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e, hash) { + console.log(hash); + }); + } + files[FILES_DATA].splice(idx, 1); + }; + + // MOVE + var pushToTrash = function (name, element, path) { + var trash = files[TRASH]; + if (typeof(trash[name]) === "undefined") { trash[name] = []; } + var trashArray = trash[name]; + var trashElement = { + element: element, + path: path + }; + trashArray.push(trashElement); + }; + var copyElement = function (elementPath, newParentPath) { + if (comparePath(elementPath, newParentPath)) { return; } // Nothing to do... + var element = find(elementPath); + var newParent = find(newParentPath); + + // Never move a folder in one of its children + if (isSubpath(newParentPath, elementPath)) { + log(Messages.fo_moveFolderToChildError); + return; + } + + // Move to Trash + if (isPathIn(newParentPath, [TRASH])) { + if (!elementPath || elementPath.length < 2 || elementPath[0] === TRASH) { + debug("Can't move an element from the trash to the trash: ", path); + return; + } + var key = elementPath[elementPath.length - 1]; + var name = isPathIn(elementPath, ['hrefArray']) ? getTitle(element) : key; + var parentPath = elementPath.slice(); + parentPath.pop(); + pushToTrash(name, element, parentPath); + return true; + } + // Move to hrefArray + if (isPathIn(newParentPath, ['hrefArray'])) { + if (isFolder(element)) { + log(Messages.fo_moveUnsortedError); + return; + } else { + if (elementPath[0] === newParentPath[0]) { return; } + var fileRoot = newParentPath[0]; + if (files[fileRoot].indexOf(element) === -1) { + files[fileRoot].push(element); + } + return true; + } + } + // Move to root + var name; + if (isPathIn(elementPath, ['hrefArray'])) { + name = getTitle(element); + } else if (isInTrashRoot(elementPath)) { + // Element from the trash root: elementPath = [TRASH, "{dirName}", 0, 'element'] + name = elementPath[1]; + } else { + name = elementPath[elementPath.length-1]; + } + var newName = !isPathInRoot(elementPath) ? getAvailableName(newParent, name) : name; + + if (typeof(newParent[newName]) !== "undefined") { + log(Messages.fo_unavailableName); + return; + } + newParent[newName] = element; + return true; + }; + var move = exp.move = function (paths, newPath, cb) { + // Copy the elements to their new location + var toRemove = []; + paths.forEach(function (p) { + var parentPath = p.slice(); + parentPath.pop(); + if (comparePath(parentPath, newPath)) { return; } + copyElement(p, newPath); + toRemove.push(p); + }); + exp.delete(toRemove, cb); + }; + + // ADD + var add = exp.add = function (href, path, name, cb) { + if (!href) { return; } + var newPath = path, parentEl; + if (path && !Array.isArray(path)) { + newPath = decodeURIComponent(path).split(','); + } + // Add to href array + if (path && isPathIn(newPath, ['hrefArray'])) { + parentEl = find(newPath); + parentEl.push(href); + return; + } + // Add to root + if (path && isPathIn(newPath, [ROOT]) && name) { + parentEl = find(newPath); + if (parentEl) { + var newName = getAvailableName(parentEl, name); + parentEl[newName] = href; + return; + } + } + // No path: push to unsorted + var filesList = getFiles([ROOT, TRASH, 'hrefArray']); + if (filesList.indexOf(href) === -1) { files[UNSORTED].push(href); } + + if (typeof cb === "function") { cb(); } + }; + var addFile = exp.addFile = function (filePath, name, type, cb) { + var parentEl = findElement(files, filePath); + var fileName = getAvailableName(parentEl, name || NEW_FILE_NAME); + var href = '/' + type + '/#' + Cryptpad.createRandomHash(); + parentEl[fileName] = href; + + pushFileData({ + href: href, + title: fileName, + atime: +new Date(), + ctime: +new Date() + }); + + var newPath = filePath.slice(); + newPath.push(fileName); + cb({ + newPath: newPath + }); + }; + var addFolder = exp.addFolder = function (folderPath, name, cb) { + var parentEl = find(folderPath); + var folderName = getAvailableName(parentEl, name || NEW_FOLDER_NAME); + parentEl[folderName] = {}; + var newPath = folderPath.slice(); + newPath.push(folderName); + cb({ + newPath: newPath + }); + }; + + // FORGET (move with href not path) + var forget = exp.forget = function (href) { + var paths = findFile(href); + move(paths, [TRASH]); + }; + + // DELETE + // Permanently delete multiple files at once using a list of paths + // NOTE: We have to be careful when removing elements from arrays (trash root, unsorted or template) + var removePadAttribute = function (f) { + Object.keys(files).forEach(function (key) { + var hash = f.indexOf('#') !== -1 ? f.slice(f.indexOf('#') + 1) : null; + if (hash && key.indexOf(hash) === 0) { + debug("Deleting pad attribute in the realtime object"); + files[key] = undefined; + delete files[key]; + } + }); + }; + var checkDeletedFiles = function () { + // Nothing in FILES_DATA for workgroups + if (workgroup) { return; } + + var filesList = getFiles[ROOT, 'hrefArray', TRASH]; + var toRemove = []; + files[FILES_DATA].forEach(function (arr) { + var f = arr.href; + if (filesList.indexOf(f) === -1) { + toRemove.push(arr); + } + }); + toRemove.forEach(function (f) { + var idx = files[FILES_DATA].indexOf(f); + if (idx !== -1) { + debug("Removing", f, "from filesData"); + spliceFileData(idx); + removePadAttribute(f.href); + } + }); + }; + var deleteHrefs = function (hrefs) { + hrefs.forEach(function (obj) { + var idx = files[obj.root].indexOf(obj.href); + files[obj.root].splice(idx, 1); + }); + }; + var deleteMultipleTrashRoot = function (roots) { + roots.forEach(function (obj) { + var idx = files[TRASH][obj.name].indexOf(obj.el); + files[TRASH][obj.name].splice(idx, 1); + }); + }; + var deleteMultiplePermanently = function (paths) { + var hrefPaths = paths.filter(isPathInHrefArray); + var rootPaths = paths.filter(isPathInRoot); + var trashPaths = paths.filter(isPathInTrash); + + var hrefs = []; + hrefPaths.forEach(function (path) { + var href = find(path); + hrefs.push({ + root: path[0], + href: href + }); + }); + deleteHrefs(hrefs); + + rootPaths.forEach(function (path) { + var parentPath = path.slice(); + var key = parentPath.pop(); + var parentEl = find(parentPath); + parentEl[key] = undefined; + delete parentEl[key]; + }); + + var trashRoot = []; + trashPaths.forEach(function (path) { + var parentPath = path.slice(); + var key = parentPath.pop(); + var parentEl = find(parentPath); + // Trash root: we have array here, we can't just splice with the path otherwise we might break the path + // of another element in the loop + if (path.length === 4) { + trashRoot.push({ + name: path[1], + el: parentEl + }); + return; + } + // Trash but not root: it's just a tree so remove the key + parentEl[key] = undefined; + delete parentEl[key]; + }); + deleteMultipleTrashRoot(trashRoot); + + checkDeletedFiles(); + }; + var deletePath = exp.delete = function (paths, cb) { + deletePathsPermanently(paths); + if (typeof cb === "function") { cb(); } + }; + var emptyTrash = exp.emptyTrash = function (cb) { + files[TRASH] = {}; + checkDeletedFiles(); + if(cb) { cb(); } + }; + + // RENAME + var rename = exp.rename = function (path, newName, cb) { + if (path.length <= 1) { + logError('Renaming `root` is forbidden'); + return; + } + if (!newName || newName.trim() === "") { return; } + // Copy the element path and remove the last value to have the parent path and the old name + var element = find(path); + var parentPath = path.slice(); + var oldName = parentPath.pop(); + if (oldName === newName) { + return; + } + var parentEl = find(parentPath); + if (typeof(parentEl[newName]) !== "undefined") { + log(Messages.fo_existingNameError); + return; + } + parentEl[newName] = element; + parentEl[oldName] = undefined; + delete parentEl[oldName]; + cb(); + }; + + /** + * INTEGRITY CHECK + */ + + var fixFiles = exp.fixFiles = function () { + // Explore the tree and check that everything is correct: + // * 'root', 'trash', 'unsorted' and 'filesData' exist and are objects + // * ROOT: Folders are objects, files are href + // * TRASH: Trash root contains only arrays, each element of the array is an object {element:.., path:..} + // * FILES_DATA: - Data (title, cdate, adte) are stored in filesData. filesData contains only href keys linking to object with title, cdate, adate. + // - Dates (adate, cdate) can be parsed/formatted + // - All files in filesData should be either in 'root', 'trash' or 'unsorted'. If that's not the case, copy the fily to 'unsorted' + // * UNSORTED: Contains only files (href), and does not contains files that are in ROOT + debug("Cleaning file system..."); + + var before = JSON.stringify(files); + + var fixRoot = function (elem) { + if (typeof(files[ROOT]) !== "object") { debug("ROOT was not an object"); files[ROOT] = {}; } + var element = elem || files[ROOT]; + for (var el in element) { + if (!isFile(element[el]) && !isFolder(element[el])) { + debug("An element in ROOT was not a folder nor a file. ", element[el]); + element[el] = undefined; + delete element[el]; + } else if (isFolder(element[el])) { + fixRoot(element[el]); + } + } + }; + var fixTrashRoot = function () { + if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; } + var tr = files[TRASH]; + var toClean; + var addToClean = function (obj, idx) { + if (typeof(obj) !== "object") { toClean.push(idx); return; } + if (!isFile(obj.element) && !isFolder(obj.element)) { toClean.push(idx); return; } + if (!$.isArray(obj.path)) { toClean.push(idx); return; } + }; + for (var el in tr) { + if (!$.isArray(tr[el])) { + debug("An element in TRASH root is not an array. ", tr[el]); + tr[el] = undefined; + delete tr[el]; + } else { + toClean = []; + tr[el].forEach(addToClean); + for (var i = toClean.length-1; i>=0; i--) { + tr[el].splice(toClean[i], 1); + } + } + } + }; + var fixUnsorted = function () { + if (!Array.isArray(files[UNSORTED])) { debug("UNSORTED was not an array"); files[UNSORTED] = []; } + files[UNSORTED] = Cryptpad.deduplicateString(files[UNSORTED].slice()); + var us = files[UNSORTED]; + var rootFiles = getFiles([ROOT, TEMPLATE]).slice(); + var toClean = []; + us.forEach(function (el, idx) { + if (!isFile(el) || rootFiles.indexOf(el) !== -1) { + toClean.push(idx); + } + }); + toClean.forEach(function (idx) { + us.splice(idx, 1); + }); + }; + var fixTemplate = function () { + if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; } + files[TEMPLATE] = Cryptpad.deduplicateString(files[TEMPLATE].slice()); + var us = files[TEMPLATE]; + var rootFiles = getFiles([ROOT, UNSORTED]).slice(); + var toClean = []; + us.forEach(function (el, idx) { + if (!isFile(el) || rootFiles.indexOf(el) !== -1) { + toClean.push(idx); + } + }); + toClean.forEach(function (idx) { + us.splice(idx, 1); + }); + }; + var fixFilesData = function () { + if (!$.isArray(files[FILES_DATA])) { debug("FILES_DATA was not an array"); files[FILES_DATA] = []; } + var fd = files[FILES_DATA]; + var rootFiles = getFiles([ROOT, TRASH, 'hrefArray']); + var toClean = []; + fd.forEach(function (el, idx) { + if (!el || typeof(el) !== "object") { + debug("An element in filesData was not an object.", el); + toClean.push(el); + return; + } + if (rootFiles.indexOf(el.href) === -1) { + debug("An element in filesData was not in ROOT, UNSORTED or TRASH.", el); + files[UNSORTED].push(el.href); + return; + } + }); + toClean.forEach(function (el) { + var idx = fd.indexOf(el); + if (idx !== -1) { + spliceFileData(idx); + } + }); + }; + + fixRoot(); + fixTrashRoot(); + if (!workgroup) { + fixUnsorted(); + fixTemplate(); + fixFilesData(); + } + + if (JSON.stringify(files) !== before) { + debug("Your file system was corrupted. It has been cleaned so that the pads you visit can be stored safely"); + return; + } + debug("File system was clean"); + }; + + return exp; + }; + + return module; +}); + diff --git a/www/drive/main.js b/www/drive/main.js index 474ea4b06..f4a8d05a8 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -661,7 +661,7 @@ define([ } // Unsorted or template if (filesOp.isPathInUnsorted(path) || filesOp.isPathInTemplate(path)) { - var file = filesOp.findElement(files, path); + var file = filesOp.find(path); if (filesOp.isFile(file) && filesOp.getTitle(file)) { return filesOp.getTitle(file); } @@ -688,7 +688,7 @@ define([ var msg = Messages._getKey('fm_removeSeveralDialog', [paths.length]); if (paths.length === 1) { var path = paths[0]; - var name = path[0] === UNSORTED ? filesOp.getTitle(filesOp.findElement(files, path)) : path[path.length - 1]; + var name = path[0] === UNSORTED ? filesOp.getTitle(filesOp.find(path)) : path[path.length - 1]; msg = Messages._getKey('fm_removeDialog', [name]); } Cryptpad.confirm(msg, function (res) { @@ -707,7 +707,7 @@ define([ $selected.each(function (idx, elmt) { var ePath = $(elmt).data('path'); if (ePath) { - var val = filesOp.findElement(files, ePath); + var val = filesOp.find(ePath); if (!val) { return; } // Error? A ".selected" element in not in the object paths.push({ path: ePath, @@ -721,7 +721,7 @@ define([ } else { removeSelected(); $element.addClass('selected'); - var val = filesOp.findElement(files, path); + var val = filesOp.find(path); if (!val) { return; } // The element in not in the object paths = [{ path: path, @@ -749,7 +749,7 @@ define([ var movedPaths = []; var importedElements = []; oldPaths.forEach(function (p) { - var el = filesOp.findElement(files, p.path); + var el = filesOp.find(p.path); if (el && (stringify(el) === stringify(p.value.el) || !p.value || !p.value.el)) { movedPaths.push(p.path); } else { @@ -882,7 +882,7 @@ define([ newPath.push(key); } - var element = filesOp.findElement(files, newPath); + var element = filesOp.find(newPath); var $icon = !isFolder ? getFileIcon(element) : undefined; var ro = filesOp.isReadOnlyFile(element); // ro undefined mens it's an old hash which doesn't support read-only @@ -1119,17 +1119,17 @@ define([ refresh(); }; $block.find('a.newFolder').click(function () { - filesOp.createNewFolder(currentPath, null, onCreated); + filesOp.addFolder(currentPath, null, onCreated); // TODO START HERE }); $block.find('a.newdoc').click(function (e) { var type = $(this).attr('data-type') || 'pad'; var name = Cryptpad.getDefaultName({type: type}); - filesOp.createNewFile(currentPath, name, type, onCreated); + filesOp.addFile(currentPath, name, type, onCreated); }); } else { $block.find('a.newdoc').click(function (e) { var type = $(this).attr('data-type') || 'pad'; - sessionStorage[Cryptpad.newPadPathKey] = filesOp.isPathInTrash(currentPath) ? '' : currentPath; + sessionStorage[Cryptpad.newPadPathKey] = filesOp.isPathIn(currentPath, [TRASH]) ? '' : currentPath; window.open('/' + type + '/'); }); } @@ -1251,11 +1251,11 @@ define([ }; var allFilesSorted = function () { - return filesOp.getUnsortedFiles().length === 0; + return filesOp.getFiles([UNSORTED]).length === 0; }; var sortElements = function (folder, path, oldkeys, prop, asc, useHref, useData) { - var root = filesOp.findElement(files, path); + var root = filesOp.find(path); var test = folder ? filesOp.isFolder : filesOp.isFile; var keys; if (!useData) { @@ -1478,7 +1478,7 @@ define([ var $atime = $('', {'class': 'col2'}).text(new Date(r.data.atime).toLocaleString()); var $ctimeName = $('', {'class': 'label2'}).text(Messages.fm_creation); var $ctime = $('', {'class': 'col2'}).text(new Date(r.data.ctime).toLocaleString()); - if (filesOp.isPathInHrefArray(path)) { + if (filesOp.isPathIn(path, ['hrefArray'])) { path.pop(); path.push(r.data.title); } @@ -1522,14 +1522,14 @@ define([ if (!path || path.length === 0) { path = [ROOT]; } - var isInRoot = filesOp.isPathInRoot(path); + var isInRoot = filesOp.isPathIn(path, [ROOT]); var isTrashRoot = filesOp.comparePath(path, [TRASH]); var isUnsorted = filesOp.comparePath(path, [UNSORTED]); var isTemplate = filesOp.comparePath(path, [TEMPLATE]); var isAllFiles = filesOp.comparePath(path, [FILES_DATA]); var isSearch = path[0] === SEARCH; - var root = isSearch ? undefined : filesOp.findElement(files, path); + var root = isSearch ? undefined : filesOp.find(path); if (!isSearch && typeof(root) === "undefined") { log(Messages.fm_unknownFolderError); debug("Unable to locate the selected directory: ", path); @@ -1642,11 +1642,11 @@ define([ var $el = $(e); if ($el.data('path')) { var path = $el.data('path'); - var element = filesOp.findElement(files, path); + var element = filesOp.find(path); if (!filesOp.isFile(element)) { return; } var data = filesOp.getFileData(element); if (!data) { return; } - if (filesOp.isPathInHrefArray(path)) { $el.find('.name').attr('title', data.title).text(data.title); } + if (filesOp.isPathIn(path, ['hrefArray'])) { $el.find('.name').attr('title', data.title).text(data.title); } $el.find('.title').attr('title', data.title).text(data.title); $el.find('.atime').attr('title', getDate(data.atime)).text(getDate(data.atime)); $el.find('.ctime').attr('title', getDate(data.ctime)).text(getDate(data.ctime)); @@ -1700,7 +1700,7 @@ define([ }; var createTree = function ($container, path) { - var root = filesOp.findElement(files, path); + var root = filesOp.find(path); // don't try to display what doesn't exist if (!root) { return; } @@ -1930,7 +1930,7 @@ define([ } else if ($(this).hasClass('open_ro')) { paths.forEach(function (p) { - var el = filesOp.findElement(files, p.path); + var el = filesOp.find(p.path); if (filesOp.isFolder(el)) { return; } var roUrl = getReadOnlyUrl(el); openFile(roUrl, false); @@ -1942,11 +1942,11 @@ define([ module.newFolder = info.newPath; module.displayDirectory(paths[0].path); }; - filesOp.createNewFolder(paths[0].path, null, onCreated); + filesOp.addFolder(paths[0].path, null, onCreated); } else if ($(this).hasClass("properties")) { if (paths.length !== 1) { return; } - var el = filesOp.findElement(files, paths[0].path); + var el = filesOp.find(paths[0].path); var prop = getProperties(el); Cryptpad.alert('', undefined, true); $('.alertify .msg').html(prop); @@ -1972,8 +1972,8 @@ define([ } else if ($(this).hasClass('open_ro')) { paths.forEach(function (p) { - var el = filesOp.findElement(files, p.path); - if (filesOp.isPathInFilesData(p.path)) { el = el.href; } + var el = filesOp.find(p.path); + if (filesOp.isPathIn(p.path, [FILES_DATA])) { el = el.href; } if (!el || filesOp.isFolder(el)) { return; } var roUrl = getReadOnlyUrl(el); openFile(roUrl, false); @@ -1986,7 +1986,7 @@ define([ } else if ($(this).hasClass("properties")) { if (paths.length !== 1) { return; } - var el = filesOp.findElement(files, paths[0].path); + var el = filesOp.find(paths[0].path); var prop = getProperties(el); Cryptpad.alert('', undefined, true); $('.alertify .msg').html(prop); @@ -2004,12 +2004,12 @@ define([ refresh(); }; if ($(this).hasClass("newfolder")) { - filesOp.createNewFolder(path, null, onCreated); + filesOp.addFolder(path, null, onCreated); } else if ($(this).hasClass("newdoc")) { var type = $(this).data('type') || 'pad'; var name = Cryptpad.getDefaultName({type: type}); - filesOp.createNewFile(path, name, type, onCreated); + filesOp.addFile(path, name, type, onCreated); } module.hideMenu(); }); @@ -2046,7 +2046,7 @@ define([ if (path.length === 4) { name = path[1]; } Cryptpad.confirm(Messages._getKey("fm_removePermanentlyDialog", [name]), function(res) { if (!res) { return; } - filesOp.removeFromTrash(path, refresh); + filesOp.removeFromTrash(path, refresh); // TODO END HERE }); return; } From fdd2716ad5dd883ebb92592802a1d8dc183e38b9 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 13 Apr 2017 10:31:08 +0200 Subject: [PATCH 002/306] move constants to the top of cryptpad-common.js --- www/common/cryptpad-common.js | 238 +++++++++++++++++----------------- 1 file changed, 121 insertions(+), 117 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 9eedd1106..1b870371e 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -27,9 +27,18 @@ load pinpad dynamically only after you know that it will be needed */ Clipboard: Clipboard }; - var store; - + // constants + var userHashKey = common.userHashKey = 'User_hash'; + var userNameKey = common.userNameKey = 'User_name'; + var fileHashKey = common.fileHashKey = 'FS_hash'; + var displayNameKey = common.displayNameKey = 'cryptpad.username'; + var LOADING = 'loading'; + var newPadNameKey = common.newPadNameKey = "newPadName"; + var newPadPathKey = common.newPadPathKey = "newPadPath"; + var storageKey = common.storageKey = 'CryptPad_RECENTPADS'; var PINNING_ENABLED = AppConfig.enablePinning; + + var store; var rpc; var find = common.find = Util.find; @@ -112,10 +121,6 @@ load pinpad dynamically only after you know that it will be needed */ return url; }; - var userHashKey = common.userHashKey = 'User_hash'; - var userNameKey = common.userNameKey = 'User_name'; - var fileHashKey = common.fileHashKey = 'FS_hash'; - var displayNameKey = common.displayNameKey = 'cryptpad.username'; var login = common.login = function (hash, name, cb) { if (!hash) { throw new Error('expected a user hash'); } @@ -212,6 +217,8 @@ load pinpad dynamically only after you know that it will be needed */ } return; }; + + // CRYPTO var getEditHashFromKeys = common.getEditHashFromKeys = function (chanKey, keys) { if (typeof keys === 'string') { return chanKey + keys; @@ -219,6 +226,7 @@ load pinpad dynamically only after you know that it will be needed */ if (!keys.editKeyStr) { return; } return '/1/edit/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.editKeyStr); }; + // CRYPTO var getViewHashFromKeys = common.getViewHashFromKeys = function (chanKey, keys) { if (typeof keys === 'string') { return; @@ -226,13 +234,12 @@ load pinpad dynamically only after you know that it will be needed */ return '/1/view/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.viewKeyStr); }; - var specialHashes = common.specialHashes = ['iframe']; - /* * Returns all needed keys for a realtime channel * - no argument: use the URL hash or create one if it doesn't exist * - secretHash provided: use secretHash to find the keys */ + // CRYPTO var getSecrets = common.getSecrets = function (secretHash) { var secret = {}; var generate = function () { @@ -244,7 +251,7 @@ load pinpad dynamically only after you know that it will be needed */ return secret; } else { var hash = secretHash || window.location.hash.slice(1); - if (hash.length === 0 || specialHashes.indexOf(hash) !== -1) { + if (hash.length === 0) { generate(); return secret; } @@ -289,6 +296,7 @@ load pinpad dynamically only after you know that it will be needed */ return secret; }; + // CRYPTO var getHashes = common.getHashes = function (channel, secret) { var hashes = {}; if (secret.keys.editKeyStr) { @@ -300,6 +308,7 @@ load pinpad dynamically only after you know that it will be needed */ return hashes; }; + // CRYPTO var createChannelId = common.createChannelId = function () { var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16)); if (id.length !== 32 || /[^a-f0-9]/.test(id)) { @@ -308,6 +317,7 @@ load pinpad dynamically only after you know that it will be needed */ return id; }; + // CRYPTO var createRandomHash = common.createRandomHash = function () { // 16 byte channel Id var channelId = hexToBase64(createChannelId()); @@ -316,8 +326,6 @@ load pinpad dynamically only after you know that it will be needed */ return '/1/edit/' + [channelId, key].join('/'); }; - var storageKey = common.storageKey = 'CryptPad_RECENTPADS'; - /* * localStorage formatting */ @@ -760,111 +768,6 @@ load pinpad dynamically only after you know that it will be needed */ }); }; - var newPadNameKey = common.newPadNameKey = "newPadName"; - var newPadPathKey = common.newPadPathKey = "newPadPath"; - - // local name? - common.ready = function (f) { - var block = 0; - var env = {}; - - var cb = function () { - block--; - if (!block) { - f(void 0, env); - } - }; - - if (sessionStorage[newPadNameKey]) { - common.initialName = sessionStorage[newPadNameKey]; - delete sessionStorage[newPadNameKey]; - } - if (sessionStorage[newPadPathKey]) { - common.initialPath = sessionStorage[newPadPathKey]; - delete sessionStorage[newPadPathKey]; - } - - Store.ready(function (err, storeObj) { - store = common.store = env.store = storeObj; - - var proxy = getProxy(); - var network = getNetwork(); - - $(function() { - // Race condition : if document.body is undefined when alertify.js is loaded, Alertify - // won't work. We have to reset it now to make sure it uses a correct "body" - Alertify.reset(); - - // Load the new pad when the hash has changed - var oldHash = document.location.hash.slice(1); - window.onhashchange = function () { - var newHash = document.location.hash.slice(1); - var parsedOld = parseHash(oldHash); - var parsedNew = parseHash(newHash); - if (parsedOld && parsedNew && ( - parsedOld.channel !== parsedNew.channel - || parsedOld.mode !== parsedNew.mode - || parsedOld.key !== parsedNew.key)) { - document.location.reload(); - return; - } - if (parsedNew) { - oldHash = newHash; - } - }; - - if (PINNING_ENABLED && isLoggedIn()) { - console.log("logged in. pads will be pinned"); - block++; - - // TODO setTimeout in case rpc doesn't - // activate in reasonable time? - Pinpad.create(network, proxy, function (e, call) { - if (e) { - console.error(e); - return cb(); - } - - console.log('RPC handshake complete'); - rpc = common.rpc = env.rpc = call; - - // TODO check if pin list is up to date - // if not, reset - common.arePinsSynced(function (err, yes) { - if (!yes) { - common.resetPins(function (err, hash) { - console.log('RESET DONE'); - }); - } - }); - cb(); - }); - } else if (PINNING_ENABLED) { - console.log('not logged in. pads will not be pinned'); - } else { - console.log('pinning disabled'); - } - - // Everything's ready, continue... - if($('#pad-iframe').length) { - block++; - var $iframe = $('#pad-iframe'); - var iframe = $iframe[0]; - var iframeDoc = iframe.contentDocument || iframe.contentWindow.document; - if (iframeDoc.readyState === 'complete') { - cb(); - return; - } - $iframe.load(cb); - return; - } - - block++; - cb(); - }); - }, common); - }; - var errorHandlers = []; common.onError = function (h) { if (typeof h !== "function") { return; } @@ -878,7 +781,6 @@ load pinpad dynamically only after you know that it will be needed */ }); }; - var LOADING = 'loading'; var getRandomTip = function () { if (!Messages.tips || !Object.keys(Messages.tips).length) { return ''; } var keys = Object.keys(Messages.tips); @@ -1641,6 +1543,108 @@ load pinpad dynamically only after you know that it will be needed */ }; }; + // local name? + common.ready = function (f) { + var block = 0; + var env = {}; + + var cb = function () { + block--; + if (!block) { + f(void 0, env); + } + }; + + if (sessionStorage[newPadNameKey]) { + common.initialName = sessionStorage[newPadNameKey]; + delete sessionStorage[newPadNameKey]; + } + if (sessionStorage[newPadPathKey]) { + common.initialPath = sessionStorage[newPadPathKey]; + delete sessionStorage[newPadPathKey]; + } + + Store.ready(function (err, storeObj) { + store = common.store = env.store = storeObj; + + var proxy = getProxy(); + var network = getNetwork(); + + $(function() { + // Race condition : if document.body is undefined when alertify.js is loaded, Alertify + // won't work. We have to reset it now to make sure it uses a correct "body" + Alertify.reset(); + + // Load the new pad when the hash has changed + var oldHash = document.location.hash.slice(1); + window.onhashchange = function () { + var newHash = document.location.hash.slice(1); + var parsedOld = parseHash(oldHash); + var parsedNew = parseHash(newHash); + if (parsedOld && parsedNew && ( + parsedOld.channel !== parsedNew.channel + || parsedOld.mode !== parsedNew.mode + || parsedOld.key !== parsedNew.key)) { + document.location.reload(); + return; + } + if (parsedNew) { + oldHash = newHash; + } + }; + + if (PINNING_ENABLED && isLoggedIn()) { + console.log("logged in. pads will be pinned"); + block++; + + // TODO setTimeout in case rpc doesn't + // activate in reasonable time? + Pinpad.create(network, proxy, function (e, call) { + if (e) { + console.error(e); + return cb(); + } + + console.log('RPC handshake complete'); + rpc = common.rpc = env.rpc = call; + + // TODO check if pin list is up to date + // if not, reset + common.arePinsSynced(function (err, yes) { + if (!yes) { + common.resetPins(function (err, hash) { + console.log('RESET DONE'); + }); + } + }); + cb(); + }); + } else if (PINNING_ENABLED) { + console.log('not logged in. pads will not be pinned'); + } else { + console.log('pinning disabled'); + } + + // Everything's ready, continue... + if($('#pad-iframe').length) { + block++; + var $iframe = $('#pad-iframe'); + var iframe = $iframe[0]; + var iframeDoc = iframe.contentDocument || iframe.contentWindow.document; + if (iframeDoc.readyState === 'complete') { + cb(); + return; + } + $iframe.load(cb); + return; + } + + block++; + cb(); + }); + }, common); + }; + $(function () { Messages._applyTranslation(); }); From e618483395132317ef7cce2b14cc121976400717 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 13 Apr 2017 11:05:28 +0200 Subject: [PATCH 003/306] extract all code for encoding and decoding hashes --- www/common/cryptpad-common.js | 250 +++------------------------------- www/common/hash.js | 236 ++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+), 230 deletions(-) create mode 100644 www/common/hash.js diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 1b870371e..7fc848d24 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -3,6 +3,7 @@ define([ '/customize/messages.js?app=' + window.location.pathname.split('/').filter(function (x) { return x; }).join('.'), '/common/fsStore.js', '/common/common-util.js', + '/common/hash.js', '/bower_components/chainpad-crypto/crypto.js?v=0.1.5', '/bower_components/alertifyjs/dist/js/alertify.js', @@ -12,7 +13,7 @@ load pinpad dynamically only after you know that it will be needed */ '/customize/application_config.js', '/bower_components/jquery/dist/jquery.min.js', -], function (Config, Messages, Store, Util, Crypto, Alertify, Clipboard, Pinpad, AppConfig) { +], function (Config, Messages, Store, Util, Hash, Crypto, Alertify, Clipboard, Pinpad, AppConfig) { /* This file exposes functionality which is specific to Cryptpad, but not to any particular pad type. This includes functions for committing metadata about pads to your local storage for future use and improved usability. @@ -41,6 +42,7 @@ load pinpad dynamically only after you know that it will be needed */ var store; var rpc; + // import common utilities for export var find = common.find = Util.find; var fixHTML = common.fixHTML = Util.fixHTML; var hexToBase64 = common.hexToBase64 = Util.hexToBase64; @@ -51,6 +53,22 @@ load pinpad dynamically only after you know that it will be needed */ var getHash = common.getHash = Util.getHash; var fixFileName = common.fixFileName = Util.fixFileName; + // import hash utilities for export + var createRandomHash = Hash.createRandomHash; + var parsePadUrl = common.parsePadUrl = Hash.parsePadUrl; + var isNotStrongestStored = common.isNotStrongestStored = Hash.isNotStrongestStored; + var hrefToHexChannelId = common.hrefToHexChannelId = Hash.hrefToHexChannelId; + var parseHash = common.parseHash = Hash.parseHash; + var getRelativeHref = common.getRelativeHref = Hash.getRelativeHref; + + common.getEditHashFromKeys = Hash.getEditHashFromKeys; + common.getViewHashFromKeys = Hash.getViewHashFromKeys; + common.getSecrets = Hash.getSecrets; + common.getHashes = Hash.getHashes; + common.createChannelId = Hash.createChannelId; + common.findWeaker = Hash.findWeaker; + common.findStronger = Hash.findStronger; + var getStore = common.getStore = function () { if (store) { return store; } throw new Error("Store is not ready!"); @@ -197,134 +215,6 @@ load pinpad dynamically only after you know that it will be needed */ return text; }; - var parseHash = common.parseHash = function (hash) { - var parsed = {}; - if (hash.slice(0,1) !== '/' && hash.length >= 56) { - // Old hash - parsed.channel = hash.slice(0, 32); - parsed.key = hash.slice(32); - parsed.version = 0; - return parsed; - } - var hashArr = hash.split('/'); - if (hashArr[1] && hashArr[1] === '1') { - parsed.version = 1; - parsed.mode = hashArr[2]; - parsed.channel = hashArr[3]; - parsed.key = hashArr[4]; - parsed.present = hashArr[5] && hashArr[5] === 'present'; - return parsed; - } - return; - }; - - // CRYPTO - var getEditHashFromKeys = common.getEditHashFromKeys = function (chanKey, keys) { - if (typeof keys === 'string') { - return chanKey + keys; - } - if (!keys.editKeyStr) { return; } - return '/1/edit/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.editKeyStr); - }; - // CRYPTO - var getViewHashFromKeys = common.getViewHashFromKeys = function (chanKey, keys) { - if (typeof keys === 'string') { - return; - } - return '/1/view/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.viewKeyStr); - }; - - /* - * Returns all needed keys for a realtime channel - * - no argument: use the URL hash or create one if it doesn't exist - * - secretHash provided: use secretHash to find the keys - */ - // CRYPTO - var getSecrets = common.getSecrets = function (secretHash) { - var secret = {}; - var generate = function () { - secret.keys = Crypto.createEditCryptor(); - secret.key = Crypto.createEditCryptor().editKeyStr; - }; - if (!secretHash && !/#/.test(window.location.href)) { - generate(); - return secret; - } else { - var hash = secretHash || window.location.hash.slice(1); - if (hash.length === 0) { - generate(); - return secret; - } - // old hash system : #{hexChanKey}{cryptKey} - // new hash system : #/{hashVersion}/{b64ChanKey}/{cryptKey} - if (hash.slice(0,1) !== '/' && hash.length >= 56) { - // Old hash - secret.channel = hash.slice(0, 32); - secret.key = hash.slice(32); - } - else { - // New hash - var hashArray = hash.split('/'); - if (hashArray.length < 4) { - common.alert("Unable to parse the key"); - throw new Error("Unable to parse the key"); - } - var version = hashArray[1]; - if (version === "1") { - var mode = hashArray[2]; - if (mode === 'edit') { - secret.channel = base64ToHex(hashArray[3]); - var keys = Crypto.createEditCryptor(hashArray[4].replace(/-/g, '/')); - secret.keys = keys; - secret.key = keys.editKeyStr; - if (secret.channel.length !== 32 || secret.key.length !== 24) { - common.alert("The channel key and/or the encryption key is invalid"); - throw new Error("The channel key and/or the encryption key is invalid"); - } - } - else if (mode === 'view') { - secret.channel = base64ToHex(hashArray[3]); - secret.keys = Crypto.createViewCryptor(hashArray[4].replace(/-/g, '/')); - if (secret.channel.length !== 32) { - common.alert("The channel key is invalid"); - throw new Error("The channel key is invalid"); - } - } - } - } - } - return secret; - }; - - // CRYPTO - var getHashes = common.getHashes = function (channel, secret) { - var hashes = {}; - if (secret.keys.editKeyStr) { - hashes.editHash = getEditHashFromKeys(channel, secret.keys); - } - if (secret.keys.viewKeyStr) { - hashes.viewHash = getViewHashFromKeys(channel, secret.keys); - } - return hashes; - }; - - // CRYPTO - var createChannelId = common.createChannelId = function () { - var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16)); - if (id.length !== 32 || /[^a-f0-9]/.test(id)) { - throw new Error('channel ids must consist of 32 hex characters'); - } - return id; - }; - - // CRYPTO - var createRandomHash = common.createRandomHash = function () { - // 16 byte channel Id - var channelId = hexToBase64(createChannelId()); - // 18 byte encryption key - var key = Crypto.b64RemoveSlashes(Crypto.rand64(18)); - return '/1/edit/' + [channelId, key].join('/'); - }; /* * localStorage formatting @@ -407,36 +297,6 @@ load pinpad dynamically only after you know that it will be needed */ }); }; - var getRelativeHref = common.getRelativeHref = function (href) { - if (!href) { return; } - if (href.indexOf('#') === -1) { return; } - var parsed = common.parsePadUrl(href); - return '/' + parsed.type + '/#' + parsed.hash; - }; - - var parsePadUrl = common.parsePadUrl = function (href) { - var patt = /^https*:\/\/([^\/]*)\/(.*?)\//i; - - var ret = {}; - - if (!href) { return ret; } - - if (!/^https*:\/\//.test(href)) { - var idx = href.indexOf('/#'); - ret.type = href.slice(1, idx); - ret.hash = href.slice(idx + 2); - return ret; - } - - var hash = href.replace(patt, function (a, domain, type, hash) { - ret.domain = domain; - ret.type = type; - return ''; - }); - ret.hash = hash.replace(/#/g, ''); - return ret; - }; - var isNameAvailable = function (title, parsed, pads) { return !pads.some(function (pad) { // another pad is already using that title @@ -619,55 +479,6 @@ load pinpad dynamically only after you know that it will be needed */ } }; - // STORAGE - var findWeaker = common.findWeaker = function (href, recents) { - var rHref = href || getRelativeHref(window.location.href); - var parsed = parsePadUrl(rHref); - if (!parsed.hash) { return false; } - var weaker; - recents.some(function (pad) { - var p = parsePadUrl(pad.href); - if (p.type !== parsed.type) { return; } // Not the same type - if (p.hash === parsed.hash) { return; } // Same hash, not stronger - var pHash = parseHash(p.hash); - var parsedHash = parseHash(parsed.hash); - if (!parsedHash || !pHash) { return; } - if (pHash.version !== parsedHash.version) { return; } - if (pHash.channel !== parsedHash.channel) { return; } - if (pHash.mode === 'view' && parsedHash.mode === 'edit') { - weaker = pad.href; - return true; - } - return; - }); - return weaker; - }; - var findStronger = common.findStronger = function (href, recents) { - var rHref = href || getRelativeHref(window.location.href); - var parsed = parsePadUrl(rHref); - if (!parsed.hash) { return false; } - var stronger; - recents.some(function (pad) { - var p = parsePadUrl(pad.href); - if (p.type !== parsed.type) { return; } // Not the same type - if (p.hash === parsed.hash) { return; } // Same hash, not stronger - var pHash = parseHash(p.hash); - var parsedHash = parseHash(parsed.hash); - if (!parsedHash || !pHash) { return; } - if (pHash.version !== parsedHash.version) { return; } - if (pHash.channel !== parsedHash.channel) { return; } - if (pHash.mode === 'edit' && parsedHash.mode === 'view') { - stronger = pad.href; - return true; - } - return; - }); - return stronger; - }; - var isNotStrongestStored = common.isNotStrongestStored = function (href, recents) { - return findStronger(href, recents); - }; - // TODO integrate pinning var setPadTitle = common.setPadTitle = function (name, cb) { var href = window.location.href; @@ -746,7 +557,7 @@ load pinpad dynamically only after you know that it will be needed */ var getPadTitle = common.getPadTitle = function (cb) { var href = window.location.href; var parsed = parsePadUrl(window.location.href); - var hashSlice = window.location.hash.slice(1,9); + var hashSlice = window.location.hash.slice(1,9); // TODO remove var title = ''; getRecentPads(function (err, pads) { @@ -862,27 +673,6 @@ load pinpad dynamically only after you know that it will be needed */ }); }; - var hrefToHexChannelId = common.hrefToHexChannelId = function (href) { - var parsed = common.parsePadUrl(href); - if (!parsed || !parsed.hash) { return; } - - parsed = common.parseHash(parsed.hash); - - if (parsed.version === 0) { - return parsed.channel; - } else if (parsed.version !== 1) { - console.error("parsed href had no version"); - console.error(parsed); - return; - } - - var channel = parsed.channel; - if (!channel) { return; } - - var hex = common.base64ToHex(channel); - return hex; - }; - var getUserChannelList = common.getUserChannelList = function () { var store = common.getStore(); var proxy = store.getProxy(); diff --git a/www/common/hash.js b/www/common/hash.js new file mode 100644 index 000000000..fa1407315 --- /dev/null +++ b/www/common/hash.js @@ -0,0 +1,236 @@ +define([ + '/Hash/Hash-util.js', + '/bower_components/chainpad-crypto/crypto.js', +], function (Util, Crypto) { + var Hash = {}; + + var uint8ArrayToHex = Util.uint8ArrayToHex; + var hexToBase64 = Util.hexToBase64; + var base64ToHex = Util.base64ToHex; + + var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) { + if (typeof keys === 'string') { + return chanKey + keys; + } + if (!keys.editKeyStr) { return; } + return '/1/edit/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.editKeyStr); + }; + var getViewHashFromKeys = Hash.getViewHashFromKeys = function (chanKey, keys) { + if (typeof keys === 'string') { + return; + } + return '/1/view/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.viewKeyStr); + }; + + var parsePadUrl = Hash.parsePadUrl = function (href) { + var patt = /^https*:\/\/([^\/]*)\/(.*?)\//i; + + var ret = {}; + + if (!href) { return ret; } + + if (!/^https*:\/\//.test(href)) { + var idx = href.indexOf('/#'); + ret.type = href.slice(1, idx); + ret.hash = href.slice(idx + 2); + return ret; + } + + var hash = href.replace(patt, function (a, domain, type, hash) { + ret.domain = domain; + ret.type = type; + return ''; + }); + ret.hash = hash.replace(/#/g, ''); + return ret; + }; + + var getRelativeHref = Hash.getRelativeHref = function (href) { + if (!href) { return; } + if (href.indexOf('#') === -1) { return; } + var parsed = parsePadUrl(href); + return '/' + parsed.type + '/#' + parsed.hash; + }; + + /* + * Returns all needed keys for a realtime channel + * - no argument: use the URL hash or create one if it doesn't exist + * - secretHash provided: use secretHash to find the keys + */ + var getSecrets = Hash.getSecrets = function (secretHash) { + var secret = {}; + var generate = function () { + secret.keys = Crypto.createEditCryptor(); + secret.key = Crypto.createEditCryptor().editKeyStr; + }; + if (!secretHash && !/#/.test(window.location.href)) { + generate(); + return secret; + } else { + var hash = secretHash || window.location.hash.slice(1); + if (hash.length === 0) { + generate(); + return secret; + } + // old hash system : #{hexChanKey}{cryptKey} + // new hash system : #/{hashVersion}/{b64ChanKey}/{cryptKey} + if (hash.slice(0,1) !== '/' && hash.length >= 56) { + // Old hash + secret.channel = hash.slice(0, 32); + secret.key = hash.slice(32); + } + else { + // New hash + var hashArray = hash.split('/'); + if (hashArray.length < 4) { + Hash.alert("Unable to parse the key"); + throw new Error("Unable to parse the key"); + } + var version = hashArray[1]; + if (version === "1") { + var mode = hashArray[2]; + if (mode === 'edit') { + secret.channel = base64ToHex(hashArray[3]); + var keys = Crypto.createEditCryptor(hashArray[4].replace(/-/g, '/')); + secret.keys = keys; + secret.key = keys.editKeyStr; + if (secret.channel.length !== 32 || secret.key.length !== 24) { + Hash.alert("The channel key and/or the encryption key is invalid"); + throw new Error("The channel key and/or the encryption key is invalid"); + } + } + else if (mode === 'view') { + secret.channel = base64ToHex(hashArray[3]); + secret.keys = Crypto.createViewCryptor(hashArray[4].replace(/-/g, '/')); + if (secret.channel.length !== 32) { + Hash.alert("The channel key is invalid"); + throw new Error("The channel key is invalid"); + } + } + } + } + } + return secret; + }; + + var getHashes = Hash.getHashes = function (channel, secret) { + var hashes = {}; + if (secret.keys.editKeyStr) { + hashes.editHash = getEditHashFromKeys(channel, secret.keys); + } + if (secret.keys.viewKeyStr) { + hashes.viewHash = getViewHashFromKeys(channel, secret.keys); + } + return hashes; + }; + + var createChannelId = Hash.createChannelId = function () { + var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16)); + if (id.length !== 32 || /[^a-f0-9]/.test(id)) { + throw new Error('channel ids must consist of 32 hex characters'); + } + return id; + }; + + var createRandomHash = Hash.createRandomHash = function () { + // 16 byte channel Id + var channelId = Util.hexToBase64(createChannelId()); + // 18 byte encryption key + var key = Crypto.b64RemoveSlashes(Crypto.rand64(18)); + return '/1/edit/' + [channelId, key].join('/'); + }; + + var parseHash = Hash.parseHash = function (hash) { + var parsed = {}; + if (hash.slice(0,1) !== '/' && hash.length >= 56) { + // Old hash + parsed.channel = hash.slice(0, 32); + parsed.key = hash.slice(32); + parsed.version = 0; + return parsed; + } + var hashArr = hash.split('/'); + if (hashArr[1] && hashArr[1] === '1') { + parsed.version = 1; + parsed.mode = hashArr[2]; + parsed.channel = hashArr[3]; + parsed.key = hashArr[4]; + parsed.present = hashArr[5] && hashArr[5] === 'present'; + return parsed; + } + return; + }; + + // STORAGE + var findWeaker = Hash.findWeaker = function (href, recents) { + var rHref = href || getRelativeHref(window.location.href); + var parsed = parsePadUrl(rHref); + if (!parsed.hash) { return false; } + var weaker; + recents.some(function (pad) { + var p = parsePadUrl(pad.href); + if (p.type !== parsed.type) { return; } // Not the same type + if (p.hash === parsed.hash) { return; } // Same hash, not stronger + var pHash = parseHash(p.hash); + var parsedHash = parseHash(parsed.hash); + if (!parsedHash || !pHash) { return; } + if (pHash.version !== parsedHash.version) { return; } + if (pHash.channel !== parsedHash.channel) { return; } + if (pHash.mode === 'view' && parsedHash.mode === 'edit') { + weaker = pad.href; + return true; + } + return; + }); + return weaker; + }; + var findStronger = Hash.findStronger = function (href, recents) { + var rHref = href || getRelativeHref(window.location.href); + var parsed = parsePadUrl(rHref); + if (!parsed.hash) { return false; } + var stronger; + recents.some(function (pad) { + var p = parsePadUrl(pad.href); + if (p.type !== parsed.type) { return; } // Not the same type + if (p.hash === parsed.hash) { return; } // Same hash, not stronger + var pHash = parseHash(p.hash); + var parsedHash = parseHash(parsed.hash); + if (!parsedHash || !pHash) { return; } + if (pHash.version !== parsedHash.version) { return; } + if (pHash.channel !== parsedHash.channel) { return; } + if (pHash.mode === 'edit' && parsedHash.mode === 'view') { + stronger = pad.href; + return true; + } + return; + }); + return stronger; + }; + var isNotStrongestStored = Hash.isNotStrongestStored = function (href, recents) { + return findStronger(href, recents); + }; + + var hrefToHexChannelId = Hash.hrefToHexChannelId = function (href) { + var parsed = Hash.parsePadUrl(href); + if (!parsed || !parsed.hash) { return; } + + parsed = Hash.parseHash(parsed.hash); + + if (parsed.version === 0) { + return parsed.channel; + } else if (parsed.version !== 1) { + console.error("parsed href had no version"); + console.error(parsed); + return; + } + + var channel = parsed.channel; + if (!channel) { return; } + + var hex = Hash.base64ToHex(channel); + return hex; + }; + + + return Hash; +}); From 86c37bfa37aa805a9fc231006fbcb39bec907cb1 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 13 Apr 2017 11:11:45 +0200 Subject: [PATCH 004/306] fix find+replace error --- www/common/hash.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/hash.js b/www/common/hash.js index fa1407315..0c535ab18 100644 --- a/www/common/hash.js +++ b/www/common/hash.js @@ -1,5 +1,5 @@ define([ - '/Hash/Hash-util.js', + '/common/common-util.js', '/bower_components/chainpad-crypto/crypto.js', ], function (Util, Crypto) { var Hash = {}; @@ -227,7 +227,7 @@ define([ var channel = parsed.channel; if (!channel) { return; } - var hex = Hash.base64ToHex(channel); + var hex = base64ToHex(channel); return hex; }; From df4a25035912ed221fa2a9da7df75a379ace2d13 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 13 Apr 2017 12:18:08 +0200 Subject: [PATCH 005/306] use feedback api instead of app= --- www/code/main.js | 1 + www/common/cryptpad-common.js | 35 +++++++++++++++++++++-------------- www/drive/main.js | 1 + www/pad/main.js | 1 + www/poll/main.js | 1 + www/slide/main.js | 1 + www/whiteboard/main.js | 1 + 7 files changed, 27 insertions(+), 14 deletions(-) diff --git a/www/code/main.js b/www/code/main.js index 227c1e1fa..bdad4cdb6 100644 --- a/www/code/main.js +++ b/www/code/main.js @@ -733,6 +733,7 @@ define([ var second = function (CM) { Cryptpad.ready(function (err, env) { andThen(CM); + Cryptpad.reportAppUsage(); }); Cryptpad.onError(function (info) { if (info && info.type === "store") { diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 7fc848d24..4cc0531f7 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1,11 +1,10 @@ define([ '/api/config', - '/customize/messages.js?app=' + window.location.pathname.split('/').filter(function (x) { return x; }).join('.'), + '/customize/messages.js', '/common/fsStore.js', '/common/common-util.js', '/common/hash.js', - '/bower_components/chainpad-crypto/crypto.js?v=0.1.5', '/bower_components/alertifyjs/dist/js/alertify.js', '/common/clipboard.js', '/common/pinpad.js', /* TODO @@ -13,7 +12,7 @@ load pinpad dynamically only after you know that it will be needed */ '/customize/application_config.js', '/bower_components/jquery/dist/jquery.min.js', -], function (Config, Messages, Store, Util, Hash, Crypto, Alertify, Clipboard, Pinpad, AppConfig) { +], function (Config, Messages, Store, Util, Hash, Alertify, Clipboard, Pinpad, AppConfig) { /* This file exposes functionality which is specific to Cryptpad, but not to any particular pad type. This includes functions for committing metadata about pads to your local storage for future use and improved usability. @@ -28,6 +27,25 @@ load pinpad dynamically only after you know that it will be needed */ Clipboard: Clipboard }; + var feedback = common.feedback = function (action) { + if (!action) { return; } + try { + if (!getStore().getProxy().proxy.allowUserFeedback) { return; } + } catch (e) { return void console.error(e); } + + var href = '/common/feedback.html?' + action + '=' + (+new Date()); + $.ajax({ + type: "HEAD", + url: href, + }); + }; + + var reportAppUsage = common.reportAppUsage = function () { + var pattern = window.location.pathname.split('/') + .filter(function (x) { return x; }).join('.'); + feedback(pattern); + }; + // constants var userHashKey = common.userHashKey = 'User_hash'; var userNameKey = common.userNameKey = 'User_name'; @@ -104,17 +122,6 @@ load pinpad dynamically only after you know that it will be needed */ return; }; - common.feedback = function (action) { - if (!action) { return; } - try { - if (!getStore().getProxy().proxy.allowUserFeedback) { return; } - } catch (e) { return void console.error(e); } - $.ajax({ - type: "HEAD", - url: '/common/feedback.html?' + action + '=' + (+new Date()), - }); - }; - var whenRealtimeSyncs = common.whenRealtimeSyncs = function (realtime, cb) { realtime.sync(); window.setTimeout(function () { diff --git a/www/drive/main.js b/www/drive/main.js index 474ea4b06..b8441c0c7 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -2243,6 +2243,7 @@ define([ // don't initialize until the store is ready. Cryptpad.ready(function () { + Cryptpad.reportAppUsage(); APP.$bar = $iframe.find('#toolbar'); var storeObj = Cryptpad.getStore().getProxy && Cryptpad.getStore().getProxy().proxy ? Cryptpad.getStore().getProxy() : undefined; diff --git a/www/pad/main.js b/www/pad/main.js index 1170e3008..4d9cd0244 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -810,6 +810,7 @@ define([ var second = function (Ckeditor) { Cryptpad.ready(function (err, env) { andThen(Ckeditor); + Cryptpad.reportAppUsage(); }); Cryptpad.onError(function (info) { if (info && info.type === "store") { diff --git a/www/poll/main.js b/www/poll/main.js index b3265531e..aa3dceabf 100644 --- a/www/poll/main.js +++ b/www/poll/main.js @@ -782,6 +782,7 @@ define([ // don't initialize until the store is ready. Cryptpad.ready(function () { + Cryptpad.reportAppUsage(); var config = { websocketURL: Cryptpad.getWebsocketURL(), channel: secret.channel, diff --git a/www/slide/main.js b/www/slide/main.js index 25c367a85..9acc6527e 100644 --- a/www/slide/main.js +++ b/www/slide/main.js @@ -929,6 +929,7 @@ define([ var second = function (CM) { Cryptpad.ready(function (err, env) { andThen(CM); + Cryptpad.reportAppUsage(); }); Cryptpad.onError(function (info) { if (info && info.type === "store") { diff --git a/www/whiteboard/main.js b/www/whiteboard/main.js index c28ad84c8..938505d50 100644 --- a/www/whiteboard/main.js +++ b/www/whiteboard/main.js @@ -618,6 +618,7 @@ window.canvas = canvas; Cryptpad.ready(function (err, env) { andThen(); + Cryptpad.reportAppUsage(); }); Cryptpad.onError(function (info) { if (info) { From daabb0b3adbee90cd995ca4c30fead39ec0ca4aa Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 13 Apr 2017 14:06:40 +0200 Subject: [PATCH 006/306] Integrate and fix the new fileObject (userObject) --- www/common/cryptpad-common.js | 2 +- www/common/fsStore.js | 4 +- www/common/mergeDrive.js | 14 ++--- www/common/userObject.js | 113 ++++++++++++++++++++++++++-------- www/drive/main.js | 66 ++++++++------------ 5 files changed, 122 insertions(+), 77 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index f815b8e4f..7f7f8b5eb 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -994,7 +994,7 @@ load pinpad dynamically only after you know that it will be needed */ var userChannel = common.parseHash(userHash).channel; if (!userChannel) { return null; } - var list = fo.getFilesDataFiles().map(hrefToHexChannelId) + var list = fo.getFiles([fo.FILES_DATA]).map(hrefToHexChannelId) .filter(function (x) { return x; }); list.push(common.base64ToHex(userChannel)); diff --git a/www/common/fsStore.js b/www/common/fsStore.js index e2b283f4b..edab0e252 100644 --- a/www/common/fsStore.js +++ b/www/common/fsStore.js @@ -2,7 +2,7 @@ define([ '/bower_components/chainpad-listmap/chainpad-listmap.js', '/bower_components/chainpad-crypto/crypto.js?v=0.1.5', '/bower_components/textpatcher/TextPatcher.amd.js', - '/common/fileObject.js', + '/common/userObject.js', '/bower_components/jquery/dist/jquery.min.js', ], function (Listmap, Crypto, TextPatcher, FO) { /* @@ -125,7 +125,7 @@ define([ }; ret.replaceHref = function (o, n) { - return filesOp.replaceHref(o, n); + return filesOp.replace(o, n); }; var changeHandlers = ret.changeHandlers = []; diff --git a/www/common/mergeDrive.js b/www/common/mergeDrive.js index 688f1cedc..784817d1d 100644 --- a/www/common/mergeDrive.js +++ b/www/common/mergeDrive.js @@ -2,7 +2,7 @@ require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/J define([ '/common/cryptpad-common.js', '/common/cryptget.js', - '/common/fileObject.js', + '/common/userObject.js', 'json.sortify' ], function (Cryptpad, Crypt, FO, Sortify) { var exp = {}; @@ -76,8 +76,8 @@ define([ console.error(msg || "Unable to find that path", path); }; - if (path[0] === FO.TRASH && path.length === 4) { - href = oldFo.getTrashElementData(path); + if (oldFo.isInTrashRoot(path)) { + href = oldFo.find(path.slice(0,3)); path.pop(); } @@ -156,13 +156,13 @@ define([ var newData = Cryptpad.getStore().getProxy(); var newFo = newData.fo; var newRecentPads = proxy.drive[Cryptpad.storageKey]; - var newFiles = newFo.getFilesDataFiles(); - var oldFiles = oldFo.getFilesDataFiles(); + var newFiles = newFo.getFiles([newFo.FILES_DATA]); + var oldFiles = oldFo.getFiles([newFo.FILES_DATA]); oldFiles.forEach(function (href) { // Do not migrate a pad if we already have it, it would create a duplicate in the drive if (newFiles.indexOf(href) !== -1) { return; } // If we have a stronger version, do not add the current href - if (Cryptpad.findStronger(href, newRecentPads)) { return; } + if (Cryptpad.findStronger(href, newRecentPads)) { console.log(href); return; } // If we have a weaker version, replace the href by the new one // NOTE: if that weaker version is in the trash, the strong one will be put in unsorted var weaker = Cryptpad.findWeaker(href, newRecentPads); @@ -176,7 +176,7 @@ define([ return; }); // Update the file in the drive - newFo.replaceHref(weaker, href); + newFo.replace(weaker, href); return; } // Here it means we have a new href, so we should add it to the drive at its old location diff --git a/www/common/userObject.js b/www/common/userObject.js index 7e7f7b292..d9669c408 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -10,10 +10,11 @@ define([ var TEMPLATE = module.TEMPLATE = "template"; var init = module.init = function (files, config) { + var exp = {}; var Cryptpad = config.Cryptpad; var Messages = Cryptpad.Messages; - var FILES_DATA = Cryptpad.storageKey; + var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Cryptpad.storageKey; var NEW_FOLDER_NAME = Messages.fm_newFolder; var NEW_FILE_NAME = Messages.fm_newFile; @@ -33,8 +34,6 @@ define([ // TODO: workgroup var workgroup = config.workgroup; - var exp = {}; - /* * UTILS @@ -49,6 +48,10 @@ define([ a[TEMPLATE] = []; return a; }; + var getHrefArray = function () { + return [UNSORTED, TEMPLATE]; + }; + var compareFiles = function (fileA, fileB) { return fileA === fileB; }; @@ -151,13 +154,19 @@ define([ return result; }; - var isSubpath = exp.isSubpath = function (path, parentPaths) { + var isSubpath = exp.isSubpath = function (path, parentPath) { var pathA = parentPath.slice(); var pathB = path.slice(0, pathA.length); return comparePath(pathA, pathB); }; - var isPathIn = function (path, categories) { + var isPathIn = exp.isPathIn = function (path, categories) { + if (!categories) { return; } + var idx = categories.indexOf('hrefArray'); + if (idx !== -1) { + categories.splice(idx, 1); + categories = categories.concat(getHrefArray()); + } return categories.some(function (c) { return Array.isArray(path) && path[0] === c; }); @@ -205,21 +214,17 @@ define([ }; var _getFiles = {}; _getFiles['array'] = function (cat) { - if (!files[cat]) { - files[cat] = []; - } + if (!files[cat]) { files[cat] = []; } return files[cat].slice(); }; - _getFiles[UNSORTED] = function () { - return _getFiles['array'](UNSORTED); - }; - _getFiles[TEMPLATE] = function () { - return _getFiles['array'](TEMPLATE); - }; + getHrefArray().forEach(function (c) { + _getFiles[c] = function () { return _getFiles['array'](c); }; + }); _getFiles['hrefArray'] = function () { var ret = []; - ret = ret.concat(_getFiles[UNSORTED]); - ret = ret.concat(_getFiles[TEMPLATE]); + getHrefArray().forEach(function (c) { + ret = ret.concat(_getFiles[c]()); + }); return Cryptpad.deduplicateString(ret); }; _getFiles[ROOT] = function () { @@ -255,11 +260,14 @@ define([ }); return ret; }; - var getFiles = function (categories) { + var getFiles = exp.getFiles = function (categories) { var ret = []; + if (!categories || !categories.length) { + categories = [ROOT, 'hrefArray', TRASH, FILES_DATA]; + } categories.forEach(function (c) { if (typeof _getFiles[c] === "function") { - ret = ret.concat(_getFiles[c]); + ret = ret.concat(_getFiles[c]()); } }); return Cryptpad.deduplicateString(ret); @@ -267,7 +275,7 @@ define([ // SEARCH var _findFileInRoot = function (path, href) { - if (!isPathIn([ROOT, TRASH])) { return []; } + if (!isPathIn(path, [ROOT, TRASH])) { return []; } var paths = []; var root = find(path); var addPaths = function (p) { @@ -338,7 +346,7 @@ define([ } return paths; }; - var findFile = function (href) { + var findFile = exp.findFile = function (href) { var rootpaths = _findFileInRoot([ROOT], href); var unsortedpaths = _findFileInHrefArray(UNSORTED, href); var templatepaths = _findFileInHrefArray(TEMPLATE, href); @@ -423,6 +431,7 @@ define([ // FILES DATA var pushFileData = exp.pushData = function (data) { Cryptpad.pinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e, hash) { + if (e) { console.log(e); return; } console.log(hash); }); files[FILES_DATA].push(data); @@ -431,6 +440,7 @@ define([ var data = files[FILES_DATA][idx]; if (typeof data === "object") { Cryptpad.unpinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e, hash) { + if (e) { console.log(e); return; } console.log(hash); }); } @@ -496,7 +506,7 @@ define([ } else { name = elementPath[elementPath.length-1]; } - var newName = !isPathInRoot(elementPath) ? getAvailableName(newParent, name) : name; + var newName = !isPathIn(elementPath, [ROOT]) ? getAvailableName(newParent, name) : name; if (typeof(newParent[newName]) !== "undefined") { log(Messages.fo_unavailableName); @@ -517,6 +527,14 @@ define([ }); exp.delete(toRemove, cb); }; + var restore = exp.restore = function (path, cb) { + if (!isInTrashRoot(path)) { return; } + var parentPath = path.slice(); + parentPath.pop(); + var oldPath = find(parentPath).path; + move([path], oldPath, cb); + }; + // ADD var add = exp.add = function (href, path, name, cb) { @@ -599,7 +617,7 @@ define([ // Nothing in FILES_DATA for workgroups if (workgroup) { return; } - var filesList = getFiles[ROOT, 'hrefArray', TRASH]; + var filesList = getFiles([ROOT, 'hrefArray', TRASH]); var toRemove = []; files[FILES_DATA].forEach(function (arr) { var f = arr.href; @@ -629,9 +647,9 @@ define([ }); }; var deleteMultiplePermanently = function (paths) { - var hrefPaths = paths.filter(isPathInHrefArray); - var rootPaths = paths.filter(isPathInRoot); - var trashPaths = paths.filter(isPathInTrash); + var hrefPaths = paths.filter(function(x) { return isPathIn(x, ['hrefArray']); }); + var rootPaths = paths.filter(function(x) { return isPathIn(x, [ROOT]); }); + var trashPaths = paths.filter(function(x) { return isPathIn(x, [TRASH]); }); var hrefs = []; hrefPaths.forEach(function (path) { @@ -674,7 +692,7 @@ define([ checkDeletedFiles(); }; var deletePath = exp.delete = function (paths, cb) { - deletePathsPermanently(paths); + deleteMultiplePermanently(paths); if (typeof cb === "function") { cb(); } }; var emptyTrash = exp.emptyTrash = function (cb) { @@ -708,6 +726,49 @@ define([ cb(); }; + // REPLACE + var replaceFile = function (path, o, n) { + var root = find(path); + + if (isFile(root)) { return; } + for (var e in root) { + if (isFile(root[e])) { + if (compareFiles(o, root[e])) { + root[e] = n; + } + } else { + var nPath = path.slice(); + nPath.push(e); + replaceFile(nPath, o, n); + } + } + }; + // Replace a href by a stronger one everywhere in the drive (except FILES_DATA) + var replaceHref = exp.replace = function (o, n) { + if (!isFile(o) || !isFile(n)) { return; } + var paths = findFile(o); + + // Remove all the occurences in the trash + // Replace all the occurences not in the trash + // If all the occurences are in the trash or no occurence, add the pad to unsorted + var allInTrash = true; + paths.forEach(function (p) { + if (p[0] === TRASH) { + removeFromTrash(p, null, true); // 3rd parameter means skip "checkDeletedFiles" + return; + } else { + allInTrash = false; + var parentPath = p.slice(); + var key = parentPath.pop(); + var parentEl = find(parentPath); + parentEl[key] = n; + } + }); + if (allInTrash) { + add(n); + } + }; + /** * INTEGRITY CHECK */ diff --git a/www/drive/main.js b/www/drive/main.js index f4a8d05a8..1b57b41ae 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -5,7 +5,7 @@ define([ '/bower_components/textpatcher/TextPatcher.amd.js', 'json.sortify', '/common/cryptpad-common.js', - '/common/fileObject.js', + '/common/userObject.js', '/common/toolbar.js', '/customize/application_config.js', '/common/cryptget.js', @@ -264,9 +264,7 @@ define([ var removeInput = function (cancel) { if (!cancel && $iframe.find('.element-row > input').length === 1) { var $input = $iframe.find('.element-row > input'); - filesOp.renameElement($input.data('path'), $input.val(), function () { - APP.refresh(); - }); + filesOp.rename($input.data('path'), $input.val(), APP.refresh); } $iframe.find('.element-row > input').remove(); $iframe.find('.element-row > span:hidden').removeAttr('style'); @@ -332,9 +330,7 @@ define([ $input.on('keyup', function (e) { if (e.which === 13) { removeInput(true); - filesOp.renameElement(path, $input.val(), function () { - refresh(); - }); + filesOp.rename(path, $input.val(), refresh); return; } if (e.which === 27) { @@ -371,6 +367,7 @@ define([ var filterContextMenu = function ($menu, paths) { //var path = $element.data('path'); + if (!paths || paths.length === 0) { console.error('no paths'); } var hide = []; var hasFolder = false; @@ -652,15 +649,11 @@ define([ var getElementName = function (path) { // Trash root - if (filesOp.isInTrashRoot(path)) { - return path[0]; - } + if (filesOp.isInTrashRoot(path)) { return path[0]; } // Root or trash - if (filesOp.isPathInRoot(path) || filesOp.isPathInTrash(path)) { - return path[path.length - 1]; - } + if (filesOp.isPathIn(path, [ROOT, TRASH])) { return path[path.length - 1]; } // Unsorted or template - if (filesOp.isPathInUnsorted(path) || filesOp.isPathInTemplate(path)) { + if (filesOp.isPathIn(path, ['hrefArray'])) { var file = filesOp.find(path); if (filesOp.isFile(file) && filesOp.getTitle(file)) { return filesOp.getTitle(file); @@ -674,10 +667,10 @@ define([ var moveElements = function (paths, newPath, force, cb) { if (!APP.editable) { return; } var andThen = function () { - filesOp.moveElements(paths, newPath, cb); + filesOp.move(paths, newPath, cb); }; // Cancel drag&drop from TRASH to TRASH - if (filesOp.comparePath(newPath, [TRASH]) && paths.length >= 1 && paths[0][0] === TRASH) { + if (filesOp.isPathIn(newPath, [TRASH]) && paths.length && paths[0][0] === TRASH) { return; } // "force" is currently unused but may be configurable by user @@ -764,7 +757,8 @@ define([ moveElements(movedPaths, newPath, null, refresh); } if (importedElements && importedElements.length) { - filesOp.importElements(importedElements, newPath, refresh); + // TODO workgroup + //filesOp.importElements(importedElements, newPath, refresh); } }; @@ -967,7 +961,7 @@ define([ // Create the title block with the "parent folder" button var createTitle = function (path, noStyle) { if (!path || path.length === 0) { return; } - var isTrash = filesOp.isPathInTrash(path); + var isTrash = filesOp.isPathIn(path, [TRASH]); var $title = $('', {'class': 'path unselectable'}); if (APP.mobile()) { return $title; @@ -1119,7 +1113,7 @@ define([ refresh(); }; $block.find('a.newFolder').click(function () { - filesOp.addFolder(currentPath, null, onCreated); // TODO START HERE + filesOp.addFolder(currentPath, null, onCreated); }); $block.find('a.newdoc').click(function (e) { var type = $(this).attr('data-type') || 'pad'; @@ -2046,7 +2040,7 @@ define([ if (path.length === 4) { name = path[1]; } Cryptpad.confirm(Messages._getKey("fm_removePermanentlyDialog", [name]), function(res) { if (!res) { return; } - filesOp.removeFromTrash(path, refresh); // TODO END HERE + filesOp.delete([path], refresh); }); return; } @@ -2055,8 +2049,7 @@ define([ var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [paths.length]); Cryptpad.confirm(msg, function(res) { if (!res) { return; } - filesOp.deletePathsPermanently(pathsList); - refresh(); + filesOp.delete(pathsList, refresh); }); } else if ($(this).hasClass("restore")) { @@ -2064,13 +2057,12 @@ define([ if (path.length === 4) { name = path[1]; } Cryptpad.confirm(Messages._getKey("fm_restoreDialog", [name]), function(res) { if (!res) { return; } - filesOp.restoreTrash(path, refresh); + filesOp.restore(path, refresh); }); } else if ($(this).hasClass("properties")) { - if (paths.length !== 1) { return; } - if (path.length !== 4) { return; } - var element = filesOp.getTrashElementData(path); + if (paths.length !== 1 || path.length !== 4) { return; } + var element = filesOp.find(path.slice(0,3)); // element containing the oldpath var sPath = stringifyPath(element.path); Cryptpad.alert('' + Messages.fm_originalPath + ":
" + sPath, undefined, true); } @@ -2094,20 +2086,17 @@ define([ $appContainer.on('keydown', function (e) { // "Del" if (e.which === 46) { - if (filesOp.isPathInFilesData(currentPath)) { return; } // We can't remove elements directly from filesData + if (filesOp.isPathIn(currentPath, [FILES_DATA])) { return; } // We can't remove elements directly from filesData var $selected = $iframe.find('.selected'); if (!$selected.length) { return; } var paths = []; - var isTrash = filesOp.isPathInTrash(currentPath); + var isTrash = filesOp.isPathIn(currentPath, [TRASH]); $selected.each(function (idx, elmt) { if (!$(elmt).data('path')) { return; } paths.push($(elmt).data('path')); }); // If we are in the trash or anon pad or if we are holding the "shift" key, delete permanently, if (isTrash || e.shiftKey) { - //var cb = filesOp.removeFromTrash; // We're in the trash - //if (!isTrash) { cb = filesOp.deletePathPermanently; } // We're in root - var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [paths.length]); if (paths.length === 1) { msg = Messages.fm_removePermanentlyDialog; @@ -2116,8 +2105,7 @@ define([ Cryptpad.confirm(msg, function(res) { $(ifrw).focus(); if (!res) { return; } - filesOp.deletePathsPermanently(paths); - refresh(); + filesOp.delete(paths, refresh); }); return; } @@ -2143,10 +2131,8 @@ define([ if (path[0] !== 'drive') { return false; } path = path.slice(1); var cPath = currentPath.slice(); - if ((filesOp.isPathInUnsorted(cPath) && filesOp.isPathInUnsorted(path)) || - (filesOp.isPathInTemplate(cPath) && filesOp.isPathInTemplate(path)) || - (path.length >= cPath.length && filesOp.isSubpath(path, cPath)) || - (filesOp.isPathInTrash(cPath) && filesOp.isPathInTrash(path))) { + if ((filesOp.isPathIn(cPath, ['hrefArray', TRASH]) && cPath[0] === path[0]) || + (path.length >= cPath.length && filesOp.isSubpath(path, cPath))) { // Reload after a few ms to make sure all the change events have been received onRefresh.refresh(); } else if (path.length && path[0] === FILES_DATA) { @@ -2159,10 +2145,8 @@ define([ if (path[0] !== 'drive') { return false; } path = path.slice(1); var cPath = currentPath.slice(); - if ((filesOp.isPathInUnsorted(cPath) && filesOp.isPathInUnsorted(path)) || - (filesOp.isPathInTemplate(cPath) && filesOp.isPathInTemplate(path)) || - (path.length >= cPath.length && filesOp.isSubpath(path, cPath)) || - (filesOp.isPathInTrash(cPath) && filesOp.isPathInTrash(path))) { + if ((filesOp.isPathIn(cPath, ['hrefArray', TRASH]) && cPath[0] === path[0]) || + (path.length >= cPath.length && filesOp.isSubpath(path, cPath))) { // Reload after a few to make sure all the change events have been received onRefresh.to = window.setTimeout(refresh, 500); } From 607d9652f55f3f42e4927646657f5d7536d0dfb9 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 13 Apr 2017 14:48:15 +0200 Subject: [PATCH 007/306] jshint compliance --- www/common/cryptpad-common.js | 37 ++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 9bf6a310e..2e78da45d 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -27,24 +27,6 @@ load pinpad dynamically only after you know that it will be needed */ Clipboard: Clipboard }; - var feedback = common.feedback = function (action) { - if (!action) { return; } - try { - if (!getStore().getProxy().proxy.allowUserFeedback) { return; } - } catch (e) { return void console.error(e); } - - var href = '/common/feedback.html?' + action + '=' + (+new Date()); - $.ajax({ - type: "HEAD", - url: href, - }); - }; - - var reportAppUsage = common.reportAppUsage = function () { - var pattern = window.location.pathname.split('/') - .filter(function (x) { return x; }).join('.'); - feedback(pattern); - }; // constants var userHashKey = common.userHashKey = 'User_hash'; @@ -105,6 +87,25 @@ load pinpad dynamically only after you know that it will be needed */ return; }; + var feedback = common.feedback = function (action) { + if (!action) { return; } + try { + if (!getStore().getProxy().proxy.allowUserFeedback) { return; } + } catch (e) { return void console.error(e); } + + var href = '/common/feedback.html?' + action + '=' + (+new Date()); + $.ajax({ + type: "HEAD", + url: href, + }); + }; + + var reportAppUsage = common.reportAppUsage = function () { + var pattern = window.location.pathname.split('/') + .filter(function (x) { return x; }).join('.'); + feedback(pattern); + }; + var getUid = common.getUid = function () { if (store) { if (store.getProxy() && store.getProxy().proxy) { From eb2a3c1bbfbed2efc22d3e6bc6c7bbc5bb601ee7 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 13 Apr 2017 15:04:17 +0200 Subject: [PATCH 008/306] Fix lint errors --- www/common/userObject.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/www/common/userObject.js b/www/common/userObject.js index d9669c408..c69be8de4 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -179,8 +179,6 @@ define([ // FIND - var compareFiles = function (fileA, fileB) { return fileA === fileB; }; - var findElement = function (root, pathInput) { if (!pathInput) { error("Invalid path:\n", pathInput, "\nin root\n", root); @@ -472,14 +470,14 @@ define([ // Move to Trash if (isPathIn(newParentPath, [TRASH])) { if (!elementPath || elementPath.length < 2 || elementPath[0] === TRASH) { - debug("Can't move an element from the trash to the trash: ", path); + debug("Can't move an element from the trash to the trash: ", elementPath); return; } var key = elementPath[elementPath.length - 1]; - var name = isPathIn(elementPath, ['hrefArray']) ? getTitle(element) : key; + var elName = isPathIn(elementPath, ['hrefArray']) ? getTitle(element) : key; var parentPath = elementPath.slice(); parentPath.pop(); - pushToTrash(name, element, parentPath); + pushToTrash(elName, element, parentPath); return true; } // Move to hrefArray @@ -646,7 +644,7 @@ define([ files[TRASH][obj.name].splice(idx, 1); }); }; - var deleteMultiplePermanently = function (paths) { + var deleteMultiplePermanently = function (paths, nocheck) { var hrefPaths = paths.filter(function(x) { return isPathIn(x, ['hrefArray']); }); var rootPaths = paths.filter(function(x) { return isPathIn(x, [ROOT]); }); var trashPaths = paths.filter(function(x) { return isPathIn(x, [TRASH]); }); @@ -689,10 +687,12 @@ define([ }); deleteMultipleTrashRoot(trashRoot); - checkDeletedFiles(); + // In some cases, we want to remove pads from a location without removing them from + // FILES_DATA (replaceHref) + if (!nocheck) { checkDeletedFiles(); } }; - var deletePath = exp.delete = function (paths, cb) { - deleteMultiplePermanently(paths); + var deletePath = exp.delete = function (paths, cb, nocheck) { + deleteMultiplePermanently(paths, nocheck); if (typeof cb === "function") { cb(); } }; var emptyTrash = exp.emptyTrash = function (cb) { @@ -754,7 +754,7 @@ define([ var allInTrash = true; paths.forEach(function (p) { if (p[0] === TRASH) { - removeFromTrash(p, null, true); // 3rd parameter means skip "checkDeletedFiles" + exp.delete(p, null, true); // 3rd parameter means skip "checkDeletedFiles" return; } else { allInTrash = false; From 2ad0a65b71e38fa588a3f678378b4b0dd2a921fa Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 13 Apr 2017 16:42:26 +0200 Subject: [PATCH 009/306] Remove shortcuts that override browser actions --- customize.dist/ckeditor-config.js | 20 ++++++++++++++++++++ www/code/main.js | 2 +- www/slide/main.js | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/customize.dist/ckeditor-config.js b/customize.dist/ckeditor-config.js index 38e1165e6..c1659300c 100644 --- a/customize.dist/ckeditor-config.js +++ b/customize.dist/ckeditor-config.js @@ -15,6 +15,26 @@ CKEDITOR.editorConfig = function( config ) { // jshint ignore:line config.fontSize_defaultLabel = '16px'; config.contentsCss = '/customize/ckeditor-contents.css'; + config.keystrokes = [ + [ CKEDITOR.ALT + 121 /*F10*/, 'toolbarFocus' ], + [ CKEDITOR.ALT + 122 /*F11*/, 'elementsPathFocus' ], + + [ CKEDITOR.SHIFT + 121 /*F10*/, 'contextMenu' ], + + [ CKEDITOR.CTRL + 90 /*Z*/, 'undo' ], + [ CKEDITOR.CTRL + 89 /*Y*/, 'redo' ], + [ CKEDITOR.CTRL + CKEDITOR.SHIFT + 90 /*Z*/, 'redo' ], + + [ CKEDITOR.CTRL + CKEDITOR.SHIFT + 76 /*L*/, 'link' ], + [ CKEDITOR.CTRL + 76 /*L*/, undefined ], + + [ CKEDITOR.CTRL + 66 /*B*/, 'bold' ], + [ CKEDITOR.CTRL + 73 /*I*/, 'italic' ], + [ CKEDITOR.CTRL + 85 /*U*/, 'underline' ], + + [ CKEDITOR.ALT + 109 /*-*/, 'toolbarCollapse' ] + ]; + //skin: 'moono-cryptpad,/pad/themes/moono-cryptpad/' //skin: 'flat,/pad/themes/flat/' //skin: 'moono-lisa,/pad/themes/moono-lisa/' diff --git a/www/code/main.js b/www/code/main.js index bdad4cdb6..fe999c6c0 100644 --- a/www/code/main.js +++ b/www/code/main.js @@ -63,7 +63,7 @@ define([ styleActiveLine : true, search: true, highlightSelectionMatches: {showToken: /\w+/}, - extraKeys: {"Ctrl-Q": function(cm){ cm.foldCode(cm.getCursor()); }}, + extraKeys: {"Shift-Ctrl-R": undefined}, foldGutter: true, gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], mode: "javascript", diff --git a/www/slide/main.js b/www/slide/main.js index 9acc6527e..995d8b767 100644 --- a/www/slide/main.js +++ b/www/slide/main.js @@ -83,7 +83,7 @@ define([ styleActiveLine : true, search: true, highlightSelectionMatches: {showToken: /\w+/}, - extraKeys: {"Ctrl-Q": function(cm){ cm.foldCode(cm.getCursor()); }}, + extraKeys: {"Shift-Ctrl-R": undefined}, foldGutter: true, gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], mode: "javascript", From 81d83897ecc59387dd37a36d6d1fe1f092478d12 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 13 Apr 2017 17:04:15 +0200 Subject: [PATCH 010/306] Rename hash.js --- www/common/{hash.js => common-hash.js} | 0 www/common/cryptpad-common.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename www/common/{hash.js => common-hash.js} (100%) diff --git a/www/common/hash.js b/www/common/common-hash.js similarity index 100% rename from www/common/hash.js rename to www/common/common-hash.js diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 2e78da45d..e1e02bf05 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -3,7 +3,7 @@ define([ '/customize/messages.js', '/common/fsStore.js', '/common/common-util.js', - '/common/hash.js', + '/common/common-hash.js', '/bower_components/alertifyjs/dist/js/alertify.js', '/common/clipboard.js', From 3e421c7289ebd8513a94cae9f4c88d1f39f6bad7 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 13 Apr 2017 17:09:35 +0200 Subject: [PATCH 011/306] Fix lint errors in ckeditor config --- customize.dist/ckeditor-config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/customize.dist/ckeditor-config.js b/customize.dist/ckeditor-config.js index c1659300c..4c55f67fc 100644 --- a/customize.dist/ckeditor-config.js +++ b/customize.dist/ckeditor-config.js @@ -1,4 +1,5 @@ -CKEDITOR.editorConfig = function( config ) { // jshint ignore:line +/* global CKEDITOR */ +CKEDITOR.editorConfig = function( config ) { var fixThings = false; // https://dev.ckeditor.com/ticket/10907 config.needsBrFiller= fixThings; From 633d60ccd0fa73772c7051c31849cef83530b12d Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 13 Apr 2017 17:19:32 +0200 Subject: [PATCH 012/306] Remove a TODO --- www/common/cryptpad-common.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index e1e02bf05..8a64d66c4 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1407,8 +1407,6 @@ load pinpad dynamically only after you know that it will be needed */ console.log('RPC handshake complete'); rpc = common.rpc = env.rpc = call; - // TODO check if pin list is up to date - // if not, reset common.arePinsSynced(function (err, yes) { if (!yes) { common.resetPins(function (err, hash) { From 40c06c4515341a45db212c5a993a24f8235c973f Mon Sep 17 00:00:00 2001 From: Pierre Bondoerffer Date: Thu, 13 Apr 2017 17:42:38 +0200 Subject: [PATCH 013/306] Spanish! --- customize.dist/translations/messages.es.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/customize.dist/translations/messages.es.js b/customize.dist/translations/messages.es.js index cfe682c48..e4167ad00 100644 --- a/customize.dist/translations/messages.es.js +++ b/customize.dist/translations/messages.es.js @@ -290,7 +290,7 @@ define(function () { out.fm_categoryError = "No se pudo abrir la categoría seleccionada, mostrando la raíz."; out.settings_userFeedbackHint1 = "CryptPad suministra informaciones muy básicas al servidor, para ayudarnos a mejorar vuestra experiencia."; out.settings_userFeedbackHint2 = "El contenido de tu pad nunca será compartido con el servidor."; - out.settings_userFeedback = "Activar feedback"; // "Disable user feedback" + out.settings_userFeedback = "Activar feedback"; out.settings_anonymous = "No has iniciado sesión. Tus ajustes se aplicarán solo a este navegador."; out.blog = "Blog"; out.initialState = "

Esto es CryptPad, el editor collaborativo en tiempo real zero knowledge.
Lo que escribes aquí es cifrado, con lo cual solo las personas con el enlace pueden accederlo.
Incluso el servido no puede ver lo que escribes.

Lo que ves aquí, lo que escuchas aquí, cuando sales, se queda aquí

 

"; @@ -356,5 +356,24 @@ define(function () { out.register_warning = "Zero Knowledge significa que no podemos recuperar tus datos si pierdes tu contraseña."; out.register_alreadyRegistered = "Este usuario ya existe, ¿iniciar sesión?"; + // 1.4.0 - Easter Bunny + + out.button_newwhiteboard = "Nueva Pizarra"; + out.wrongApp = "No se pudo mostrar el contenido de la sessión en tiempo real en tu navigador. Por favor, actualiza la página."; + out.synced = "Todo está guardado."; + out.saveTemplateButton = "Guardar como plantilla"; + out.saveTemplatePrompt = "Élige un título para la plantilla"; + out.templateSaved = "¡Plantilla guardada!" + out.selectTemplate = "Élige una plantilla o pulsa ESC"; + out.slideOptionsTitle = "Personaliza tus diapositivas"; + out.slideOptionsButton = "Guardar (enter)"; + out.canvas_clear = "Limpiar"; + out.canvas_delete = "Borrar selección"; + out.canvas_disable = "No permitir dibujos"; + out.canvas_enable = "Permitir dibujos"; + out.canvas_width = "Talla"; + out.canvas_opacity = "Opacidad"; + out.settings_publicSigningKey = "Clave de Firma Pública"; + return out; }); From 65e848fd4d729da6d733f88962bb35fd3dbd8c59 Mon Sep 17 00:00:00 2001 From: Pierre Bondoerffer Date: Thu, 13 Apr 2017 17:50:28 +0200 Subject: [PATCH 014/306] linter linted the lints --- customize.dist/translations/messages.es.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/customize.dist/translations/messages.es.js b/customize.dist/translations/messages.es.js index e4167ad00..aafa9bf70 100644 --- a/customize.dist/translations/messages.es.js +++ b/customize.dist/translations/messages.es.js @@ -363,7 +363,7 @@ define(function () { out.synced = "Todo está guardado."; out.saveTemplateButton = "Guardar como plantilla"; out.saveTemplatePrompt = "Élige un título para la plantilla"; - out.templateSaved = "¡Plantilla guardada!" + out.templateSaved = "¡Plantilla guardada!"; out.selectTemplate = "Élige una plantilla o pulsa ESC"; out.slideOptionsTitle = "Personaliza tus diapositivas"; out.slideOptionsButton = "Guardar (enter)"; From 50277cb0ae925e545d066b42d303d4c96b3ccf2f Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 13 Apr 2017 19:46:38 +0200 Subject: [PATCH 015/306] don't rely on 'info' in onRemote so we can call it anywhere --- www/code/main.js | 2 +- www/pad/main.js | 6 +++--- www/slide/main.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/www/code/main.js b/www/code/main.js index fe999c6c0..2b6553cf0 100644 --- a/www/code/main.js +++ b/www/code/main.js @@ -646,7 +646,7 @@ define([ return cursor; }; - var onRemote = config.onRemote = function (info) { + var onRemote = config.onRemote = function () { if (initializing) { return; } var scroll = editor.getScrollInfo(); diff --git a/www/pad/main.js b/www/pad/main.js index 4d9cd0244..443463b1d 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -475,12 +475,12 @@ define([ } }; - var onRemote = realtimeOptions.onRemote = function (info) { + var onRemote = realtimeOptions.onRemote = function () { if (initializing) { return; } var oldShjson = stringifyDOM(inner); - var shjson = info.realtime.getUserDoc(); + var shjson = module.realtime.getUserDoc(); // remember where the cursor is cursor.update(); @@ -679,7 +679,7 @@ define([ module.users = info.userList.users; module.realtime = info.realtime; - var shjson = info.realtime.getUserDoc(); + var shjson = module.realtime.getUserDoc(); var newPad = false; if (shjson === '') { newPad = true; } diff --git a/www/slide/main.js b/www/slide/main.js index 995d8b767..5654f43ab 100644 --- a/www/slide/main.js +++ b/www/slide/main.js @@ -840,7 +840,7 @@ define([ return cursor; }; - var onRemote = config.onRemote = function (info) { + var onRemote = config.onRemote = function () { if (initializing) { return; } var scroll = editor.getScrollInfo(); From 520ae822fc54f3c6888ef3d9387430a00be54439 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 13 Apr 2017 19:46:52 +0200 Subject: [PATCH 016/306] correct undefined reference --- www/common/cryptpad-common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index e1e02bf05..4ec0fabae 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -54,7 +54,7 @@ load pinpad dynamically only after you know that it will be needed */ var fixFileName = common.fixFileName = Util.fixFileName; // import hash utilities for export - var createRandomHash = Hash.createRandomHash; + var createRandomHash = common.createRandomHash = Hash.createRandomHash; var parsePadUrl = common.parsePadUrl = Hash.parsePadUrl; var isNotStrongestStored = common.isNotStrongestStored = Hash.isNotStrongestStored; var hrefToHexChannelId = common.hrefToHexChannelId = Hash.hrefToHexChannelId; From f42da4ecd83a674855057e12ae7a66ce31c7b0e8 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 14 Apr 2017 11:40:28 +0200 Subject: [PATCH 017/306] move hashChannelList into common-hash.js --- www/common/common-hash.js | 11 ++++++++- www/common/cryptpad-common.js | 2 +- www/common/pinpad.js | 42 ++++++++++++++++++++++++----------- 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 0c535ab18..034ee5bbb 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -1,13 +1,23 @@ define([ '/common/common-util.js', '/bower_components/chainpad-crypto/crypto.js', + '/bower_components/tweetnacl/nacl-fast.min.js' ], function (Util, Crypto) { + var Nacl = window.nacl; + var Hash = {}; var uint8ArrayToHex = Util.uint8ArrayToHex; var hexToBase64 = Util.hexToBase64; var base64ToHex = Util.base64ToHex; + // This implementation must match that on the server + // it's used for a checksum + Hash.hashChannelList = function (list) { + return Nacl.util.encodeBase64(Nacl.hash(Nacl.util + .decodeUTF8(JSON.stringify(list)))); + }; + var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) { if (typeof keys === 'string') { return chanKey + keys; @@ -231,6 +241,5 @@ define([ return hex; }; - return Hash; }); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 67609e2dc..919177e33 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -726,7 +726,7 @@ load pinpad dynamically only after you know that it will be needed */ if (!pinsReady()) { return void cb ('[RPC_NOT_READY]'); } var list = getCanonicalChannelList(); - var local = rpc.hashChannelList(list); + var local = Hash.hashChannelList(list); rpc.getServerHash(function (e, hash) { if (e) { return void cb(e); } cb(void 0, hash === local); diff --git a/www/common/pinpad.js b/www/common/pinpad.js index aa2cc532f..275a68666 100644 --- a/www/common/pinpad.js +++ b/www/common/pinpad.js @@ -1,9 +1,6 @@ define([ '/common/rpc.js', - '/bower_components/tweetnacl/nacl-fast.min.js' ], function (Rpc) { - var Nacl = window.nacl; - var create = function (network, proxy, cb) { if (!network) { window.setTimeout(function () { @@ -41,21 +38,28 @@ define([ // you can ask the server to pin a particular channel for you exp.pin = function (channels, cb) { + // TODO use isArray if it's safe + if (!channels && channels.length) { + window.setTimeout(function () { + cb('[TypeError] pin expects an array'); + }); + return; + } rpc.send('PIN', channels, cb); }; // you can also ask to unpin a particular channel exp.unpin = function (channels, cb) { + // TODO use isArray if it's safe + if (!channels && channels.length) { + window.setTimeout(function () { + cb('[TypeError] pin expects an array'); + }); + return; + } rpc.send('UNPIN', channels, cb); }; - // This implementation must match that on the server - // it's used for a checksum - exp.hashChannelList = function (list) { - return Nacl.util.encodeBase64(Nacl.hash(Nacl.util - .decodeUTF8(JSON.stringify(list)))); - }; - // ask the server what it thinks your hash is exp.getServerHash = function (cb) { rpc.send('GET_HASH', edPublic, function (e, hash) { @@ -67,8 +71,15 @@ define([ }; // if local and remote hashes don't match, send a reset - exp.reset = function (list, cb) { - rpc.send('RESET', list, function (e, response) { + exp.reset = function (channels, cb) { + // TODO use isArray if it's safe + if (!channels && channels.length) { + window.setTimeout(function () { + cb('[TypeError] pin expects an array'); + }); + return; + } + rpc.send('RESET', channels, function (e, response) { cb(e, response[0]); }); }; @@ -81,7 +92,12 @@ define([ // get the combined size of all channels (in bytes) for all the // channels which the server has pinned for your publicKey exp.getFileListSize = function (cb) { - rpc.send('GET_TOTAL_SIZE', undefined, cb); + rpc.send('GET_TOTAL_SIZE', undefined, function (e, response) { + if (e) { return void cb(e); } + if (response && response.length) { + cb(void 0, response[0]); + } + }); }; cb(e, exp); From 8f4dbcf4c6e7398f6709902cec3c79ae8d005ddd Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 14 Apr 2017 11:41:18 +0200 Subject: [PATCH 018/306] remove getPadTitle --- www/common/cryptpad-common.js | 26 -------------------------- www/poll/main.js | 9 --------- 2 files changed, 35 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 919177e33..dc903777c 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -562,32 +562,6 @@ load pinpad dynamically only after you know that it will be needed */ }); }; - // STORAGE - var getPadTitle = common.getPadTitle = function (cb) { - var href = window.location.href; - var parsed = parsePadUrl(window.location.href); - var hashSlice = window.location.hash.slice(1,9); // TODO remove - var title = ''; - - getRecentPads(function (err, pads) { - if (err) { - cb(err); - return; - } - pads.some(function (pad) { - var p = parsePadUrl(pad.href); - if (p.hash === parsed.hash && p.type === parsed.type) { - title = pad.title || hashSlice; - return true; - } - }); - - if (title === '') { title = getDefaultName(parsed, pads); } - - cb(void 0, title); - }); - }; - var errorHandlers = []; common.onError = function (h) { if (typeof h !== "function") { return; } diff --git a/www/poll/main.js b/www/poll/main.js index b474d41f2..acd4d0af2 100644 --- a/www/poll/main.js +++ b/www/poll/main.js @@ -781,15 +781,6 @@ define([ } Cryptpad.onDisplayNameChanged(setName); - - Cryptpad.getPadTitle(function (err, title) { - if (err) { - error(err); - debug("Couldn't get pad title"); - return; - } - updateTitle(title || defaultName); - }); }; // don't initialize until the store is ready. From 95789d3cbe5463f014fa4dee105d78626f201507 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 14 Apr 2017 11:41:51 +0200 Subject: [PATCH 019/306] expose rpc for pinned usage --- www/common/cryptpad-common.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index dc903777c..9218c0022 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -735,6 +735,11 @@ load pinpad dynamically only after you know that it will be needed */ }); }; + var getPinnedUsage = common.getPinnedUsage = function (cb) { + if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); } + rpc.getFileListSize(cb); + }; + var createButton = common.createButton = function (type, rightside, data, callback) { var button; var size = "17px"; From e10901edb28d9e17e3804946b21c2b5706fa3ec7 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 14 Apr 2017 12:09:00 +0200 Subject: [PATCH 020/306] begin to move interface code out of cryptpad-common --- www/common/common-interface.js | 134 ++++++++++++++++++++++++++++++++ www/common/cryptpad-common.js | 137 ++++----------------------------- 2 files changed, 148 insertions(+), 123 deletions(-) create mode 100644 www/common/common-interface.js diff --git a/www/common/common-interface.js b/www/common/common-interface.js new file mode 100644 index 000000000..e23756cec --- /dev/null +++ b/www/common/common-interface.js @@ -0,0 +1,134 @@ +define([ + '/customize/messages.js', + '/common/common-util.js', + '/customize/application_config.js', + '/bower_components/alertifyjs/dist/js/alertify.js', + '/bower_components/jquery/dist/jquery.min.js', +], function (Messages, Util, AppConfig, Alertify) { + var $ = window.jQuery; + + var UI = {}; + + /* + * Alertifyjs + */ + UI.Alertify = Alertify; + + // set notification timeout + Alertify._$$alertify.delay = AppConfig.notificationTimeout || 5000; + + var findCancelButton = UI.findCancelButton = function () { + return $('button.cancel'); + }; + + var findOKButton = UI.findOKButton = function () { + return $('button.ok'); + }; + + var listenForKeys = UI.listenForKeys = function (yes, no) { + var handler = function (e) { + switch (e.which) { + case 27: // cancel + if (typeof(no) === 'function') { no(e); } + no(); + break; + case 13: // enter + if (typeof(yes) === 'function') { yes(e); } + break; + } + }; + + $(window).keyup(handler); + return handler; + }; + + var stopListening = UI.stopListening = function (handler) { + $(window).off('keyup', handler); + }; + + UI.alert = function (msg, cb, force) { + cb = cb || function () {}; + if (force !== true) { msg = Util.fixHTML(msg); } + var close = function (e) { + findOKButton().click(); + }; + var keyHandler = listenForKeys(close, close); + Alertify.alert(msg, function (ev) { + cb(ev); + stopListening(keyHandler); + }); + window.setTimeout(function () { + findOKButton().focus(); + }); + }; + + UI.prompt = function (msg, def, cb, opt, force) { + opt = opt || {}; + cb = cb || function () {}; + if (force !== true) { msg = Util.fixHTML(msg); } + + var keyHandler = listenForKeys(function (e) { // yes + findOKButton().click(); + }, function (e) { // no + findCancelButton().click(); + }); + + Alertify + .defaultValue(def || '') + .okBtn(opt.ok || Messages.okButton || 'OK') + .cancelBtn(opt.cancel || Messages.cancelButton || 'Cancel') + .prompt(msg, function (val, ev) { + cb(val, ev); + stopListening(keyHandler); + }, function (ev) { + cb(null, ev); + stopListening(keyHandler); + }); + }; + + UI.confirm = function (msg, cb, opt, force, styleCB) { + opt = opt || {}; + cb = cb || function () {}; + if (force !== true) { msg = Util.fixHTML(msg); } + + var keyHandler = listenForKeys(function (e) { + findOKButton().click(); + }, function (e) { + findCancelButton().click(); + }); + + Alertify + .okBtn(opt.ok || Messages.okButton || 'OK') + .cancelBtn(opt.cancel || Messages.cancelButton || 'Cancel') + .confirm(msg, function () { + cb(true); + stopListening(keyHandler); + }, function () { + cb(false); + stopListening(keyHandler); + }); + + window.setTimeout(function () { + var $ok = findOKButton(); + var $cancel = findCancelButton(); + if (opt.okClass) { $ok.addClass(opt.okClass); } + if (opt.cancelClass) { $cancel.addClass(opt.cancelClass); } + if (opt.reverseOrder) { + $ok.insertBefore($ok.prev()); + } + if (typeof(styleCB) === 'function') { + styleCB($ok.closest('.dialog')); + } + }, 0); + }; + + UI.log = function (msg) { + Alertify.success(Util.fixHTML(msg)); + }; + + UI.warn = function (msg) { + Alertify.error(Util.fixHTML(msg)); + }; + + return UI; +}); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 9218c0022..75f7dad2d 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -4,15 +4,14 @@ define([ '/common/fsStore.js', '/common/common-util.js', '/common/common-hash.js', + '/common/common-interface.js', - '/bower_components/alertifyjs/dist/js/alertify.js', '/common/clipboard.js', - '/common/pinpad.js', /* TODO -load pinpad dynamically only after you know that it will be needed */ + '/common/pinpad.js', '/customize/application_config.js', '/bower_components/jquery/dist/jquery.min.js', -], function (Config, Messages, Store, Util, Hash, Alertify, Clipboard, Pinpad, AppConfig) { +], function (Config, Messages, Store, Util, Hash, UI, Clipboard, Pinpad, AppConfig) { /* This file exposes functionality which is specific to Cryptpad, but not to any particular pad type. This includes functions for committing metadata about pads to your local storage for future use and improved usability. @@ -23,7 +22,6 @@ load pinpad dynamically only after you know that it will be needed */ var common = window.Cryptpad = { Messages: Messages, - Alertify: Alertify, Clipboard: Clipboard }; @@ -42,6 +40,16 @@ load pinpad dynamically only after you know that it will be needed */ var store; var rpc; + // import UI elements + var findCancelButton = common.findCancelButton = UI.findCancelButton; + var findOKButton = common.findOKButton = UI.findOKButton; + var listenForKeys = common.listenForKeys = UI.listenForKeys; + var stopListening = common.stopListening = UI.stopListening; + common.prompt = UI.prompt; + common.confirm = UI.confirm; + common.log = UI.log; + common.warn = UI.warn; + // import common utilities for export var find = common.find = Util.find; var fixHTML = common.fixHTML = Util.fixHTML; @@ -1180,121 +1188,6 @@ load pinpad dynamically only after you know that it will be needed */ return $userAdmin; }; - /* - * Alertifyjs - */ - var findCancelButton = common.findCancelButton = function () { - return $('button.cancel'); - }; - - var findOKButton = common.findOKButton = function () { - return $('button.ok'); - }; - - var listenForKeys = common.listenForKeys = function (yes, no) { - var handler = function (e) { - switch (e.which) { - case 27: // cancel - if (typeof(no) === 'function') { no(e); } - no(); - break; - case 13: // enter - if (typeof(yes) === 'function') { yes(e); } - break; - } - }; - - $(window).keyup(handler); - return handler; - }; - - var stopListening = common.stopListening = function (handler) { - $(window).off('keyup', handler); - }; - - common.alert = function (msg, cb, force) { - cb = cb || function () {}; - if (force !== true) { msg = fixHTML(msg); } - var close = function (e) { - findOKButton().click(); - }; - var keyHandler = listenForKeys(close, close); - Alertify.alert(msg, function (ev) { - cb(ev); - stopListening(keyHandler); - }); - window.setTimeout(function () { - findOKButton().focus(); - }); - }; - - common.prompt = function (msg, def, cb, opt, force) { - opt = opt || {}; - cb = cb || function () {}; - if (force !== true) { msg = fixHTML(msg); } - - var keyHandler = listenForKeys(function (e) { // yes - findOKButton().click(); - }, function (e) { // no - findCancelButton().click(); - }); - - Alertify - .defaultValue(def || '') - .okBtn(opt.ok || Messages.okButton || 'OK') - .cancelBtn(opt.cancel || Messages.cancelButton || 'Cancel') - .prompt(msg, function (val, ev) { - cb(val, ev); - stopListening(keyHandler); - }, function (ev) { - cb(null, ev); - stopListening(keyHandler); - }); - }; - - common.confirm = function (msg, cb, opt, force, styleCB) { - opt = opt || {}; - cb = cb || function () {}; - if (force !== true) { msg = fixHTML(msg); } - - var keyHandler = listenForKeys(function (e) { - findOKButton().click(); - }, function (e) { - findCancelButton().click(); - }); - - Alertify - .okBtn(opt.ok || Messages.okButton || 'OK') - .cancelBtn(opt.cancel || Messages.cancelButton || 'Cancel') - .confirm(msg, function () { - cb(true); - stopListening(keyHandler); - }, function () { - cb(false); - stopListening(keyHandler); - }); - - window.setTimeout(function () { - var $ok = findOKButton(); - var $cancel = findCancelButton(); - if (opt.okClass) { $ok.addClass(opt.okClass); } - if (opt.cancelClass) { $cancel.addClass(opt.cancelClass); } - if (opt.reverseOrder) { - $ok.insertBefore($ok.prev()); - } - if (typeof(styleCB) === 'function') { - styleCB($ok.closest('.dialog')); - } - }, 0); - }; - - common.log = function (msg) { - Alertify.success(fixHTML(msg)); - }; - - common.warn = function (msg) { - Alertify.error(fixHTML(msg)); - }; /* * spinner @@ -1351,7 +1244,7 @@ load pinpad dynamically only after you know that it will be needed */ $(function() { // Race condition : if document.body is undefined when alertify.js is loaded, Alertify // won't work. We have to reset it now to make sure it uses a correct "body" - Alertify.reset(); + UI.Alertify.reset(); // Load the new pad when the hash has changed var oldHash = document.location.hash.slice(1); @@ -1425,7 +1318,5 @@ load pinpad dynamically only after you know that it will be needed */ Messages._applyTranslation(); }); - Alertify._$$alertify.delay = AppConfig.notificationTimeout || 5000; - return common; }); From a4157b9908ec8a65cf9b9461742a2a782c1f82e6 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 14 Apr 2017 12:14:10 +0200 Subject: [PATCH 021/306] remove more unused functions --- www/common/cryptpad-common.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 75f7dad2d..68d5204fb 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -224,14 +224,6 @@ define([ var isArray = common.isArray = $.isArray; - var truncate = common.truncate = function (text, len) { - if (typeof(text) === 'string' && text.length > len) { - return text.slice(0, len) + '…'; - } - return text; - }; - - /* * localStorage formatting */ @@ -313,15 +305,6 @@ define([ }); }; - var isNameAvailable = function (title, parsed, pads) { - return !pads.some(function (pad) { - // another pad is already using that title - if (pad.title === title) { - return true; - } - }); - }; - // Create untitled documents when no name is given var getDefaultName = common.getDefaultName = function (parsed) { var type = parsed.type; From f694a81a5c37ebb11d39b9aadb7df3841717fbd2 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 14 Apr 2017 12:25:32 +0200 Subject: [PATCH 022/306] move more interface code out of cryptpad-common --- www/common/common-interface.js | 87 +++++++++++++++++++++++++++++++ www/common/cryptpad-common.js | 93 ++-------------------------------- 2 files changed, 92 insertions(+), 88 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index e23756cec..cc3f838f6 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -130,5 +130,92 @@ define([ Alertify.error(Util.fixHTML(msg)); }; + /* + * spinner + */ + UI.spinner = function (parent) { + var $target = $('', { + 'class': 'fa fa-spinner fa-pulse fa-4x fa-fw' + }).hide(); + + $(parent).append($target); + + return { + show: function () { + $target.show(); + return this; + }, + hide: function () { + $target.hide(); + return this; + }, + get: function () { + return $target; + }, + }; + }; + + var LOADING = 'loading'; + + var getRandomTip = function () { + if (!Messages.tips || !Object.keys(Messages.tips).length) { return ''; } + var keys = Object.keys(Messages.tips); + var rdm = Math.floor(Math.random() * keys.length); + return Messages.tips[keys[rdm]]; + }; + UI.addLoadingScreen = function (loadingText, hideTips) { + var $loading, $container; + if ($('#' + LOADING).length) { + $loading = $('#' + LOADING).show(); + if (loadingText) { + $('#' + LOADING).find('p').text(loadingText); + } + $container = $loading.find('.loadingContainer'); + } else { + $loading = $('
', {id: LOADING}); + $container = $('
', {'class': 'loadingContainer'}); + $container.append(''); + var $spinner = $('
', {'class': 'spinnerContainer'}); + UI.spinner($spinner).show(); + var $text = $('

').text(loadingText || Messages.loading); + $container.append($spinner).append($text); + $loading.append($container); + $('body').append($loading); + } + if (Messages.tips && !hideTips) { + var $loadingTip = $('

', {'id': 'loadingTip'}); + var $tip = $('', {'class': 'tips'}).text(getRandomTip()).appendTo($loadingTip); + $loadingTip.css({ + 'top': $('body').height()/2 + $container.height()/2 + 20 + 'px' + }); + $('body').append($loadingTip); + } + }; + UI.removeLoadingScreen = function (cb) { + $('#' + LOADING).fadeOut(750, cb); + $('#loadingTip').css('top', ''); + window.setTimeout(function () { + $('#loadingTip').fadeOut(750); + }, 3000); + }; + UI.errorLoadingScreen = function (error, transparent) { + if (!$('#' + LOADING).is(':visible')) { UI.addLoadingScreen(undefined, true); } + $('.spinnerContainer').hide(); + if (transparent) { $('#' + LOADING).css('opacity', 0.8); } + $('#' + LOADING).find('p').html(error || Messages.error); + }; + + var importContent = UI.importContent = function (type, f) { + return function () { + var $files = $('').click(); + $files.on('change', function (e) { + var file = e.target.files[0]; + var reader = new FileReader(); + reader.onload = function (e) { f(e.target.result, file); }; + reader.readAsText(file, type); + }); + }; + }; + return UI; }); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 68d5204fb..620445bce 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -31,7 +31,6 @@ define([ var userNameKey = common.userNameKey = 'User_name'; var fileHashKey = common.fileHashKey = 'FS_hash'; var displayNameKey = common.displayNameKey = 'cryptpad.username'; - var LOADING = 'loading'; var newPadNameKey = common.newPadNameKey = "newPadName"; var newPadPathKey = common.newPadPathKey = "newPadPath"; var storageKey = common.storageKey = 'CryptPad_RECENTPADS'; @@ -49,6 +48,10 @@ define([ common.confirm = UI.confirm; common.log = UI.log; common.warn = UI.warn; + common.spinner = UI.spinner; + common.addLoadingScreen = UI.addLoadingScreen; + common.removeLoadingScreen = UI.removeLoadingScreen; + common.errorLoadingScreen = UI.errorLoadingScreen; // import common utilities for export var find = common.find = Util.find; @@ -155,7 +158,6 @@ define([ return url; }; - var login = common.login = function (hash, name, cb) { if (!hash) { throw new Error('expected a user hash'); } if (!name) { throw new Error('expected a user name'); } @@ -566,66 +568,6 @@ define([ }); }; - var getRandomTip = function () { - if (!Messages.tips || !Object.keys(Messages.tips).length) { return ''; } - var keys = Object.keys(Messages.tips); - var rdm = Math.floor(Math.random() * keys.length); - return Messages.tips[keys[rdm]]; - }; - common.addLoadingScreen = function (loadingText, hideTips) { - var $loading, $container; - if ($('#' + LOADING).length) { - $loading = $('#' + LOADING).show(); - if (loadingText) { - $('#' + LOADING).find('p').text(loadingText); - } - $container = $loading.find('.loadingContainer'); - } else { - $loading = $('
', {id: LOADING}); - $container = $('
', {'class': 'loadingContainer'}); - $container.append(''); - var $spinner = $('
', {'class': 'spinnerContainer'}); - common.spinner($spinner).show(); - var $text = $('

').text(loadingText || Messages.loading); - $container.append($spinner).append($text); - $loading.append($container); - $('body').append($loading); - } - if (Messages.tips && !hideTips) { - var $loadingTip = $('

', {'id': 'loadingTip'}); - var $tip = $('', {'class': 'tips'}).text(getRandomTip()).appendTo($loadingTip); - $loadingTip.css({ - 'top': $('body').height()/2 + $container.height()/2 + 20 + 'px' - }); - $('body').append($loadingTip); - } - }; - common.removeLoadingScreen = function (cb) { - $('#' + LOADING).fadeOut(750, cb); - $('#loadingTip').css('top', ''); - window.setTimeout(function () { - $('#loadingTip').fadeOut(750); - }, 3000); - }; - common.errorLoadingScreen = function (error, transparent) { - if (!$('#' + LOADING).is(':visible')) { common.addLoadingScreen(undefined, true); } - $('.spinnerContainer').hide(); - if (transparent) { $('#' + LOADING).css('opacity', 0.8); } - $('#' + LOADING).find('p').html(error || Messages.error); - }; - - var importContent = common.importContent = function (type, f) { - return function () { - var $files = $('').click(); - $files.on('change', function (e) { - var file = e.target.files[0]; - var reader = new FileReader(); - reader.onload = function (e) { f(e.target.result, file); }; - reader.readAsText(file, type); - }); - }; - }; - /* * Buttons */ @@ -748,7 +690,7 @@ define([ title: Messages.importButtonTitle, }).append($('', {'class':'fa fa-upload', style: 'font:'+size+' FontAwesome'})); if (callback) { - button.click(common.importContent('text/plain', function (content, file) { + button.click(UI.importContent('text/plain', function (content, file) { callback(content, file); })); } @@ -1172,31 +1114,6 @@ define([ }; - /* - * spinner - */ - common.spinner = function (parent) { - var $target = $('', { - 'class': 'fa fa-spinner fa-pulse fa-4x fa-fw' - }).hide(); - - $(parent).append($target); - - return { - show: function () { - $target.show(); - return this; - }, - hide: function () { - $target.hide(); - return this; - }, - get: function () { - return $target; - }, - }; - }; - // local name? common.ready = function (f) { var block = 0; From 525d35c9ece0e9ce43531fee90781a5b49544dee Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 14 Apr 2017 14:54:43 +0200 Subject: [PATCH 023/306] redefine alert --- www/common/cryptpad-common.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 620445bce..ffabcd1f2 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -46,6 +46,7 @@ define([ var stopListening = common.stopListening = UI.stopListening; common.prompt = UI.prompt; common.confirm = UI.confirm; + common.alert = UI.alert; common.log = UI.log; common.warn = UI.warn; common.spinner = UI.spinner; From fbebbf0dcafd5ab5d48409da180464beab9ba5f5 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 14 Apr 2017 15:32:12 +0200 Subject: [PATCH 024/306] minor clean up --- www/common/cryptpad-common.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index ffabcd1f2..c63c43f62 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -119,18 +119,14 @@ define([ }; var getUid = common.getUid = function () { - if (store) { - if (store.getProxy() && store.getProxy().proxy) { - return store.getProxy().proxy.uid; - } + if (store && store.getProxy() && store.getProxy().proxy) { + return store.getProxy().proxy.uid; } }; var getRealtime = common.getRealtime = function () { - if (store) { - if (store.getProxy() && store.getProxy().info) { + if (store && store.getProxy() && store.getProxy().info) { return store.getProxy().info.realtime; - } } return; }; @@ -255,7 +251,7 @@ define([ var migrateRecentPads = common.migrateRecentPads = function (pads) { return pads.map(function (pad) { var hash; - if (isArray(pad)) { + if (Array.isArray(pad)) { var href = pad[0]; href.replace(/\#(.*)$/, function (a, h) { hash = h; @@ -298,7 +294,7 @@ define([ if (err) { cb(err, null); return; } legacy.get(storageKey, function (err2, recentPads) { if (err2) { cb(err2, null); return; } - if (isArray(recentPads)) { + if (Array.isArray(recentPads)) { cb(void 0, migrateRecentPads(recentPads)); return; } @@ -421,7 +417,7 @@ define([ /* fetch and migrate your pad history from the store */ var getRecentPads = common.getRecentPads = function (cb) { getStore().getDrive(storageKey, function (err, recentPads) { - if (isArray(recentPads)) { + if (Array.isArray(recentPads)) { checkRecentPads(recentPads); cb(void 0, recentPads); return; @@ -822,7 +818,7 @@ define([ // // allowed options tags: ['a', 'hr', 'p'] var createDropdown = common.createDropdown = function (config) { - if (typeof config !== "object" || !isArray(config.options)) { return; } + if (typeof config !== "object" || !Array.isArray(config.options)) { return; } var allowedTags = ['a', 'p', 'hr']; var isValidOption = function (o) { From c7eae9675bf07d70b9f269e230029af9c8fcee45 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 14 Apr 2017 15:34:05 +0200 Subject: [PATCH 025/306] keep exporting isArray for backwards compatibility but prevent internal use --- www/common/cryptpad-common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index c63c43f62..cc30c6803 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -221,7 +221,7 @@ define([ return typeof getUserHash() === "string"; }; - var isArray = common.isArray = $.isArray; + common.isArray = $.isArray; /* * localStorage formatting From 402ce2db93fb56b4f2e7874b7231e94fdbb24958 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 14 Apr 2017 15:34:22 +0200 Subject: [PATCH 026/306] better validation of inputs --- www/common/pinpad.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/www/common/pinpad.js b/www/common/pinpad.js index 275a68666..a9467cff9 100644 --- a/www/common/pinpad.js +++ b/www/common/pinpad.js @@ -38,8 +38,7 @@ define([ // you can ask the server to pin a particular channel for you exp.pin = function (channels, cb) { - // TODO use isArray if it's safe - if (!channels && channels.length) { + if (!Array.isArray(channels)) { window.setTimeout(function () { cb('[TypeError] pin expects an array'); }); @@ -50,8 +49,7 @@ define([ // you can also ask to unpin a particular channel exp.unpin = function (channels, cb) { - // TODO use isArray if it's safe - if (!channels && channels.length) { + if (!Array.isArray(channels)) { window.setTimeout(function () { cb('[TypeError] pin expects an array'); }); @@ -72,8 +70,7 @@ define([ // if local and remote hashes don't match, send a reset exp.reset = function (channels, cb) { - // TODO use isArray if it's safe - if (!channels && channels.length) { + if (!Array.isArray(channels)) { window.setTimeout(function () { cb('[TypeError] pin expects an array'); }); From a27cc76fc0c9ce5ab695f276a6628bfeb49c7642 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 14 Apr 2017 15:35:02 +0200 Subject: [PATCH 027/306] uppercase LOL --- www/common/themes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/themes.js b/www/common/themes.js index 76ae740f5..3df706c88 100644 --- a/www/common/themes.js +++ b/www/common/themes.js @@ -22,7 +22,7 @@ define(function () { "isotope isotope.css", "lesser-dark lesser-dark.css", "liquibyte liquibyte.css", - "lol lol.css", + "LOL lol.css", "material material.css", "mbo mbo.css", "mdn-like mdn-like.css", From 5a83759ee21d4b63e56ece984909d16ddf505371 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 14 Apr 2017 17:18:17 +0200 Subject: [PATCH 028/306] add usage button to settings page --- www/settings/main.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/www/settings/main.js b/www/settings/main.js index 64bf15f7b..cee0772b0 100644 --- a/www/settings/main.js +++ b/www/settings/main.js @@ -227,6 +227,40 @@ define([ return $div; }; + var bytesToMegabytes = function (bytes) { + return Math.floor((bytes / (1024 * 1024) * 100)) / 100; + }; + + var createUsageButton = function (obj) { + var proxy = obj.proxy; + + var $div = $('
', { 'class': 'pinned-usage' }) + .text(Messages.settings_usageTitle) + .append('
'); + + $('
- + diff --git a/customize.dist/contact.html b/customize.dist/contact.html index b653529c4..619f98df4 100644 --- a/customize.dist/contact.html +++ b/customize.dist/contact.html @@ -111,7 +111,7 @@
- + diff --git a/customize.dist/index.html b/customize.dist/index.html index e05858e37..7aa29d8e9 100644 --- a/customize.dist/index.html +++ b/customize.dist/index.html @@ -233,7 +233,7 @@
- + diff --git a/customize.dist/privacy.html b/customize.dist/privacy.html index 203bb1008..8d2e09f5f 100644 --- a/customize.dist/privacy.html +++ b/customize.dist/privacy.html @@ -132,7 +132,7 @@ - + diff --git a/customize.dist/src/fragments/footer.html b/customize.dist/src/fragments/footer.html index 0882cecbd..ec80ed27d 100644 --- a/customize.dist/src/fragments/footer.html +++ b/customize.dist/src/fragments/footer.html @@ -39,5 +39,5 @@ - + diff --git a/customize.dist/terms.html b/customize.dist/terms.html index 391020c07..73a13cea7 100644 --- a/customize.dist/terms.html +++ b/customize.dist/terms.html @@ -115,7 +115,7 @@ - + diff --git a/www/settings/index.html b/www/settings/index.html index 56d77905e..a84a60963 100644 --- a/www/settings/index.html +++ b/www/settings/index.html @@ -105,7 +105,7 @@ - + From d1fa68e30dc7b89e81d5b8694b15548b9901da18 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 24 Apr 2017 17:13:32 +0200 Subject: [PATCH 067/306] build UCF Media Tag --- www/common/media-tag.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 www/common/media-tag.js diff --git a/www/common/media-tag.js b/www/common/media-tag.js new file mode 100644 index 000000000..d2459d7ba --- /dev/null +++ b/www/common/media-tag.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.mediaTag=t():e.mediaTag=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=68)}([function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0;n1?{modules:t}:null}},{key:"findModuleChain",value:function(t){var n=e.findRequiredModuleIdentifiers(t),r=n.map(function(e){var t=u.getFilter(e)||a.getPlugin(e);if(t)return t;throw new Error("Module "+e+" not found")}),i=e.searchLogicCollision(r);if(i){var o=i.modules.map(function(e){return e.identifier});throw new Error("Has collision between ["+o+"]'s logics")}return r}},{key:"startup",value:function(t){if(o.hasLogics()===!1)return void console.warn("Guidelines havn't logics ... The orchestrator cannot do anything !");var n=t.getId();e.chains[n]=e.findModuleChain(t),e.history[n]={identifiers:[e.findRequiredModuleIdentifiers(t)],cycles:{}},e.chains[n].length>0&&e.run(t)}},{key:"coherence",value:function(t){var n=t.getId(),r=e.history[n].identifiers.length-1;if(Object.keys(e.history[n].cycles).some(function(t){return e.history[n].cycles[t]>e.allowedCycles}))throw console.log(e.history[n]),new Error("Maximum cycles exceed "+e.history[n].cycles);var i=e.history[n].identifiers[r-1].every(function(t){return e.history[n].identifiers[r].includes(t)})||e.history[n].identifiers[r-1][0]===e.history[n].identifiers[r][0],o=e.history[n].identifiers[r][0];i&&(console.info('Chain has not evolved since the last module execution, the module "%s" is skipped',o),e.chains[n].shift())}},{key:"run",value:function(t){var n=t.getId();e.chains[n][0]?e.chains[n][0].startup(t):console.warn("Attempts to run a empty chain ...","History : ",e.history[n])}},{key:"chain",value:function(t){e.update(t),e.coherence(t),e.run(t)}},{key:"update",value:function(t){var n=t.getId(),r=e.chains[n].shift();e.findModuleChain(t).filter(function(t){return!e.chains[n].includes(t)}).forEach(function(t){e.chains[n].unshift(t)});var i=e.history[n].cycles[r.identifier];e.history[n].cycles[r.identifier]="number"==typeof i?i+1:1;var o=e.chains[n].map(function(e){return e.identifier});e.history[n].identifiers.push(o)}}]),e}();c.chains={},c.history={},c.allowedCycles=1,c.forbiddenIdentifiers=[],c.printHistory=function(){var e=String();Object.keys(c.history).forEach(function(t){var n=c.history[t];e+="Cycle["+t+"]: \n",Object.keys(n.cycles).forEach(function(t){e+=t+": "+n.cycles[t]+"\n"}),e+="Identifiers["+t+"]: \n",n.identifiers.forEach(function(t){e+="[",t.forEach(function(t){e+=t+", "}),e+="]",e=e.replace(", ]","]\n")})}),console.log(e)},e.exports=c},,,function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0;n0}}]),e}();o.logicsMap={},e.exports=o},function(e,t,n){"use strict";var r=n(11),i=n(27);r.registerPlugin(i);var o=n(29);r.registerPlugin(o);var u=n(25);r.registerPlugin(u);var a=n(26);r.registerPlugin(a);var c=n(28);r.registerPlugin(c);var s=n(15);r.registerFilter(s);var l=n(14);r.registerFilter(l);var f=n(13);r.registerFilter(f);n(16);e.exports=r},,,function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function o(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u={PluginExists:function(e){function t(e){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,'Plugin with same "'+e.identifier+'" identifier found.'))}return o(t,e),t}(Error),TypeNotFound:function(e){function t(){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"Media Tag could not find the content type of an instance.}."))}return o(t,e),t}(Error),FilterExists:function(e){function t(e){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,'Filter with same "'+e.identifier+' identifier found."'))}return o(t,e),t}(Error),FetchFail:function(e){function t(e){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,'Could not fetch "'+e.url+'", received "'+e.status+": "+e.statusText+'".'))}return o(t,e),t}(Error),InvalidCryptoKey:function(e){function t(){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"Invalid cryptographic key."))}return o(t,e),t}(Error),InvalidCryptoLib:function(e){function t(){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"Invalid cryptographic algorithm name."))}return o(t,e),t}(Error),FailedCrypto:function(e){function t(e){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"Failed to decrypt file"+(e&&e.message?" "+e.message:"")+"."))}return o(t,e),t}(Error)};e.exports=u},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0;n1?t[0]:window.location.protocol}},{key:"hostname",value:function(e){var t=e.getAttribute("src").split("://");return t.length>1?t[1].split("/")[0]:window.location.hostname}},{key:"source",value:function(e){return e.getAttribute("src")}},{key:"parse",value:function(t){return{protocol:e.protocol(t),hostname:e.hostname(t),src:e.source(t),type:e.type(t),extension:e.extension(t),mime:e.mime(t)}}}]),e}();e.exports=o},function(e,t,n){"use strict";var r=n(0),i={identifier:"clear-key",startup:function(e){var t=e.getAttribute("data-clear-key"),n=t.substring(0,32),i=t.substring(33,65);e.setAttribute("id",n),e.setAttribute("key",i),e.removeAttribute("data-clear-key"),r.chain(e)}};e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0;n Date: Tue, 25 Apr 2017 11:01:19 +0200 Subject: [PATCH 068/306] bump version to 1.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48aed5d0d..ed76592b6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "1.4.0", + "version": "1.5.0", "dependencies": { "express": "~4.10.1", "ws": "^1.0.1", From a840fb9e85095c54bfab2b92b1050d9d267c1b5d Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 25 Apr 2017 14:33:53 +0200 Subject: [PATCH 069/306] add example hashes and start working on hash v2 --- www/common/common-hash.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 034ee5bbb..40fe6bc7b 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -117,6 +117,9 @@ define([ throw new Error("The channel key is invalid"); } } + } else if (version === "2") { + // version 2 hashes are to be used for encrypted blobs + // TODO } } } @@ -150,6 +153,15 @@ define([ return '/1/edit/' + [channelId, key].join('/'); }; +/* +Version 0 + /pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy +Version 1 + /code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI +Version 2 + /file//#/2// + /file//#/2/ajExFODrFH4lVBwxxsrOKw/pdf +*/ var parseHash = Hash.parseHash = function (hash) { var parsed = {}; if (hash.slice(0,1) !== '/' && hash.length >= 56) { From c9c7d61ad1300fa36d921e2ec48fb8f8f9d0f989 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 25 Apr 2017 14:55:38 +0200 Subject: [PATCH 070/306] Media tag viewer --- www/common/media-tag.js | 2 +- www/common/toolbar.js | 61 +++++++++++-------- www/examples/file/assets/image.png-encrypted | Bin 0 -> 27455 bytes www/examples/file/index.html | 47 ++++++++++++++ www/examples/file/inner.html | 25 ++++++++ www/examples/file/main.js | 52 ++++++++++++++++ 6 files changed, 159 insertions(+), 28 deletions(-) create mode 100644 www/examples/file/assets/image.png-encrypted create mode 100644 www/examples/file/index.html create mode 100644 www/examples/file/inner.html create mode 100644 www/examples/file/main.js diff --git a/www/common/media-tag.js b/www/common/media-tag.js index d2459d7ba..ad8600274 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.mediaTag=t():e.mediaTag=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=68)}([function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0;n1?{modules:t}:null}},{key:"findModuleChain",value:function(t){var n=e.findRequiredModuleIdentifiers(t),r=n.map(function(e){var t=u.getFilter(e)||a.getPlugin(e);if(t)return t;throw new Error("Module "+e+" not found")}),i=e.searchLogicCollision(r);if(i){var o=i.modules.map(function(e){return e.identifier});throw new Error("Has collision between ["+o+"]'s logics")}return r}},{key:"startup",value:function(t){if(o.hasLogics()===!1)return void console.warn("Guidelines havn't logics ... The orchestrator cannot do anything !");var n=t.getId();e.chains[n]=e.findModuleChain(t),e.history[n]={identifiers:[e.findRequiredModuleIdentifiers(t)],cycles:{}},e.chains[n].length>0&&e.run(t)}},{key:"coherence",value:function(t){var n=t.getId(),r=e.history[n].identifiers.length-1;if(Object.keys(e.history[n].cycles).some(function(t){return e.history[n].cycles[t]>e.allowedCycles}))throw console.log(e.history[n]),new Error("Maximum cycles exceed "+e.history[n].cycles);var i=e.history[n].identifiers[r-1].every(function(t){return e.history[n].identifiers[r].includes(t)})||e.history[n].identifiers[r-1][0]===e.history[n].identifiers[r][0],o=e.history[n].identifiers[r][0];i&&(console.info('Chain has not evolved since the last module execution, the module "%s" is skipped',o),e.chains[n].shift())}},{key:"run",value:function(t){var n=t.getId();e.chains[n][0]?e.chains[n][0].startup(t):console.warn("Attempts to run a empty chain ...","History : ",e.history[n])}},{key:"chain",value:function(t){e.update(t),e.coherence(t),e.run(t)}},{key:"update",value:function(t){var n=t.getId(),r=e.chains[n].shift();e.findModuleChain(t).filter(function(t){return!e.chains[n].includes(t)}).forEach(function(t){e.chains[n].unshift(t)});var i=e.history[n].cycles[r.identifier];e.history[n].cycles[r.identifier]="number"==typeof i?i+1:1;var o=e.chains[n].map(function(e){return e.identifier});e.history[n].identifiers.push(o)}}]),e}();c.chains={},c.history={},c.allowedCycles=1,c.forbiddenIdentifiers=[],c.printHistory=function(){var e=String();Object.keys(c.history).forEach(function(t){var n=c.history[t];e+="Cycle["+t+"]: \n",Object.keys(n.cycles).forEach(function(t){e+=t+": "+n.cycles[t]+"\n"}),e+="Identifiers["+t+"]: \n",n.identifiers.forEach(function(t){e+="[",t.forEach(function(t){e+=t+", "}),e+="]",e=e.replace(", ]","]\n")})}),console.log(e)},e.exports=c},,,function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0;n0}}]),e}();o.logicsMap={},e.exports=o},function(e,t,n){"use strict";var r=n(11),i=n(27);r.registerPlugin(i);var o=n(29);r.registerPlugin(o);var u=n(25);r.registerPlugin(u);var a=n(26);r.registerPlugin(a);var c=n(28);r.registerPlugin(c);var s=n(15);r.registerFilter(s);var l=n(14);r.registerFilter(l);var f=n(13);r.registerFilter(f);n(16);e.exports=r},,,function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function o(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u={PluginExists:function(e){function t(e){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,'Plugin with same "'+e.identifier+'" identifier found.'))}return o(t,e),t}(Error),TypeNotFound:function(e){function t(){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"Media Tag could not find the content type of an instance.}."))}return o(t,e),t}(Error),FilterExists:function(e){function t(e){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,'Filter with same "'+e.identifier+' identifier found."'))}return o(t,e),t}(Error),FetchFail:function(e){function t(e){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,'Could not fetch "'+e.url+'", received "'+e.status+": "+e.statusText+'".'))}return o(t,e),t}(Error),InvalidCryptoKey:function(e){function t(){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"Invalid cryptographic key."))}return o(t,e),t}(Error),InvalidCryptoLib:function(e){function t(){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"Invalid cryptographic algorithm name."))}return o(t,e),t}(Error),FailedCrypto:function(e){function t(e){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"Failed to decrypt file"+(e&&e.message?" "+e.message:"")+"."))}return o(t,e),t}(Error)};e.exports=u},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0;n1?t[0]:window.location.protocol}},{key:"hostname",value:function(e){var t=e.getAttribute("src").split("://");return t.length>1?t[1].split("/")[0]:window.location.hostname}},{key:"source",value:function(e){return e.getAttribute("src")}},{key:"parse",value:function(t){return{protocol:e.protocol(t),hostname:e.hostname(t),src:e.source(t),type:e.type(t),extension:e.extension(t),mime:e.mime(t)}}}]),e}();e.exports=o},function(e,t,n){"use strict";var r=n(0),i={identifier:"clear-key",startup:function(e){var t=e.getAttribute("data-clear-key"),n=t.substring(0,32),i=t.substring(33,65);e.setAttribute("id",n),e.setAttribute("key",i),e.removeAttribute("data-clear-key"),r.chain(e)}};e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0;n1?{modules:t}:null}},{key:"findModuleChain",value:function(t){var n=e.findRequiredModuleIdentifiers(t),r=n.map(function(e){var t=u.getFilter(e)||a.getPlugin(e);if(t)return t;throw new Error("Module "+e+" not found")}),i=e.searchLogicCollision(r);if(i){var o=i.modules.map(function(e){return e.identifier});throw new Error("Has collision between ["+o+"]'s logics")}return r}},{key:"startup",value:function(t){if(o.hasLogics()===!1)return void console.warn("Guidelines havn't logics ... The orchestrator cannot do anything !");var n=t.getId();e.chains[n]=e.findModuleChain(t),e.history[n]={identifiers:[e.findRequiredModuleIdentifiers(t)],cycles:{}},e.chains[n].length>0&&e.run(t)}},{key:"coherence",value:function(t){var n=t.getId(),r=e.history[n].identifiers.length-1;if(Object.keys(e.history[n].cycles).some(function(t){return e.history[n].cycles[t]>e.allowedCycles}))throw console.log(e.history[n]),new Error("Maximum cycles exceed "+e.history[n].cycles);var i=e.history[n].identifiers[r-1].every(function(t){return e.history[n].identifiers[r].includes(t)})||e.history[n].identifiers[r-1][0]===e.history[n].identifiers[r][0],o=e.history[n].identifiers[r][0];i&&(console.info('Chain has not evolved since the last module execution, the module "%s" is skipped',o),e.chains[n].shift())}},{key:"run",value:function(t){var n=t.getId();e.chains[n][0]?e.chains[n][0].startup(t):console.warn("Attempts to run a empty chain ...","History : ",e.history[n])}},{key:"chain",value:function(t){e.update(t),e.coherence(t),e.run(t)}},{key:"update",value:function(t){var n=t.getId(),r=e.chains[n].shift();e.findModuleChain(t).filter(function(t){return!e.chains[n].includes(t)}).forEach(function(t){e.chains[n].unshift(t)});var i=e.history[n].cycles[r.identifier];e.history[n].cycles[r.identifier]="number"==typeof i?i+1:1;var o=e.chains[n].map(function(e){return e.identifier});e.history[n].identifiers.push(o)}}]),e}();c.chains={},c.history={},c.allowedCycles=1,c.forbiddenIdentifiers=[],e.exports=c},,,function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0;n0}}]),e}();o.logicsMap={},e.exports=o},function(e,t,n){"use strict";var r=n(11),i=n(27);r.registerPlugin(i);var o=n(29);r.registerPlugin(o);var u=n(25);r.registerPlugin(u);var a=n(26);r.registerPlugin(a);var c=n(28);r.registerPlugin(c);var s=n(15);r.registerFilter(s);var l=n(14);r.registerFilter(l);var f=n(13);r.registerFilter(f);n(16);e.exports=r},,,function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function o(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u={PluginExists:function(e){function t(e){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,'Plugin with same "'+e.identifier+'" identifier found.'))}return o(t,e),t}(Error),TypeNotFound:function(e){function t(){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"Media Tag could not find the content type of an instance.}."))}return o(t,e),t}(Error),FilterExists:function(e){function t(e){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,'Filter with same "'+e.identifier+' identifier found."'))}return o(t,e),t}(Error),FetchFail:function(e){function t(e){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,'Could not fetch "'+e.url+'", received "'+e.status+": "+e.statusText+'".'))}return o(t,e),t}(Error),InvalidCryptoKey:function(e){function t(){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"Invalid cryptographic key."))}return o(t,e),t}(Error),InvalidCryptoLib:function(e){function t(){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"Invalid cryptographic algorithm name."))}return o(t,e),t}(Error),FailedCrypto:function(e){function t(e){return r(this,t),i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"Failed to decrypt file"+(e&&e.message?" "+e.message:"")+"."))}return o(t,e),t}(Error)};e.exports=u},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0;n1?t[0]:window.location.protocol}},{key:"hostname",value:function(e){var t=e.getAttribute("src").split("://");return t.length>1?t[1].split("/")[0]:window.location.hostname}},{key:"source",value:function(e){return e.getAttribute("src")}},{key:"parse",value:function(t){return{protocol:e.protocol(t),hostname:e.hostname(t),src:e.source(t),type:e.type(t),extension:e.extension(t),mime:e.mime(t)}}}]),e}();e.exports=o},function(e,t,n){"use strict";var r=n(0),i={identifier:"clear-key",startup:function(e){var t=e.getAttribute("data-clear-key"),n=t.substring(0,32),i=t.substring(33,65);e.setAttribute("id",n),e.setAttribute("key",i),e.removeAttribute("data-clear-key"),r.chain(e)}};e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var 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 + + + CryptPad + + + + + + + + + + +
+
+ +
+ +
+

+
+
+ + diff --git a/www/examples/file/inner.html b/www/examples/file/inner.html new file mode 100644 index 000000000..bba73059e --- /dev/null +++ b/www/examples/file/inner.html @@ -0,0 +1,25 @@ + + + + + + + + + + + +
+ + + + diff --git a/www/examples/file/main.js b/www/examples/file/main.js new file mode 100644 index 000000000..9a0d142fc --- /dev/null +++ b/www/examples/file/main.js @@ -0,0 +1,52 @@ +define([ + 'jquery', + '/bower_components/chainpad-crypto/crypto.js', + '/bower_components/chainpad-netflux/chainpad-netflux.js', + '/common/toolbar.js', + '/common/cryptpad-common.js', + '/common/visible.js', + '/common/notify.js', + '/bower_components/tweetnacl/nacl-fast.min.js', +], function ($, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify) { + var Messages = Cryptpad.Messages; + window.Nacl = window.nacl; + $(function () { + + var ifrw = $('#pad-iframe')[0].contentWindow; + var $iframe = $('#pad-iframe').contents(); + + Cryptpad.addLoadingScreen(); + + var andThen = function () { + var $bar = $iframe.find('.toolbar-container'); + var secret = Cryptpad.getSecrets(); + var readOnly = secret.keys && !secret.keys.editKeyStr; + if (!secret.keys) { + secret.keys = secret.key; + } + + var $mt = $iframe.find('#encryptedFile'); + $mt.attr('src', './assets/image.png-encrypted'); + $mt.attr('data-crypto-key', 'TBo77200c0e/FdldQFcnQx4Y'); + $mt.attr('data-type', 'image/png'); + require(['/common/media-tag.js'], function (MediaTag) { + MediaTag($mt[0]); + Cryptpad.removeLoadingScreen(); + var configTb = { + displayed: ['useradmin', 'newpad'], + ifrw: ifrw, + common: Cryptpad + }; + toolbar = Toolbar.create($bar, null, null, null, null, configTb); + + }); + + }; + + Cryptpad.ready(function (err, anv) { + andThen(); + Cryptpad.reportAppUsage(); + }); + + }); +}); From 211113fb1aa37b8dbfe82fd5ea40969d81d64093 Mon Sep 17 00:00:00 2001 From: Caleb James DeLisle Date: Tue, 25 Apr 2017 16:04:17 +0200 Subject: [PATCH 071/306] little flowey --- .flowconfig | 7 +++++++ .travis.yml | 2 +- ARCHITECTURE.md | 4 +--- config.js.dist => config.example.js | 1 + container-start.sh | 6 +++--- readme.md | 5 ++--- rpc.js | 11 ++++++++--- storage/README.md | 4 ++-- 8 files changed, 25 insertions(+), 15 deletions(-) create mode 100644 .flowconfig rename config.js.dist => config.example.js (99%) diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 000000000..4a58bdcde --- /dev/null +++ b/.flowconfig @@ -0,0 +1,7 @@ +[ignore] + +[include] + +[libs] + +[options] diff --git a/.travis.yml b/.travis.yml index 24288c6f2..4160b8719 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ node_js: - "6.6.0" before_script: - npm run-script lint - - cp config.js.dist config.js + - cp config.example.js config.js - npm install bower - ./node_modules/bower/bin/bower install - node ./server.js & diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index b1d2515f1..0d1d94873 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -76,7 +76,7 @@ Chainpad can handle out of order messages, but it performs best when its message By architecting your system such that all clients send to a server which then relays to other clients, you guarantee that a particular chain of patches is consistent between the participants of your session. Cryptpad is capable of using a variety of data stores. -Which data store your instance employs can be [easily configured](https://github.com/xwiki-labs/cryptpad/blob/master/config.js.dist). +Which data store your instance employs can be [easily configured](https://github.com/xwiki-labs/cryptpad/blob/master/config.example.js). You simply need to write an adaptor which conforms to a simple API. The documentation for writing such an adaptor, and the complete list of implemented adaptors, is available [here](https://github.com/xwiki-labs/cryptpad/tree/master/storage). @@ -243,5 +243,3 @@ A session could still have difficulty with very large chains, however, in practi ## Conclusion - - diff --git a/config.js.dist b/config.example.js similarity index 99% rename from config.js.dist rename to config.example.js index 6ef7267c1..692a91c59 100644 --- a/config.js.dist +++ b/config.example.js @@ -1,3 +1,4 @@ +/*@flow*/ /* globals module */ diff --git a/container-start.sh b/container-start.sh index 89f3be1f1..2aa4ae10f 100755 --- a/container-start.sh +++ b/container-start.sh @@ -4,12 +4,12 @@ mkdir -p customize [ -z "$(ls -A customize)" ] && echo "Creating customize folder" \ && cp -R customize.dist/* customize/ \ - && cp config.js.dist customize/config.js + && cp config.example.js customize/config.js -# Linking config.js +# Linking config.js [ ! -h config.js ] && echo "Linking config.js" && ln -s customize/config.js config.js -# Configure +# Configure [ -n "$USE_SSL" ] && echo "Using secure websockets: $USE_SSL" \ && sed -i "s/useSecureWebsockets: .*/useSecureWebsockets: ${USE_SSL},/g" customize/config.js diff --git a/readme.md b/readme.md index b3c8a1a61..60da12331 100644 --- a/readme.md +++ b/readme.md @@ -32,8 +32,8 @@ npm install npm install -g bower ## if necessary bower install -## copy config.js.dist to config.js -cp config.js.dist config.js +## copy config.example.js to config.js +cp config.example.js config.js node ./server.js ``` @@ -162,4 +162,3 @@ sales@xwiki.com [fragment identifier]: https://en.wikipedia.org/wiki/Fragment_identifier [active attack]: https://en.wikipedia.org/wiki/Attack_(computing)#Types_of_attacks [Creative Commons Attribution 2.5 License]: http://creativecommons.org/licenses/by/2.5/ - diff --git a/rpc.js b/rpc.js index d68fa78b4..808815d84 100644 --- a/rpc.js +++ b/rpc.js @@ -1,3 +1,4 @@ +/*@flow*/ /* Use Nacl for checking signatures of messages */ var Nacl = require("tweetnacl"); @@ -375,7 +376,8 @@ var resetUserPins = function (store, Sessions, publicKey, channelList, cb) { }); }; -RPC.create = function (config, cb) { +/*::const ConfigType = require('./config.example.js');*/ +RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)=>void*/) { // load pin-store... console.log('loading rpc module...'); @@ -384,7 +386,11 @@ RPC.create = function (config, cb) { var store; - var rpc = function (ctx, data, respond) { + var rpc = function ( + ctx /*:{ store: Object }*/, + data /*:Array>*/, + respond /*:(?string, ?Array)=>void*/) + { if (!Array.isArray(data)) { return void respond('INVALID_ARG_FORMAT'); } @@ -494,4 +500,3 @@ RPC.create = function (config, cb) { }, 60000); }); }; - diff --git a/storage/README.md b/storage/README.md index 03117d95c..8fabdf53b 100644 --- a/storage/README.md +++ b/storage/README.md @@ -46,14 +46,14 @@ While we migrate to our new Netflux API, only the leveldb adaptor will be suppor ## removeChannel(channelName, callback) -This method is called (optionally, see config.js.dist for more info) some amount of time after the last client in a channel disconnects. +This method is called (optionally, see config.example.js for more info) some amount of time after the last client in a channel disconnects. It should remove any history of that channel, and execute a callback which takes an error message as an argument. ## Documenting your adaptor Naturally, you should comment your code well before making a PR. -Failing that, you should definitely add notes to `cryptpad/config.js.dist` such that people who wish to install your adaptor know how to do so. +Failing that, you should definitely add notes to `cryptpad/config.example.js` such that people who wish to install your adaptor know how to do so. Notes on how to install the back end, as well as how to install the client for connecting to the back end (as is the case with many datastores), as well as how to configure cryptpad to use your adaptor. The current configuration file should serve as an example of what to add, and how to comment. From 48973bccd617f530a26842b3c74407555d2bbc5a Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 25 Apr 2017 16:11:19 +0200 Subject: [PATCH 072/306] add tests to assert for hash parsing --- www/assert/main.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/www/assert/main.js b/www/assert/main.js index eb9bb9157..3306c3bab 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -139,6 +139,49 @@ define([ strungJSON(orig); }); + // check that old hashes parse correctly + assert(function () { + var secret = Cryptpad.parseHash('67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy'); + return secret.channel === "67b8385b07352be53e40746d2be6ccd7" && + secret.key === "XAYSuJYYqa9NfmInyHci7LNy" && + secret.version === 0; + }, "Old hash failed to parse"); + + // make sure version 1 hashes parse correctly + assert(function () { + var secret = Cryptpad.parseHash('/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI'); + return secret.version === 1 && + secret.mode === "edit" && + secret.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" && + secret.key === "usn4+9CqVja8Q7RZOGTfRgqI" && + !secret.present; + }, "version 1 hash failed to parse"); + + // test support for present mode in hashes + assert(function () { + var secret = Cryptpad.parseHash('/1/edit/CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/present'); + return secret.version === 1 + && secret.mode === "edit" + && secret.channel === "CmN5+YJkrHFS3NSBg-P7Sg" + && secret.key === "DNZ2wcG683GscU4fyOyqA87G" + && secret.present; + }, "version 1 hash failed to parse"); + + // test support for trailing slash + assert(function () { + var secret = Cryptpad.parseHash('/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/'); + return secret.version === 1 && + secret.mode === "edit" && + secret.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" && + secret.key === "usn4+9CqVja8Q7RZOGTfRgqI" && + !secret.present; + }, "test support for trailing slashes in version 1 hash failed to parse"); + + assert(function () { + // TODO + return true; + }, "version 2 hash failed to parse correctly"); + var swap = function (str, dict) { return str.replace(/\{\{(.*?)\}\}/g, function (all, key) { return typeof dict[key] !== 'undefined'? dict[key] : all; From f8f97036be76599ee5b812ca5aedd0c9ac32f574 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 25 Apr 2017 16:12:28 +0200 Subject: [PATCH 073/306] ignore media-tag.js --- .jshintignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.jshintignore b/.jshintignore index ae60d4ba0..51636ed77 100644 --- a/.jshintignore +++ b/.jshintignore @@ -9,4 +9,4 @@ server.js NetFluxWebsocketSrv.js NetFluxWebsocketServer.js WebRTCSrv.js - +www/common/media-tag.js From f196b836db801cab6908ee4a641e8de3b6647635 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 25 Apr 2017 16:17:52 +0200 Subject: [PATCH 074/306] fix undefined reference --- www/examples/file/main.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/www/examples/file/main.js b/www/examples/file/main.js index 9a0d142fc..35bf4b191 100644 --- a/www/examples/file/main.js +++ b/www/examples/file/main.js @@ -16,6 +16,7 @@ define([ var $iframe = $('#pad-iframe').contents(); Cryptpad.addLoadingScreen(); + var toolbar; var andThen = function () { var $bar = $iframe.find('.toolbar-container'); @@ -38,9 +39,7 @@ define([ common: Cryptpad }; toolbar = Toolbar.create($bar, null, null, null, null, configTb); - }); - }; Cryptpad.ready(function (err, anv) { From 33090872778d3228e9d2bc7f42dc14b2a420a908 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 25 Apr 2017 17:09:39 +0200 Subject: [PATCH 075/306] send feedback if isArray is not supported --- www/common/cryptpad-common.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 1d5015497..c2559bde2 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1189,6 +1189,11 @@ define([ if (typeof(window.Proxy) === 'undefined') { feedback("NO_PROXIES"); } + + if (typeof(Array.isArray) === 'function') { + feedback("NO_ISARRAY"); + } + $(function() { // Race condition : if document.body is undefined when alertify.js is loaded, Alertify // won't work. We have to reset it now to make sure it uses a correct "body" From d985b144cc1ecfe4c90b79f10751ace2fe6c8ccb Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 25 Apr 2017 17:19:13 +0200 Subject: [PATCH 076/306] Add a new hash version for the file viewer --- .gitignore | 1 + server.js | 2 ++ www/common/common-hash.js | 26 +++++++++++++++++++------- www/common/cryptpad-common.js | 2 +- www/examples/file/main.js | 33 +++++++++++++++++++-------------- 5 files changed, 42 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 996e55b97..76bc0ea38 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ www/scratch data npm-debug.log pins/ +blob/ diff --git a/server.js b/server.js index f12b90229..d7f5b90fc 100644 --- a/server.js +++ b/server.js @@ -82,6 +82,8 @@ var mainPages = config.mainPages || ['index', 'privacy', 'terms', 'about', 'cont var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$'); app.get(mainPagePattern, Express.static(__dirname + '/customize.dist')); +app.use("/blob", Express.static(__dirname + '/blob')); + app.use("/customize", Express.static(__dirname + '/customize')); app.use("/customize", Express.static(__dirname + '/customize.dist')); app.use(/^\/[^\/]*$/, Express.static('customize')); diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 40fe6bc7b..12f23c0a7 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -23,13 +23,16 @@ define([ return chanKey + keys; } if (!keys.editKeyStr) { return; } - return '/1/edit/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.editKeyStr); + return '/1/edit/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.editKeyStr)+'/'; }; var getViewHashFromKeys = Hash.getViewHashFromKeys = function (chanKey, keys) { if (typeof keys === 'string') { return; } - return '/1/view/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.viewKeyStr); + return '/1/view/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.viewKeyStr)+'/'; + }; + var getFileHashFromKey = Hash.getFileHashFromKey = function (fileKey, cryptKey, type) { + return '/2/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/' + Crypto.base64RemoveSlashes(type); }; var parsePadUrl = Hash.parsePadUrl = function (href) { @@ -119,7 +122,9 @@ define([ } } else if (version === "2") { // version 2 hashes are to be used for encrypted blobs - // TODO + var fileId = secret.file = hashArray[2].replace(/-/g, '/'); + var key = secret.key = hashArray[3].replace(/-/g, '/'); + var type = secret.type = hashArray[4].replace(/-/g, '/'); } } } @@ -150,7 +155,7 @@ define([ var channelId = Util.hexToBase64(createChannelId()); // 18 byte encryption key var key = Crypto.b64RemoveSlashes(Crypto.rand64(18)); - return '/1/edit/' + [channelId, key].join('/'); + return '/1/edit/' + [channelId, key].join('/') + '/'; }; /* @@ -159,8 +164,8 @@ Version 0 Version 1 /code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI Version 2 - /file//#/2// - /file//#/2/ajExFODrFH4lVBwxxsrOKw/pdf + /file/#/2/// + /file/#/2/K6xWU-LT9BJHCQcDCT-DcQ/ajExFODrFH4lVBwxxsrOKw/image-png */ var parseHash = Hash.parseHash = function (hash) { var parsed = {}; @@ -177,7 +182,14 @@ Version 2 parsed.mode = hashArr[2]; parsed.channel = hashArr[3]; parsed.key = hashArr[4]; - parsed.present = hashArr[5] && hashArr[5] === 'present'; + parsed.present = typeof(hashArr[5]) === "string" && hashArr[5] === 'present'; + return parsed; + } + if (hashArr[1] && hashArr[1] === '2') { + parsed.version = 2; + parsed.file = hashArr[2].replace(/-/g, '/'); + parsed.key = hashArr[3].replace(/-/g, '/'); + parsed.type = hashArr[4].replace(/-/g, '/'); return parsed; } return; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 1d5015497..757b04e15 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -514,7 +514,7 @@ define([ if (p.type !== parsed.type) { return pad; } - var shouldUpdate = p.hash === parsed.hash; + var shouldUpdate = p.hash.replace(/\/$/, '') === parsed.hash.replace(/\/$/, ''); // Version 1 : we have up to 4 differents hash for 1 pad, keep the strongest : // Edit > Edit (present) > View > View (present) diff --git a/www/examples/file/main.js b/www/examples/file/main.js index 9a0d142fc..4db3eb8ba 100644 --- a/www/examples/file/main.js +++ b/www/examples/file/main.js @@ -20,25 +20,30 @@ define([ var andThen = function () { var $bar = $iframe.find('.toolbar-container'); var secret = Cryptpad.getSecrets(); - var readOnly = secret.keys && !secret.keys.editKeyStr; - if (!secret.keys) { - secret.keys = secret.key; - } + + if (secret.keys) { throw new Error("You need a hash"); } // TODO + + var cryptKey = secret.key; + var fileId = secret.file; + var hexFileName = Cryptpad.base64ToHex(fileId); + var type = secret.type; + +// Test hash: +// #/2/K6xWU-LT9BJHCQcDCT-DcQ/TBo77200c0e-FdldQFcnQx4Y/image-png var $mt = $iframe.find('#encryptedFile'); - $mt.attr('src', './assets/image.png-encrypted'); - $mt.attr('data-crypto-key', 'TBo77200c0e/FdldQFcnQx4Y'); - $mt.attr('data-type', 'image/png'); + $mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName); + $mt.attr('data-crypto-key', cryptKey); + $mt.attr('data-type', type); require(['/common/media-tag.js'], function (MediaTag) { MediaTag($mt[0]); Cryptpad.removeLoadingScreen(); - var configTb = { - displayed: ['useradmin', 'newpad'], - ifrw: ifrw, - common: Cryptpad - }; - toolbar = Toolbar.create($bar, null, null, null, null, configTb); - + var configTb = { + displayed: ['useradmin', 'newpad'], + ifrw: ifrw, + common: Cryptpad + }; + Toolbar.create($bar, null, null, null, null, configTb); }); }; From 74bcec8b3116a5ebb89883bab34f3640a8dbd15d Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 25 Apr 2017 18:28:39 +0200 Subject: [PATCH 077/306] programming is hard okay --- www/common/cryptpad-common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index c2559bde2..f0c710460 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1190,7 +1190,7 @@ define([ feedback("NO_PROXIES"); } - if (typeof(Array.isArray) === 'function') { + if (typeof(Array.isArray) !== 'function') { feedback("NO_ISARRAY"); } From 9882a3a923a6a8cd51f65dfa37e2042b600af11d Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 25 Apr 2017 18:42:21 +0200 Subject: [PATCH 078/306] Add support for the file applicaiton in the drive --- customize.dist/translations/messages.fr.js | 1 + customize.dist/translations/messages.js | 1 + www/common/common-hash.js | 2 +- www/common/toolbar.js | 2 +- .../file/assets/image.png-encrypted | Bin www/{examples => }/file/index.html | 0 www/{examples => }/file/inner.html | 0 www/{examples => }/file/main.js | 40 +++++++++++++++++- 8 files changed, 43 insertions(+), 3 deletions(-) rename www/{examples => }/file/assets/image.png-encrypted (100%) rename www/{examples => }/file/index.html (100%) rename www/{examples => }/file/inner.html (100%) rename www/{examples => }/file/main.js (55%) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 97aec74ab..429e2f928 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -11,6 +11,7 @@ define(function () { out.type.slide = 'Présentation'; out.type.drive = 'Drive'; out.type.whiteboard = "Tableau Blanc"; + out.type.file = "Fichier"; out.button_newpad = 'Nouveau document texte'; out.button_newcode = 'Nouvelle page de code'; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index e27a074b5..678d3c4fb 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -11,6 +11,7 @@ define(function () { out.type.slide = 'Presentation'; out.type.drive = 'Drive'; out.type.whiteboard = 'Whiteboard'; + out.type.file = 'File'; out.button_newpad = 'New Rich Text pad'; out.button_newcode = 'New Code pad'; diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 12f23c0a7..23f5f6a24 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -252,7 +252,7 @@ Version 2 if (parsed.version === 0) { return parsed.channel; - } else if (parsed.version !== 1) { + } else if (parsed.version !== 1 && parsed.version !== 2) { console.error("parsed href had no version"); console.error(parsed); return; diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 9302441f0..4162e3dbf 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -791,7 +791,7 @@ define([ if (!connected) { return; } checkLag(getLag, lagElement); }, 3000); - } + } else { connected = true; } var failed = function () { connected = false; diff --git a/www/examples/file/assets/image.png-encrypted b/www/file/assets/image.png-encrypted similarity index 100% rename from www/examples/file/assets/image.png-encrypted rename to www/file/assets/image.png-encrypted diff --git a/www/examples/file/index.html b/www/file/index.html similarity index 100% rename from www/examples/file/index.html rename to www/file/index.html diff --git a/www/examples/file/inner.html b/www/file/inner.html similarity index 100% rename from www/examples/file/inner.html rename to www/file/inner.html diff --git a/www/examples/file/main.js b/www/file/main.js similarity index 55% rename from www/examples/file/main.js rename to www/file/main.js index 298d88904..b628edee9 100644 --- a/www/examples/file/main.js +++ b/www/file/main.js @@ -31,6 +31,37 @@ define([ // Test hash: // #/2/K6xWU-LT9BJHCQcDCT-DcQ/TBo77200c0e-FdldQFcnQx4Y/image-png + var parsed = Cryptpad.parsePadUrl(window.location.href); + var defaultName = Cryptpad.getDefaultName(parsed); + + var getTitle = function () { + var pad = Cryptpad.getRelativeHref(window.location.href); + var fo = Cryptpad.getStore().getProxy().fo; + var data = fo.getFileData(pad); + return data ? data.title : undefined; + }; + + var updateTitle = function (newTitle) { + Cryptpad.renamePad(newTitle, function (err, data) { + if (err) { + console.log("Couldn't set pad title"); + console.error(err); + return; + } + document.title = newTitle; + $bar.find('.' + Toolbar.constants.title).find('span.title').text(data); + $bar.find('.' + Toolbar.constants.title).find('input').val(data); + }); + }; + + var suggestName = function () { + return document.title || getTitle() || ''; + }; + + var renameCb = function (err, title) { + document.title = title; + }; + var $mt = $iframe.find('#encryptedFile'); $mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName); $mt.attr('data-crypto-key', cryptKey); @@ -41,9 +72,16 @@ define([ var configTb = { displayed: ['useradmin', 'newpad'], ifrw: ifrw, - common: Cryptpad + common: Cryptpad, + title: { + onRename: renameCb, + defaultName: defaultName, + suggestName: suggestName + } }; Toolbar.create($bar, null, null, null, null, configTb); + + updateTitle(Cryptpad.initialName || getTitle() || defaultName); }); }; From f50aa5c29b959331da921bb9e8be757f4e5804fc Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 26 Apr 2017 14:55:06 +0200 Subject: [PATCH 079/306] Move the mediatag viewer into a media app --- customize.dist/translations/messages.fr.js | 1 + customize.dist/translations/messages.js | 1 + www/common/common-hash.js | 15 +-- www/common/toolbar.js | 16 +++- www/file/inner.html | 6 +- www/file/main.js | 43 +++++++-- www/media/assets/image.png-encrypted | Bin 0 -> 27455 bytes www/media/index.html | 47 ++++++++++ www/media/inner.html | 27 ++++++ www/media/main.js | 103 +++++++++++++++++++++ 10 files changed, 239 insertions(+), 20 deletions(-) create mode 100644 www/media/assets/image.png-encrypted create mode 100644 www/media/index.html create mode 100644 www/media/inner.html create mode 100644 www/media/main.js diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 429e2f928..2b50a088d 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -12,6 +12,7 @@ define(function () { out.type.drive = 'Drive'; out.type.whiteboard = "Tableau Blanc"; out.type.file = "Fichier"; + out.type.media = "Média"; out.button_newpad = 'Nouveau document texte'; out.button_newcode = 'Nouvelle page de code'; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 678d3c4fb..6c3ec7c4d 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -12,6 +12,7 @@ define(function () { out.type.drive = 'Drive'; out.type.whiteboard = 'Whiteboard'; out.type.file = 'File'; + out.type.media = 'Media'; out.button_newpad = 'New Rich Text pad'; out.button_newcode = 'New Code pad'; diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 23f5f6a24..620acd319 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -31,8 +31,8 @@ define([ } return '/1/view/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.viewKeyStr)+'/'; }; - var getFileHashFromKey = Hash.getFileHashFromKey = function (fileKey, cryptKey, type) { - return '/2/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/' + Crypto.base64RemoveSlashes(type); + var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) { + return '/2/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/'; }; var parsePadUrl = Hash.parsePadUrl = function (href) { @@ -122,9 +122,8 @@ define([ } } else if (version === "2") { // version 2 hashes are to be used for encrypted blobs - var fileId = secret.file = hashArray[2].replace(/-/g, '/'); - var key = secret.key = hashArray[3].replace(/-/g, '/'); - var type = secret.type = hashArray[4].replace(/-/g, '/'); + secret.channel = hashArray[2].replace(/-/g, '/'); + secret.keys = { fileKeyStr: hashArray[3].replace(/-/g, '/') }; } } } @@ -139,6 +138,9 @@ define([ if (secret.keys.viewKeyStr) { hashes.viewHash = getViewHashFromKeys(channel, secret.keys); } + if (secret.keys.fileKeyStr) { + hashes.fileHash = getFileHashFromKeys(channel, secret.keys.fileKeyStr); + } return hashes; }; @@ -187,9 +189,8 @@ Version 2 } if (hashArr[1] && hashArr[1] === '2') { parsed.version = 2; - parsed.file = hashArr[2].replace(/-/g, '/'); + parsed.channel = hashArr[2].replace(/-/g, '/'); parsed.key = hashArr[3].replace(/-/g, '/'); - parsed.type = hashArr[4].replace(/-/g, '/'); return parsed; } return; diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 4162e3dbf..7ff4f2b51 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -205,6 +205,13 @@ define([ }); } } + if (hashes.fileHash) { + options.push({ + tag: 'a', + attributes: {title: Messages.viewShareTitle, 'class': 'fileShare'}, + content: ' ' + Messages.viewShare + }); + } var dropdownConfigShare = { text: $('
').append($shareIcon).append($span).html(), options: options @@ -223,7 +230,14 @@ define([ } if (hashes.viewHash) { $shareBlock.find('a.viewShare').click(function () { - var url = window.location.origin + window.location.pathname + '#' + hashes.viewHash; + var url = window.location.origin + window.location.pathname + '#' + hashes.viewHash ; + var success = Cryptpad.Clipboard.copy(url); + if (success) { Cryptpad.log(Messages.shareSuccess); } + }); + } + if (hashes.fileHash) { + $shareBlock.find('a.fileShare').click(function () { + var url = window.location.origin + window.location.pathname + '#' + hashes.fileHash ; var success = Cryptpad.Clipboard.copy(url); if (success) { Cryptpad.log(Messages.shareSuccess); } }); diff --git a/www/file/inner.html b/www/file/inner.html index bba73059e..7f315cef2 100644 --- a/www/file/inner.html +++ b/www/file/inner.html @@ -5,7 +5,6 @@ -
- + diff --git a/www/file/main.js b/www/file/main.js index b628edee9..bf7cb9e00 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -7,9 +7,11 @@ define([ '/common/visible.js', '/common/notify.js', '/bower_components/tweetnacl/nacl-fast.min.js', + '/bower_components/file-saver/FileSaver.min.js', ], function ($, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify) { var Messages = Cryptpad.Messages; - window.Nacl = window.nacl; + var saveAs = window.saveAs; + //window.Nacl = window.nacl; $(function () { var ifrw = $('#pad-iframe')[0].contentWindow; @@ -21,15 +23,14 @@ define([ var $bar = $iframe.find('.toolbar-container'); var secret = Cryptpad.getSecrets(); - if (secret.keys) { throw new Error("You need a hash"); } // TODO + if (!secret.keys) { throw new Error("You need a hash"); } // TODO - var cryptKey = secret.key; - var fileId = secret.file; + var cryptKey = secret.keys && secret.keys.fileKeyStr; + var fileId = secret.channel; var hexFileName = Cryptpad.base64ToHex(fileId); - var type = secret.type; - + var type = "image/png"; // Test hash: -// #/2/K6xWU-LT9BJHCQcDCT-DcQ/TBo77200c0e-FdldQFcnQx4Y/image-png +// #/2/K6xWU-LT9BJHCQcDCT-DcQ/TBo77200c0e-FdldQFcnQx4Y/ var parsed = Cryptpad.parsePadUrl(window.location.href); var defaultName = Cryptpad.getDefaultName(parsed); @@ -62,26 +63,48 @@ define([ document.title = title; }; + var blob; + var exportFile = function () { + var suggestion = document.title; + Cryptpad.prompt(Messages.exportPrompt, + Cryptpad.fixFileName(suggestion) + '.html', function (filename) { + if (!(typeof(filename) === 'string' && filename)) { return; } + //var blob = new Blob([html], {type: "text/html;charset=utf-8"}); + saveAs(blob, filename); + }); + }; + var $mt = $iframe.find('#encryptedFile'); $mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName); $mt.attr('data-crypto-key', cryptKey); $mt.attr('data-type', type); + require(['/common/media-tag.js'], function (MediaTag) { - MediaTag($mt[0]); - Cryptpad.removeLoadingScreen(); var configTb = { - displayed: ['useradmin', 'newpad'], + displayed: ['useradmin', 'share', 'newpad'], ifrw: ifrw, common: Cryptpad, title: { onRename: renameCb, defaultName: defaultName, suggestName: suggestName + }, + share: { + secret: secret, + channel: hexFileName } }; Toolbar.create($bar, null, null, null, null, configTb); + var $rightside = $bar.find('.' + Toolbar.constants.rightside); + + var $export = Cryptpad.createButton('export', true, {}, exportFile); + $rightside.append($export); updateTitle(Cryptpad.initialName || getTitle() || defaultName); + + var mt = MediaTag($mt[0]); + + Cryptpad.removeLoadingScreen(); }); }; diff --git a/www/media/assets/image.png-encrypted b/www/media/assets/image.png-encrypted new file mode 100644 index 0000000000000000000000000000000000000000..634bb90f22b5c88fa8295ea79c50e80feb480db0 GIT binary patch literal 27455 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
(s!i4 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 + + + CryptPad + + + + + + + + + + +
+
+ +
+ +
+

+
+
+ + diff --git a/www/media/inner.html b/www/media/inner.html new file mode 100644 index 000000000..bc5b96ae0 --- /dev/null +++ b/www/media/inner.html @@ -0,0 +1,27 @@ + + + + + + + + + + +
+ + + + diff --git a/www/media/main.js b/www/media/main.js new file mode 100644 index 000000000..e176701c8 --- /dev/null +++ b/www/media/main.js @@ -0,0 +1,103 @@ +define([ + 'jquery', + '/bower_components/chainpad-crypto/crypto.js', + '/bower_components/chainpad-netflux/chainpad-netflux.js', + '/common/toolbar.js', + '/common/cryptpad-common.js', + '/common/visible.js', + '/common/notify.js', + '/bower_components/tweetnacl/nacl-fast.min.js', + '/bower_components/file-saver/FileSaver.min.js', +], function ($, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify) { + var Messages = Cryptpad.Messages; + var saveAs = window.saveAs; + //window.Nacl = window.nacl; + $(function () { + + var ifrw = $('#pad-iframe')[0].contentWindow; + var $iframe = $('#pad-iframe').contents(); + + Cryptpad.addLoadingScreen(); + + var andThen = function () { + var $bar = $iframe.find('.toolbar-container'); + var secret = Cryptpad.getSecrets(); + + if (!secret.keys) { throw new Error("You need a hash"); } // TODO + + var cryptKey = secret.keys && secret.keys.fileKeyStr; + var fileId = secret.channel; + var hexFileName = Cryptpad.base64ToHex(fileId); + var type = "image/png"; +// Test hash: +// #/2/K6xWU-LT9BJHCQcDCT-DcQ/TBo77200c0e-FdldQFcnQx4Y/ + + var parsed = Cryptpad.parsePadUrl(window.location.href); + var defaultName = Cryptpad.getDefaultName(parsed); + + var getTitle = function () { + var pad = Cryptpad.getRelativeHref(window.location.href); + var fo = Cryptpad.getStore().getProxy().fo; + var data = fo.getFileData(pad); + return data ? data.title : undefined; + }; + + var updateTitle = function (newTitle) { + Cryptpad.renamePad(newTitle, function (err, data) { + if (err) { + console.log("Couldn't set pad title"); + console.error(err); + return; + } + document.title = newTitle; + $bar.find('.' + Toolbar.constants.title).find('span.title').text(data); + $bar.find('.' + Toolbar.constants.title).find('input').val(data); + }); + }; + + var suggestName = function () { + return document.title || getTitle() || ''; + }; + + var renameCb = function (err, title) { + document.title = title; + }; + + var $mt = $iframe.find('#encryptedFile'); + $mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName); + $mt.attr('data-crypto-key', cryptKey); + $mt.attr('data-type', type); + + require(['/common/media-tag.js'], function (MediaTag) { + var configTb = { + displayed: ['useradmin', 'share', 'newpad'], + ifrw: ifrw, + common: Cryptpad, + title: { + onRename: renameCb, + defaultName: defaultName, + suggestName: suggestName + }, + share: { + secret: secret, + channel: hexFileName + } + }; + Toolbar.create($bar, null, null, null, null, configTb); + var $rightside = $bar.find('.' + Toolbar.constants.rightside); + + updateTitle(Cryptpad.initialName || getTitle() || defaultName); + + var mt = MediaTag($mt[0]); + + Cryptpad.removeLoadingScreen(); + }); + }; + + Cryptpad.ready(function (err, anv) { + andThen(); + Cryptpad.reportAppUsage(); + }); + + }); +}); From 520dabe09426a9453be8045b7c6d91d489af8edf Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 26 Apr 2017 18:46:40 +0200 Subject: [PATCH 080/306] Remove the unsorted files category --- www/common/cryptpad-common.js | 16 +++++++++- www/common/fsStore.js | 4 +-- www/common/userObject.js | 59 +++++++++++++++++++++-------------- www/drive/main.js | 42 ++++++------------------- 4 files changed, 62 insertions(+), 59 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 5d8ab7804..4c119654e 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -495,6 +495,18 @@ define([ } }; + var updateFileName = function (href, oldName, newName) { + var fo = getStore().getProxy().fo; + var paths = fo.findFileInRoot(href); + paths.forEach(function (path) { + if (path.length !== 2) { return; } + var name = path[1].split('_')[0]; + var parsed = parsePadUrl(href); + if (path.length === 2 && name === oldName && isDefaultName(parsed, name)) { + fo.rename(path, newName); + } + }); + }; var setPadTitle = common.setPadTitle = function (name, cb) { var href = window.location.href; var parsed = parsePadUrl(href); @@ -540,6 +552,7 @@ define([ pad.atime = +new Date(); // set the name + var old = pad.title; pad.title = name; // If we now have a stronger version of a stored href, replace the weaker one by the strong one @@ -550,6 +563,7 @@ define([ }); } pad.href = href; + updateFileName(href, old, name); } return pad; }); @@ -557,7 +571,7 @@ define([ if (!contains) { var data = makePad(href, name); getStore().pushData(data); - getStore().addPad(href, common.initialPath, common.initialName || name); + getStore().addPad(data, common.initialPath); } if (updateWeaker.length > 0) { updateWeaker.forEach(function (obj) { diff --git a/www/common/fsStore.js b/www/common/fsStore.js index 74ccd710a..c5e30558c 100644 --- a/www/common/fsStore.js +++ b/www/common/fsStore.js @@ -88,8 +88,8 @@ define([ ret.removeData = filesOp.removeData; ret.pushData = filesOp.pushData; - ret.addPad = function (href, path, name) { - filesOp.add(href, path, name); + ret.addPad = function (data, path) { + filesOp.add(data, path); }; ret.forgetPad = function (href, cb) { diff --git a/www/common/userObject.js b/www/common/userObject.js index ca2bae258..9ea1af726 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -41,14 +41,13 @@ define([ var getStructure = exp.getStructure = function () { var a = {}; a[ROOT] = {}; - a[UNSORTED] = []; a[TRASH] = {}; a[FILES_DATA] = []; a[TEMPLATE] = []; return a; }; var getHrefArray = function () { - return [UNSORTED, TEMPLATE]; + return [TEMPLATE]; }; @@ -297,6 +296,9 @@ define([ return paths; }; + var findFileInRoot = exp.findFileInRoot = function (href) { + return _findFileInRoot([ROOT], href); + }; var _findFileInHrefArray = function (rootName, href) { var unsorted = files[rootName].slice(); var ret = []; @@ -345,10 +347,9 @@ define([ }; var findFile = exp.findFile = function (href) { var rootpaths = _findFileInRoot([ROOT], href); - var unsortedpaths = _findFileInHrefArray(UNSORTED, href); var templatepaths = _findFileInHrefArray(TEMPLATE, href); var trashpaths = _findFileInTrash([TRASH], href); - return rootpaths.concat(unsortedpaths, templatepaths, trashpaths); + return rootpaths.concat(templatepaths, trashpaths); }; var search = exp.search = function (value) { if (typeof(value) !== "string") { return []; } @@ -534,8 +535,10 @@ define([ // ADD - var add = exp.add = function (href, path, name, cb) { - if (!href) { return; } + var add = exp.add = function (data, path) { + if (!data || typeof(data) !== "object") { return; } + var href = data.href; + var name = data.title; var newPath = path, parentEl; if (path && !Array.isArray(path)) { newPath = decodeURIComponent(path).split(','); @@ -546,20 +549,16 @@ define([ parentEl.push(href); return; } - // Add to root - if (path && isPathIn(newPath, [ROOT]) && name) { - parentEl = find(newPath); + // Add to root if path is ROOT or if no path + var filesList = getFiles([ROOT, TRASH, 'hrefArray']); + if ((path && isPathIn(newPath, [ROOT]) || filesList.indexOf(href) === -1) && name) { + parentEl = find(newPath || [ROOT]); if (parentEl) { var newName = getAvailableName(parentEl, name); parentEl[newName] = href; return; } } - // No path: push to unsorted - var filesList = getFiles([ROOT, TRASH, 'hrefArray']); - if (filesList.indexOf(href) === -1) { files[UNSORTED].push(href); } - - if (typeof cb === "function") { cb(); } }; var addFile = exp.addFile = function (filePath, name, type, cb) { var parentEl = findElement(files, filePath); @@ -780,7 +779,7 @@ define([ // * FILES_DATA: - Data (title, cdate, adte) are stored in filesData. filesData contains only href keys linking to object with title, cdate, adate. // - Dates (adate, cdate) can be parsed/formatted // - All files in filesData should be either in 'root', 'trash' or 'unsorted'. If that's not the case, copy the fily to 'unsorted' - // * UNSORTED: Contains only files (href), and does not contains files that are in ROOT + // * TEMPLATE: Contains only files (href), and does not contains files that are in ROOT debug("Cleaning file system..."); var before = JSON.stringify(files); @@ -821,26 +820,37 @@ define([ } } }; + // Make sure unsorted doesn't exist anymore var fixUnsorted = function () { - if (!Array.isArray(files[UNSORTED])) { debug("UNSORTED was not an array"); files[UNSORTED] = []; } - files[UNSORTED] = Cryptpad.deduplicateString(files[UNSORTED].slice()); + if (!files[UNSORTED]) { return; } + debug("UNSORTED still exists in the object, removing it..."); var us = files[UNSORTED]; + if (us.length === 0) { + delete files[UNSORTED]; + return; + } var rootFiles = getFiles([ROOT, TEMPLATE]).slice(); var toClean = []; + var root = find([ROOT]); us.forEach(function (el, idx) { if (!isFile(el) || rootFiles.indexOf(el) !== -1) { - toClean.push(idx); + return; + //toClean.push(idx); } + var name = getFileData(el).title || NEW_FILE_NAME; + var newName = getAvailableName(root, name); + root[newName] = el; }); - toClean.forEach(function (idx) { + delete files[UNSORTED]; + /*toClean.forEach(function (idx) { us.splice(idx, 1); - }); + });*/ }; var fixTemplate = function () { if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; } files[TEMPLATE] = Cryptpad.deduplicateString(files[TEMPLATE].slice()); var us = files[TEMPLATE]; - var rootFiles = getFiles([ROOT, UNSORTED]).slice(); + var rootFiles = getFiles([ROOT]).slice(); var toClean = []; us.forEach(function (el, idx) { if (!isFile(el) || rootFiles.indexOf(el) !== -1) { @@ -855,6 +865,7 @@ define([ if (!$.isArray(files[FILES_DATA])) { debug("FILES_DATA was not an array"); files[FILES_DATA] = []; } var fd = files[FILES_DATA]; var rootFiles = getFiles([ROOT, TRASH, 'hrefArray']); + var root = find([ROOT]); var toClean = []; fd.forEach(function (el, idx) { if (!el || typeof(el) !== "object") { @@ -863,8 +874,10 @@ define([ return; } if (rootFiles.indexOf(el.href) === -1) { - debug("An element in filesData was not in ROOT, UNSORTED or TRASH.", el); - files[UNSORTED].push(el.href); + debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", el); + var name = el.title || NEW_FILE_NAME; + var newName = getAvailableName(root, name); + root[newName] = el.href; return; } }); diff --git a/www/drive/main.js b/www/drive/main.js index d7d90a9d8..038f9402e 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -41,8 +41,6 @@ define([ var SEARCH_NAME = Messages.fm_searchName; var ROOT = "root"; var ROOT_NAME = Messages.fm_rootName; - var UNSORTED = "unsorted"; - var UNSORTED_NAME = Messages.fm_unsortedName; var FILES_DATA = Cryptpad.storageKey; var FILES_DATA_NAME = Messages.fm_filesDataName; var TEMPLATE = "template"; @@ -68,9 +66,9 @@ define([ var getLastOpenedFolder = function () { var path; try { - path = localStorage[LOCALSTORAGE_LAST] ? JSON.parse(localStorage[LOCALSTORAGE_LAST]) : [UNSORTED]; + path = localStorage[LOCALSTORAGE_LAST] ? JSON.parse(localStorage[LOCALSTORAGE_LAST]) : [ROOT]; } catch (e) { - path = [UNSORTED]; + path = [ROOT]; } return path; }; @@ -218,7 +216,7 @@ define([ // Categories dislayed in the menu // _WORKGROUP_ : do not display unsorted - var displayedCategories = [ROOT, UNSORTED, TRASH, SEARCH]; + var displayedCategories = [ROOT, TRASH, SEARCH]; if (AppConfig.enableTemplates) { displayedCategories.push(TEMPLATE); } if (isWorkgroup()) { displayedCategories = [ROOT, TRASH, SEARCH]; } @@ -680,7 +678,7 @@ define([ var msg = Messages._getKey('fm_removeSeveralDialog', [paths.length]); if (paths.length === 1) { var path = paths[0]; - var name = path[0] === UNSORTED ? filesOp.getTitle(filesOp.find(path)) : path[path.length - 1]; + var name = path[0] === TEMPLATE ? filesOp.getTitle(filesOp.find(path)) : path[path.length - 1]; msg = Messages._getKey('fm_removeDialog', [name]); } Cryptpad.confirm(msg, function (res) { @@ -948,7 +946,6 @@ define([ switch (name) { case ROOT: pName = ROOT_NAME; break; case TRASH: pName = TRASH_NAME; break; - case UNSORTED: pName = UNSORTED_NAME; break; case TEMPLATE: pName = TEMPLATE_NAME; break; case FILES_DATA: pName = FILES_DATA_NAME; break; case SEARCH: pName = SEARCH_NAME; break; @@ -997,9 +994,6 @@ define([ case ROOT: msg = Messages.fm_info_root; break; - case UNSORTED: - msg = Messages.fm_info_unsorted; - break; case TEMPLATE: msg = Messages.fm_info_template; break; @@ -1243,10 +1237,6 @@ define([ //return $fileHeader; }; - var allFilesSorted = function () { - return filesOp.getFiles([UNSORTED]).length === 0; - }; - var sortElements = function (folder, path, oldkeys, prop, asc, useHref, useData) { var root = filesOp.find(path); var test = folder ? filesOp.isFolder : filesOp.isFile; @@ -1343,7 +1333,6 @@ define([ // and they don't hav a hierarchical structure (folder/subfolders) var displayHrefArray = function ($container, rootName, draggable) { var unsorted = files[rootName]; - if (rootName === UNSORTED && allFilesSorted()) { return; } var $fileHeader = getFileListHeader(false); $container.append($fileHeader); var keys = unsorted; @@ -1517,7 +1506,6 @@ define([ } var isInRoot = filesOp.isPathIn(path, [ROOT]); var isTrashRoot = filesOp.comparePath(path, [TRASH]); - var isUnsorted = filesOp.comparePath(path, [UNSORTED]); var isTemplate = filesOp.comparePath(path, [TEMPLATE]); var isAllFiles = filesOp.comparePath(path, [FILES_DATA]); var isSearch = path[0] === SEARCH; @@ -1596,7 +1584,7 @@ define([ var $folderHeader = getFolderListHeader(); var $fileHeader = getFileListHeader(true); - if (isUnsorted || isTemplate) { + if (isTemplate) { displayHrefArray($list, path[0], true); } else if (isAllFiles) { displayAllFiles($list); @@ -1733,15 +1721,6 @@ define([ }); }; - var createUnsorted = function ($container, path) { - var $icon = $unsortedIcon.clone(); - var isOpened = filesOp.comparePath(path, currentPath); - var $unsortedElement = createTreeElement(UNSORTED_NAME, $icon, [UNSORTED], false, true, false, isOpened); - $unsortedElement.addClass('root'); - var $unsortedList = $('
    ', { id: 'unsortedTree', 'class': 'category2' }).append($unsortedElement); - $container.append($unsortedList); - }; - var createTemplate = function ($container, path) { var $icon = $templateIcon.clone(); var isOpened = filesOp.comparePath(path, currentPath); @@ -1808,7 +1787,6 @@ define([ $tree.html(''); if (displayedCategories.indexOf(SEARCH) !== -1) { createSearch($tree); } if (displayedCategories.indexOf(ROOT) !== -1) { createTree($tree, [ROOT]); } - if (displayedCategories.indexOf(UNSORTED) !== -1) { createUnsorted($tree, [UNSORTED]); } if (displayedCategories.indexOf(TEMPLATE) !== -1) { createTemplate($tree, [TEMPLATE]); } if (displayedCategories.indexOf(FILES_DATA) !== -1) { createAllFiles($tree, [FILES_DATA]); } if (displayedCategories.indexOf(TRASH) !== -1) { createTrash($tree, [TRASH]); } @@ -1830,9 +1808,6 @@ define([ case ROOT: prettyName = ROOT_NAME; break; - case UNSORTED: - prettyName = UNSORTED_NAME; - break; case FILES_DATA: prettyName = FILES_DATA_NAME; break; @@ -2210,13 +2185,14 @@ define([ Get.put(hash, Messages.driveReadme, function (e) { if (e) { logError(e); } var href = '/pad/#' + hash; - proxy.drive[UNSORTED].push(href); - proxy.drive[FILES_DATA].push({ + var data = { href: href, title: Messages.driveReadmeTitle, atime: new Date().toISOString(), ctime: new Date().toISOString() - }); + }; + filesOp.pushData(data); + filesOp.add(data); if (typeof(cb) === "function") { cb(); } }); delete sessionStorage.createReadme; From a97e7223f1ac52b0589edc2ca127bd6fc1341175 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 27 Apr 2017 12:47:21 +0200 Subject: [PATCH 081/306] implement getBlobPathFromHex --- www/common/common-hash.js | 4 ++++ www/common/cryptpad-common.js | 1 + 2 files changed, 5 insertions(+) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 620acd319..b282a1b94 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -266,5 +266,9 @@ Version 2 return hex; }; + var getBlobPath = Hash.getBlobPathFromHex = function (id) { + return '/blob/' + id.slice(0,2) + '/' + id; + }; + return Hash; }); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 5d8ab7804..f4834b285 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -73,6 +73,7 @@ define([ var hrefToHexChannelId = common.hrefToHexChannelId = Hash.hrefToHexChannelId; var parseHash = common.parseHash = Hash.parseHash; var getRelativeHref = common.getRelativeHref = Hash.getRelativeHref; + common.getBlobPathFromHex = Hash.getBlobPathFromHex; common.getEditHashFromKeys = Hash.getEditHashFromKeys; common.getViewHashFromKeys = Hash.getViewHashFromKeys; From e2942f959b549d72faeffbe6cf833e9cf4a93779 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 27 Apr 2017 12:56:42 +0200 Subject: [PATCH 082/306] add crypto for decrypting a chunked file --- www/file/file-crypto.js | 88 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 www/file/file-crypto.js diff --git a/www/file/file-crypto.js b/www/file/file-crypto.js new file mode 100644 index 000000000..8c520dd59 --- /dev/null +++ b/www/file/file-crypto.js @@ -0,0 +1,88 @@ +define([ + '/bower_components/tweetnacl/nacl-fast.min.js', +], function () { + var Nacl = window.nacl; + + var chunkLength = 131088; + + var slice = function (A) { + return Array.prototype.slice.call(A); + }; + + var increment = function (N) { + var l = N.length; + while (l-- > 1) { + if (N[l] !== 255) { return void N[l]++; } + N[l] = 0; + if (l === 0) { return true; } + } + }; + + var joinChunks = function (B) { + return new Uint8Array(chunks.reduce(function (A, B) { + return slice(A).concat(slice(B)); + }, [])); + }; + + var decrypt = function (u8, key, cb) { + var nonce = new Uint8Array(new Array(24).fill(0)); + var i = 0; + var takeChunk = function () { + let start = i * chunkLength; + let end = start + chunkLength; + i++; + let box = new Uint8Array(u8.subarray(start, end)); + + // decrypt the chunk + let plaintext = Nacl.secretbox.open(box, nonce, key); + increment(nonce); + return plaintext; + }; + + var buffer = ''; + + var res = { + metadata: undefined, + }; + + // decrypt metadata + for (; !res.metadata && i * chunkLength < u8.length;) { + var chunk = takeChunk(); + buffer += Nacl.util.encodeUTF8(chunk); + try { + res.metadata = JSON.parse(buffer); + //console.log(res.metadata); + } catch (e) { + console.log('buffering another chunk for metadata'); + } + } + + if (!res.metadata) { + return void setTimeout(function () { + cb('NO_METADATA'); + }); + } + + var chunks = []; + // decrypt file contents + for (;i * chunkLength < u8.length;) { + let chunk = takeChunk(); + if (!chunk) { + return void window.setTimeout(function () { + cb('DECRYPTION_ERROR'); + }); + //throw new Error('failed to parse'); + } + chunks.push(chunk); + } + + // send chunks + res.content = joinChunks(chunks); + + cb(void 0, res); + }; + + return { + decrypt: decrypt, + }; +}); From 197b366712db2e8a6667160c192124c3d76d7de4 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 27 Apr 2017 17:01:56 +0200 Subject: [PATCH 083/306] Ability to drag&select in the drive --- www/drive/file.css | 25 +++++++++-- www/drive/file.less | 23 ++++++++-- www/drive/main.js | 103 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 6 deletions(-) diff --git a/www/drive/file.css b/www/drive/file.css index a68b8e7bc..38b314d56 100644 --- a/www/drive/file.css +++ b/www/drive/file.css @@ -89,6 +89,16 @@ li { .selected .fa-plus-square-o { color: #000; } +.selectedTmp { + border: 1px dotted #bbb; + background: #AAA; + color: #ddd; + margin: -1px; +} +.selectedTmp .fa-minus-square-o, +.selectedTmp .fa-plus-square-o { + color: #000; +} span.fa-folder, span.fa-folder-open { color: #FEDE8B; @@ -215,6 +225,12 @@ span.fa-folder-open { flex: 1; display: flex; flex-flow: column; + position: relative; +} +#content .selectBox { + display: none; + background-color: rgba(100, 100, 100, 0.7); + position: absolute; } #content.readonly { background: #e6e6e6; @@ -242,7 +258,7 @@ span.fa-folder-open { #content li:not(.header) *:not(input) { /*pointer-events: none;*/ } -#content li:not(.header):hover:not(.selected) { +#content li:not(.header):hover:not(.selected, .selectedTmp) { background-color: #eee; } #content li:not(.header):hover .name { @@ -304,8 +320,8 @@ span.fa-folder-open { padding-bottom: 5px; max-height: 145px; } -#content div.grid li:not(.selected) { - border: transparent 1px; +#content div.grid li:not(.selected):not(.selectedTmp) { + border: 1px solid transparent; } #content div.grid li .name { width: 100%; @@ -326,6 +342,9 @@ span.fa-folder-open { #content div.grid .listElement { display: none; } +#content .list { + padding-left: 20px; +} #content .list ul { display: table; width: 100%; diff --git a/www/drive/file.less b/www/drive/file.less index 82c6a8657..03bc4750f 100644 --- a/www/drive/file.less +++ b/www/drive/file.less @@ -121,6 +121,16 @@ li { } } +.selectedTmp { + border: 1px dotted #bbb; + background: #AAA; + color: #ddd; + margin: -1px; + .fa-minus-square-o, .fa-plus-square-o { + color: @tree-fg; + } +} + span { &.fa-folder, &.fa-folder-open { color: #FEDE8B; @@ -260,6 +270,12 @@ span { flex: 1; display: flex; flex-flow: column; + position: relative; + .selectBox { + display: none; + background-color: rgba(100, 100, 100, 0.7); + position: absolute; + } &.readonly { background: @content-bg-ro; } @@ -287,7 +303,7 @@ span { /*pointer-events: none;*/ } &:hover { - &:not(.selected) { + &:not(.selected, .selectedTmp) { background-color: @drive-hover; } .name { @@ -357,8 +373,8 @@ span { padding-bottom: 5px; max-height: 145px; - &:not(.selected) { - border: transparent 1px; + &:not(.selected):not(.selectedTmp) { + border: 1px solid transparent; } .name { width: 100%; @@ -384,6 +400,7 @@ span { .list { // Make it act as a table! + padding-left: 20px; ul { display: table; width: 100%; diff --git a/www/drive/main.js b/www/drive/main.js index 038f9402e..057a0a8ee 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -199,6 +199,7 @@ define([ var $trashTreeContextMenu = $iframe.find("#trashTreeContextMenu"); var $trashContextMenu = $iframe.find("#trashContextMenu"); + // TOOLBAR /* add a "change username" button */ @@ -252,12 +253,113 @@ define([ return $el.is('.element-row') ? $el : $el.closest('.element-row'); }; + + // Selection var removeSelected = function () { $iframe.find('.selected').removeClass("selected"); var $container = $driveToolbar.find('#contextButtonsContainer'); if (!$container.length) { return; } $container.html(''); }; + + var sel = {}; + sel.refresh = 200; + sel.$selectBox = $('
    ', {'class': 'selectBox'}).appendTo($content); + var checkSelected = function () { + if (!sel.down) { return; } + var pos = sel.pos; + var l = $content[0].querySelectorAll('.element:not(.selected):not(.header)'); + var p, el; + for (var i = 0; i < l.length; i++) { + el = l[i]; + p = $(el).position(); + p.top += 10 + $content.scrollTop(); + p.left += 10; + p.bottom = p.top + $(el).outerHeight(); + p.right = p.left + $(el).outerWidth(); + if (p.right < pos.left || p.left > pos.right + || p.top > pos.bottom || p.bottom < pos.top) { + $(el).removeClass('selectedTmp'); + } else { + $(el).addClass('selectedTmp'); + } + } + }; + $content.on('mousedown', function (e) { + console.log('down'); + sel.down = true; + if (!e.ctrlKey) { removeSelected(); } + var rect = e.currentTarget.getBoundingClientRect(); + sel.startX = e.clientX - rect.left, + sel.startY = e.clientY - rect.top + $content.scrollTop(); + sel.$selectBox.show().css({ + left: sel.startX + 'px', + top: sel.startY + 'px', + width: '0px', + height: '0px' + }); + if (sel.move) { console.log('ret'); return; } + sel.move = function (ev) { + var rectMove = ev.currentTarget.getBoundingClientRect(), + offX = ev.clientX - rectMove.left, + offY = ev.clientY - rectMove.top + $content.scrollTop(); + + + var left = sel.startX, + top = sel.startY; + var width = offX - sel.startX; + if (width < 0) { + left = Math.max(0, offX); + var diffX = left-offX; + width = Math.abs(width) - diffX; + } + var height = offY - sel.startY; + if (height < 0) { + top = Math.max(0, offY); + var diffY = top-offY; + height = Math.abs(height) - diffY; + } + sel.$selectBox.css({ + width: width + 'px', + left: left + 'px', + height: height + 'px', + top: top + 'px' + }); + + + sel.pos = { + top: top, + left: left, + bottom: top + height, + right: left + width + }; + var diffT = sel.update ? +new Date() - sel.update : sel.refresh; + if (diffT < sel.refresh) { + if (!sel.to) { + sel.to = window.setTimeout(function () { + sel.update = +new Date(); + checkSelected(); + sel.to = undefined; + }, (sel.refresh - diffT)); + } + console.log('cancelled'); + return; + } + sel.update = +new Date(); + checkSelected(); + }; + $content.mousemove(sel.move); + }); + $content.on('mouseup', function (e) { + console.log(sel.pos); + sel.down = false; + sel.$selectBox.hide(); + $content.off('mousemove', sel.move); + delete sel.move; + $content.find('.selectedTmp').removeClass('selectedTmp').addClass('selected'); + }); + + var removeInput = function (cancel) { if (!cancel && $iframe.find('.element-row > input').length === 1) { var $input = $iframe.find('.element-row > input'); @@ -1501,6 +1603,7 @@ define([ currentPath = path; var s = $content.scrollTop() || 0; $content.html(""); + sel.$selectBox = $('
    ', {'class': 'selectBox'}).appendTo($content); if (!path || path.length === 0) { path = [ROOT]; } From bf7c7c45d0d62edb226597a17ea8d84c214b34fd Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 27 Apr 2017 18:46:46 +0200 Subject: [PATCH 084/306] Add the storage limit warning in the toolbar --- customize.dist/src/less/toolbar.less | 16 +++++++++++++++ customize.dist/toolbar.css | 14 +++++++++++++ customize.dist/translations/messages.fr.js | 4 ++++ customize.dist/translations/messages.js | 4 ++++ www/code/main.js | 2 +- www/common/cryptpad-common.js | 4 ++++ www/common/toolbar.js | 24 ++++++++++++++++++++++ 7 files changed, 67 insertions(+), 1 deletion(-) diff --git a/customize.dist/src/less/toolbar.less b/customize.dist/src/less/toolbar.less index 5e4f74dcf..f67e2ee96 100644 --- a/customize.dist/src/less/toolbar.less +++ b/customize.dist/src/less/toolbar.less @@ -79,6 +79,22 @@ } } + .cryptpad-limit { + color: red; + box-sizing: content-box; + height: 16px; + width: 16px; + display: inline-block; + padding: 3px; + margin: 3px; + margin-right: 6px; + font-size: 20px; + span { + cursor: pointer; + margin: auto; + } + } + .cryptpad-lag { box-sizing: content-box; height: 16px; diff --git a/customize.dist/toolbar.css b/customize.dist/toolbar.css index 1165c6df4..db8ba7776 100644 --- a/customize.dist/toolbar.css +++ b/customize.dist/toolbar.css @@ -150,6 +150,20 @@ .cryptpad-toolbar button.hidden { display: none; } +.cryptpad-toolbar .cryptpad-limit { + color: red; + box-sizing: content-box; + height: 16px; + width: 16px; + display: inline-block; + padding: 3px; + margin: 3px; + margin-right: 6px; + font-size: 20px; +} +.cryptpad-toolbar .cryptpad-limit span { + margin: auto; +} .cryptpad-toolbar .cryptpad-lag { box-sizing: content-box; height: 16px; diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 2b50a088d..a826659ca 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -55,6 +55,10 @@ define(function () { out.orangeLight = "Votre connexion est lente, ce qui réduit la qualité de l'éditeur"; out.redLight = "Vous êtes déconnectés de la session"; + out.pinLimitReached = "Vous avez atteint votre limite de stockage"; + out.pinLimitReachedAlert = "Vous avez atteint votre limite de stockage. Ce pad ne sera pas enregistré dans votre CrypDrive.
    " + + "Pour résoudre ce problème, vous pouvez soit supprimer des pads de votre CryptDrive (y compris la corbeille), soit vous abonner à une offre premium pour augmenter la limite maximale."; + out.importButtonTitle = 'Importer un pad depuis un fichier local'; out.exportButtonTitle = 'Exporter ce pad vers un fichier local'; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 6c3ec7c4d..5e37ffdc6 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -57,6 +57,10 @@ define(function () { out.orangeLight = "Your slow connection may impact your experience"; out.redLight = "You are disconnected from the session"; + out.pinLimitReached = "You've reached your storage limit"; + out.pinLimitReachedAlert = "You've reached your storage limit. This pad won't be stored in your CryptDrive.
    " + + "To fix this problem, you can either remove pads from your CryptDrive (including the trash) or subscribe to a premium offer to increase your limit."; + out.importButtonTitle = 'Import a pad from a local file'; out.exportButtonTitle = 'Export this pad to a local file'; diff --git a/www/code/main.js b/www/code/main.js index 4f12d0926..35bcd90d4 100644 --- a/www/code/main.js +++ b/www/code/main.js @@ -382,7 +382,7 @@ define([ userList = info.userList; var configTb = { - displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'], + displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'], userData: userData, readOnly: readOnly, ifrw: ifrw, diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 4c119654e..66030927f 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -708,6 +708,10 @@ define([ }); }; + var getPinLimit = common.getPinLimit = function (cb) { + cb(void 0, 10); + }; + var createButton = common.createButton = function (type, rightside, data, callback) { var button; var size = "17px"; diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 7ff4f2b51..ed7b88459 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -16,6 +16,8 @@ define([ /** Id of the div containing the lag info. */ var LAG_ELEM_CLS = Bar.constants.lag = 'cryptpad-lag'; + var LIMIT_ELEM_CLS = Bar.constants.lag = 'cryptpad-limit'; + /** The toolbar class which contains the user list, debug link and lag. */ var TOOLBAR_CLS = Bar.constants.toolbar = 'cryptpad-toolbar'; @@ -488,6 +490,28 @@ define([ $userContainer.append($lag); } + if (config.displayed.indexOf('limit') !== -1 && Config.enablePinning) { + var usage; + var $limitIcon = $('', {'class': 'fa fa-exclamation-triangle'}); + var $limit = $('', { + 'class': LIMIT_ELEM_CLS, + 'title': Messages.pinLimitReached + }).append($limitIcon).hide().appendTo($userContainer); + var andThen = function (e, limit) { + if (usage > limit) { + $limit.show().click(function () { + Cryptpad.alert(Messages.pinLimitReachedAlert, null, true); + }); + } + }; + var todo = function (e, used) { + usage = Cryptpad.bytesToMegabytes(used); + if (e) { console.error("Unable tog et the pinned usage"); return; } + Cryptpad.getPinLimit(andThen); + }; + Cryptpad.getPinnedUsage(todo); + } + if (config.displayed.indexOf('newpad') !== -1) { var pads_options = []; Config.availablePadTypes.forEach(function (p) { From e132ccf94ac7d381c0a30289855cba67e62a9dfd Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 28 Apr 2017 11:45:53 +0200 Subject: [PATCH 085/306] prepare for upload --- .jshintignore | 1 + www/file/file-crypto.js | 126 ++++++++++++++++++++++++++++++++----- www/file/inner.html | 36 ++++++++++- www/file/main.js | 135 ++++++++++++++++++++++++++++------------ 4 files changed, 239 insertions(+), 59 deletions(-) diff --git a/.jshintignore b/.jshintignore index 51636ed77..93e467aef 100644 --- a/.jshintignore +++ b/.jshintignore @@ -10,3 +10,4 @@ NetFluxWebsocketSrv.js NetFluxWebsocketServer.js WebRTCSrv.js www/common/media-tag.js +www/scratch diff --git a/www/file/file-crypto.js b/www/file/file-crypto.js index 8c520dd59..924cbe334 100644 --- a/www/file/file-crypto.js +++ b/www/file/file-crypto.js @@ -2,39 +2,75 @@ define([ '/bower_components/tweetnacl/nacl-fast.min.js', ], function () { var Nacl = window.nacl; + var PARANOIA = true; - var chunkLength = 131088; + var plainChunkLength = 128 * 1024; + var cypherChunkLength = 131088; var slice = function (A) { return Array.prototype.slice.call(A); }; + var createNonce = function () { + return new Uint8Array(new Array(24).fill(0)); + }; + var increment = function (N) { var l = N.length; while (l-- > 1) { - if (N[l] !== 255) { return void N[l]++; } + if (PARANOIA) { + if (typeof(N[l]) !== 'number') { + throw new Error('E_UNSAFE_TYPE'); + } + if (N[l] > 255) { + throw new Error('E_OUT_OF_BOUNDS'); + } + } + /* jshint probably suspects this is unsafe because we lack types + but as long as this is only used on nonces, it should be safe */ + if (N[l] !== 255) { return void N[l]++; } // jshint ignore:line N[l] = 0; + + // you don't need to worry about this running out. + // you'd need a REAAAALLY big file if (l === 0) { return true; } } }; - var joinChunks = function (B) { + var joinChunks = function (chunks) { return new Uint8Array(chunks.reduce(function (A, B) { return slice(A).concat(slice(B)); }, [])); }; + var padChunk = function (A) { + var padding; + if (A.length === plainChunkLength) { return A; } + if (A.length < plainChunkLength) { + padding = new Array(plainChunkLength - A.length).fill(32); + return A.concat(padding); + } + if (A.length > plainChunkLength) { + // how many times larger is it? + var chunks = Math.ceil(A.length / plainChunkLength); + padding = new Array((plainChunkLength * chunks) - A.length).fill(32); + return A.concat(padding); + } + }; + var decrypt = function (u8, key, cb) { - var nonce = new Uint8Array(new Array(24).fill(0)); + var nonce = createNonce(); var i = 0; + var takeChunk = function () { - let start = i * chunkLength; - let end = start + chunkLength; + var start = i * cypherChunkLength; + var end = start + cypherChunkLength; i++; - let box = new Uint8Array(u8.subarray(start, end)); + var box = new Uint8Array(u8.subarray(start, end)); // decrypt the chunk - let plaintext = Nacl.secretbox.open(box, nonce, key); + var plaintext = Nacl.secretbox.open(box, nonce, key); + // TODO handle nonce-too-large-error increment(nonce); return plaintext; }; @@ -46,8 +82,9 @@ define([ }; // decrypt metadata - for (; !res.metadata && i * chunkLength < u8.length;) { - var chunk = takeChunk(); + var chunk; + for (; !res.metadata && i * cypherChunkLength < u8.length;) { + chunk = takeChunk(); buffer += Nacl.util.encodeUTF8(chunk); try { res.metadata = JSON.parse(buffer); @@ -63,15 +100,16 @@ define([ }); } + var fail = function () { + cb("DECRYPTION_ERROR"); + }; + var chunks = []; // decrypt file contents - for (;i * chunkLength < u8.length;) { - let chunk = takeChunk(); + for (;i * cypherChunkLength < u8.length;) { + chunk = takeChunk(); if (!chunk) { - return void window.setTimeout(function () { - cb('DECRYPTION_ERROR'); - }); - //throw new Error('failed to parse'); + return window.setTimeout(fail); } chunks.push(chunk); } @@ -82,7 +120,63 @@ define([ cb(void 0, res); }; + // metadata + /* { filename: 'raccoon.jpg', type: 'image/jpeg' } */ + + + /* TODO + in your callback, return an object which you can iterate... + + + */ + + var encrypt = function (u8, metadata, key, cb) { + var nonce = createNonce(); + + // encode metadata + var metaBuffer = Array.prototype.slice + .call(Nacl.util.decodeUTF8(JSON.stringify(metadata))); + + var plaintext = new Uint8Array(padChunk(metaBuffer)); + + var chunks = []; + var j = 0; + + var start; + var end; + + var part; + var box; + + // prepend some metadata + for (;j * plainChunkLength < plaintext.length; j++) { + start = j * plainChunkLength; + end = start + plainChunkLength; + + part = plaintext.subarray(start, end); + box = Nacl.secretbox(part, nonce, key); + chunks.push(box); + increment(nonce); + } + + // append the encrypted file chunks + var i = 0; + for (;i * plainChunkLength < u8.length; i++) { + start = i * plainChunkLength; + end = start + plainChunkLength; + + part = new Uint8Array(u8.subarray(start, end)); + box = Nacl.secretbox(part, nonce, key); + chunks.push(box); + increment(nonce); + } + + + // TODO do something with the chunks... + }; + return { decrypt: decrypt, + encrypt: encrypt, }; }); diff --git a/www/file/inner.html b/www/file/inner.html index 7f315cef2..09f627842 100644 --- a/www/file/inner.html +++ b/www/file/inner.html @@ -14,14 +14,44 @@ padding: 0px; display: inline-block; } - media-tag * { - max-width: 100%; + #file { + display: block; + height: 300px; + width: 300px; + border: 2px solid black; + margin: 50px; } + + .inputfile { + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; + } + .inputfile + label { + border: 2px solid black; + display: block; + height: 500px; + width: 500px; + background-color: rgba(50, 50, 50, .10); + margin: 50px; + } + + .inputfile:focus + label, + .inputfile + label:hover { + background-color: rgba(50, 50, 50, 0.30); + } +
    - + diff --git a/www/file/main.js b/www/file/main.js index bf7cb9e00..e237d7a63 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -6,12 +6,14 @@ define([ '/common/cryptpad-common.js', '/common/visible.js', '/common/notify.js', + '/file/file-crypto.js', '/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/file-saver/FileSaver.min.js', -], function ($, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify) { +], function ($, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify, FileCrypto) { var Messages = Cryptpad.Messages; var saveAs = window.saveAs; - //window.Nacl = window.nacl; + var Nacl = window.nacl; + $(function () { var ifrw = $('#pad-iframe')[0].contentWindow; @@ -19,18 +21,40 @@ define([ Cryptpad.addLoadingScreen(); + var fetch = function (src, cb) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", src, true); + xhr.responseType = "arraybuffer"; + xhr.onload = function (e) { + return void cb(void 0, new Uint8Array(xhr.response)); + }; + xhr.send(null); + }; + + var upload = function (blob, id, key) { + Cryptpad.alert("UPLOAD IS NOT IMPLEMENTED YET"); + }; + + var myFile; + var myDataType; + var uploadMode = false; + var andThen = function () { var $bar = $iframe.find('.toolbar-container'); - var secret = Cryptpad.getSecrets(); - if (!secret.keys) { throw new Error("You need a hash"); } // TODO - - var cryptKey = secret.keys && secret.keys.fileKeyStr; - var fileId = secret.channel; - var hexFileName = Cryptpad.base64ToHex(fileId); - var type = "image/png"; // Test hash: // #/2/K6xWU-LT9BJHCQcDCT-DcQ/TBo77200c0e-FdldQFcnQx4Y/ + var secret; + var hexFileName; + if (window.location.hash) { + secret = Cryptpad.getSecrets(); + if (!secret.keys) { throw new Error("You need a hash"); } // TODO + hexFileName = Cryptpad.base64ToHex(secret.channel); + } else { + uploadMode = true; + } + + //window.location.hash = '/2/K6xWU-LT9BJHCQcDCT-DcQ/VLIgpQOgmSaW3AQcUCCoJnYvCbMSO0MKBqaICSly9fo='; var parsed = Cryptpad.parsePadUrl(window.location.href); var defaultName = Cryptpad.getDefaultName(parsed); @@ -67,45 +91,76 @@ define([ var exportFile = function () { var suggestion = document.title; Cryptpad.prompt(Messages.exportPrompt, - Cryptpad.fixFileName(suggestion) + '.html', function (filename) { + Cryptpad.fixFileName(suggestion), function (filename) { if (!(typeof(filename) === 'string' && filename)) { return; } - //var blob = new Blob([html], {type: "text/html;charset=utf-8"}); + var blob = new Blob([myFile], {type: myDataType}); saveAs(blob, filename); }); }; - var $mt = $iframe.find('#encryptedFile'); - $mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName); - $mt.attr('data-crypto-key', cryptKey); - $mt.attr('data-type', type); - - require(['/common/media-tag.js'], function (MediaTag) { - var configTb = { - displayed: ['useradmin', 'share', 'newpad'], - ifrw: ifrw, - common: Cryptpad, - title: { - onRename: renameCb, - defaultName: defaultName, - suggestName: suggestName - }, - share: { - secret: secret, - channel: hexFileName - } - }; - Toolbar.create($bar, null, null, null, null, configTb); - var $rightside = $bar.find('.' + Toolbar.constants.rightside); - - var $export = Cryptpad.createButton('export', true, {}, exportFile); - $rightside.append($export); - - updateTitle(Cryptpad.initialName || getTitle() || defaultName); + var displayed = ['useradmin', 'newpad', 'limit']; + if (secret && hexFileName) { + displayed.push('share'); + } + + var configTb = { + displayed: displayed, + ifrw: ifrw, + common: Cryptpad, + title: { + onRename: renameCb, + defaultName: defaultName, + suggestName: suggestName + }, + share: { + secret: secret, + channel: hexFileName + } + }; + Toolbar.create($bar, null, null, null, null, configTb); + var $rightside = $bar.find('.' + Toolbar.constants.rightside); + + var $export = Cryptpad.createButton('export', true, {}, exportFile); + $rightside.append($export); + + updateTitle(Cryptpad.initialName || getTitle() || defaultName); + + if (!uploadMode) { + var src = Cryptpad.getBlobPathFromHex(hexFileName); + return fetch(src, function (e, u8) { + // now decrypt the u8 + if (e) { return window.alert('error'); } + var cryptKey = secret.keys && secret.keys.fileKeyStr; + var key = Nacl.util.decodeBase64(cryptKey); + + FileCrypto.decrypt(u8, key, function (e, data) { + console.log(data); + var title = document.title = data.metadata.filename; + myFile = data.content; + myDataType = data.metadata.type; + updateTitle(title || defaultName); + + Cryptpad.removeLoadingScreen(); + }); + }); + } - var mt = MediaTag($mt[0]); + var $form = $iframe.find('#upload-form'); + $form.css({ + display: 'block', + }); - Cryptpad.removeLoadingScreen(); + var $file = $form.find("#file").on('change', function (e) { + var file = e.target.files[0]; + var reader = new FileReader(); + reader.onload = function (e) { + upload(e.target.result); + }; + reader.readAsText(file); }); + + // we're in upload mode + Cryptpad.removeLoadingScreen(); }; Cryptpad.ready(function (err, anv) { From fe93da8817901ecb495dda23b1702f6af2099dc0 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 28 Apr 2017 11:46:13 +0200 Subject: [PATCH 086/306] get ready to implement blob storage --- rpc.js | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/rpc.js b/rpc.js index 808815d84..5670f1c7a 100644 --- a/rpc.js +++ b/rpc.js @@ -376,6 +376,23 @@ var resetUserPins = function (store, Sessions, publicKey, channelList, cb) { }); }; +var getLimit = function (cb) { + +}; + +var createBlobStaging = function (cb) { + +}; + +var createBlobStore = function (cb) { +}; + +var upload = function (store, Sessions, publicKey, cb) { + +}; + + + /*::const ConfigType = require('./config.example.js');*/ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)=>void*/) { // load pin-store... @@ -428,7 +445,6 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) return void respond('INVALID_MESSAGE_OR_PUBLIC_KEY'); } - if (checkSignature(serialized, signature, publicKey) !== true) { return void respond("INVALID_SIGNATURE_OR_PUBLIC_KEY"); } @@ -459,7 +475,8 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) return resetUserPins(store, Sessions, safeKey, msg[1], function (e, hash) { return void Respond(e, hash); }); - case 'PIN': + case 'PIN': // TODO don't pin if over the limit + // if over, send error E_OVER_LIMIT return pinChannel(store, Sessions, safeKey, msg[1], function (e, hash) { Respond(e, hash); }); @@ -471,13 +488,17 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) return void getHash(store, Sessions, safeKey, function (e, hash) { Respond(e, hash); }); - case 'GET_TOTAL_SIZE': + case 'GET_TOTAL_SIZE': // TODO cache this, since it will get called quite a bit return getTotalSize(store, ctx.store, Sessions, safeKey, function (e, size) { if (e) { return void Respond(e); } Respond(e, size); }); case 'GET_FILE_SIZE': return void getFileSize(ctx.store, msg[1], Respond); + case 'GET_LIMIT': // TODO implement this and cache it per-user + return void getLimit(function (e, limit) { + Respond('NOT_IMPLEMENTED'); + }); case 'GET_MULTIPLE_FILE_SIZE': return void getMultipleFileSize(ctx.store, msg[1], function (e, dict) { if (e) { return void Respond(e); } From a165332c15293639959f047633c04c35ccafeb15 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 28 Apr 2017 12:01:47 +0200 Subject: [PATCH 087/306] Don't store a pad in the drive if the limit has been reached --- customize.dist/toolbar.css | 1 + customize.dist/translations/messages.fr.js | 4 ++- customize.dist/translations/messages.js | 4 ++- www/common/cryptpad-common.js | 30 +++++++++++++++++++--- www/common/toolbar.js | 12 +++------ www/common/userObject.js | 23 +++++++++++------ www/drive/main.js | 2 +- www/pad/main.js | 2 +- www/poll/main.js | 2 +- www/slide/main.js | 2 +- www/whiteboard/main.js | 2 +- 11 files changed, 58 insertions(+), 26 deletions(-) diff --git a/customize.dist/toolbar.css b/customize.dist/toolbar.css index db8ba7776..4ef7a4567 100644 --- a/customize.dist/toolbar.css +++ b/customize.dist/toolbar.css @@ -162,6 +162,7 @@ font-size: 20px; } .cryptpad-toolbar .cryptpad-limit span { + cursor: pointer; margin: auto; } .cryptpad-toolbar .cryptpad-lag { diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index a826659ca..50a2bf667 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -56,8 +56,10 @@ define(function () { out.redLight = "Vous êtes déconnectés de la session"; out.pinLimitReached = "Vous avez atteint votre limite de stockage"; - out.pinLimitReachedAlert = "Vous avez atteint votre limite de stockage. Ce pad ne sera pas enregistré dans votre CrypDrive.
    " + + out.pinLimitReachedAlert = "Vous avez atteint votre limite de stockage. Les nouveaux pads ne seront pas enregistrés dans votre CrypDrive.
    " + "Pour résoudre ce problème, vous pouvez soit supprimer des pads de votre CryptDrive (y compris la corbeille), soit vous abonner à une offre premium pour augmenter la limite maximale."; + out.pinLimitNotPinned = "Vous avez atteint votre limite de stockage.
    "+ + "Ce pad n'est pas enregistré dans votre CryptDrive."; out.importButtonTitle = 'Importer un pad depuis un fichier local'; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 5e37ffdc6..7c5fd8df5 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -58,8 +58,10 @@ define(function () { out.redLight = "You are disconnected from the session"; out.pinLimitReached = "You've reached your storage limit"; - out.pinLimitReachedAlert = "You've reached your storage limit. This pad won't be stored in your CryptDrive.
    " + + out.pinLimitReachedAlert = "You've reached your storage limit. New pads won't be stored in your CryptDrive.
    " + "To fix this problem, you can either remove pads from your CryptDrive (including the trash) or subscribe to a premium offer to increase your limit."; + out.pinLimitNotPinned = "You've reached your storage limit.
    "+ + "This pad is not stored in your CryptDrive."; out.importButtonTitle = 'Import a pad from a local file'; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 66030927f..0fb7d7ad8 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -570,8 +570,16 @@ define([ if (!contains) { var data = makePad(href, name); - getStore().pushData(data); - getStore().addPad(data, common.initialPath); + getStore().pushData(data, function (e, state) { + if (e) { + if (e === 'E_OVER_LIMIT') { + Cryptpad.alert(Messages.pinLimitNotPinned, null, true); + return; + } + else { throw new Error("Cannot push this pad to CryptDrive", e); } + } + getStore().addPad(data, common.initialPath); + }); } if (updateWeaker.length > 0) { updateWeaker.forEach(function (obj) { @@ -709,7 +717,23 @@ define([ }; var getPinLimit = common.getPinLimit = function (cb) { - cb(void 0, 10); + cb(void 0, 1000); + }; + + var isOverPinLimit = common.isOverPinLimit = function (cb) { + var andThen = function (e, limit) { + if (e) { return void cb(e); } + if (usage > limit) { + return void cb (null, true); + } + return void cb (null, false); + }; + var todo = function (e, used) { + usage = common.bytesToMegabytes(used); + if (e) { return void cb(e); } + common.getPinLimit(andThen); + }; + common.getPinnedUsage(todo); }; var createButton = common.createButton = function (type, rightside, data, callback) { diff --git a/www/common/toolbar.js b/www/common/toolbar.js index ed7b88459..23de3262d 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -497,19 +497,15 @@ define([ 'class': LIMIT_ELEM_CLS, 'title': Messages.pinLimitReached }).append($limitIcon).hide().appendTo($userContainer); - var andThen = function (e, limit) { - if (usage > limit) { + var todo = function (e, overLimit) { + if (e) { return void console.error("Unable tog et the pinned usage"); } + if (overLimit) { $limit.show().click(function () { Cryptpad.alert(Messages.pinLimitReachedAlert, null, true); }); } }; - var todo = function (e, used) { - usage = Cryptpad.bytesToMegabytes(used); - if (e) { console.error("Unable tog et the pinned usage"); return; } - Cryptpad.getPinLimit(andThen); - }; - Cryptpad.getPinnedUsage(todo); + Cryptpad.isOverPinLimit(todo); } if (config.displayed.indexOf('newpad') !== -1) { diff --git a/www/common/userObject.js b/www/common/userObject.js index 9ea1af726..6757e7cd7 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -1,6 +1,7 @@ define([ 'jquery', -], function ($) { + '/customize/application_config.js' +], function ($, AppConfig) { var module = {}; var ROOT = module.ROOT = "root"; @@ -427,19 +428,25 @@ define([ }; // FILES DATA - var pushFileData = exp.pushData = function (data) { + var pushFileData = exp.pushData = function (data, cb) { + if (typeof cb !== "function") { cb = function () {}; } + var todo = function () { + files[FILES_DATA].push(data); + cb(); + }; + if (!Cryptpad.isLoggedIn() || !AppConfig.enablePinning) { todo(); } Cryptpad.pinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e, hash) { - if (e) { console.log(e); return; } - console.log(hash); + if (e) { return void cb(e); } + cb('E_OVER_LIMIT'); return; //TODO + todo(); }); - files[FILES_DATA].push(data); }; var spliceFileData = exp.removeData = function (idx) { var data = files[FILES_DATA][idx]; - if (typeof data === "object") { + if (typeof data === "object" && Cryptpad.isLoggedIn() && AppConfig.enablePinning) { Cryptpad.unpinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e, hash) { - if (e) { console.log(e); return; } - console.log(hash); + if (e) { return void logError(e); } + debug('UNPIN', hash); }); } files[FILES_DATA].splice(idx, 1); diff --git a/www/drive/main.js b/www/drive/main.js index 057a0a8ee..e05772676 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -2392,7 +2392,7 @@ define([ var userList = APP.userList = info.userList; var config = { - displayed: ['useradmin', 'spinner', 'lag', 'state'], + displayed: ['useradmin', 'spinner', 'lag', 'state', 'limit'], readOnly: readOnly, ifrw: window, common: Cryptpad, diff --git a/www/pad/main.js b/www/pad/main.js index c2bbdc5d2..e1b552a48 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -578,7 +578,7 @@ define([ userList = info.userList; var configTb = { - displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'], + displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'], userData: userData, readOnly: readOnly, ifrw: ifrw, diff --git a/www/poll/main.js b/www/poll/main.js index 5aa3e4ee2..1d6a151e5 100644 --- a/www/poll/main.js +++ b/www/poll/main.js @@ -733,7 +733,7 @@ define([ userList = APP.userList = info.userList; var config = { - displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'], + displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'], userData: userData, readOnly: readOnly, share: { diff --git a/www/slide/main.js b/www/slide/main.js index bef2a4e52..eb9919abc 100644 --- a/www/slide/main.js +++ b/www/slide/main.js @@ -513,7 +513,7 @@ define([ userList = info.userList; var configTb = { - displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'], + displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'], userData: userData, readOnly: readOnly, ifrw: ifrw, diff --git a/www/whiteboard/main.js b/www/whiteboard/main.js index 6cadfc487..b35ae705d 100644 --- a/www/whiteboard/main.js +++ b/www/whiteboard/main.js @@ -334,7 +334,7 @@ window.canvas = canvas; var onInit = config.onInit = function (info) { userList = info.userList; var config = { - displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'], + displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'], userData: userData, readOnly: readOnly, share: { From 0ef1c14d7fd0c4982c6604f675989a992ee3b0c9 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 28 Apr 2017 12:12:17 +0200 Subject: [PATCH 088/306] Fix lint errors --- www/common/cryptpad-common.js | 3 ++- www/common/userObject.js | 1 - www/drive/main.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 3462affdb..20f68f3be 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -574,7 +574,7 @@ define([ getStore().pushData(data, function (e, state) { if (e) { if (e === 'E_OVER_LIMIT') { - Cryptpad.alert(Messages.pinLimitNotPinned, null, true); + common.alert(Messages.pinLimitNotPinned, null, true); return; } else { throw new Error("Cannot push this pad to CryptDrive", e); } @@ -722,6 +722,7 @@ define([ }; var isOverPinLimit = common.isOverPinLimit = function (cb) { + var usage; var andThen = function (e, limit) { if (e) { return void cb(e); } if (usage > limit) { diff --git a/www/common/userObject.js b/www/common/userObject.js index 6757e7cd7..66d507a27 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -437,7 +437,6 @@ define([ if (!Cryptpad.isLoggedIn() || !AppConfig.enablePinning) { todo(); } Cryptpad.pinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e, hash) { if (e) { return void cb(e); } - cb('E_OVER_LIMIT'); return; //TODO todo(); }); }; diff --git a/www/drive/main.js b/www/drive/main.js index e05772676..37a699a21 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -290,7 +290,7 @@ define([ sel.down = true; if (!e.ctrlKey) { removeSelected(); } var rect = e.currentTarget.getBoundingClientRect(); - sel.startX = e.clientX - rect.left, + sel.startX = e.clientX - rect.left; sel.startY = e.clientY - rect.top + $content.scrollTop(); sel.$selectBox.show().css({ left: sel.startX + 'px', From c820b3485c2c3c4cedbbbcca35d424fcd044ccf3 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 28 Apr 2017 12:16:05 +0200 Subject: [PATCH 089/306] Fix function undefined when loading a template --- www/common/userObject.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/userObject.js b/www/common/userObject.js index 66d507a27..0acb44c68 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -727,7 +727,7 @@ define([ parentEl[newName] = element; parentEl[oldName] = undefined; delete parentEl[oldName]; - cb(); + if (typeof cb === "function") { cb(); } }; // REPLACE From ab1dc48b6dede9fb881dbc67448798299eb5e6e7 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 28 Apr 2017 12:32:49 +0200 Subject: [PATCH 090/306] Fix the 'limit reached' icon in the pad app --- customize.dist/src/less/toolbar.less | 4 ++-- customize.dist/toolbar.css | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/customize.dist/src/less/toolbar.less b/customize.dist/src/less/toolbar.less index f67e2ee96..2248c8e63 100644 --- a/customize.dist/src/less/toolbar.less +++ b/customize.dist/src/less/toolbar.less @@ -80,7 +80,6 @@ } .cryptpad-limit { - color: red; box-sizing: content-box; height: 16px; width: 16px; @@ -88,10 +87,11 @@ padding: 3px; margin: 3px; margin-right: 6px; - font-size: 20px; span { + color: red; cursor: pointer; margin: auto; + font-size: 20px; } } diff --git a/customize.dist/toolbar.css b/customize.dist/toolbar.css index 4ef7a4567..58fa58033 100644 --- a/customize.dist/toolbar.css +++ b/customize.dist/toolbar.css @@ -151,7 +151,6 @@ display: none; } .cryptpad-toolbar .cryptpad-limit { - color: red; box-sizing: content-box; height: 16px; width: 16px; @@ -159,11 +158,12 @@ padding: 3px; margin: 3px; margin-right: 6px; - font-size: 20px; } .cryptpad-toolbar .cryptpad-limit span { + color: red; cursor: pointer; margin: auto; + font-size: 20px; } .cryptpad-toolbar .cryptpad-lag { box-sizing: content-box; From 0ee228666b3e3d37b6bd2fea4af2ad289a51a81f Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 28 Apr 2017 12:40:39 +0200 Subject: [PATCH 091/306] Replace the IRC link by the Matrix one --- customize.dist/about.html | 2 +- customize.dist/contact.html | 2 +- customize.dist/index.html | 2 +- customize.dist/privacy.html | 2 +- customize.dist/src/fragments/footer.html | 2 +- customize.dist/terms.html | 2 +- www/settings/index.html | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/customize.dist/about.html b/customize.dist/about.html index c751125a7..2b13aca95 100644 --- a/customize.dist/about.html +++ b/customize.dist/about.html @@ -106,7 +106,7 @@
    • -
    • IRC
    • +
    • Chat
    • Twitter
    • GitHub
    • Email
    • diff --git a/customize.dist/contact.html b/customize.dist/contact.html index 619f98df4..eff9c1869 100644 --- a/customize.dist/contact.html +++ b/customize.dist/contact.html @@ -103,7 +103,7 @@
      • -
      • IRC
      • +
      • Chat
      • Twitter
      • GitHub
      • Email
      • diff --git a/customize.dist/index.html b/customize.dist/index.html index 7aa29d8e9..f6ab10fa6 100644 --- a/customize.dist/index.html +++ b/customize.dist/index.html @@ -225,7 +225,7 @@
        • -
        • IRC
        • +
        • Chat
        • Twitter
        • GitHub
        • Email
        • diff --git a/customize.dist/privacy.html b/customize.dist/privacy.html index 8d2e09f5f..a7751db84 100644 --- a/customize.dist/privacy.html +++ b/customize.dist/privacy.html @@ -124,7 +124,7 @@
          • -
          • IRC
          • +
          • Chat
          • Twitter
          • GitHub
          • Email
          • diff --git a/customize.dist/src/fragments/footer.html b/customize.dist/src/fragments/footer.html index ec80ed27d..b15a2b5da 100644 --- a/customize.dist/src/fragments/footer.html +++ b/customize.dist/src/fragments/footer.html @@ -31,7 +31,7 @@
            • -
            • IRC
            • +
            • Chat
            • Twitter
            • GitHub
            • Email
            • diff --git a/customize.dist/terms.html b/customize.dist/terms.html index 73a13cea7..edfd77f86 100644 --- a/customize.dist/terms.html +++ b/customize.dist/terms.html @@ -107,7 +107,7 @@
              • -
              • IRC
              • +
              • Chat
              • Twitter
              • GitHub
              • Email
              • diff --git a/www/settings/index.html b/www/settings/index.html index a84a60963..c24748490 100644 --- a/www/settings/index.html +++ b/www/settings/index.html @@ -97,7 +97,7 @@
                • -
                • IRC
                • +
                • Chat
                • Twitter
                • GitHub
                • Email
                • From 69368def48a4981195317067dfdca3a50b6dfa21 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 28 Apr 2017 12:48:36 +0200 Subject: [PATCH 092/306] make pin, blob, and blobstage path configurable --- config.example.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/config.example.js b/config.example.js index 692a91c59..859c4b872 100644 --- a/config.example.js +++ b/config.example.js @@ -141,6 +141,23 @@ module.exports = { */ filePath: './datastore/', + /* CryptPad allows logged in users to request that particular documents be + * stored by the server indefinitely. This is called 'pinning'. + * Pin requests are stored in a pin-store. The location of this store is + * defined here. + */ + pinPath: './pins', + + /* CryptPad allows logged in users to upload encrypted files. Files/blobs + * are stored in a 'blob-store'. Set its location here. + */ + blobPath: './blob', + + /* CryptPad stores incomplete blobs in a 'staging' area until they are + * fully uploaded. Set its location here. + */ + blobStagingPath: './blobstage', + /* Cryptpad's file storage adaptor closes unused files after a configurale * number of milliseconds (default 30000 (30 seconds)) */ From 4d48c88807b49a88afc73222b9d47415852951c3 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 28 Apr 2017 12:49:08 +0200 Subject: [PATCH 093/306] create blob and blobstage stores if not exists --- rpc.js | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/rpc.js b/rpc.js index 5670f1c7a..4d1e0005d 100644 --- a/rpc.js +++ b/rpc.js @@ -2,6 +2,8 @@ /* Use Nacl for checking signatures of messages */ var Nacl = require("tweetnacl"); +var Fs = require("fs"); + var RPC = module.exports; var Store = require("./storage/file"); @@ -380,19 +382,17 @@ var getLimit = function (cb) { }; -var createBlobStaging = function (cb) { - -}; - -var createBlobStore = function (cb) { +var safeMkdir = function (path, cb) { + Fs.mkdir(path, function (e) { + if (!e || e.code === 'EEXIST') { return void cb(); } + cb(e); + }); }; var upload = function (store, Sessions, publicKey, cb) { }; - - /*::const ConfigType = require('./config.example.js');*/ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)=>void*/) { // load pin-store... @@ -509,15 +509,29 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) } }; + var keyOrDefaultString = function (key, def) { + return typeof(config[key]) === 'string'? config[key]: def; + }; + + var pinPath = keyOrDefaultString('pinPath', './pins'); + var blobPath = keyOrDefaultString('blobPath', './blob'); + var blobStagingPath = keyOrDefaultString('blobStagingPath', './blobstage'); + Store.create({ - filePath: './pins' + filePath: pinPath, }, function (s) { store = s; - cb(void 0, rpc); - // expire old sessions once per minute - setInterval(function () { - expireSessions(Sessions); - }, 60000); + safeMkdir(blobPath, function (e) { + if (e) { throw e; } + safeMkdir(blobStagingPath, function (e) { + if (e) { throw e; } + cb(void 0, rpc); + // expire old sessions once per minute + setInterval(function () { + expireSessions(Sessions); + }, 60000); + }) + }); }); }; From 16f6ab813c8df795a9468601239f636cba41ca16 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 28 Apr 2017 13:06:55 +0200 Subject: [PATCH 094/306] Make it clear that the number is the history represents a version --- customize.dist/src/less/toolbar.less | 37 ++++++++------ customize.dist/toolbar.css | 57 ++++++++++++++-------- customize.dist/translations/messages.fr.js | 1 + customize.dist/translations/messages.js | 1 + www/common/common-history.js | 28 +++++------ 5 files changed, 72 insertions(+), 52 deletions(-) diff --git a/customize.dist/src/less/toolbar.less b/customize.dist/src/less/toolbar.less index 2248c8e63..40f895c24 100644 --- a/customize.dist/src/less/toolbar.less +++ b/customize.dist/src/less/toolbar.less @@ -42,7 +42,7 @@ } button { - &#shareButton { + &#shareButton, &.btn.btn-success { // Bootstrap 4 colors color: #fff; background: @toolbar-green; @@ -58,7 +58,7 @@ margin-left: 5px; } } - &#newdoc { + &#newdoc, &.btn.btn-primary { // Bootstrap 4 colors color: #fff; background: #0275d8; @@ -77,6 +77,18 @@ &.hidden { display: none; } + + // Bootstrap 4 colors (btn-secondary) + border: 1px solid transparent; + border-radius: .25rem; + color: #292b2c; + background-color: #fff; + border-color: #ccc; + &:hover { + color: #292b2c; + background-color: #e6e6e6; + border-color: #adadad; + } } .cryptpad-limit { @@ -195,17 +207,6 @@ margin-right: 2px; } - button { - color: #000; - background-color: inherit; - background-image: linear-gradient(to bottom,#fff,#e4e4e4); - border: 1px solid #A6A6A6; - border-bottom-color: #979797; - border-radius: 3px; - &:hover { - background-image:linear-gradient(to bottom,#f2f2f2,#ccc); - } - } .cryptpad-state { line-height: 32px; /* equivalent to 26px + 2*2px margin used for buttons */ } @@ -429,13 +430,19 @@ display: none; text-align: center; .next { - float: right; + display: inline-block; + vertical-align: middle; + margin: 20px; } .previous { - float: left; + display: inline-block; + vertical-align: middle; + margin: 20px; } .goto { display: inline-block; + vertical-align: middle; + text-align: center; input { width: 50px; } } .gotoInput { diff --git a/customize.dist/toolbar.css b/customize.dist/toolbar.css index 58fa58033..76eeb5b4d 100644 --- a/customize.dist/toolbar.css +++ b/customize.dist/toolbar.css @@ -117,39 +117,59 @@ .cryptpad-toolbar a { float: right; } -.cryptpad-toolbar button#shareButton { +.cryptpad-toolbar button { + border: 1px solid transparent; + border-radius: .25rem; + color: #292b2c; + background-color: #fff; + border-color: #ccc; +} +.cryptpad-toolbar button#shareButton, +.cryptpad-toolbar button.btn.btn-success { color: #fff; background: #5cb85c; border-color: #5cb85c; } -.cryptpad-toolbar button#shareButton:hover { +.cryptpad-toolbar button#shareButton:hover, +.cryptpad-toolbar button.btn.btn-success:hover { background: #449d44; border: 1px solid #419641; } -.cryptpad-toolbar button#shareButton span { +.cryptpad-toolbar button#shareButton span, +.cryptpad-toolbar button.btn.btn-success span { color: #fff; } -.cryptpad-toolbar button#shareButton .large { +.cryptpad-toolbar button#shareButton .large, +.cryptpad-toolbar button.btn.btn-success .large { margin-left: 5px; } -.cryptpad-toolbar button#newdoc { +.cryptpad-toolbar button#newdoc, +.cryptpad-toolbar button.btn.btn-primary { color: #fff; background: #0275d8; border-color: #0275d8; } -.cryptpad-toolbar button#newdoc:hover { +.cryptpad-toolbar button#newdoc:hover, +.cryptpad-toolbar button.btn.btn-primary:hover { background: #025aa5; border: 1px solid #01549b; } -.cryptpad-toolbar button#newdoc span { +.cryptpad-toolbar button#newdoc span, +.cryptpad-toolbar button.btn.btn-primary span { color: #fff; } -.cryptpad-toolbar button#newdoc .large { +.cryptpad-toolbar button#newdoc .large, +.cryptpad-toolbar button.btn.btn-primary .large { margin-left: 5px; } .cryptpad-toolbar button.hidden { display: none; } +.cryptpad-toolbar button:hover { + color: #292b2c; + background-color: #e6e6e6; + border-color: #adadad; +} .cryptpad-toolbar .cryptpad-limit { box-sizing: content-box; height: 16px; @@ -265,17 +285,6 @@ margin-top: -3px; margin-right: 2px; } -.cryptpad-toolbar button { - color: #000; - background-color: inherit; - background-image: linear-gradient(to bottom, #fff, #e4e4e4); - border: 1px solid #A6A6A6; - border-bottom-color: #979797; - border-radius: 3px; -} -.cryptpad-toolbar button:hover { - background-image: linear-gradient(to bottom, #f2f2f2, #ccc); -} .cryptpad-toolbar .cryptpad-state { line-height: 32px; /* equivalent to 26px + 2*2px margin used for buttons */ @@ -492,13 +501,19 @@ text-align: center; } .cryptpad-toolbar-history .next { - float: right; + display: inline-block; + vertical-align: middle; + margin: 20px; } .cryptpad-toolbar-history .previous { - float: left; + display: inline-block; + vertical-align: middle; + margin: 20px; } .cryptpad-toolbar-history .goto { display: inline-block; + vertical-align: middle; + text-align: center; } .cryptpad-toolbar-history .goto input { width: 50px; diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 50a2bf667..030817836 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -133,6 +133,7 @@ define(function () { out.history_restoreTitle = "Restaurer la version du document sélectionnée"; out.history_restorePrompt = "Êtes-vous sûr de vouloir remplacer la version actuelle du document par la version affichée ?"; out.history_restoreDone = "Document restauré"; + out.history_version = "Version :"; // Polls diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 7c5fd8df5..0c4c74a95 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -135,6 +135,7 @@ define(function () { out.history_restoreTitle = "Restore the selected version of the document"; out.history_restorePrompt = "Are you sure you want to replace the current version of the document by the displayed one?"; out.history_restoreDone = "Document restored"; + out.history_version = "Version:"; // Polls diff --git a/www/common/common-history.js b/www/common/common-history.js index e006210a6..58fd54900 100644 --- a/www/common/common-history.js +++ b/www/common/common-history.js @@ -112,9 +112,9 @@ define([ var val = states[i].getContent().doc; c = i; if (typeof onUpdate === "function") { onUpdate(); } - $hist.find('.next, .previous').show(); - if (c === states.length - 1) { $hist.find('.next').hide(); } - if (c === 0) { $hist.find('.previous').hide(); } + $hist.find('.next, .previous').css('visibility', ''); + if (c === states.length - 1) { $hist.find('.next').css('visibility', 'hidden'); } + if (c === 0) { $hist.find('.previous').css('visibility', 'hidden'); } return val || ''; }; @@ -132,15 +132,16 @@ define([ $right.hide(); $cke.hide(); var $prev =$('
            - + diff --git a/customize.dist/contact.html b/customize.dist/contact.html index eff9c1869..aa81fac60 100644 --- a/customize.dist/contact.html +++ b/customize.dist/contact.html @@ -111,7 +111,7 @@
      - + diff --git a/customize.dist/index.html b/customize.dist/index.html index f6ab10fa6..d246d84d0 100644 --- a/customize.dist/index.html +++ b/customize.dist/index.html @@ -233,7 +233,7 @@
    - + diff --git a/customize.dist/privacy.html b/customize.dist/privacy.html index a7751db84..00a266236 100644 --- a/customize.dist/privacy.html +++ b/customize.dist/privacy.html @@ -132,7 +132,7 @@
- + diff --git a/customize.dist/src/fragments/footer.html b/customize.dist/src/fragments/footer.html index b15a2b5da..4cf4b101d 100644 --- a/customize.dist/src/fragments/footer.html +++ b/customize.dist/src/fragments/footer.html @@ -39,5 +39,5 @@ - + diff --git a/customize.dist/terms.html b/customize.dist/terms.html index edfd77f86..68eb51599 100644 --- a/customize.dist/terms.html +++ b/customize.dist/terms.html @@ -115,7 +115,7 @@ - + diff --git a/package.json b/package.json index ed76592b6..696091528 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "1.5.0", + "version": "1.6.0", "dependencies": { "express": "~4.10.1", "ws": "^1.0.1", diff --git a/www/settings/index.html b/www/settings/index.html index c24748490..752d04468 100644 --- a/www/settings/index.html +++ b/www/settings/index.html @@ -105,7 +105,7 @@ - + From 5a5b02b82b9abe3db45ae7ded35fe4df9adaa798 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 5 May 2017 11:55:19 +0200 Subject: [PATCH 139/306] Don't store in the drive pads without a hash --- www/common/cryptpad-common.js | 10 +++++++--- www/common/userObject.js | 5 +++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 104b67a9b..8096e3044 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -299,10 +299,14 @@ define([ pads.forEach(function (pad, i) { if (pad && typeof(pad) === 'object') { var hash = checkObjectData(pad); - if (!hash || !common.parseHash(hash)) { return; } + if (!hash || !common.parseHash(hash)) { + console.error("[Cryptpad.checkRecentPads] pad had unexpected value", pad); + getStore().removeData(i); + return; + } return pad; } - console.error("[Cryptpad.migrateRecentPads] pad had unexpected value"); + console.error("[Cryptpad.checkRecentPads] pad had unexpected value", pad); getStore().removeData(i); }); }; @@ -571,7 +575,7 @@ define([ return pad; }); - if (!contains) { + if (!contains && href) { var data = makePad(href, name); getStore().pushData(data, function (e) { if (e) { diff --git a/www/common/userObject.js b/www/common/userObject.js index aa61c8573..a7c21f3f3 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -883,6 +883,11 @@ define([ toClean.push(el); return; } + if (!el.href) { + debug("Rmoving an element in filesData with a missing href.", el); + toClean.push(el); + return; + } if (rootFiles.indexOf(el.href) === -1) { debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", el); var name = el.title || NEW_FILE_NAME; From a173e4c7a033ec985be76e6b11d9a04357f71737 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 5 May 2017 15:09:07 +0200 Subject: [PATCH 140/306] add 'log out everywhere' functionality in settings --- customize.dist/translations/messages.js | 4 +++ www/common/cryptpad-common.js | 1 + www/common/fsStore.js | 47 +++++++++++++++++++++---- www/settings/main.js | 40 +++++++++++++++++++++ 4 files changed, 85 insertions(+), 7 deletions(-) diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index b0b7a9304..2eaf65345 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -333,6 +333,10 @@ define(function () { out.settings_pinningError = "Something went wrong"; out.settings_usageAmount = "Your pinned pads occupy {0}MB"; + out.settings_logoutEverywhere = "Expire remote sessions"; + out.settings_logoutEverywhereTitle = "Log out of all other sessions"; + out.settings_logoutEverywhereConfirm = "Are you sure? You will need to log in with all your devices."; + // index.html diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 104b67a9b..b3a660432 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -192,6 +192,7 @@ define([ [ userNameKey, userHashKey, + 'loginToken', ].forEach(function (k) { sessionStorage.removeItem(k); localStorage.removeItem(k); diff --git a/www/common/fsStore.js b/www/common/fsStore.js index a929e84b3..8d6aa31bf 100644 --- a/www/common/fsStore.js +++ b/www/common/fsStore.js @@ -134,6 +134,23 @@ define([ return ret; }; + var requestLogin = function () { + // log out so that you don't go into an endless loop... + Cryptpad.logout(); + + // redirect them to log in, and come back when they're done. + sessionStorage.redirectTo = window.location.href; + window.location.href = '/login/'; + }; + + var tryParsing = function (x) { + try { return JSON.parse(x); } + catch (e) { + console.error(e); + return null; + } + }; + var onReady = function (f, proxy, Cryptpad, exp) { var fo = exp.fo = FO.init(proxy.drive, { Cryptpad: Cryptpad @@ -145,6 +162,28 @@ define([ f(void 0, store); } + if (Cryptpad.isLoggedIn()) { +/* This isn't truly secure, since anyone who can read the user's object can + set their local loginToken to match that in the object. However, it exposes + a UI that will work most of the time. */ + var tokenKey = 'loginToken'; + + // every user object should have a persistent, random number + if (typeof(proxy.loginToken) !== 'number') { + proxy[tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); + } + + var localToken = tryParsing(localStorage.getItem(tokenKey)); + if (localToken === null) { + // if that number hasn't been set to localStorage, do so. + localStorage.setItem(tokenKey, proxy.loginToken); + } else if (localToken !== proxy[tokenKey]) { + // if it has been, and the local number doesn't match that in + // the user object, request that they reauthenticate. + return void requestLogin(); + } + } + if (typeof(proxy.allowUserFeedback) !== 'boolean') { proxy.allowUserFeedback = true; } @@ -157,13 +196,7 @@ define([ // if the user is logged in, but does not have signing keys... if (Cryptpad.isLoggedIn() && !Cryptpad.hasSigningKeys(proxy)) { - // log out so that you don't go into an endless loop... - Cryptpad.logout(); - - // redirect them to log in, and come back when they're done. - sessionStorage.redirectTo = window.location.href; - window.location.href = '/login/'; - return; + return void requestLogin(); } proxy.on('change', [Cryptpad.displayNameKey], function (o, n) { diff --git a/www/settings/main.js b/www/settings/main.js index 2a2a7cd3d..1e79371fd 100644 --- a/www/settings/main.js +++ b/www/settings/main.js @@ -252,6 +252,42 @@ define([ return $div; }; + var createLogoutEverywhere = function (obj) { + var proxy = obj.proxy; + var $div = $('
', { 'class': 'logoutEverywhere', }); + $('
- + diff --git a/customize.dist/contact.html b/customize.dist/contact.html index aa81fac60..70dbf9083 100644 --- a/customize.dist/contact.html +++ b/customize.dist/contact.html @@ -103,7 +103,7 @@
- + diff --git a/customize.dist/index.html b/customize.dist/index.html index d246d84d0..420d3664a 100644 --- a/customize.dist/index.html +++ b/customize.dist/index.html @@ -225,7 +225,7 @@
- + diff --git a/customize.dist/privacy.html b/customize.dist/privacy.html index 00a266236..345bac0f1 100644 --- a/customize.dist/privacy.html +++ b/customize.dist/privacy.html @@ -124,7 +124,7 @@
- + diff --git a/customize.dist/src/fragments/footer.html b/customize.dist/src/fragments/footer.html index 4cf4b101d..68f4c4610 100644 --- a/customize.dist/src/fragments/footer.html +++ b/customize.dist/src/fragments/footer.html @@ -31,7 +31,7 @@
- + diff --git a/customize.dist/terms.html b/customize.dist/terms.html index 68eb51599..e652f6aa4 100644 --- a/customize.dist/terms.html +++ b/customize.dist/terms.html @@ -107,7 +107,7 @@
- + diff --git a/www/settings/index.html b/www/settings/index.html index 752d04468..3fde61f4a 100644 --- a/www/settings/index.html +++ b/www/settings/index.html @@ -97,7 +97,7 @@
- + From d13dde856a7f4e400d64611f5efea64a9d446934 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 18 May 2017 14:26:56 +0200 Subject: [PATCH 247/306] Add the markdown renderer file and change code's default content --- customize.dist/translations/messages.fr.js | 9 +- customize.dist/translations/messages.js | 9 +- www/common/diffMarked.js | 109 +++++++++++++++++++++ 3 files changed, 117 insertions(+), 10 deletions(-) create mode 100644 www/common/diffMarked.js diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index e719a44d9..24fff7a37 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -436,11 +436,10 @@ define(function () { ].join(''); out.codeInitialState = [ - '/*\n', - ' Voici l\'éditeur de code collaboratif et Zero Knowledge de CryptPad.\n', - ' Ce que vous tapez ici est chiffré de manière que seules les personnes avec le lien peuvent y accéder.\n', - ' Vous pouvez choisir le langage de programmation pour la coloration syntaxique, ainsi que le thème de couleurs, dans le coin supérieur droit.\n', - '*/' + '# Éditeur de code collaboratif et Zero Knowledge de CryptPad\n', + '\n', + '* Ce que vous tapez ici est chiffré de manière que seules les personnes avec le lien peuvent y accéder.\n', + '* Vous pouvez choisir le langage de programmation pour la coloration syntaxique, ainsi que le thème de couleurs, dans le coin supérieur droit.' ].join(''); out.slideInitialState = [ diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 10efcb2df..c357cb557 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -450,11 +450,10 @@ define(function () { ].join(''); out.codeInitialState = [ - '/*\n', - ' This is the CryptPad Zero Knowledge collaborative code editor.\n', - ' What you type here is encrypted so only people who have the link can access it.\n', - ' You can choose the programming language to highlight and the UI color scheme in the upper right.\n', - '*/' + '# CryptPad\'s Zero Knowledge collaborative code editor\n', + '\n', + '* What you type here is encrypted so only people who have the link can access it.\n', + '* You can choose the programming language to highlight and the UI color scheme in the upper right.' ].join(''); out.slideInitialState = [ diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js new file mode 100644 index 000000000..b3cc5fe1e --- /dev/null +++ b/www/common/diffMarked.js @@ -0,0 +1,109 @@ +define([ + 'jquery', + '/bower_components/marked/marked.min.js', + '/bower_components/diff-dom/diffDOM.js' +],function ($, Marked) { + var DiffMd = {} + + var DiffDOM = window.diffDOM; + var renderer = DiffMd.renderer = new Marked.Renderer(); + + Marked.setOptions({ + renderer: renderer + }); + + DiffMd.render = function (md) { + return Marked(md); + }; + + var forbiddenTags = [ + 'SCRIPT', + 'IFRAME', + 'OBJECT', + 'APPLET', + 'VIDEO', + 'AUDIO', + ]; + var unsafeTag = function (info) { + if (['addAttribute', 'modifyAttribute'].indexOf(info.diff.action) !== -1) { + if (/^on/.test(info.diff.name)) { + console.log("Rejecting forbidden element attribute with name", info.diff.name); + return true; + } + } + if (['addElement', 'replaceElement'].indexOf(info.diff.action) !== -1) { + var msg = "Rejecting forbidden tag of type (%s)"; + if (info.diff.element && forbiddenTags.indexOf(info.diff.element.nodeName) !== -1) { + console.log(msg, info.diff.element.nodeName); + return true; + } else if (info.diff.newValue && forbiddenTags.indexOf(info.diff.newValue.nodeName) !== -1) { + console.log("Replacing restricted element type (%s) with PRE", info.diff.newValue.nodeName); + info.diff.newValue.nodeName = 'PRE'; + } + } + }; + + var slice = function (coll) { + return Array.prototype.slice.call(coll); + }; + + /* remove listeners from the DOM */ + var removeListeners = function (root) { + slice(root.attributes).map(function (attr) { + if (/^on/.test(attr.name)) { + root.attributes.removeNamedItem(attr.name); + } + }); + // all the way down + slice(root.children).forEach(removeListeners); + }; + + var domFromHTML = function (html) { + var Dom = new DOMParser().parseFromString(html, "text/html"); + removeListeners(Dom.body); + return Dom; + }; + + var DD = new DiffDOM({ + preDiffApply: function (info) { + if (unsafeTag(info)) { return true; } + } + }); + + var makeDiff = function (A, B, id) { + var Err; + var Els = [A, B].map(function (frag) { + if (typeof(frag) === 'object') { + if (!frag || (frag && !frag.body)) { + Err = "No body"; + return; + } + var els = frag.body.querySelectorAll('#'+id); + if (els.length) { + return els[0]; + } + } + Err = 'No candidate found'; + }); + if (Err) { return Err; } + var patch = DD.diff(Els[0], Els[1]); + return patch; + }; + + var apply = DiffMd.apply = function (newHtml, $content) { + var id = $content.attr('id'); + if (!id) { throw new Error("The element must have a valid id"); } + var $div = $('
', {id: id}).append(newHtml); + var Dom = domFromHTML($('
').append($div).html()); + var oldDom = domFromHTML($content[0].outerHTML); + var patch = makeDiff(oldDom, Dom, id); + if (typeof(patch) === 'string') { + throw new Error(patch); + } else { + DD.apply($content[0], patch); + } + }; + + return DiffMd; +}); + From 3ba94a1ba24ad8709a200abcf8451bdeac2c67a1 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 18 May 2017 14:54:02 +0200 Subject: [PATCH 248/306] correctly set release version number --- customize.dist/about.html | 2 +- customize.dist/contact.html | 2 +- customize.dist/index.html | 2 +- customize.dist/privacy.html | 2 +- customize.dist/src/fragments/footer.html | 2 +- customize.dist/terms.html | 2 +- package.json | 2 +- www/settings/index.html | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/customize.dist/about.html b/customize.dist/about.html index 044343a24..954a3fb6f 100644 --- a/customize.dist/about.html +++ b/customize.dist/about.html @@ -114,7 +114,7 @@
- + diff --git a/customize.dist/contact.html b/customize.dist/contact.html index 70dbf9083..5d43d15a5 100644 --- a/customize.dist/contact.html +++ b/customize.dist/contact.html @@ -111,7 +111,7 @@ - + diff --git a/customize.dist/index.html b/customize.dist/index.html index 420d3664a..a1b1fe56b 100644 --- a/customize.dist/index.html +++ b/customize.dist/index.html @@ -233,7 +233,7 @@ - + diff --git a/customize.dist/privacy.html b/customize.dist/privacy.html index 345bac0f1..35bab1958 100644 --- a/customize.dist/privacy.html +++ b/customize.dist/privacy.html @@ -132,7 +132,7 @@ - + diff --git a/customize.dist/src/fragments/footer.html b/customize.dist/src/fragments/footer.html index 68f4c4610..55b332a0f 100644 --- a/customize.dist/src/fragments/footer.html +++ b/customize.dist/src/fragments/footer.html @@ -39,5 +39,5 @@ - + diff --git a/customize.dist/terms.html b/customize.dist/terms.html index e652f6aa4..2504b1147 100644 --- a/customize.dist/terms.html +++ b/customize.dist/terms.html @@ -115,7 +115,7 @@ - + diff --git a/package.json b/package.json index 75a4fbb11..dd089605c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "1.8.0", + "version": "1.7.0", "dependencies": { "chainpad-server": "^1.0.1", "express": "~4.10.1", diff --git a/www/settings/index.html b/www/settings/index.html index 3fde61f4a..f6ab459c9 100644 --- a/www/settings/index.html +++ b/www/settings/index.html @@ -105,7 +105,7 @@ - + From d187945e94afc229eb211287291f1a10c7348e95 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 18 May 2017 14:55:16 +0200 Subject: [PATCH 249/306] jshint compliance --- www/common/common-hash.js | 6 +++--- www/common/diffMarked.js | 4 ++-- www/common/pinpad.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 9e72bcdeb..5de76ab99 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -103,10 +103,10 @@ Version 1 if (!href) { return ret; } if (href.slice(-1) !== '/') { href += '/'; } - + var idx; if (!/^https*:\/\//.test(href)) { - var idx = href.indexOf('/#'); + idx = href.indexOf('/#'); ret.type = href.slice(1, idx); ret.hash = href.slice(idx + 2); ret.hashData = parseTypeHash(ret.type, ret.hash); @@ -118,7 +118,7 @@ Version 1 ret.type = type; return ''; }); - var idx = href.indexOf('/#'); + idx = href.indexOf('/#'); ret.hash = href.slice(idx + 2); ret.hashData = parseTypeHash(ret.type, ret.hash); return ret; diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index b3cc5fe1e..2de647e86 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -3,7 +3,7 @@ define([ '/bower_components/marked/marked.min.js', '/bower_components/diff-dom/diffDOM.js' ],function ($, Marked) { - var DiffMd = {} + var DiffMd = {}; var DiffDOM = window.diffDOM; var renderer = DiffMd.renderer = new Marked.Renderer(); @@ -90,7 +90,7 @@ define([ return patch; }; - var apply = DiffMd.apply = function (newHtml, $content) { + DiffMd.apply = function (newHtml, $content) { var id = $content.attr('id'); if (!id) { throw new Error("The element must have a valid id"); } var $div = $('
', {id: id}).append(newHtml); diff --git a/www/common/pinpad.js b/www/common/pinpad.js index c0b057cf8..b14a5bac0 100644 --- a/www/common/pinpad.js +++ b/www/common/pinpad.js @@ -172,7 +172,7 @@ define([ }; exp.uploadCancel = function (cb) { - rpc.send('UPLOAD_CANCEL', void 0, function (e, res) { + rpc.send('UPLOAD_CANCEL', void 0, function (e) { if (e) { return void cb(e); } cb(); }); From 0bd4caeb7e83e7399affe107c07ba2f259eabb99 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 18 May 2017 14:57:04 +0200 Subject: [PATCH 250/306] Add the task list code to the markdown renderer --- www/code/inner.html | 5 +++++ www/code/main.js | 48 ++++++++++++++++++++++++---------------- www/common/diffMarked.js | 20 ++++++++++++++++- www/slide/slide.js | 17 -------------- 4 files changed, 53 insertions(+), 37 deletions(-) diff --git a/www/code/inner.html b/www/code/inner.html index 8dd02548a..36d5911ee 100644 --- a/www/code/inner.html +++ b/www/code/inner.html @@ -55,6 +55,11 @@ max-width: 80%; resize: horizontal; } + .CodeMirror.fullPage { + min-width: 100%; + max-width: 100%; + resize: none; + } .CodeMirror-focused .cm-matchhighlight { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==); background-position: bottom; diff --git a/www/code/main.js b/www/code/main.js index 04e20141a..a1261b8ce 100644 --- a/www/code/main.js +++ b/www/code/main.js @@ -13,14 +13,14 @@ define([ Cryptget, DiffMd) { var Messages = Cryptpad.Messages; - var module = window.APP = { + var APP = window.APP = { Cryptpad: Cryptpad, }; $(function () { Cryptpad.addLoadingScreen(); - var ifrw = module.ifrw = $('#pad-iframe')[0].contentWindow; + var ifrw = APP.ifrw = $('#pad-iframe')[0].contentWindow; var stringify = function (obj) { return JSONSortify(obj); }; @@ -28,6 +28,7 @@ define([ var toolbar; var editor; var $iframe = $('#pad-iframe').contents(); + var $previewContainer = $iframe.find('#previewContainer'); var $preview = $iframe.find('#preview'); $preview.click(function (e) { if (!e.target) { return; } @@ -58,7 +59,7 @@ define([ var isHistoryMode = false; - var setEditable = module.setEditable = function (bool) { + var setEditable = APP.setEditable = function (bool) { if (readOnly && bool) { return; } editor.setOption('readOnly', !bool); }; @@ -121,21 +122,24 @@ define([ var textValue = canonicalize(CodeMirror.$textarea.val()); var shjson = stringifyInner(textValue); - module.patchText(shjson); + APP.patchText(shjson); - if (module.realtime.getUserDoc() !== shjson) { + if (APP.realtime.getUserDoc() !== shjson) { console.error("realtime.getUserDoc() !== shjson"); } }; var onModeChanged = function (mode) { - if (mode === "markdown") { + var $codeMirror = $iframe.find('.CodeMirror'); + if (mode === "markdown") { APP.$previewButton.show(); - $preview.show(); + $previewContainer.show(); + $codeMirror.removeClass('fullPage'); return; } APP.$previewButton.hide(); - $preview.hide(); + $previewContainer.hide(); + $codeMirror.addClass('fullPage'); }; config.onInit = function (info) { @@ -161,7 +165,7 @@ define([ network: info.network, $container: $bar }; - toolbar = module.toolbar = Toolbar.create(configTb); + toolbar = APP.toolbar = Toolbar.create(configTb); Title.setToolbar(toolbar); CodeMirror.init(config.onLocal, Title, toolbar); @@ -221,10 +225,16 @@ define([ $previewButton.removeClass('fa-question').addClass('fa-eye'); $previewButton.attr('title', 'TODO Preview'); //TODO $previewButton.click(function () { + var $codeMirror = $iframe.find('.CodeMirror'); if (CodeMirror.highlightMode !== 'markdown') { - return void $preview.hide(); + $previewContainer.show(); + } + $previewContainer.toggle(); + if ($previewContainer.is(':visible')) { + $codeMirror.removeClass('fullPage'); + } else { + $codeMirror.addClass('fullPage'); } - $preview.toggle(); }); $rightside.append($previewButton); @@ -240,15 +250,15 @@ define([ }; config.onReady = function (info) { - if (module.realtime !== info.realtime) { - var realtime = module.realtime = info.realtime; - module.patchText = TextPatcher.create({ + if (APP.realtime !== info.realtime) { + var realtime = APP.realtime = info.realtime; + APP.patchText = TextPatcher.create({ realtime: realtime, //logging: true }); } - var userDoc = module.realtime.getUserDoc(); + var userDoc = APP.realtime.getUserDoc(); var isNew = false; if (userDoc === "" || userDoc === "{}") { isNew = true; } @@ -301,7 +311,7 @@ define([ if (isHistoryMode) { return; } var oldDoc = canonicalize(CodeMirror.$textarea.val()); - var shjson = module.realtime.getUserDoc(); + var shjson = APP.realtime.getUserDoc(); // Update the user list (metadata) from the hyperjson Metadata.update(shjson); @@ -312,7 +322,7 @@ define([ DiffMd.apply(DiffMd.render(remoteDoc), $preview); var highlightMode = hjson.highlightMode; - if (highlightMode && highlightMode !== module.highlightMode) { + if (highlightMode && highlightMode !== APP.highlightMode) { CodeMirror.setMode(highlightMode, onModeChanged); } @@ -324,7 +334,7 @@ define([ if (shjson2 !== shjson) { console.error("shjson2 !== shjson"); TextPatcher.log(shjson, TextPatcher.diff(shjson, shjson2)); - module.patchText(shjson2); + APP.patchText(shjson2); } } if (oldDoc !== remoteDoc) { Cryptpad.notify(); } @@ -351,7 +361,7 @@ define([ config.onError = onConnectError; - module.realtime = Realtime.start(config); + APP.realtime = Realtime.start(config); editor.on('change', onLocal); diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index b3cc5fe1e..c73dfd072 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -6,7 +6,7 @@ define([ var DiffMd = {} var DiffDOM = window.diffDOM; - var renderer = DiffMd.renderer = new Marked.Renderer(); + var renderer = new Marked.Renderer(); Marked.setOptions({ renderer: renderer @@ -16,6 +16,24 @@ define([ return Marked(md); }; + // Tasks list + var checkedTaskItemPtn = /^\s*\[x\]\s*/; + var uncheckedTaskItemPtn = /^\s*\[ \]\s*/; + renderer.listitem = function (text) { + var isCheckedTaskItem = checkedTaskItemPtn.test(text); + var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text); + if (isCheckedTaskItem) { + text = text.replace(checkedTaskItemPtn, + ' ') + '\n'; + } + if (isUncheckedTaskItem) { + text = text.replace(uncheckedTaskItemPtn, + ' ') + '\n'; + } + var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : ''; + return '' + text + '\n'; + }; + var forbiddenTags = [ 'SCRIPT', 'IFRAME', diff --git a/www/slide/slide.js b/www/slide/slide.js index 108a8848a..9f919ecfa 100644 --- a/www/slide/slide.js +++ b/www/slide/slide.js @@ -2,23 +2,6 @@ define([ 'jquery', '/common/diffMarked.js', ],function ($, DiffMd) { - // Tasks list - var checkedTaskItemPtn = /^\s*\[x\]\s*/; - var uncheckedTaskItemPtn = /^\s*\[ \]\s*/; - DiffMd.renderer.listitem = function (text) { - var isCheckedTaskItem = checkedTaskItemPtn.test(text); - var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text); - if (isCheckedTaskItem) { - text = text.replace(checkedTaskItemPtn, - ' ') + '\n'; - } - if (isUncheckedTaskItem) { - text = text.replace(uncheckedTaskItemPtn, - ' ') + '\n'; - } - var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : ''; - return '' + text + '\n'; - }; var Slide = { index: 0, From 620f4dc5126f2ffc6ddaa1e66e0821420ba398a7 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 18 May 2017 15:05:37 +0200 Subject: [PATCH 251/306] Add translations for the markdown preview button title --- customize.dist/translations/messages.fr.js | 2 ++ customize.dist/translations/messages.js | 2 ++ www/code/main.js | 6 ++++-- www/common/common-codemirror.js | 5 +++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 24fff7a37..1c204e38c 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -95,6 +95,8 @@ define(function () { out.templateSaved = "Modèle enregistré !"; out.selectTemplate = "Sélectionner un modèle ou appuyer sur Échap"; + 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'; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index c357cb557..4b2c9f78e 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -97,6 +97,8 @@ define(function () { out.templateSaved = "Template saved!"; out.selectTemplate = "Select a template or press escape"; + out.previewButtonTitle = "Display or hide the Markdown preview mode"; + out.presentButtonTitle = "Enter presentation mode"; out.presentSuccess = 'Hit ESC to exit presentation mode'; diff --git a/www/code/main.js b/www/code/main.js index a1261b8ce..6c9fbd0e1 100644 --- a/www/code/main.js +++ b/www/code/main.js @@ -223,7 +223,7 @@ define([ var $previewButton = APP.$previewButton = Cryptpad.createButton(null, true); $previewButton.removeClass('fa-question').addClass('fa-eye'); - $previewButton.attr('title', 'TODO Preview'); //TODO + $previewButton.attr('title', Messages.previewButtonTitle); $previewButton.click(function () { var $codeMirror = $iframe.find('.CodeMirror'); if (CodeMirror.highlightMode !== 'markdown') { @@ -239,7 +239,9 @@ define([ $rightside.append($previewButton); if (!readOnly) { - CodeMirror.configureLanguage(CodeMirror.configureTheme, onModeChanged); + CodeMirror.configureTheme(function () { + CodeMirror.configureLanguage(null, onModeChanged); + }); } else { CodeMirror.configureTheme(); diff --git a/www/common/common-codemirror.js b/www/common/common-codemirror.js index 4bc38cc46..429e9bd9a 100644 --- a/www/common/common-codemirror.js +++ b/www/common/common-codemirror.js @@ -158,10 +158,10 @@ define([ }); if ($rightside) { $rightside.append($block); } - cb(); + if (cb) { cb(); } }; - exp.configureTheme = function () { + exp.configureTheme = function (cb) { /* Remember the user's last choice of theme using localStorage */ var themeKey = 'CRYPTPAD_CODE_THEME'; var lastTheme = localStorage.getItem(themeKey) || 'default'; @@ -195,6 +195,7 @@ define([ }); if ($rightside) { $rightside.append($block); } + if (cb) { cb(); } }; exp.exportText = function () { From 164342920e1cea9710f72a5a45bd5bc5ee2bb05a Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 18 May 2017 15:39:24 +0200 Subject: [PATCH 252/306] fix inverted condition --- www/common/cryptpad-common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 14f0a8b66..ac2a6f875 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1336,7 +1336,7 @@ define([ feedback("NO_PROXIES"); } - if (!/CRYPTPAD_SHIM/.test(Array.isArray.toString())) { + if (/CRYPTPAD_SHIM/.test(Array.isArray.toString())) { feedback("NO_ISARRAY"); } From 21f9e31e75f0644172c718cd86bd75361c7ee284 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 18 May 2017 15:56:35 +0200 Subject: [PATCH 253/306] Add a placeholder for the user app --- customize.dist/src/build.js | 5 +- customize.dist/translations/messages.fr.js | 2 + customize.dist/translations/messages.js | 2 + www/user/index.html | 112 +++++++++++++++++++++ www/user/main.css | 14 +++ www/user/main.js | 63 ++++++++++++ 6 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 www/user/index.html create mode 100644 www/user/main.css create mode 100644 www/user/main.js diff --git a/customize.dist/src/build.js b/customize.dist/src/build.js index c5c5c8d77..fbcc34942 100644 --- a/customize.dist/src/build.js +++ b/customize.dist/src/build.js @@ -60,7 +60,10 @@ var fragments = {}; }); // build static pages -['../www/settings/index'].forEach(function (page) { +[ + '../www/settings/index', + '../www/user/index' +].forEach(function (page) { var source = swap(template, { topbar: fragments.topbar, fork: fragments.fork, diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 1c204e38c..26cfc3129 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -52,6 +52,8 @@ define(function () { out.language = "Langue"; + out.comingSoon = "Bientôt disponible..."; + out.upgrade = "Améliorer"; out.upgradeTitle = "Améliorer votre compte pour augmenter la limite de stockage"; out.MB = "Mo"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 4b2c9f78e..dcafe6283 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -54,6 +54,8 @@ define(function () { out.language = "Language"; + out.comingSoon = "Coming soon..."; + out.upgrade = "Upgrade"; out.upgradeTitle = "Upgrade your account to increase the storage limit"; out.MB = "MB"; diff --git a/www/user/index.html b/www/user/index.html new file mode 100644 index 000000000..f6ab459c9 --- /dev/null +++ b/www/user/index.html @@ -0,0 +1,112 @@ + + + + + Cryptpad: Zero Knowledge, Collaborative Real Time Editing + + + + + + + + + + + +
+ + + CryptPad + + + + + + + + + About + + + Privacy + + + ToS + + + Contact + + + Blog + +
+ + + + + + + + + + + diff --git a/www/user/main.css b/www/user/main.css new file mode 100644 index 000000000..5cfc2ce85 --- /dev/null +++ b/www/user/main.css @@ -0,0 +1,14 @@ +.cp #mainBlock { + z-index: 1; + width: 1000px; + max-width: 90%; + margin: auto; + display: flex; + align-items: center; + justify-content: center; +} +.cp #mainBlock #container { + text-align: center; + font-size: 25px; +} + diff --git a/www/user/main.js b/www/user/main.js new file mode 100644 index 000000000..82f16682b --- /dev/null +++ b/www/user/main.js @@ -0,0 +1,63 @@ +define([ + 'jquery', + '/common/cryptpad-common.js', +], function ($, Cryptpad) { + + var APP = window.APP = { + Cryptpad: Cryptpad, + _onRefresh: [] + }; + + var Messages = Cryptpad.Messages; + + var comingSoon = function () { + var $div = $('
', { 'class': 'coming-soon' }) + .text(Messages.comingSoon) + .append('
'); + console.log($div); + return $div; + }; + + var andThen = function () { + console.log(APP.$container); + APP.$container.append(comingSoon()); + }; + + $(function () { + var $main = $('#mainBlock'); + // Language selector + var $sel = $('#language-selector'); + Cryptpad.createLanguageSelector(undefined, $sel); + $sel.find('button').addClass('btn').addClass('btn-secondary'); + $sel.show(); + + // User admin menu + var $userMenu = $('#user-menu'); + var userMenuCfg = { + $initBlock: $userMenu + }; + var $userAdmin = Cryptpad.createUserAdminMenu(userMenuCfg); + $userAdmin.find('button').addClass('btn').addClass('btn-secondary'); + + $(window).click(function () { + $('.cryptpad-dropdown').hide(); + }); + + // main block is hidden in case javascript is disabled + $main.removeClass('hidden'); + + APP.$container = $('#container'); + + Cryptpad.ready(function () { + //if (!Cryptpad.getUserHash()) { return redirectToMain(); } + + //var storeObj = Cryptpad.getStore().getProxy && Cryptpad.getStore().getProxy().proxy + // ? Cryptpad.getStore().getProxy() : undefined; + + //andThen(storeObj); + andThen(); + Cryptpad.reportAppUsage(); + }); + }); + +}); From d324a054f2009d05212ff11963e26ba55ea3bd82 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 18 May 2017 17:57:42 +0200 Subject: [PATCH 254/306] add progress bar to file upload --- www/file/inner.html | 38 +++++++++++++++++++++++++++++++++++--- www/file/main.js | 30 ++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/www/file/inner.html b/www/file/inner.html index 2f82b3461..482fb4955 100644 --- a/www/file/inner.html +++ b/www/file/inner.html @@ -31,10 +31,28 @@ z-index: -1; } + #upload-form { + padding: 0px; + margin: 0px; + + position: relative; + width: 50vh; + height: 50vh; + display: block; + margin: auto; + } + #upload-form label{ + position: relative; + } + + .hovering { + background-color: rgba(255, 0, 115, 0.5) !important; + } + .block { display: block; - height: 500px; - width: 500px; + height: 50vh; + width: 50vh; } .hidden { display: none; @@ -43,6 +61,7 @@ border: 2px solid black; background-color: rgba(50, 50, 50, .10); margin: 50px; + display: block; } .inputfile:focus + label, @@ -50,13 +69,26 @@ background-color: rgba(50, 50, 50, 0.30); } + #progress { + position: absolute; + top: 0px; + left: 0px; + height: 100%; + width: 0%; + max-width: 100%; + max-height: 100%; + background-color: rgba(255, 0, 115, 0.75); + z-index: 10000; + display: block; + } +
diff --git a/www/file/main.js b/www/file/main.js index 41b8230e3..ba42f9afb 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -21,6 +21,8 @@ define([ var ifrw = $('#pad-iframe')[0].contentWindow; var $iframe = $('#pad-iframe').contents(); var $form = $iframe.find('#upload-form'); + var $progress = $form.find('#progress'); + var $label = $form.find('label'); Cryptpad.addLoadingScreen(); @@ -54,8 +56,15 @@ define([ if (err) { throw new Error(err); } if (box) { actual += box.length; + var progress = (actual / estimate * 100) + '%'; + console.log(progress); + return void sendChunk(box, function (e) { if (e) { return console.error(e); } + $progress.css({ + width: progress, + }); + next(again); }); } @@ -71,7 +80,7 @@ define([ console.log("encrypted blob is now available as %s", uri); var b64Key = Nacl.util.encodeBase64(key); - window.location.hash = Cryptpad.getFileHashFromKeys(id, b64Key); + Cryptpad.replaceHash(Cryptpad.getFileHashFromKeys(id, b64Key)); $form.hide(); @@ -221,8 +230,25 @@ define([ handleFile(file); }); + var counter = 0; + $label + .on('dragenter', function (e) { + e.preventDefault(); + e.stopPropagation(); + counter++; + $label.addClass('hovering'); + }) + .on('dragleave', function (e) { + e.preventDefault(); + e.stopPropagation(); + counter--; + if (counter <= 0) { + $label.removeClass('hovering'); // FIXME Can get stuck... + } + }); + $form - .on('drag dragstart dragend dragover dragenter dragleave drop', function (e) { + .on('drag dragstart dragend dragover drop dragenter dragleave', function (e) { e.preventDefault(); e.stopPropagation(); }) From 5a83259233cd0204b8f987d0d8ccc536e01d334d Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 18 May 2017 18:24:04 +0200 Subject: [PATCH 255/306] listen for decryption errors --- www/media/main.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/www/media/main.js b/www/media/main.js index 044fba0c0..08efb7714 100644 --- a/www/media/main.js +++ b/www/media/main.js @@ -70,11 +70,16 @@ define([ var decrypted = e.originalEvent; var metadata = decrypted.metadata; - console.log(metadata); - console.log(defaultName); + if (decrypted.callback) { decrypted.callback(); } + //console.log(metadata); + //console.log(defaultName); if (!metadata || metadata.name !== defaultName) { return; } var title = document.title = metadata.name; updateTitle(title || defaultName); + }) + .on('decryptionError', function (e) { + var error = e.originalEvent; + Cryptpad.alert(error.message); }); require(['/common/media-tag.js'], function (MediaTag) { From 34606ea8ba4e7ee661e64a03f0103f585fee309f Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 18 May 2017 18:24:24 +0200 Subject: [PATCH 256/306] use newest xwiki media-tag --- www/common/media-tag.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/media-tag.js b/www/common/media-tag.js index 0d18138ce..cf8c78210 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.MediaTag=t():e.MediaTag=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=82)}([function(e,t,n){"use strict";var r={IMAGE:"image",AUDIO:"audio",VIDEO:"video",PDF:"pdf",DASH:"dash",DOWNLOAD:"download",CRYPTO:"crypto",CLEAR_KEY:"clear-key",MEDIA_OBJECT:"media-object"};e.exports=r},function(e,t,n){"use strict";var r={MATCHER:"matcher",RENDERER:"renderer",FILTER:"filter",SANITIZER:"sanitizer"};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n=e.STACK_LIMIT)throw console.error(e.snapshots[n]),new Error("Plugin stack size exceed");if(e.snapshots[n].length>=e.SNAPSHOT_LIMIT)throw console.error(e.snapshots[n]),new Error("Plugin snapshots size exceed");var r=0;if(e.stacks[n].forEach(function(e){e.type===u.RENDERER&&r++}),r<1&&e.stacks[n].unshift(e.defaultPlugin),r>1)throw new Error("More of one renderer in the stack")}},{key:"return",value:function(t){e.start(t)}},{key:"run",value:function(t){var n=t.getId(),r=e.stacks[n].length,o=e.stacks[n][r-1];if(!o)throw console.log(e.stacks),new Error("Impossible to run a undefined plugin");o.process(t)}}]),e}();f.stacks={},f.STACK_LIMIT=1e3,f.snapshots={},f.SNAPSHOT_LIMIT=1e3,f.defaultPlugin=new s,e.exports=f},function(e,t,n){"use strict";var r={EVERY:"every",ANY:"any",ONCE:"once"};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n1;){if(c){if("number"!=typeof e[t])throw new Error("E_UNSAFE_TYPE");if(e[t]>255)throw new Error("E_OUT_OF_BOUNDS")}if(255!==e[t])return void e[t]++;if(e[t]=0,0===t)throw new Error("E_NONCE_TOO_LARGE")}}},{key:"encodePrefix",value:function(e){return[65280,255].map(function(t,n){return(e&t)>>8*(1-n)})}},{key:"decodePrefix",value:function(e){return e[0]<<8|e[1]}},{key:"joinChunks",value:function(t){return new Uint8Array(t.reduce(function(t,n){return e.slice(t).concat(e.slice(n))},[]))}},{key:"slice",value:function(e){return Array.prototype.slice.call(e)}},{key:"getRandomKeyStr",value:function(){var t=e.Nacl,n=t.randomBytes(18);return t.util.encodeBase64(n)}},{key:"getKeyFromStr",value:function(t){return e.Nacl.util.decodeBase64(t)}},{key:"encrypt",value:function(t,n){var r=t,o=e.Nacl.randomBytes(24),i=e.Nacl.secretbox(r,o,n);if(i)return new Uint8Array(e.slice(o).concat(e.slice(i)));throw new Error}},{key:"decrypt",value:function(t,n){var r=e.Nacl,o=function(e){throw new Error(e||"DECRYPTION_ERROR")},i=new Uint8Array(new Array(24).fill(0)),u=0,a=t.subarray(0,2),c=e.decodePrefix(a),f={metadata:void 0},l=new Uint8Array(t.subarray(2,2+c)),p=r.secretbox.open(l,i,n);e.increment(i);try{f.metadata=JSON.parse(r.util.encodeUTF8(p))}catch(e){return o("E_METADATA_DECRYPTION")}f.metadata||o("NO_METADATA");for(var y,b=function(){var o=u*s+2+c,a=o+s;u++;var f=new Uint8Array(t.subarray(o,a)),l=r.secretbox.open(f,i,n);return e.increment(i),l},h=[];u*s1?t[0]:window.location.protocol}},{key:"hostname",value:function(e){var t=e.getAttribute("src").split("://");return t.length>1?t[1].split("/")[0]:window.location.hostname}},{key:"source",value:function(e){return e.getAttribute("src")}},{key:"schemes",value:function(e){return/\w+:/.exec(e.getAttribute("src"))}},{key:"parse",value:function(t){return{protocol:e.protocol(t),hostname:e.hostname(t),src:e.source(t),type:e.type(t),extension:e.extension(t),mime:e.mime(t)}}}]),e}();e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n=e.STACK_LIMIT)throw console.error(e.snapshots[n]),new Error("Plugin stack size exceed");if(e.snapshots[n].length>=e.SNAPSHOT_LIMIT)throw console.error(e.snapshots[n]),new Error("Plugin snapshots size exceed");var r=0;if(e.stacks[n].forEach(function(e){e.type===u.RENDERER&&r++}),r<1&&e.stacks[n].unshift(e.defaultPlugin),r>1)throw new Error("More of one renderer in the stack")}},{key:"return",value:function(t){e.start(t)}},{key:"run",value:function(t){var n=t.getId(),r=e.stacks[n].length,o=e.stacks[n][r-1];if(!o)throw console.log(e.stacks),new Error("Impossible to run a undefined plugin");o.process(t)}}]),e}();f.stacks={},f.STACK_LIMIT=1e3,f.snapshots={},f.SNAPSHOT_LIMIT=1e3,f.defaultPlugin=new s,e.exports=f},function(e,t,n){"use strict";var r={EVERY:"every",ANY:"any",ONCE:"once"};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n1;){if(c){if("number"!=typeof e[t])throw new Error("E_UNSAFE_TYPE");if(e[t]>255)throw new Error("E_OUT_OF_BOUNDS")}if(255!==e[t])return void e[t]++;if(e[t]=0,0===t)throw new Error("E_NONCE_TOO_LARGE")}}},{key:"encodePrefix",value:function(e){return[65280,255].map(function(t,n){return(e&t)>>8*(1-n)})}},{key:"decodePrefix",value:function(e){return e[0]<<8|e[1]}},{key:"joinChunks",value:function(t){return new Uint8Array(t.reduce(function(t,n){return e.slice(t).concat(e.slice(n))},[]))}},{key:"slice",value:function(e){return Array.prototype.slice.call(e)}},{key:"getRandomKeyStr",value:function(){var t=e.Nacl,n=t.randomBytes(18);return t.util.encodeBase64(n)}},{key:"getKeyFromStr",value:function(t){return e.Nacl.util.decodeBase64(t)}},{key:"encrypt",value:function(t,n){var r=t,o=e.Nacl.randomBytes(24),i=e.Nacl.secretbox(r,o,n);if(i)return new Uint8Array(e.slice(o).concat(e.slice(i)));throw new Error}},{key:"decrypt",value:function(t,n){var r=e.Nacl,o=function(e){throw new Error(e||"DECRYPTION_ERROR")};o();var i=new Uint8Array(new Array(24).fill(0)),u=0,a=t.subarray(0,2),c=e.decodePrefix(a),f={metadata:void 0},l=new Uint8Array(t.subarray(2,2+c)),p=r.secretbox.open(l,i,n);e.increment(i);try{f.metadata=JSON.parse(r.util.encodeUTF8(p))}catch(e){return o("E_METADATA_DECRYPTION")}f.metadata||o("NO_METADATA");for(var y,b=function(){var o=u*s+2+c,a=o+s;u++;var f=new Uint8Array(t.subarray(o,a)),l=r.secretbox.open(f,i,n);return e.increment(i),l},h=[];u*s1?t[0]:window.location.protocol}},{key:"hostname",value:function(e){var t=e.getAttribute("src").split("://");return t.length>1?t[1].split("/")[0]:window.location.hostname}},{key:"source",value:function(e){return e.getAttribute("src")}},{key:"schemes",value:function(e){return/\w+:/.exec(e.getAttribute("src"))}},{key:"parse",value:function(t){return{protocol:e.protocol(t),hostname:e.hostname(t),src:e.source(t),type:e.type(t),extension:e.extension(t),mime:e.mime(t)}}}]),e}();e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n Date: Thu, 18 May 2017 18:51:08 +0200 Subject: [PATCH 257/306] Display an alert when CryptPad has a new version --- customize.dist/translations/messages.fr.js | 4 ++++ customize.dist/translations/messages.js | 4 ++++ www/common/cryptpad-common.js | 23 +++++++++++++++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 26cfc3129..e8cc86ce2 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -54,6 +54,10 @@ define(function () { out.comingSoon = "Bientôt disponible..."; + out.newVersion = 'CryptPad a été mis à jour !
' + + 'Découvrez les nouveautés de la dernière version :
'+ + 'Notes de version pour CryptPad {0}'; + out.upgrade = "Améliorer"; out.upgradeTitle = "Améliorer votre compte pour augmenter la limite de stockage"; out.MB = "Mo"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index dcafe6283..6280a5732 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -56,6 +56,10 @@ define(function () { out.comingSoon = "Coming soon..."; + out.newVersion = 'CryptPad has been updated!
' + + 'Check out what\'s new in the latest version:
'+ + 'Release notes for CryptPad {0}'; + out.upgrade = "Upgrade"; out.upgradeTitle = "Upgrade your account to increase the storage limit"; out.MB = "MB"; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 14f0a8b66..2f4cdf8e5 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -23,7 +23,6 @@ define([ Additionally, there is some basic functionality for import/export. */ - var common = window.Cryptpad = { Messages: Messages, Clipboard: Clipboard @@ -1296,6 +1295,25 @@ define([ return $userAdmin; }; + var CRYPTPAD_VERSION = 'cryptpad-version'; + var updateLocalVersion = function () { + // Check for CryptPad updates + var urlArgs = Config.requireConf ? Config.requireConf.urlArgs : null; + if (!urlArgs) { return; } + var arr = /ver=([0-9.]+)(-[0-9]*)?/.exec(urlArgs); + var ver = arr[1]; + if (!ver) { return; } + var verArr = ver.split('.'); + if (verArr.length !== 3) { return; } + var stored = localStorage[CRYPTPAD_VERSION] || '0.0.0'; + var storedArr = stored.split('.'); + var shouldUpdate = parseInt(verArr[0]) > parseInt(storedArr[0]) || + (parseInt(verArr[0]) === parseInt(storedArr[0]) && + parseInt(verArr[1]) > parseInt(storedArr[1])); + if (!shouldUpdate) { return; } + Cryptpad.alert(Messages._getKey('newVersion', [ver]), null, true); + localStorage[CRYPTPAD_VERSION] = ver; + }; common.ready = (function () { var env = {}; @@ -1313,6 +1331,9 @@ define([ block--; if (!block) { initialized = true; + + updateLocalVersion(); + f(void 0, env); } }; From d014fccfce1f89d19be59b4965de4151833091df Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 18 May 2017 18:52:14 +0200 Subject: [PATCH 258/306] Fix lint error --- www/common/cryptpad-common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 485bf2f44..6f0a5e256 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1311,7 +1311,7 @@ define([ (parseInt(verArr[0]) === parseInt(storedArr[0]) && parseInt(verArr[1]) > parseInt(storedArr[1])); if (!shouldUpdate) { return; } - Cryptpad.alert(Messages._getKey('newVersion', [ver]), null, true); + common.alert(Messages._getKey('newVersion', [ver]), null, true); localStorage[CRYPTPAD_VERSION] = ver; }; From e7a458295b19d24111fcf17fcf27919099fcb624 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 19 May 2017 09:10:36 +0200 Subject: [PATCH 259/306] new translation keys for file upload --- customize.dist/translations/messages.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index dcafe6283..69bce34de 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -353,7 +353,12 @@ define(function () { 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_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_tooLarge = "This file exceeds the maximum upload size."; + + // general warnings + out.warn_notPinned = "This pad is not in anyone's CryptDrive. It will expire after 3 months. Learn more..."; // index.html From d71e12713a13b3668ab9bdb4d184525ef8f80eea Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 19 May 2017 09:11:28 +0200 Subject: [PATCH 260/306] handle empty arrays when reducing --- rpc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc.js b/rpc.js index e2f62230b..54d7c14ec 100644 --- a/rpc.js +++ b/rpc.js @@ -456,7 +456,7 @@ var sumChannelSizes = function (sizes) { // only allow positive numbers return !(typeof(x) !== 'number' || x <= 0); }) - .reduce(function (a, b) { return a + b; }); + .reduce(function (a, b) { return a + b; }, 0); }; var pinChannel = function (Env, publicKey, channels, cb) { From d9f128e9236ed8b51624d850d5ce844c61f8e891 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 19 May 2017 09:12:39 +0200 Subject: [PATCH 261/306] send appropriate responses to upload rpc calls --- rpc.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc.js b/rpc.js index 54d7c14ec..2f2b96a4c 100644 --- a/rpc.js +++ b/rpc.js @@ -635,7 +635,7 @@ var upload = function (Env, publicKey, content, cb) { } if (session.currentUploadSize > session.pendingUploadSize) { - return cb('TOO_LARGE'); + return cb('E_OVER_LIMIT'); } if (!session.blobstage) { @@ -739,7 +739,7 @@ var upload_status = function (Env, publicKey, filesize, cb) { getFreeSpace(Env, publicKey, function (e, free) { if (e) { return void cb(e); } - if (filesize >= free) { return cb('TOO_LARGE'); } + if (filesize >= free) { return cb('NOT_ENOUGH_SPACE'); } isFile(filePath, function (e, yes) { if (e) { console.error("uploadError: [%s]", e); From 310909dc9ee26012a5090c9ad6ddb7aa5f04f41f Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 19 May 2017 09:12:59 +0200 Subject: [PATCH 262/306] handle new error codes --- www/file/main.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/www/file/main.js b/www/file/main.js index ba42f9afb..4ffcaa127 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -99,6 +99,12 @@ define([ Cryptpad.uploadStatus(estimate, function (e, pending) { if (e) { + if (e === 'TOO_LARGE') { + return void Cryptpad.alert(Messages.upload_tooLarge); + } + if (e === 'NOT_ENOUGH_SPACE') { + return void Cryptpad.alert(Messages.upload_notEnoughSpace); + } console.error(e); return void Cryptpad.alert(Messages.upload_serverError); } @@ -108,7 +114,9 @@ define([ return void Cryptpad.confirm(Messages.upload_uploadPending, function (yes) { if (!yes) { return; } Cryptpad.uploadCancel(function (e, res) { - if (e) { return void console.error(e); } + if (e) { + return void console.error(e); + } console.log(res); next(again); }); @@ -243,7 +251,7 @@ define([ e.stopPropagation(); counter--; if (counter <= 0) { - $label.removeClass('hovering'); // FIXME Can get stuck... + $label.removeClass('hovering'); } }); @@ -254,6 +262,8 @@ define([ }) .on('drop', function (e) { var dropped = e.originalEvent.dataTransfer.files; + counter = 0; + $label.removeClass('hovering'); handleFile(dropped[0]); }); From bcd03038215160e0a70c0826e1af05fab0312451 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 19 May 2017 11:46:10 +0200 Subject: [PATCH 263/306] style /file/ with .less --- www/file/file.css | 70 ++++++++++++++++++++++++++++++++++++++ www/file/file.less | 82 +++++++++++++++++++++++++++++++++++++++++++++ www/file/inner.html | 79 +------------------------------------------ 3 files changed, 153 insertions(+), 78 deletions(-) create mode 100644 www/file/file.css create mode 100644 www/file/file.less diff --git a/www/file/file.css b/www/file/file.css new file mode 100644 index 000000000..08c95f730 --- /dev/null +++ b/www/file/file.css @@ -0,0 +1,70 @@ +html, +body { + margin: 0px; +} +.cryptpad-toolbar { + margin-bottom: 1px; + padding: 0px; + display: inline-block; +} +#file { + display: block; + height: 300px; + width: 300px; + border: 2px solid black; + margin: 50px; +} +.inputfile { + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; +} +#upload-form { + padding: 0px; + margin: 0px; + position: relative; + width: 50vh; + height: 50vh; + display: block; + margin: auto; +} +#upload-form label { + position: relative; +} +.hovering { + background-color: rgba(255, 0, 115, 0.5) !important; +} +.block { + display: block; + height: 50vh; + width: 50vh; +} +.hidden { + display: none; +} +.inputfile + label { + border: 2px solid black; + background-color: rgba(50, 50, 50, 0.1); + margin: 50px; + display: block; +} +.inputfile:focus + label, +.inputfile + label:hover { + background-color: rgba(50, 50, 50, 0.3); +} +#progress { + position: absolute; + top: 0px; + left: 0px; + height: 100%; + transition: width 500ms; + width: 0%; + max-width: 100%; + max-height: 100%; + background-color: rgba(255, 0, 115, 0.75); + z-index: 10000; + display: block; +} diff --git a/www/file/file.less b/www/file/file.less new file mode 100644 index 000000000..69432a539 --- /dev/null +++ b/www/file/file.less @@ -0,0 +1,82 @@ +@import "../../customize.dist/src/less/variables.less"; +@import "../../customize.dist/src/less/mixins.less"; + +html, body { + margin: 0px; +} +.cryptpad-toolbar { + margin-bottom: 1px; + padding: 0px; + display: inline-block; +} +#file { + display: block; + height: 300px; + width: 300px; + border: 2px solid black; + margin: 50px; +} + +.inputfile { + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; +} + +#upload-form { + padding: 0px; + margin: 0px; + + position: relative; + width: 50vh; + height: 50vh; + display: block; + margin: auto; +} +#upload-form label{ + position: relative; +} + +.hovering { + background-color: rgba(255, 0, 115, 0.5) !important; +} + +.block { + display: block; + height: 50vh; + width: 50vh; +} +.hidden { + display: none; +} +.inputfile + label { + border: 2px solid black; + background-color: rgba(50, 50, 50, .10); + margin: 50px; + display: block; +} + +.inputfile:focus + label, +.inputfile + label:hover { + background-color: rgba(50, 50, 50, 0.30); +} + +#progress { + position: absolute; + top: 0px; + left: 0px; + height: 100%; + + + transition: width 500ms; + width: 0%; + max-width: 100%; + max-height: 100%; + background-color: rgba(255, 0, 115, 0.75); + z-index: 10000; + display: block; +} + diff --git a/www/file/inner.html b/www/file/inner.html index 482fb4955..727e11498 100644 --- a/www/file/inner.html +++ b/www/file/inner.html @@ -5,84 +5,7 @@ - +
From e78299aed6a913a461142b80c37f21f722b7a0d1 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 19 May 2017 11:46:47 +0200 Subject: [PATCH 264/306] use async file decryption, listen for progress events. --- www/common/media-tag.js | 2 +- www/media/main.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/www/common/media-tag.js b/www/common/media-tag.js index cf8c78210..370b0e93d 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.MediaTag=t():e.MediaTag=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=82)}([function(e,t,n){"use strict";var r={IMAGE:"image",AUDIO:"audio",VIDEO:"video",PDF:"pdf",DASH:"dash",DOWNLOAD:"download",CRYPTO:"crypto",CLEAR_KEY:"clear-key",MEDIA_OBJECT:"media-object"};e.exports=r},function(e,t,n){"use strict";var r={MATCHER:"matcher",RENDERER:"renderer",FILTER:"filter",SANITIZER:"sanitizer"};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n=e.STACK_LIMIT)throw console.error(e.snapshots[n]),new Error("Plugin stack size exceed");if(e.snapshots[n].length>=e.SNAPSHOT_LIMIT)throw console.error(e.snapshots[n]),new Error("Plugin snapshots size exceed");var r=0;if(e.stacks[n].forEach(function(e){e.type===u.RENDERER&&r++}),r<1&&e.stacks[n].unshift(e.defaultPlugin),r>1)throw new Error("More of one renderer in the stack")}},{key:"return",value:function(t){e.start(t)}},{key:"run",value:function(t){var n=t.getId(),r=e.stacks[n].length,o=e.stacks[n][r-1];if(!o)throw console.log(e.stacks),new Error("Impossible to run a undefined plugin");o.process(t)}}]),e}();f.stacks={},f.STACK_LIMIT=1e3,f.snapshots={},f.SNAPSHOT_LIMIT=1e3,f.defaultPlugin=new s,e.exports=f},function(e,t,n){"use strict";var r={EVERY:"every",ANY:"any",ONCE:"once"};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n1;){if(c){if("number"!=typeof e[t])throw new Error("E_UNSAFE_TYPE");if(e[t]>255)throw new Error("E_OUT_OF_BOUNDS")}if(255!==e[t])return void e[t]++;if(e[t]=0,0===t)throw new Error("E_NONCE_TOO_LARGE")}}},{key:"encodePrefix",value:function(e){return[65280,255].map(function(t,n){return(e&t)>>8*(1-n)})}},{key:"decodePrefix",value:function(e){return e[0]<<8|e[1]}},{key:"joinChunks",value:function(t){return new Uint8Array(t.reduce(function(t,n){return e.slice(t).concat(e.slice(n))},[]))}},{key:"slice",value:function(e){return Array.prototype.slice.call(e)}},{key:"getRandomKeyStr",value:function(){var t=e.Nacl,n=t.randomBytes(18);return t.util.encodeBase64(n)}},{key:"getKeyFromStr",value:function(t){return e.Nacl.util.decodeBase64(t)}},{key:"encrypt",value:function(t,n){var r=t,o=e.Nacl.randomBytes(24),i=e.Nacl.secretbox(r,o,n);if(i)return new Uint8Array(e.slice(o).concat(e.slice(i)));throw new Error}},{key:"decrypt",value:function(t,n){var r=e.Nacl,o=function(e){throw new Error(e||"DECRYPTION_ERROR")};o();var i=new Uint8Array(new Array(24).fill(0)),u=0,a=t.subarray(0,2),c=e.decodePrefix(a),f={metadata:void 0},l=new Uint8Array(t.subarray(2,2+c)),p=r.secretbox.open(l,i,n);e.increment(i);try{f.metadata=JSON.parse(r.util.encodeUTF8(p))}catch(e){return o("E_METADATA_DECRYPTION")}f.metadata||o("NO_METADATA");for(var y,b=function(){var o=u*s+2+c,a=o+s;u++;var f=new Uint8Array(t.subarray(o,a)),l=r.secretbox.open(f,i,n);return e.increment(i),l},h=[];u*s1?t[0]:window.location.protocol}},{key:"hostname",value:function(e){var t=e.getAttribute("src").split("://");return t.length>1?t[1].split("/")[0]:window.location.hostname}},{key:"source",value:function(e){return e.getAttribute("src")}},{key:"schemes",value:function(e){return/\w+:/.exec(e.getAttribute("src"))}},{key:"parse",value:function(t){return{protocol:e.protocol(t),hostname:e.hostname(t),src:e.source(t),type:e.type(t),extension:e.extension(t),mime:e.mime(t)}}}]),e}();e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n=e.STACK_LIMIT)throw console.error(e.snapshots[n]),new Error("Plugin stack size exceed");if(e.snapshots[n].length>=e.SNAPSHOT_LIMIT)throw console.error(e.snapshots[n]),new Error("Plugin snapshots size exceed");var r=0;if(e.stacks[n].forEach(function(e){e.type===u.RENDERER&&r++}),r<1&&e.stacks[n].unshift(e.defaultPlugin),r>1)throw new Error("More of one renderer in the stack")}},{key:"return",value:function(t){e.start(t)}},{key:"run",value:function(t){var n=t.getId(),r=e.stacks[n].length,o=e.stacks[n][r-1];if(!o)throw console.log(e.stacks),new Error("Impossible to run a undefined plugin");o.process(t)}}]),e}();f.stacks={},f.STACK_LIMIT=1e3,f.snapshots={},f.SNAPSHOT_LIMIT=1e3,f.defaultPlugin=new s,e.exports=f},function(e,t,n){"use strict";var r={EVERY:"every",ANY:"any",ONCE:"once"};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n1;){if(c){if("number"!=typeof e[t])throw new Error("E_UNSAFE_TYPE");if(e[t]>255)throw new Error("E_OUT_OF_BOUNDS")}if(255!==e[t])return void e[t]++;if(e[t]=0,0===t)throw new Error("E_NONCE_TOO_LARGE")}}},{key:"encodePrefix",value:function(e){return[65280,255].map(function(t,n){return(e&t)>>8*(1-n)})}},{key:"decodePrefix",value:function(e){return e[0]<<8|e[1]}},{key:"joinChunks",value:function(t){return new Uint8Array(t.reduce(function(t,n){return e.slice(t).concat(e.slice(n))},[]))}},{key:"slice",value:function(e){return Array.prototype.slice.call(e)}},{key:"getRandomKeyStr",value:function(){var t=e.Nacl,n=t.randomBytes(18);return t.util.encodeBase64(n)}},{key:"getKeyFromStr",value:function(t){return e.Nacl.util.decodeBase64(t)}},{key:"encrypt",value:function(t,n){var r=t,o=e.Nacl.randomBytes(24),i=e.Nacl.secretbox(r,o,n);if(i)return new Uint8Array(e.slice(o).concat(e.slice(i)));throw new Error}},{key:"decrypt",value:function(t,n,r){var o=e.Nacl,i=function(e){var n=new Event("decryptionProgress");n.percent=e/t.length*100,window.document.dispatchEvent(n)},u=e.createNonce(),a=0,c=t.subarray(0,2),f=e.decodePrefix(c),l={metadata:void 0},p=new Uint8Array(t.subarray(2,2+f)),y=o.secretbox.open(p,u,n);e.increment(u);try{l.metadata=JSON.parse(o.util.encodeUTF8(y))}catch(e){return r("E_METADATA_DECRYPTION")}if(!l.metadata)return r("NO_METADATA");var b=function(r){var c=a*s+2+f,l=c+s;a++;var p=new Uint8Array(t.subarray(c,l)),y=o.secretbox.open(p,u,n);if(e.increment(u),!y)return void r("DECRYPTION_FAILURE");i(Math.min(l,t.length)),r(void 0,y)},h=[];!function n(){b(function(o,i){return o?setTimeout(function(){r(o)}):i?a*s1?t[0]:window.location.protocol}},{key:"hostname",value:function(e){var t=e.getAttribute("src").split("://");return t.length>1?t[1].split("/")[0]:window.location.hostname}},{key:"source",value:function(e){return e.getAttribute("src")}},{key:"schemes",value:function(e){return/\w+:/.exec(e.getAttribute("src"))}},{key:"parse",value:function(t){return{protocol:e.protocol(t),hostname:e.hostname(t),src:e.source(t),type:e.type(t),extension:e.extension(t),mime:e.mime(t)}}}]),e}();e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n Date: Fri, 19 May 2017 14:37:00 +0200 Subject: [PATCH 265/306] move rpc warnings out of server and into rpc --- rpc.js | 45 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/rpc.js b/rpc.js index 2f2b96a4c..e7d3c1627 100644 --- a/rpc.js +++ b/rpc.js @@ -286,7 +286,10 @@ var getFileSize = function (Env, channel, cb) { } return void Env.msgStore.getChannelSize(channel, function (e, size) { - if (e) { return void cb(e.code); } + if (e) { + if (e === 'ENOENT') { return void cb(void 0, 0); } + return void cb(e.code); + } cb(void 0, size); }); } @@ -331,7 +334,7 @@ var getTotalSize = function (Env, publicKey, cb) { //var msgStore = Env.msgStore; return void getChannelList(Env, publicKey, function (channels) { - if (!channels) { cb('NO_ARRAY'); } // unexpected + if (!channels) { return cb('NO_ARRAY'); } // unexpected var count = channels.length; if (!count) { cb(void 0, 0); } @@ -755,6 +758,12 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) // load pin-store... console.log('loading rpc module...'); + var warn = function (e, output) { + if (e && !config.suppressRPCErrors) { + console.error('[' + e + ']', output); + } + }; + var keyOrDefaultString = function (key, def) { return typeof(config[key]) === 'string'? config[key]: def; }; @@ -848,40 +857,59 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) case 'COOKIE': return void Respond(void 0); case 'RESET': return resetUserPins(Env, safeKey, msg[1], function (e, hash) { + //warn(e, hash); return void Respond(e, hash); }); case 'PIN': return pinChannel(Env, safeKey, msg[1], function (e, hash) { + warn(e, hash); Respond(e, hash); }); case 'UNPIN': return unpinChannel(Env, safeKey, msg[1], function (e, hash) { + warn(e, hash); Respond(e, hash); }); case 'GET_HASH': return void getHash(Env, safeKey, function (e, hash) { + warn(e, hash); Respond(e, hash); }); case 'GET_TOTAL_SIZE': // TODO cache this, since it will get called quite a bit return getTotalSize(Env, safeKey, function (e, size) { - if (e) { return void Respond(e); } + if (e) { + warn(e, safeKey); + return void Respond(e); + } Respond(e, size); }); case 'GET_FILE_SIZE': - return void getFileSize(Env, msg[1], Respond); + return void getFileSize(Env, msg[2], function (e, size) { + warn(e, msg[2]); + Respond(e, size); + }); case 'UPDATE_LIMITS': return void updateLimits(config, safeKey, function (e, limit) { - if (e) { return void Respond(e); } + if (e) { + warn(e, limit); + return void Respond(e); + } Respond(void 0, limit); }); case 'GET_LIMIT': return void getLimit(Env, safeKey, function (e, limit) { - if (e) { return void Respond(e); } + if (e) { + warn(e, limit); + return void Respond(e); + } Respond(void 0, limit); }); case 'GET_MULTIPLE_FILE_SIZE': return void getMultipleFileSize(Env, msg[1], function (e, dict) { - if (e) { return void Respond(e); } + if (e) { + warn(e, dict); + return void Respond(e); + } Respond(void 0, dict); }); @@ -889,6 +917,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) case 'UPLOAD': if (!privileged) { return deny(); } return void upload(Env, safeKey, msg[1], function (e, len) { + warn(e, len); Respond(e, len); }); case 'UPLOAD_STATUS': @@ -906,11 +935,13 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) case 'UPLOAD_COMPLETE': if (!privileged) { return deny(); } return void upload_complete(Env, safeKey, function (e, hash) { + warn(e, hash); Respond(e, hash); }); case 'UPLOAD_CANCEL': if (!privileged) { return deny(); } return void upload_cancel(Env, safeKey, function (e) { + warn(e); Respond(e); }); default: From 0d265f543ea1f46e9b69665f817dc17c0b610544 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 19 May 2017 14:41:55 +0200 Subject: [PATCH 266/306] add file.css to npm style command --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dd089605c..e75ec7b32 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "scripts": { "lint": "jshint --config .jshintrc --exclude-path .jshintignore .", "test": "node TestSelenium.js", - "style": "lessc ./customize.dist/src/less/cryptpad.less > ./customize.dist/main.css && lessc ./customize.dist/src/less/toolbar.less > ./customize.dist/toolbar.css && lessc ./www/drive/file.less > ./www/drive/file.css && lessc ./www/settings/main.less > ./www/settings/main.css && lessc ./www/slide/slide.less > ./www/slide/slide.css && lessc ./www/whiteboard/whiteboard.less > ./www/whiteboard/whiteboard.css && lessc ./www/poll/poll.less > ./www/poll/poll.css", + "style": "lessc ./customize.dist/src/less/cryptpad.less > ./customize.dist/main.css && lessc ./customize.dist/src/less/toolbar.less > ./customize.dist/toolbar.css && lessc ./www/drive/file.less > ./www/drive/file.css && lessc ./www/settings/main.less > ./www/settings/main.css && lessc ./www/slide/slide.less > ./www/slide/slide.css && lessc ./www/whiteboard/whiteboard.less > ./www/whiteboard/whiteboard.css && lessc ./www/poll/poll.less > ./www/poll/poll.css && lessc ./www/file/file.less > ./www/file/file.css", "template": "cd customize.dist/src && node build.js" } } From 0a54f0cf2d332c010d8819f1883fb6007c856bf8 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 19 May 2017 16:21:26 +0200 Subject: [PATCH 267/306] better alignment of preview and code entry --- www/code/inner.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/code/inner.html b/www/code/inner.html index 36d5911ee..0ef693f1c 100644 --- a/www/code/inner.html +++ b/www/code/inner.html @@ -78,7 +78,7 @@ overflow: auto; display: inline-block; height: 100%; - border: 1px solid black; + border-left: 1px solid black; box-sizing: border-box; font-family: Calibri,Ubuntu,sans-serif; } From 1378a0c1f62f678e115168a94d13db5fdec3568e Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 19 May 2017 16:56:45 +0200 Subject: [PATCH 268/306] serve blobs from configured location --- server.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index d7f5b90fc..037a8adeb 100644 --- a/server.js +++ b/server.js @@ -8,6 +8,7 @@ var Fs = require('fs'); var WebSocketServer = require('ws').Server; var NetfluxSrv = require('./node_modules/chainpad-server/NetfluxWebsocketSrv'); var Package = require('./package.json'); +var Path = require("path"); var config = require('./config'); var websocketPort = config.websocketPort || config.httpPort; @@ -82,7 +83,7 @@ var mainPages = config.mainPages || ['index', 'privacy', 'terms', 'about', 'cont var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$'); app.get(mainPagePattern, Express.static(__dirname + '/customize.dist')); -app.use("/blob", Express.static(__dirname + '/blob')); +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')); From ba5ef5157e93c85d92f02fc4a5cb4527b46b5c51 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 19 May 2017 18:37:31 +0200 Subject: [PATCH 269/306] show pretty representations of storage usage --- customize.dist/translations/messages.es.js | 4 +++ customize.dist/translations/messages.fr.js | 3 ++ customize.dist/translations/messages.js | 4 +++ www/common/common-util.js | 17 ++++++++++-- www/common/cryptpad-common.js | 32 ++++++++++++++++------ 5 files changed, 49 insertions(+), 11 deletions(-) diff --git a/customize.dist/translations/messages.es.js b/customize.dist/translations/messages.es.js index 2346579fb..d2359c44b 100644 --- a/customize.dist/translations/messages.es.js +++ b/customize.dist/translations/messages.es.js @@ -410,6 +410,10 @@ define(function () { out.upgrade = "Mejorar"; out.upgradeTitle = "Mejora tu cuenta para obtener más espacio"; out.MB = "MB"; + out.GB = "GB"; + out.formattedMB = "{0} MB"; + out.formattedGB = "{0} GB"; + out.pinLimitReached = "Has llegado al limite de espacio"; out.pinLimitReachedAlert = "Has llegado al limite de espacio. Nuevos pads no serán movidos a tu CryptDrive.
Para resolver este problema, puedes quitar pads de tu CryptDrive (incluso en la papelera) o mejorar tu cuenta para obtener más espacio."; out.pinLimitNotPinned = "Has llegado al limite de espacio.
Este pad no estará presente en tu CryptDrive."; diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index e8cc86ce2..5dfd383b0 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -61,6 +61,9 @@ define(function () { out.upgrade = "Améliorer"; out.upgradeTitle = "Améliorer votre compte pour augmenter la limite de stockage"; out.MB = "Mo"; + out.GB = "Go"; + out.formattedMB = "{0} Mo"; + out.formattedGB = "{0} Go"; out.greenLight = "Tout fonctionne bien"; out.orangeLight = "Votre connexion est lente, ce qui réduit la qualité de l'éditeur"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index f0ce247fc..6cbff3d7f 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -63,6 +63,10 @@ define(function () { out.upgrade = "Upgrade"; out.upgradeTitle = "Upgrade your account to increase the storage limit"; out.MB = "MB"; + out.GB = "GB"; + + out.formattedMB = "{0} MB"; + out.formattedGB = "{0} GB"; out.greenLight = "Everything is working fine"; out.orangeLight = "Your slow connection may impact your experience"; diff --git a/www/common/common-util.js b/www/common/common-util.js index a5822c743..f04b85199 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -81,12 +81,25 @@ define([], function () { .replace(/_+/g, '_'); }; + var oneKilobyte = 1024; + var oneMegabyte = 1024 * oneKilobyte; + var oneGigabyte = 1024 * oneMegabyte; + + Util.bytesToGigabytes = function (bytes) { + return Math.ceil(bytes / oneGigabyte * 100) / 100; + }; + Util.bytesToMegabytes = function (bytes) { - return Math.floor((bytes / (1024 * 1024) * 100)) / 100; + return Math.ceil(bytes / oneMegabyte * 100) / 100; }; Util.bytesToKilobytes = function (bytes) { - return Math.floor(bytes / 1024 * 100) / 100; + return Math.ceil(bytes / oneKilobyte * 100) / 100; + }; + + Util.magnitudeOfBytes = function (bytes) { + if (bytes >= oneGigabyte) { return 'GB'; } + else if (bytes >= oneMegabyte) { return 'MB'; } }; Util.fetch = function (src, cb) { diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 6f0a5e256..9bbebbed3 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -745,8 +745,7 @@ define([ if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); } rpc.updatePinLimits(function (e, limit, plan) { if (e) { return cb(e); } - var MB = common.bytesToMegabytes(limit); - cb(e, MB, plan); + cb(e, limit, plan); }); }; @@ -754,8 +753,7 @@ define([ if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); } rpc.getLimit(function (e, limit, plan) { if (e) { return cb(e); } - var MB = common.bytesToMegabytes(limit); - cb(void 0, MB, plan); + cb(void 0, limit, plan); }); }; @@ -771,7 +769,7 @@ define([ return void cb (null, false, data); }; var todo = function (e, used) { - usage = common.bytesToMegabytes(used); + usage = used; //common.bytesToMegabytes(used); if (e) { return void cb(e); } common.getPinLimit(andThen); }; @@ -802,9 +800,14 @@ define([ common.isOverPinLimit(todo); }, LIMIT_REFRESH_RATE); } - var usage = data.usage; - var limit = data.limit; - var unit = Messages.MB; + + var unit = Util.magnitudeOfBytes(data.limit); + + var usage = unit === 'GB'? Util.bytesToGigabytes(data.usage): + Util.bytesToMegabytes(data.usage); + var limit = unit === 'GB'? Util.bytesToGigabytes(data.limit): + Util.bytesToMegabytes(data.limit); + var $limit = $('', {'class': 'cryptpad-limit-bar'}).appendTo($container); var quota = usage/limit; var width = Math.floor(Math.min(quota, 1)*200); // the bar is 200px width @@ -823,11 +826,22 @@ define([ }).text(Messages.upgrade).appendTo($upgradeLink); } + var prettyUsage; + var prettyLimit; + + if (unit === 'GB') { + prettyUsage = usage; //Messages._getKey('formattedGB', [usage]); + prettyLimit = Messages._getKey('formattedGB', [limit]); + } else { + prettyUsage = usage; //Messages._getKey('formattedMB', [usage]); + prettyLimit = Messages._getKey('formattedMB', [limit]); + } + if (quota < 0.8) { $usage.addClass('normal'); } else if (quota < 1) { $usage.addClass('warning'); } else { $usage.addClass('above'); } var $text = $('', {'class': 'usageText'}); - $text.text(usage + ' / ' + limit + ' ' + unit); + $text.text(prettyUsage + ' / ' + prettyLimit); $limit.append($usage).append($text); window.setTimeout(function () { common.isOverPinLimit(todo); From 1ab6eb3c5df892d72eb56a45699db99a5beadf68 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 19 May 2017 18:41:34 +0200 Subject: [PATCH 270/306] Add a queue for multiple uploads in the file app --- customize.dist/translations/messages.fr.js | 16 ++++ customize.dist/translations/messages.js | 9 ++- rpc.js | 2 +- www/file/inner.html | 46 ++++++++++- www/file/main.js | 91 +++++++++++++++++++--- 5 files changed, 150 insertions(+), 14 deletions(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index e8cc86ce2..5db44bf2d 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -61,6 +61,7 @@ define(function () { out.upgrade = "Améliorer"; out.upgradeTitle = "Améliorer votre compte pour augmenter la limite de stockage"; out.MB = "Mo"; + out.KB = "Ko"; out.greenLight = "Tout fonctionne bien"; out.orangeLight = "Votre connexion est lente, ce qui réduit la qualité de l'éditeur"; @@ -348,6 +349,21 @@ define(function () { out.settings_logoutEverywhere = "Se déconnecter de toutes les autres sessions."; out.settings_logoutEverywhereConfirm = "Êtes-vous sûr ? Vous devrez vous reconnecter sur tous vos autres appareils."; + out.upload_serverError = "Erreur interne: impossible d'uploader le fichier pour l'instant."; + out.upload_uploadPending = "Vous avez déjà un fichier en cours d'upload. Souhaitez-vous l'annuler et uploader ce nouveau fichier ?"; + out.upload_success = "Votre fichier ({0}) a été uploadé 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_tooLarge = "Ce fichier dépasse la taille maximale autorisée."; + out.upload_choose = "Choisir un fichier"; + out.upload_pending = "En attente"; + out.upload_cancelled = "Annulé"; + out.upload_name = "Nom du fichier"; + out.upload_size = "Taille"; + out.upload_progress = "État"; + + // 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..."; + // index.html //about.html diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index f0ce247fc..8a1de05a5 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -63,6 +63,7 @@ define(function () { out.upgrade = "Upgrade"; out.upgradeTitle = "Upgrade your account to increase the storage limit"; out.MB = "MB"; + out.KB = "KB"; out.greenLight = "Everything is working fine"; out.orangeLight = "Your slow connection may impact your experience"; @@ -236,8 +237,6 @@ define(function () { out.fm_info_template = 'Contains all the pads stored as templates and that you can re-use when you create a new pad.'; out.fm_info_trash = 'Files deleted from the trash are also removed from "All files" and it is impossible to recover them from the file manager.'; // Same here for "All files" and "out.fm_filesDataName" 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_login = "Log in"; - out.fm_info_register = "Sign up"; out.fm_info_anonymous = 'You are not logged in so these pads may be deleted (find out why). ' + 'Sign up or Log in to keep them alive.'; out.fm_alert_backupUrl = "Backup link for this drive.
" + @@ -360,6 +359,12 @@ define(function () { 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_tooLarge = "This file exceeds the maximum upload size."; + out.upload_choose = "Choose a file"; + out.upload_pending = "Pending"; + out.upload_cancelled = "Cancelled"; + out.upload_name = "File name"; + out.upload_size = "Size"; + out.upload_progress = "Progress"; // general warnings out.warn_notPinned = "This pad is not in anyone's CryptDrive. It will expire after 3 months. Learn more..."; diff --git a/rpc.js b/rpc.js index 2f2b96a4c..ef2e547c3 100644 --- a/rpc.js +++ b/rpc.js @@ -13,7 +13,7 @@ var RPC = module.exports; var Store = require("./storage/file"); -var DEFAULT_LIMIT = 50 * 1024 * 1024; +var DEFAULT_LIMIT = 150 * 1024 * 1024; var isValidId = function (chan) { return /^[a-fA-F0-9]/.test(chan) || diff --git a/www/file/inner.html b/www/file/inner.html index 482fb4955..9b982b648 100644 --- a/www/file/inner.html +++ b/www/file/inner.html @@ -42,6 +42,8 @@ margin: auto; } #upload-form label{ + text-align: center; + line-height: 50vh; position: relative; } @@ -82,14 +84,56 @@ display: block; } + #status { + display: none; + width: 80vw; + margin-top: 50px; + margin-left: 10vw; + border: 1px solid black; + border-collapse: collapse; + } + #status tr:nth-child(1) { + background-color: #ccc; + border: 1px solid #999; + } + #status tr:nth-child(1) td { text-align: center; } + #status td { + border-left: 1px solid #BBB; + border-right: 1px solid #BBB; + padding: 0 10px; + } + #status .upProgress { + width: 200px; + position: relative; + text-align: center; + } + #status .progressContainer { + position: absolute; + width: 0px; + left: 5px; + top: 1px; bottom: 1px; + background-color: rgba(0,0,255,0.3); + } + #status .upCancel { text-align: center; } + #status .fa.cancel { + color: rgb(255, 0, 115); + }
+ + + + + + + +
File nameSizeProgressCancel
diff --git a/www/file/main.js b/www/file/main.js index 4ffcaa127..eb53cca47 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -21,8 +21,9 @@ define([ var ifrw = $('#pad-iframe')[0].contentWindow; var $iframe = $('#pad-iframe').contents(); var $form = $iframe.find('#upload-form'); - var $progress = $form.find('#progress'); + //var $progress = $form.find('#progress'); var $label = $form.find('label'); + var $table = $iframe.find('#status'); Cryptpad.addLoadingScreen(); @@ -31,8 +32,23 @@ define([ var myFile; var myDataType; - var upload = function (blob, metadata) { + var queue = { + queue: [], + inProgress: false + }; + + var uid = function () { + return 'file-' + String(Math.random()).substring(2); + }; + + var upload = function (blob, metadata, id) { console.log(metadata); + if (queue.inProgress) { return; } + queue.inProgress = true; + + var $cancelCell = $table.find('tr[id="'+id+'"]').find('.upCancel'); + $cancelCell.html('-'); + var u8 = new Uint8Array(blob); var key = Nacl.randomBytes(32); @@ -56,13 +72,19 @@ define([ if (err) { throw new Error(err); } if (box) { actual += box.length; - var progress = (actual / estimate * 100) + '%'; - console.log(progress); + var progressValue = (actual / estimate * 100); + var progress = progressValue + '%'; return void sendChunk(box, function (e) { if (e) { return console.error(e); } - $progress.css({ + /*$progress.css({ width: progress, + });*/ + var $pv = $table.find('tr[id="'+id+'"]').find('.progressValue'); + $pv.text(Math.round(progressValue*100)/100 + '%'); + var $pb = $table.find('tr[id="'+id+'"]').find('.progressContainer'); + $pb.css({ + width: (progressValue/100)*188+'px' }); next(again); @@ -82,7 +104,7 @@ define([ var b64Key = Nacl.util.encodeBase64(key); Cryptpad.replaceHash(Cryptpad.getFileHashFromKeys(id, b64Key)); - $form.hide(); + //$form.hide(); APP.toolbar.addElement(['fileshare'], {}); @@ -94,11 +116,15 @@ define([ APP.toolbar.title.show(); console.log(title); Cryptpad.alert(Messages._getKey('upload_success', [title])); + queue.inProgress = false; + queue.next(); }); }; Cryptpad.uploadStatus(estimate, function (e, pending) { if (e) { + queue.inProgress = false; + queue.next(); if (e === 'TOO_LARGE') { return void Cryptpad.alert(Messages.upload_tooLarge); } @@ -110,7 +136,7 @@ define([ } if (pending) { - // TODO queue uploads... ? + // TODO keep this message in case of pending files in another window? return void Cryptpad.confirm(Messages.upload_uploadPending, function (yes) { if (!yes) { return; } Cryptpad.uploadCancel(function (e, res) { @@ -126,6 +152,48 @@ define([ }); }; + var prettySize = function (bytes) { + var kB = Cryptpad.bytesToKilobytes(bytes); + if (kB < 1024) { return kB + Messages.KB; } + var mB = Cryptpad.bytesToMegabytes(bytes); + return mB + Messages.MB; + }; + + queue.next = function () { + if (queue.queue.length === 0) { return; } + if (queue.inProgress) { return; } + var file = queue.queue.shift(); + upload(file.blob, file.metadata, file.id); + }; + queue.push = function (obj) { + var id = uid(); + obj.id = id; + queue.queue.push(obj); + + $table.show(); + var estimate = FileCrypto.computeEncryptedSize(obj.blob.byteLength, obj.metadata); + + var $progressBar = $('
', {'class':'progressContainer'}); + var $progressValue = $('', {'class':'progressValue'}).text(Messages.upload_pending); + + var $tr = $('', {id: id}).appendTo($table); + + var $cancel = $('', {'class': 'cancel fa fa-times'}).click(function () { + queue.queue = queue.queue.filter(function (el) { return el.id !== id }); + $cancel.remove(); + $tr.find('.upCancel').text('-'); + $tr.find('.progressValue').text(Messages.upload_cancelled); + }); + + var $tr = $('', {id: id}).appendTo($table); + $('').text(obj.metadata.name).appendTo($tr); + $('').text(prettySize(estimate)).appendTo($tr); + $('', {'class': 'upProgress'}).append($progressBar).append($progressValue).appendTo($tr); + $('', {'class': 'upCancel'}).append($cancel).appendTo($tr); + + queue.next(); + }; + var uploadMode = false; var andThen = function () { @@ -225,9 +293,12 @@ define([ console.log(file); var reader = new FileReader(); reader.onloadend = function () { - upload(this.result, { - name: file.name, - type: file.type, + queue.push({ + blob: this.result, + metadata: { + name: file.name, + type: file.type, + } }); }; reader.readAsArrayBuffer(file); From 03da870a7a4a3188496aa12f9ec7f6ec0e31496b Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 19 May 2017 19:38:57 +0200 Subject: [PATCH 271/306] jshint compliance --- www/file/main.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/www/file/main.js b/www/file/main.js index eb53cca47..ada0b50d2 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -73,13 +73,9 @@ define([ if (box) { actual += box.length; var progressValue = (actual / estimate * 100); - var progress = progressValue + '%'; return void sendChunk(box, function (e) { if (e) { return console.error(e); } - /*$progress.css({ - width: progress, - });*/ var $pv = $table.find('tr[id="'+id+'"]').find('.progressValue'); $pv.text(Math.round(progressValue*100)/100 + '%'); var $pb = $table.find('tr[id="'+id+'"]').find('.progressContainer'); @@ -179,17 +175,17 @@ define([ var $tr = $('', {id: id}).appendTo($table); var $cancel = $('', {'class': 'cancel fa fa-times'}).click(function () { - queue.queue = queue.queue.filter(function (el) { return el.id !== id }); + queue.queue = queue.queue.filter(function (el) { return el.id !== id; }); $cancel.remove(); $tr.find('.upCancel').text('-'); $tr.find('.progressValue').text(Messages.upload_cancelled); }); - var $tr = $('', {id: id}).appendTo($table); - $('').text(obj.metadata.name).appendTo($tr); - $('').text(prettySize(estimate)).appendTo($tr); - $('', {'class': 'upProgress'}).append($progressBar).append($progressValue).appendTo($tr); - $('', {'class': 'upCancel'}).append($cancel).appendTo($tr); + var $tr2 = $('', {id: id}).appendTo($table); + $('').text(obj.metadata.name).appendTo($tr2); + $('').text(prettySize(estimate)).appendTo($tr2); + $('', {'class': 'upProgress'}).append($progressBar).append($progressValue).appendTo($tr2); + $('', {'class': 'upCancel'}).append($cancel).appendTo($tr2); queue.next(); }; From 538aec6ef5639f583a7ed9ce9b5cbf085c846537 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 May 2017 10:41:47 +0200 Subject: [PATCH 272/306] Display a message when pinPads returns an error --- customize.dist/translations/messages.fr.js | 1 + customize.dist/translations/messages.js | 1 + www/common/userObject.js | 5 ++++- www/drive/main.js | 9 ++++++--- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 5db44bf2d..2302a1d62 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -247,6 +247,7 @@ define(function () { 'nos raisons pour ces changements et pourquoi vous devriez vraiment vous enregistrer et vous connecter.'; out.fm_backup_title = 'Lien de secours'; out.fm_nameFile = 'Comment souhaitez-vous nommer ce fichier ?'; + out.fm_error_cantPin = "Erreur interne du serveur. Veuillez recharger la page et essayer de nouveau."; // File - Context menu out.fc_newfolder = "Nouveau dossier"; out.fc_rename = "Renommer"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 8a1de05a5..69e9cc3c2 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -249,6 +249,7 @@ define(function () { 'why we are doing this and why you really should Sign up and Log in.'; out.fm_backup_title = 'Backup link'; out.fm_nameFile = 'How would you like to name that file?'; + out.fm_error_cantPin = "Internal server error. Please reload the page and try again."; // File - Context menu out.fc_newfolder = "New folder"; out.fc_rename = "Rename"; diff --git a/www/common/userObject.js b/www/common/userObject.js index 51840dca5..1559397d0 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -575,7 +575,10 @@ define([ atime: +new Date(), ctime: +new Date() }, function (err) { - if (err) { return void cb(err); } + if (err) { + logError(err); + return void cb(err); + } parentEl[fileName] = href; var newPath = filePath.slice(); newPath.push(fileName); diff --git a/www/drive/main.js b/www/drive/main.js index a85c86e47..861136454 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -1397,8 +1397,11 @@ define([ // Handlers if (isInRoot) { var onCreated = function (err, info) { - if (err && err === E_OVER_LIMIT) { - return void Cryptpad.alert(Messages.pinLimitDrive, null, true); + if (err) { + if (err === E_OVER_LIMIT) { + return void Cryptpad.alert(Messages.pinLimitDrive, null, true); + } + return void Cryptpad.alert(Messages.fm_error_cantPin); } module.newFolder = info.newPath; refresh(); @@ -2361,7 +2364,7 @@ define([ return void Cryptpad.alert(Messages.pinLimitDrive, null, true); } if (err) { - return void console.error("Unable to create the file", err); + return void Cryptpad.alert(Messages.fm_error_cantPin); } module.newFolder = info.newPath; refresh(); From 0c2f84058b1c857a593d159c951eb86fe42dfeef Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 May 2017 11:16:01 +0200 Subject: [PATCH 273/306] Improve css for the file app --- customize.dist/src/less/cryptpad.less | 9 +++++++++ rpc.js | 2 +- www/file/file.css | 17 ++++++++++------- www/file/file.less | 19 +++++++++++-------- www/file/inner.html | 4 +++- www/file/main.js | 9 ++++----- 6 files changed, 38 insertions(+), 22 deletions(-) diff --git a/customize.dist/src/less/cryptpad.less b/customize.dist/src/less/cryptpad.less index 883e62ce4..0ee8785e6 100644 --- a/customize.dist/src/less/cryptpad.less +++ b/customize.dist/src/less/cryptpad.less @@ -43,6 +43,15 @@ a.github-corner > svg { 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; diff --git a/rpc.js b/rpc.js index a155c8272..e7d3c1627 100644 --- a/rpc.js +++ b/rpc.js @@ -13,7 +13,7 @@ var RPC = module.exports; var Store = require("./storage/file"); -var DEFAULT_LIMIT = 150 * 1024 * 1024; +var DEFAULT_LIMIT = 50 * 1024 * 1024; var isValidId = function (chan) { return /^[a-fA-F0-9]/.test(chan) || diff --git a/www/file/file.css b/www/file/file.css index 375a356cc..f2fd8e6d5 100644 --- a/www/file/file.css +++ b/www/file/file.css @@ -9,10 +9,9 @@ body { } #file { display: block; - height: 300px; - width: 300px; + height: 100%; + width: 100%; border: 2px solid black; - margin: 50px; } .inputfile { width: 0.1px; @@ -29,18 +28,23 @@ body { width: 50vh; height: 50vh; display: block; - margin: auto; + margin: 50px auto; + max-width: 80vw; } #upload-form label { + line-height: 50vh; + text-align: center; position: relative; + padding: 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .hovering { background-color: rgba(255, 0, 115, 0.5) !important; } .block { display: block; - height: 50vh; - width: 50vh; } .hidden { display: none; @@ -48,7 +52,6 @@ body { .inputfile + label { border: 2px solid black; background-color: rgba(50, 50, 50, 0.1); - margin: 50px; display: block; } .inputfile:focus + label, diff --git a/www/file/file.less b/www/file/file.less index b3074e33c..db6942596 100644 --- a/www/file/file.less +++ b/www/file/file.less @@ -11,10 +11,9 @@ html, body { } #file { display: block; - height: 300px; - width: 300px; + height: 100%; + width: 100%; border: 2px solid black; - margin: 50px; } .inputfile { @@ -34,10 +33,17 @@ html, body { width: 50vh; height: 50vh; display: block; - margin: auto; + margin: 50px auto; + max-width: 80vw; } -#upload-form label{ +#upload-form label { + line-height: 50vh; + text-align: center; position: relative; + padding: 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .hovering { @@ -46,8 +52,6 @@ html, body { .block { display: block; - height: 50vh; - width: 50vh; } .hidden { display: none; @@ -55,7 +59,6 @@ html, body { .inputfile + label { border: 2px solid black; background-color: rgba(50, 50, 50, .10); - margin: 50px; display: block; } diff --git a/www/file/inner.html b/www/file/inner.html index c6fe5fb32..548ac6a47 100644 --- a/www/file/inner.html +++ b/www/file/inner.html @@ -6,12 +6,14 @@ +
diff --git a/www/file/main.js b/www/file/main.js index ada0b50d2..56b46b1dd 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -181,11 +181,10 @@ define([ $tr.find('.progressValue').text(Messages.upload_cancelled); }); - var $tr2 = $('', {id: id}).appendTo($table); - $('
').text(obj.metadata.name).appendTo($tr2); - $('').text(prettySize(estimate)).appendTo($tr2); - $('', {'class': 'upProgress'}).append($progressBar).append($progressValue).appendTo($tr2); - $('', {'class': 'upCancel'}).append($cancel).appendTo($tr2); + $('').text(obj.metadata.name).appendTo($tr); + $('').text(prettySize(estimate)).appendTo($tr); + $('', {'class': 'upProgress'}).append($progressBar).append($progressValue).appendTo($tr); + $('', {'class': 'upCancel'}).append($cancel).appendTo($tr); queue.next(); }; From c45f08f14755ba3ec79e4c87c129f1d5c001b746 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 May 2017 11:43:45 +0200 Subject: [PATCH 274/306] Use a download button instead of downloading a file automatically --- customize.dist/main.css | 8 ++++ customize.dist/translations/messages.fr.js | 1 + customize.dist/translations/messages.js | 1 + www/file/file.css | 9 ++-- www/file/file.less | 23 +++++----- www/file/inner.html | 9 +++- www/file/main.js | 53 ++++++++++++---------- 7 files changed, 64 insertions(+), 40 deletions(-) diff --git a/customize.dist/main.css b/customize.dist/main.css index 6ec0172ea..063324504 100644 --- a/customize.dist/main.css +++ b/customize.dist/main.css @@ -603,6 +603,14 @@ html.cp, font-family: lato, Helvetica, sans-serif; font-size: 1.02em; } +.cp .unselectable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} .cp h1, .cp h2, .cp h3, diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index a475dba00..6499258e0 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -366,6 +366,7 @@ define(function () { out.upload_name = "Nom du fichier"; out.upload_size = "Taille"; out.upload_progress = "État"; + out.download_button = "Déchiffrer et télécharger"; // 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..."; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index fd99ba7b7..737b271c9 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -371,6 +371,7 @@ define(function () { out.upload_name = "File name"; out.upload_size = "Size"; out.upload_progress = "Progress"; + out.download_button = "Decrypt & Download"; // general warnings out.warn_notPinned = "This pad is not in anyone's CryptDrive. It will expire after 3 months. Learn more..."; diff --git a/www/file/file.css b/www/file/file.css index f2fd8e6d5..6554a77f0 100644 --- a/www/file/file.css +++ b/www/file/file.css @@ -7,7 +7,8 @@ body { padding: 0px; display: inline-block; } -#file { +#file, +#dl { display: block; height: 100%; width: 100%; @@ -21,7 +22,8 @@ body { position: absolute; z-index: -1; } -#upload-form { +#upload-form, +#download-form { padding: 0px; margin: 0px; position: relative; @@ -31,7 +33,8 @@ body { margin: 50px auto; max-width: 80vw; } -#upload-form label { +#upload-form label, +#download-form label { line-height: 50vh; text-align: center; position: relative; diff --git a/www/file/file.less b/www/file/file.less index db6942596..a7da1bb3e 100644 --- a/www/file/file.less +++ b/www/file/file.less @@ -9,7 +9,7 @@ html, body { padding: 0px; display: inline-block; } -#file { +#file, #dl { display: block; height: 100%; width: 100%; @@ -25,7 +25,7 @@ html, body { z-index: -1; } -#upload-form { +#upload-form, #download-form { padding: 0px; margin: 0px; @@ -35,17 +35,16 @@ html, body { display: block; margin: 50px auto; max-width: 80vw; + label { + line-height: 50vh; + text-align: center; + position: relative; + padding: 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } } -#upload-form label { - line-height: 50vh; - text-align: center; - position: relative; - padding: 10px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - .hovering { background-color: rgba(255, 0, 115, 0.5) !important; } diff --git a/www/file/inner.html b/www/file/inner.html index 548ac6a47..a9660b2d3 100644 --- a/www/file/inner.html +++ b/www/file/inner.html @@ -13,9 +13,14 @@ - + +
diff --git a/www/file/main.js b/www/file/main.js index 56b46b1dd..3c25ec070 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -21,8 +21,9 @@ define([ var ifrw = $('#pad-iframe')[0].contentWindow; var $iframe = $('#pad-iframe').contents(); var $form = $iframe.find('#upload-form'); - //var $progress = $form.find('#progress'); + var $dlform = $iframe.find('#download-form'); var $label = $form.find('label'); + var $dllabel = $dlform.find('label'); var $table = $iframe.find('#status'); Cryptpad.addLoadingScreen(); @@ -100,8 +101,6 @@ define([ var b64Key = Nacl.util.encodeBase64(key); Cryptpad.replaceHash(Cryptpad.getFileHashFromKeys(id, b64Key)); - //$form.hide(); - APP.toolbar.addElement(['fileshare'], {}); var title = document.title = metadata.name; @@ -250,30 +249,38 @@ define([ Title.updateTitle(Cryptpad.initialName || getTitle() || Title.defaultTitle); if (!uploadMode) { - var src = Cryptpad.getBlobPathFromHex(hexFileName); - return Cryptpad.fetch(src, function (e, u8) { - if (e) { return void Cryptpad.alert(e); } - // now decrypt the u8 - var cryptKey = secret.keys && secret.keys.fileKeyStr; - var key = Nacl.util.decodeBase64(cryptKey); - - if (!u8 || !u8.length) { - return void Cryptpad.errorLoadingScreen(e); - } + $dlform.show(); + Cryptpad.removeLoadingScreen(); + $dlform.find('#dl').click(function (e) { + + if (myFile) { exportFile(); } - FileCrypto.decrypt(u8, key, function (e, data) { - if (e) { - Cryptpad.removeLoadingScreen(); - return console.error(e); + var src = Cryptpad.getBlobPathFromHex(hexFileName); + return Cryptpad.fetch(src, function (e, u8) { + if (e) { return void Cryptpad.alert(e); } + // now decrypt the u8 + var cryptKey = secret.keys && secret.keys.fileKeyStr; + var key = Nacl.util.decodeBase64(cryptKey); + + if (!u8 || !u8.length) { + return void Cryptpad.errorLoadingScreen(e); } - console.log(data); - var title = document.title = data.metadata.name; - myFile = data.content; - myDataType = data.metadata.type; - Title.updateTitle(title || Title.defaultTitle); - Cryptpad.removeLoadingScreen(); + + FileCrypto.decrypt(u8, key, function (e, data) { + if (e) { + return console.error(e); + } + console.log(data); + var title = document.title = data.metadata.name; + myFile = data.content; + myDataType = data.metadata.type; + Title.updateTitle(title || Title.defaultTitle); + exportFile(); + }); }); + }); + return; } if (!Cryptpad.isLoggedIn()) { From 4ee42b87d47ce1d4189ea88e54749d15681bfa6f Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 May 2017 12:04:47 +0200 Subject: [PATCH 275/306] make file crypto great again --- www/file/file-crypto.js | 58 +++++++++++++++++++++++++---------------- www/file/main.js | 2 ++ 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/www/file/file-crypto.js b/www/file/file-crypto.js index cde49759c..c7bd74ea3 100644 --- a/www/file/file-crypto.js +++ b/www/file/file-crypto.js @@ -58,14 +58,14 @@ define([ }; var joinChunks = function (chunks) { - return new Uint8Array(chunks.reduce(function (A, B) { - return slice(A).concat(slice(B)); - }, [])); + return new Blob(chunks); }; - var decrypt = function (u8, key, cb) { - var fail = function (e) { - cb(e || "DECRYPTION_ERROR"); + var decrypt = function (u8, key, done, progress) { + var MAX = u8.length; + var _progress = function (offset) { + if (typeof(progress) !== 'function') { return; } + progress(Math.min(1, offset / MAX)); }; var nonce = createNonce(); @@ -86,16 +86,18 @@ define([ try { res.metadata = JSON.parse(Nacl.util.encodeUTF8(metaChunk)); } catch (e) { - return fail('E_METADATA_DECRYPTION'); + return window.setTimeout(function () { + done('E_METADATA_DECRYPTION'); + }); } if (!res.metadata) { return void setTimeout(function () { - cb('NO_METADATA'); + done('NO_METADATA'); }); } - var takeChunk = function () { + var takeChunk = function (cb) { var start = i * cypherChunkLength + 2 + metadataLength; var end = start + cypherChunkLength; i++; @@ -104,24 +106,36 @@ define([ // decrypt the chunk var plaintext = Nacl.secretbox.open(box, nonce, key); increment(nonce); - return plaintext; + + if (!plaintext) { return cb('DECRYPTION_ERROR'); } + + _progress(end); + cb(void 0, plaintext); }; var chunks = []; - // decrypt file contents - var chunk; - for (;i * cypherChunkLength < u8.length;) { - chunk = takeChunk(); - if (!chunk) { - return window.setTimeout(fail); - } - chunks.push(chunk); - } - // send chunks - res.content = joinChunks(chunks); + var again = function () { + takeChunk(function (e, plaintext) { + if (e) { + return setTimeout(function () { + done(e); + }); + } + if (plaintext) { + if (i * cypherChunkLength < u8.length) { // not done + chunks.push(plaintext); + return setTimeout(again); + } + chunks.push(plaintext); + res.content = joinChunks(chunks); + return done(void 0, res); + } + done('UNEXPECTED_ENDING'); + }); + }; - cb(void 0, res); + again(); }; // metadata diff --git a/www/file/main.js b/www/file/main.js index ada0b50d2..0ce6ecefa 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -273,6 +273,8 @@ define([ myDataType = data.metadata.type; Title.updateTitle(title || Title.defaultTitle); Cryptpad.removeLoadingScreen(); + }, function (progress) { + console.error(progress); }); }); } From 6e7fe879745e4c63dbc9f2eda54d0350bb50a09e Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 May 2017 12:06:29 +0200 Subject: [PATCH 276/306] WIP debug file upload errors. use better error strings --- rpc.js | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/rpc.js b/rpc.js index a155c8272..b0c5dbb12 100644 --- a/rpc.js +++ b/rpc.js @@ -303,7 +303,7 @@ var getFileSize = function (Env, channel, cb) { var getMultipleFileSize = function (Env, channels, cb) { var msgStore = Env.msgStore; - if (!Array.isArray(channels)) { return cb('INVALID_LIST'); } + if (!Array.isArray(channels)) { return cb('INVALID_PIN_LIST'); } if (typeof(msgStore.getChannelSize) !== 'function') { return cb('GET_CHANNEL_SIZE_UNSUPPORTED'); } @@ -331,10 +331,8 @@ var getMultipleFileSize = function (Env, channels, cb) { var getTotalSize = function (Env, publicKey, cb) { var bytes = 0; - //var msgStore = Env.msgStore; - return void getChannelList(Env, publicKey, function (channels) { - if (!channels) { return cb('NO_ARRAY'); } // unexpected + if (!channels) { return cb('INVALID_PIN_LIST'); } // unexpected var count = channels.length; if (!count) { cb(void 0, 0); } @@ -464,7 +462,7 @@ var sumChannelSizes = function (sizes) { var pinChannel = function (Env, publicKey, channels, cb) { if (!channels && channels.filter) { - return void cb('[TYPE_ERROR] pin expects channel list argument'); + return void cb('INVALID_PIN_LIST'); } // get channel list ensures your session has a cached channel list @@ -508,7 +506,7 @@ var unpinChannel = function (Env, publicKey, channels, cb) { var pinStore = Env.pinStore; if (!channels && channels.filter) { // expected array - return void cb('[TYPE_ERROR] unpin expects channel list argument'); + return void cb('INVALID_PIN_LIST'); } getChannelList(Env, publicKey, function (pinned) { @@ -700,7 +698,9 @@ var upload_complete = function (Env, publicKey, cb) { safeMkdir(Path.join(paths.blob, prefix), function (e) { if (e) { + console.error('[safeMkdir]'); console.error(e); + console.log(); return void cb('RENAME_ERR'); } isFile(newPath, function (e, yes) { @@ -717,20 +717,39 @@ var upload_complete = function (Env, publicKey, cb) { }); }; - tryRandomLocation(function (e, newPath, id) { + var retries = 3; + + var handleMove = function (e, newPath, id) { + if (e) { + if (retries--) { + setTimeout(function () { + return tryRandomLocation(handleMove); + }, 750); + } + } + + // lol wut handle ur errors Fs.rename(oldPath, newPath, function (e) { if (e) { console.error(e); + + if (retries--) { + return setTimeout(function () { + tryRandomLocation(handleMove); + }, 750); + } + return cb(e); } cb(void 0, id); }); - }); + }; + + tryRandomLocation(handleMove); }; var upload_status = function (Env, publicKey, filesize, cb) { var paths = Env.paths; - //var msgStore = Env.msgStore; // validate that the provided size is actually a positive number if (typeof(filesize) !== 'number' && From dd73ad2dd5096bc37bd825c11384ee43bb452119 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 May 2017 12:26:44 +0200 Subject: [PATCH 277/306] add decryptMetadata function --- www/file/main.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/www/file/main.js b/www/file/main.js index 7fb0d0e88..66c749b89 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -257,6 +257,8 @@ define([ var src = Cryptpad.getBlobPathFromHex(hexFileName); return Cryptpad.fetch(src, function (e, u8) { if (e) { return void Cryptpad.alert(e); } + + // now decrypt the u8 var cryptKey = secret.keys && secret.keys.fileKeyStr; var key = Nacl.util.decodeBase64(cryptKey); @@ -265,6 +267,7 @@ define([ return void Cryptpad.errorLoadingScreen(e); } + return console.error(FileCrypto.decryptMetadata(u8, key)); FileCrypto.decrypt(u8, key, function (e, data) { if (e) { return console.error(e); From 3964f4feee598e38f02b7bb1a95b92bde219ace8 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 May 2017 12:30:00 +0200 Subject: [PATCH 278/306] Display the progress when downloading a file --- www/file/file.css | 8 +++++--- www/file/file.less | 12 ++++++++---- www/file/inner.html | 1 + www/file/main.js | 3 +++ 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/www/file/file.css b/www/file/file.css index 6554a77f0..d4248fa80 100644 --- a/www/file/file.css +++ b/www/file/file.css @@ -35,13 +35,15 @@ body { } #upload-form label, #download-form label { - line-height: 50vh; + line-height: calc(50vh - 20px); text-align: center; position: relative; padding: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + height: 50vh; + box-sizing: border-box; } .hovering { background-color: rgba(255, 0, 115, 0.5) !important; @@ -63,8 +65,8 @@ body { } #progress { position: absolute; - top: 0px; - left: 0px; + top: 0; + left: 0; height: 100%; transition: width 500ms; width: 0%; diff --git a/www/file/file.less b/www/file/file.less index a7da1bb3e..ebf800584 100644 --- a/www/file/file.less +++ b/www/file/file.less @@ -1,6 +1,8 @@ @import "../../customize.dist/src/less/variables.less"; @import "../../customize.dist/src/less/mixins.less"; +@button-border: 2px; + html, body { margin: 0px; } @@ -13,7 +15,7 @@ html, body { display: block; height: 100%; width: 100%; - border: 2px solid black; + border: @button-border solid black; } .inputfile { @@ -36,13 +38,15 @@ html, body { margin: 50px auto; max-width: 80vw; label { - line-height: 50vh; + line-height: ~"calc(50vh - 20px)"; text-align: center; position: relative; padding: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + height: 50vh; + box-sizing: border-box; } } .hovering { @@ -68,8 +72,8 @@ html, body { #progress { position: absolute; - top: 0px; - left: 0px; + top: 0; + left: 0; height: 100%; diff --git a/www/file/inner.html b/www/file/inner.html index a9660b2d3..c56aa80bf 100644 --- a/www/file/inner.html +++ b/www/file/inner.html @@ -19,6 +19,7 @@ +   diff --git a/www/file/main.js b/www/file/main.js index 7fb0d0e88..a80e571c9 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -25,6 +25,7 @@ define([ var $label = $form.find('label'); var $dllabel = $dlform.find('label'); var $table = $iframe.find('#status'); + var $progress = $iframe.find('#progress'); Cryptpad.addLoadingScreen(); @@ -276,6 +277,8 @@ define([ Title.updateTitle(title || Title.defaultTitle); exportFile(); }, function (progress) { + var p = progress * 100 +'%'; + $progress.width(p); console.error(progress); }); }); From a99080fb74ffd875d9d975ef08d45add1ae0112a Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 May 2017 12:31:00 +0200 Subject: [PATCH 279/306] push the rest of the code --- www/file/file-crypto.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/www/file/file-crypto.js b/www/file/file-crypto.js index c7bd74ea3..a36a3fc19 100644 --- a/www/file/file-crypto.js +++ b/www/file/file-crypto.js @@ -61,6 +61,19 @@ define([ return new Blob(chunks); }; + var decryptMetadata = function (u8, key) { + var prefix = u8.subarray(0, 2); + var metadataLength = decodePrefix(prefix); + + var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength)); + var metaChunk = Nacl.secretbox.open(metaBox, createNonce(), key); + + try { + return JSON.parse(Nacl.util.encodeUTF8(metaChunk)); + } + catch (e) { return null; } + }; + var decrypt = function (u8, key, done, progress) { var MAX = u8.length; var _progress = function (offset) { @@ -198,5 +211,6 @@ define([ encrypt: encrypt, joinChunks: joinChunks, computeEncryptedSize: computeEncryptedSize, + decryptMetadata: decryptMetadata, }; }); From 0b6c587e2bce9739f38e9d0e7a8ce95fcd24505c Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 May 2017 14:37:38 +0200 Subject: [PATCH 280/306] fetch and decrypt metadata with range requests --- www/file/file-crypto.js | 46 +++++++++++++++++++++++++++++++++++++++++ www/file/main.js | 12 ++++++----- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/www/file/file-crypto.js b/www/file/file-crypto.js index a36a3fc19..61e11ecdc 100644 --- a/www/file/file-crypto.js +++ b/www/file/file-crypto.js @@ -61,6 +61,43 @@ define([ return new Blob(chunks); }; + var concatBuffer = function (a, b) { // TODO make this not so ugly + return new Uint8Array(slice(a).concat(slice(b))); + }; + + var fetchMetadata = function (src, cb) { + var done = false; + var CB = function (err, res) { + if (done) { return; } + done = true; + cb(err, res); + }; + + var xhr = new XMLHttpRequest(); + xhr.open("GET", src, true); + xhr.setRequestHeader('Range', 'bytes=0-1'); + xhr.responseType = 'arraybuffer'; + + xhr.onload = function () { + if (/^4/.test('' + this.status)) { return CB('XHR_ERROR'); } + var res = new Uint8Array(xhr.response); + var size = decodePrefix(res); + var xhr2 = new XMLHttpRequest(); + + xhr2.open("GET", src, true); + xhr2.setRequestHeader('Range', 'bytes=2-' + (size + 2)); + xhr2.responseType = 'arraybuffer'; + xhr2.onload = function () { + if (/^4/.test('' + this.status)) { return CB('XHR_ERROR'); } + var res2 = new Uint8Array(xhr2.response); + var all = concatBuffer(res, res2); + CB(void 0, all); + }; + xhr2.send(null); + }; + xhr.send(null); + }; + var decryptMetadata = function (u8, key) { var prefix = u8.subarray(0, 2); var metadataLength = decodePrefix(prefix); @@ -74,6 +111,13 @@ define([ catch (e) { return null; } }; + var fetchDecryptedMetadata = function (src, key, cb) { + fetchMetadata(src, function (e, buffer) { + if (e) { return cb(e); } + cb(void 0, decryptMetadata(buffer, key)); + }); + }; + var decrypt = function (u8, key, done, progress) { var MAX = u8.length; var _progress = function (offset) { @@ -212,5 +256,7 @@ define([ joinChunks: joinChunks, computeEncryptedSize: computeEncryptedSize, decryptMetadata: decryptMetadata, + fetchMetadata: fetchMetadata, + fetchDecryptedMetadata: fetchDecryptedMetadata, }; }); diff --git a/www/file/main.js b/www/file/main.js index 66c749b89..04bf80259 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -255,19 +255,21 @@ define([ if (myFile) { return void exportFile(); } var src = Cryptpad.getBlobPathFromHex(hexFileName); + var cryptKey = secret.keys && secret.keys.fileKeyStr; + var key = Nacl.util.decodeBase64(cryptKey); + +/* return FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) { + if (e) { return console.error(e); } + console.log(metadata); + });*/ return Cryptpad.fetch(src, function (e, u8) { if (e) { return void Cryptpad.alert(e); } - // now decrypt the u8 - var cryptKey = secret.keys && secret.keys.fileKeyStr; - var key = Nacl.util.decodeBase64(cryptKey); - if (!u8 || !u8.length) { return void Cryptpad.errorLoadingScreen(e); } - return console.error(FileCrypto.decryptMetadata(u8, key)); FileCrypto.decrypt(u8, key, function (e, data) { if (e) { return console.error(e); From 71dc0af6c76a4ab04040070a11e44fc41cb5611d Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 May 2017 14:37:57 +0200 Subject: [PATCH 281/306] better test for success error codes --- www/common/common-util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-util.js b/www/common/common-util.js index f04b85199..6fb2ad7bb 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -114,7 +114,7 @@ define([], function () { xhr.open("GET", src, true); xhr.responseType = "arraybuffer"; xhr.onload = function () { - if ([200, 304].indexOf(this.status) === -1) { + if (/^4/.test(''+this.status)) { return CB('XHR_ERROR'); } return void CB(void 0, new Uint8Array(xhr.response)); From 346c289204d1f2612764ed2589cf95bd6dbeab4a Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 May 2017 14:39:58 +0200 Subject: [PATCH 282/306] Fix lint errors --- www/file/main.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/www/file/main.js b/www/file/main.js index 7ceb40885..36f3aea7d 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -23,7 +23,6 @@ define([ var $form = $iframe.find('#upload-form'); var $dlform = $iframe.find('#download-form'); var $label = $form.find('label'); - var $dllabel = $dlform.find('label'); var $table = $iframe.find('#status'); var $progress = $iframe.find('#progress'); @@ -252,7 +251,7 @@ define([ if (!uploadMode) { $dlform.show(); Cryptpad.removeLoadingScreen(); - $dlform.find('#dl').click(function (e) { + $dlform.find('#dl').click(function () { if (myFile) { return void exportFile(); } var src = Cryptpad.getBlobPathFromHex(hexFileName); @@ -268,7 +267,7 @@ define([ return void Cryptpad.errorLoadingScreen(e); } - return console.error(FileCrypto.decryptMetadata(u8, key)); +// return console.error(FileCrypto.decryptMetadata(u8, key)); FileCrypto.decrypt(u8, key, function (e, data) { if (e) { return console.error(e); From a6cf761172dcae7c654f3cb70e9d7489b4a8ce2d Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 May 2017 14:45:14 +0200 Subject: [PATCH 283/306] don't store media tag pages in the drive --- www/media/main.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/www/media/main.js b/www/media/main.js index 4b8f45ecf..bd1764f9c 100644 --- a/www/media/main.js +++ b/www/media/main.js @@ -41,16 +41,9 @@ define([ }; var updateTitle = function (newTitle) { - Cryptpad.renamePad(newTitle, function (err, data) { - if (err) { - console.log("Couldn't set pad title"); - console.error(err); - return; - } - document.title = newTitle; - $bar.find('.' + Toolbar.constants.title).find('span.title').text(data); - $bar.find('.' + Toolbar.constants.title).find('input').val(data); - }); + document.title = newTitle; + $bar.find('.' + Toolbar.constants.title).find('span.title').text(data); + $bar.find('.' + Toolbar.constants.title).find('input').val(data); }; var suggestName = function () { From 6dc3c62ba46fed98386368509d0ce071ebc118b3 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 May 2017 15:12:39 +0200 Subject: [PATCH 284/306] check for src before trying to ajax --- www/file/file-crypto.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/www/file/file-crypto.js b/www/file/file-crypto.js index 61e11ecdc..49ced7553 100644 --- a/www/file/file-crypto.js +++ b/www/file/file-crypto.js @@ -112,6 +112,11 @@ define([ }; var fetchDecryptedMetadata = function (src, key, cb) { + if (typeof(src) !== 'string') { + return window.setTimeout(function () { + cb('NO_SOURCE'); + }); + } fetchMetadata(src, function (e, buffer) { if (e) { return cb(e); } cb(void 0, decryptMetadata(buffer, key)); From 40b99e7bbfbfd175a39c5714a861c32eb1f70ad3 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 May 2017 15:30:59 +0200 Subject: [PATCH 285/306] Get the file name before downloading the entire file --- www/file/file.css | 1 + www/file/file.less | 1 + www/file/inner.html | 2 +- www/file/main.js | 83 +++++++++++++++++++++++++-------------------- 4 files changed, 50 insertions(+), 37 deletions(-) diff --git a/www/file/file.css b/www/file/file.css index d4248fa80..f1e78d72a 100644 --- a/www/file/file.css +++ b/www/file/file.css @@ -1,6 +1,7 @@ html, body { margin: 0px; + height: 100%; } .cryptpad-toolbar { margin-bottom: 1px; diff --git a/www/file/file.less b/www/file/file.less index ebf800584..04407f8e5 100644 --- a/www/file/file.less +++ b/www/file/file.less @@ -5,6 +5,7 @@ html, body { margin: 0px; + height: 100%; } .cryptpad-toolbar { margin-bottom: 1px; diff --git a/www/file/inner.html b/www/file/inner.html index c56aa80bf..f5946c099 100644 --- a/www/file/inner.html +++ b/www/file/inner.html @@ -19,7 +19,7 @@ -   + diff --git a/www/file/main.js b/www/file/main.js index 0e8a39294..ff0d52ec4 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -26,6 +26,9 @@ define([ var $table = $iframe.find('#status'); var $progress = $iframe.find('#progress'); + $iframe.find('body').on('dragover', function (e) { e.preventDefault(); }); + $iframe.find('body').on('drop', function (e) { e.preventDefault(); }); + Cryptpad.addLoadingScreen(); var Title; @@ -211,13 +214,10 @@ define([ }; var exportFile = function () { - var suggestion = document.title; - Cryptpad.prompt(Messages.exportPrompt, - Cryptpad.fixFileName(suggestion), function (filename) { - if (!(typeof(filename) === 'string' && filename)) { return; } - var blob = new Blob([myFile], {type: myDataType}); - saveAs(blob, filename); - }); + var filename = Cryptpad.fixFileName(document.title); + if (!(typeof(filename) === 'string' && filename)) { return; } + var blob = new Blob([myFile], {type: myDataType}); + saveAs(blob, filename); }; Title = Cryptpad.createTitle({}, function(){}, Cryptpad); @@ -250,40 +250,50 @@ define([ if (!uploadMode) { $dlform.show(); - Cryptpad.removeLoadingScreen(); - $dlform.find('#dl').click(function () { - if (myFile) { return void exportFile(); } + var src = Cryptpad.getBlobPathFromHex(hexFileName); + var cryptKey = secret.keys && secret.keys.fileKeyStr; + var key = Nacl.util.decodeBase64(cryptKey); - var src = Cryptpad.getBlobPathFromHex(hexFileName); - var cryptKey = secret.keys && secret.keys.fileKeyStr; - var key = Nacl.util.decodeBase64(cryptKey); + FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) { + if (e) { return void console.error(e); } + var title = document.title = metadata.name; + Title.updateTitle(title || Title.defaultTitle); -/* return FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) { - if (e) { return console.error(e); } - console.log(metadata); - });*/ - return Cryptpad.fetch(src, function (e, u8) { - if (e) { return void Cryptpad.alert(e); } - - // now decrypt the u8 - if (!u8 || !u8.length) { - return void Cryptpad.errorLoadingScreen(e); - } + Cryptpad.removeLoadingScreen(); + var decrypting = false; + $dlform.find('#dl, #progress').click(function () { + if (decrypting) { return; } + if (myFile) { return void exportFile(); } + decrypting = true; - FileCrypto.decrypt(u8, key, function (e, data) { + return Cryptpad.fetch(src, function (e, u8) { if (e) { - return console.error(e); + decrypting = false; + return void Cryptpad.alert(e); + } + + // now decrypt the u8 + if (!u8 || !u8.length) { + return void Cryptpad.errorLoadingScreen(e); } - console.log(data); - var title = document.title = data.metadata.name; - myFile = data.content; - myDataType = data.metadata.type; - Title.updateTitle(title || Title.defaultTitle); - exportFile(); - }, function (progress) { - var p = progress * 100 +'%'; - $progress.width(p); - console.error(progress); + + FileCrypto.decrypt(u8, key, function (e, data) { + if (e) { + decrypting = false; + return console.error(e); + } + console.log(data); + var title = document.title = data.metadata.name; + myFile = data.content; + myDataType = data.metadata.type; + Title.updateTitle(title || Title.defaultTitle); + exportFile(); + decrypting = false; + }, function (progress) { + var p = progress * 100 +'%'; + $progress.width(p); + console.error(progress); + }); }); }); }); @@ -341,6 +351,7 @@ define([ e.stopPropagation(); }) .on('drop', function (e) { + e.stopPropagation(); var dropped = e.originalEvent.dataTransfer.files; counter = 0; $label.removeClass('hovering'); From ff8701b6ba753ea2647a0067b08b3804fa12ad91 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 May 2017 15:56:24 +0200 Subject: [PATCH 286/306] send note along with limit --- rpc.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc.js b/rpc.js index 3442a4c96..489f8b725 100644 --- a/rpc.js +++ b/rpc.js @@ -407,7 +407,7 @@ var updateLimits = function (config, publicKey, cb) { if (publicKey) { var limit = limits[publicKey]; l = limit && typeof limit.limit === "number" ? - [limit.limit, limit.plan] : [defaultLimit, '']; + [limit.limit, limit.plan, limit.note] : [defaultLimit, '', '']; } cb(void 0, l); } catch (e) { @@ -431,7 +431,7 @@ var getLimit = function (Env, publicKey, cb) { Env.defaultStorageLimit: DEFAULT_LIMIT; var toSend = limit && typeof(limit.limit) === "number"? - [limit.limit, limit.plan] : [defaultLimit, '']; + [limit.limit, limit.plan, limit.note] : [defaultLimit, '', '']; cb(void 0, toSend); }; From 259d34b7c8128e5500e6a3c2fd04e68685ad4c32 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 May 2017 16:34:22 +0200 Subject: [PATCH 287/306] jshint compliance --- www/media/main.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/www/media/main.js b/www/media/main.js index bd1764f9c..81820f8c0 100644 --- a/www/media/main.js +++ b/www/media/main.js @@ -41,9 +41,9 @@ define([ }; var updateTitle = function (newTitle) { - document.title = newTitle; - $bar.find('.' + Toolbar.constants.title).find('span.title').text(data); - $bar.find('.' + Toolbar.constants.title).find('input').val(data); + var title = document.title = newTitle; + $bar.find('.' + Toolbar.constants.title).find('span.title').text(title); + $bar.find('.' + Toolbar.constants.title).find('input').val(title); }; var suggestName = function () { From 6d38a5abc1523706a2b2b04380243f910a0c7794 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 May 2017 16:49:50 +0200 Subject: [PATCH 288/306] add new variables to example config --- config.example.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/config.example.js b/config.example.js index a91c384bd..fe3f2fb91 100644 --- a/config.example.js +++ b/config.example.js @@ -197,7 +197,7 @@ module.exports = { /* Setting this value to anything other than true will cause file upload * attempts to be rejected outright. */ - enableUploads: true, + enableUploads: false, /* If you have enabled file upload, you have the option of restricting it * to a list of users identified by their public keys. If this value is set @@ -209,7 +209,19 @@ module.exports = { * This is a temporary measure until a better quota system is in place. * registered users' public keys can be found on the settings page. */ - restrictUploads: true, + //restrictUploads: false, + + /* Default user storage limit (bytes) + * if you don't want to limit users, + * you can set this to the size of your hard disk + */ + defaultStorageLimit: 50 * 1024 * 1024, + + /* Max Upload Size (bytes) + * this sets the maximum size of any one file uploaded to the server. + * anything larger than this size will be rejected + */ + maxUploadSize: 20 * 1024 * 1024, /* clients can use the /settings/ app to opt out of usage feedback * which informs the server of things like how much each app is being From 2ca62776601b6da066b6d47c71a9145ae4d13824 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 May 2017 16:50:32 +0200 Subject: [PATCH 289/306] enable pin limits by default --- customize.dist/application_config.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/customize.dist/application_config.js b/customize.dist/application_config.js index 949c612ee..24ed2c740 100644 --- a/customize.dist/application_config.js +++ b/customize.dist/application_config.js @@ -37,8 +37,7 @@ define(function() { config.enableHistory = true; - //config.enablePinLimit = true; - //config.pinLimit = 1000; + config.enablePinLimit = true; /* user passwords are hashed with scrypt, and salted with their username. this value will be appended to the username, causing the resulting hash From d55b7e411cf92457d8b9b8747c3a3ec446c951b4 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 22 May 2017 17:55:10 +0200 Subject: [PATCH 290/306] Display an alert in the drive when the limit is reached --- customize.dist/translations/messages.fr.js | 9 ++++++--- customize.dist/translations/messages.js | 9 ++++++--- www/common/cryptpad-common.js | 15 +++++++++++---- www/common/toolbar2.js | 2 +- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 6499258e0..da69e49f2 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -73,8 +73,10 @@ define(function () { out.redLight = "Vous êtes déconnectés de la session"; out.pinLimitReached = "Vous avez atteint votre limite de stockage"; - out.pinLimitReachedAlert = "Vous avez atteint votre limite de stockage. Les nouveaux pads ne seront pas enregistrés dans votre CrypDrive.
" + - "Pour résoudre ce problème, vous pouvez soit supprimer des pads de votre CryptDrive (y compris la corbeille), soit vous abonner à une offre premium pour augmenter la limite maximale."; + out.updated_0_pinLimitReachedAlert = "Vous avez atteint votre limite de stockage. Les nouveaux pads ne seront pas enregistrés dans votre CryptDrive.
" + + 'Vous pouvez soit supprimer des pads de votre CryptDrive, soit vous abonner à une offre premium pour augmenter la limite maximale.'; + out.pinLimitReachedAlert = out.updated_0_pinLimitReachedAlert; + out.pinAboveLimitAlert = 'Depuis la dernière version, nous imposons désormais une limite de 50 Mo de stockage gratuit et vous utilisez actuellement {0}. You devriez soit supprimer certains pads ou soit vous abonner sur accounts.cryptpad.fr. Votre contribution nous aidera à améliorer CryptPad et à répandre le Zero Knowledge. Vous pouvez contacter le support pour tout problème ou question concernant ces changements.'; out.pinLimitNotPinned = "Vous avez atteint votre limite de stockage.
"+ "Ce pad n'est pas enregistré dans votre CryptDrive."; out.pinLimitDrive = out.pinLimitReached+ ".
" + @@ -238,7 +240,8 @@ define(function () { out.fm_info_root = "Créez ici autant de dossiers que vous le souhaitez pour trier vos fichiers."; out.fm_info_unsorted = 'Contient tous les pads que vous avez ouvert et qui ne sont pas triés dans "Documents" ou déplacés vers la "Corbeille".'; // "My Documents" should match with the "out.fm_rootName" key, and "Trash" with "out.fm_trashName" out.fm_info_template = "Contient tous les fichiers que vous avez sauvés en tant que modèle afin de les réutiliser lors de la création d'un nouveau pad."; - out.fm_info_trash = 'Les fichiers supprimés dans la corbeille sont également enlevés de "Tous les fichiers" et il est impossible de les récupérer depuis l\'explorateur de fichiers.'; // Same here for "All files" and "out.fm_filesDataName" + 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és, ces pads risquent donc d\'être supprimés (découvrez pourquoi). ' + 'Inscrivez-vous ou connectez-vous pour les maintenir en vie.'; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 737b271c9..186bb3684 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -75,8 +75,10 @@ define(function () { out.redLight = "You are disconnected from the session"; out.pinLimitReached = "You've reached your storage limit"; - out.pinLimitReachedAlert = "You've reached your storage limit. New pads won't be stored in your CryptDrive.
" + - "To fix this problem, you can either remove pads from your CryptDrive (including the trash) or subscribe to a premium offer to increase your limit."; + out.updated_0_pinLimitReachedAlert = "You've reached your storage limit. New pads won't be stored in your CryptDrive.
" + + 'You can either remove pads from your CryptDrive or subscribe to a premium offer to increase your limit.'; + out.pinLimitReachedAlert = out.updated_0_pinLimitReachedAlert; + out.pinAboveLimitAlert = 'As of this release, we are imposing a 50MB limit on free data storage and you are currently using {0}. You will need to either delete some pads or subscribe on accounts.cryptpad.fr. Your contribution will help us improve CryptPad and spread Zero Knowledge. Please contact support if you have any other questions.'; out.pinLimitNotPinned = "You've reached your storage limit.
"+ "This pad is not stored in your CryptDrive."; out.pinLimitDrive = "You've reached your storage limit.
" + @@ -240,7 +242,8 @@ define(function () { out.fm_info_root = "Create as many nested folders here as you want to sort your files."; out.fm_info_unsorted = 'Contains all the files you\'ve visited that are not yet sorted in "Documents" or moved to the "Trash".'; // "My Documents" should match with the "out.fm_rootName" key, and "Trash" with "out.fm_trashName" out.fm_info_template = 'Contains all the pads stored as templates and that you can re-use when you create a new pad.'; - out.fm_info_trash = 'Files deleted from the trash are also removed from "All files" and it is impossible to recover them from the file manager.'; // Same here for "All files" and "out.fm_filesDataName" + out.updated_0_fm_info_trash = 'Empty your trash to free space in your CryptDrive.'; + 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 these pads may be deleted (find out why). ' + 'Sign up or Log in to keep them alive.'; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 9bbebbed3..7a2264864 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -792,6 +792,7 @@ define([ }; var LIMIT_REFRESH_RATE = 30000; // milliseconds + var limitReachedDisplayed = false; common.createUsageBar = function (cb, alwaysDisplayUpgrade) { var todo = function (err, state, data) { var $container = $('', {'class':'limit-container'}); @@ -830,18 +831,24 @@ define([ var prettyLimit; if (unit === 'GB') { - prettyUsage = usage; //Messages._getKey('formattedGB', [usage]); + prettyUsage = Messages._getKey('formattedGB', [usage]); prettyLimit = Messages._getKey('formattedGB', [limit]); } else { - prettyUsage = usage; //Messages._getKey('formattedMB', [usage]); + prettyUsage = Messages._getKey('formattedMB', [usage]); prettyLimit = Messages._getKey('formattedMB', [limit]); } if (quota < 0.8) { $usage.addClass('normal'); } else if (quota < 1) { $usage.addClass('warning'); } - else { $usage.addClass('above'); } + else { + $usage.addClass('above'); + if (!limitReachedDisplayed) { + limitReachedDisplayed = true; + common.alert(Messages._getKey('pinAboveLimitAlert', [prettyUsage, encodeURIComponent(window.location.hostname)]), null, true); + } + } var $text = $('', {'class': 'usageText'}); - $text.text(prettyUsage + ' / ' + prettyLimit); + $text.text(usage + ' / ' + prettyLimit); $limit.append($usage).append($text); window.setTimeout(function () { common.isOverPinLimit(todo); diff --git a/www/common/toolbar2.js b/www/common/toolbar2.js index 49e2376e8..57d113a48 100644 --- a/www/common/toolbar2.js +++ b/www/common/toolbar2.js @@ -617,7 +617,7 @@ define([ if (e) { return void console.error("Unable to get the pinned usage"); } if (overLimit) { $limit.show().click(function () { - Cryptpad.alert(Messages.pinLimitReachedAlert, null, true); + Cryptpad.alert(Messages._getKey('pinLimitReachedAlert', [encodeURIComponent(window.location.hostname)]), null, true); }); } }; From 70cae0d2c3ec62496cff97d573f925ad6d236790 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 22 May 2017 18:43:06 +0200 Subject: [PATCH 291/306] safely access pin reset response --- www/common/pinpad.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/www/common/pinpad.js b/www/common/pinpad.js index b14a5bac0..dbd7c8b22 100644 --- a/www/common/pinpad.js +++ b/www/common/pinpad.js @@ -77,6 +77,13 @@ define([ return; } rpc.send('RESET', channels, function (e, response) { + if (e) { + return void cb(e); + } + if (!response.length) { + console.log(response); + return void cb('INVALID_RESPONSE'); + } cb(e, response[0]); }); }; From c3437382fb7b0683da3a68def7e4898fd513d15f Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 23 May 2017 11:22:32 +0200 Subject: [PATCH 292/306] Fix the release version in the what's new popup --- www/common/cryptpad-common.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 7a2264864..18ea8678e 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1325,14 +1325,16 @@ define([ var ver = arr[1]; if (!ver) { return; } var verArr = ver.split('.'); + verArr[2] = 0; if (verArr.length !== 3) { return; } var stored = localStorage[CRYPTPAD_VERSION] || '0.0.0'; var storedArr = stored.split('.'); + storedArr[2] = 0; var shouldUpdate = parseInt(verArr[0]) > parseInt(storedArr[0]) || (parseInt(verArr[0]) === parseInt(storedArr[0]) && parseInt(verArr[1]) > parseInt(storedArr[1])); if (!shouldUpdate) { return; } - common.alert(Messages._getKey('newVersion', [ver]), null, true); + common.alert(Messages._getKey('newVersion', [verArr.join('.')]), null, true); localStorage[CRYPTPAD_VERSION] = ver; }; From 91ba41b8e8a7f75eee8dc42773e65ebe1c673ebc Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 23 May 2017 11:46:59 +0200 Subject: [PATCH 293/306] hotfix for account ids --- rpc.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rpc.js b/rpc.js index 489f8b725..0257c498f 100644 --- a/rpc.js +++ b/rpc.js @@ -376,6 +376,11 @@ var updateLimits = function (config, publicKey, cb) { var defaultLimit = typeof(config.defaultStorageLimit) === 'number'? config.defaultStorageLimit: DEFAULT_LIMIT; + var userId; + if (publicKey) { + userId = unescapeKeyCharacters(publicKey); + } + var body = JSON.stringify({ domain: config.domain, subdomain: config.subdomain @@ -404,8 +409,8 @@ var updateLimits = function (config, publicKey, cb) { var json = JSON.parse(str); limits = json; var l; - if (publicKey) { - var limit = limits[publicKey]; + if (userId) { + var limit = limits[userId]; l = limit && typeof limit.limit === "number" ? [limit.limit, limit.plan, limit.note] : [defaultLimit, '', '']; } From 6ddfe0e901f7484d7f6a6b04ccb8407d9a3e170e Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 23 May 2017 12:14:31 +0200 Subject: [PATCH 294/306] Fix new lines in /pad userlist --- customize.dist/src/less/toolbar.less | 2 ++ customize.dist/toolbar.css | 2 ++ 2 files changed, 4 insertions(+) diff --git a/customize.dist/src/less/toolbar.less b/customize.dist/src/less/toolbar.less index c216e2835..df68bc8c4 100644 --- a/customize.dist/src/less/toolbar.less +++ b/customize.dist/src/less/toolbar.less @@ -414,6 +414,8 @@ margin-bottom: -1px; .cryptpad-dropdown-users { pre { + /* needed for ckeditor */ + white-space: pre; margin: 5px 0px; } } diff --git a/customize.dist/toolbar.css b/customize.dist/toolbar.css index 2b80dc69c..ba7d7ea35 100644 --- a/customize.dist/toolbar.css +++ b/customize.dist/toolbar.css @@ -489,6 +489,8 @@ margin-bottom: -1px; } .cryptpad-toolbar-leftside .cryptpad-dropdown-users pre { + /* needed for ckeditor */ + white-space: pre; margin: 5px 0px; } .cryptpad-toolbar-leftside button { From 0a646a9534538ec874b69784cfcb8404fee67d88 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 23 May 2017 12:35:49 +0200 Subject: [PATCH 295/306] remove unnecessary space --- customize.dist/translations/messages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 186bb3684..751cf3633 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -481,7 +481,7 @@ define(function () { '# CryptPad\'s Zero Knowledge collaborative code editor\n', '\n', '* What you type here is encrypted so only people who have the link can access it.\n', - '* You can choose the programming language to highlight and the UI color scheme in the upper right.' + '* You can choose the programming language to highlight and the UI color scheme in the upper right.' ].join(''); out.slideInitialState = [ From 964d0461549097155c66b86b767792cc1659e3ef Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 23 May 2017 14:50:28 +0200 Subject: [PATCH 296/306] Update a french translation key --- customize.dist/translations/messages.fr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index da69e49f2..576e0d403 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -58,7 +58,7 @@ define(function () { 'Découvrez les nouveautés de la dernière version :
'+ 'Notes de version pour CryptPad {0}'; - out.upgrade = "Améliorer"; + out.upgrade = "Augmenter votre limite"; out.upgradeTitle = "Améliorer votre compte pour augmenter la limite de stockage"; out.MB = "Mo"; out.GB = "Go"; From 6324d9bbb0026d922e6039d6fd477ffc379d2410 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 23 May 2017 14:54:40 +0200 Subject: [PATCH 297/306] feedback on template feature usage --- www/common/cryptpad-common.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 18ea8678e..0559c1ce9 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -445,6 +445,7 @@ define([ Crypt.put(p.hash, val, function () { common.findOKButton().click(); common.removeLoadingScreen(); + common.feedback('TEMPLATE_USED'); }); }); }).appendTo($p); @@ -923,6 +924,7 @@ define([ common.addTemplate(makePad(href, title)); whenRealtimeSyncs(getStore().getProxy().info.realtime, function () { common.alert(Messages.templateSaved); + common.feedback('TEMPLATE_CREATED'); }); }); }; From 6faca87e73f55d00dd3e16b58065e40e6ec1ec30 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 23 May 2017 15:54:04 +0200 Subject: [PATCH 298/306] set preview content in read only mode --- www/code/main.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/www/code/main.js b/www/code/main.js index 6c9fbd0e1..fa6ecf12b 100644 --- a/www/code/main.js +++ b/www/code/main.js @@ -304,7 +304,10 @@ define([ onLocal(); // push local state to avoid parse errors later. - if (readOnly) { return; } + if (readOnly) { + config.onRemote(); + return; + } UserList.getLastName(toolbar.$userNameButton, isNew); }; From e6c04ccb49f1ab0aa52265a5ea391c0af066966f Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 23 May 2017 16:11:07 +0200 Subject: [PATCH 299/306] bump server after paying for an account --- www/auth/main.js | 5 +++++ www/common/cryptpad-common.js | 8 ++++---- www/common/pinpad.js | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/www/auth/main.js b/www/auth/main.js index 032f406ba..488165911 100644 --- a/www/auth/main.js +++ b/www/auth/main.js @@ -42,6 +42,11 @@ define([ sig: sig }; } + } else if (data.cmd === 'UPDATE_LIMIT') { + return Cryptpad.updatePinLimit(function (e, limit, plan, note) { + ret.res = [limit, plan, note]; + srcWindow.postMessage(JSON.stringify(ret), domain); + }); } else { ret.error = "UNKNOWN_CMD"; } diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 0559c1ce9..730a7984f 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -744,17 +744,17 @@ define([ common.updatePinLimit = function (cb) { if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); } - rpc.updatePinLimits(function (e, limit, plan) { + rpc.updatePinLimits(function (e, limit, plan, note) { if (e) { return cb(e); } - cb(e, limit, plan); + cb(e, limit, plan, note); }); }; common.getPinLimit = function (cb) { if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); } - rpc.getLimit(function (e, limit, plan) { + rpc.getLimit(function (e, limit, plan, note) { if (e) { return cb(e); } - cb(void 0, limit, plan); + cb(void 0, limit, plan, note); }); }; diff --git a/www/common/pinpad.js b/www/common/pinpad.js index dbd7c8b22..ff1a37c44 100644 --- a/www/common/pinpad.js +++ b/www/common/pinpad.js @@ -133,7 +133,7 @@ define([ rpc.send('UPDATE_LIMITS', undefined, function (e, response) { if (e) { return void cb(e); } if (response && response.length && typeof(response[0]) === "number") { - cb (void 0, response[0], response[1]); + cb (void 0, response[0], response[1], response[2]); } else { cb('INVALID_RESPONSE'); } @@ -144,7 +144,7 @@ define([ rpc.send('GET_LIMIT', undefined, function (e, response) { if (e) { return void cb(e); } if (response && response.length && typeof(response[0]) === "number") { - cb (void 0, response[0], response[1]); + cb (void 0, response[0], response[1], response[2]); } else { cb('INVALID_RESPONSE'); } From 97cc4a6d54539b5d799cfd698f056b29b1b6f9b1 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 23 May 2017 18:03:35 +0200 Subject: [PATCH 300/306] add styling for tables in markdown preview --- www/code/inner.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/www/code/inner.html b/www/code/inner.html index 0ef693f1c..f27544238 100644 --- a/www/code/inner.html +++ b/www/code/inner.html @@ -86,6 +86,13 @@ max-width: 40vw; margin: auto; } + #preview table tr td, #preview table tr th { + border: 1px solid black; + padding: 15px; + } + #preview table tr th { + border: 3px solid black; + } From 83c0a6d37d348d80a4a3bfca781634c29e80c76a Mon Sep 17 00:00:00 2001 From: Caleb James DeLisle Date: Wed, 24 May 2017 14:37:13 +0200 Subject: [PATCH 301/306] Romanian translation --- customize.dist/translations/messages.ro.js | 706 +++++++++++---------- 1 file changed, 357 insertions(+), 349 deletions(-) diff --git a/customize.dist/translations/messages.ro.js b/customize.dist/translations/messages.ro.js index 4e2134c7c..162603908 100644 --- a/customize.dist/translations/messages.ro.js +++ b/customize.dist/translations/messages.ro.js @@ -1,357 +1,365 @@ define(function () { var out = {}; - /* - * - * ro - * - */ + out.main_title = "CryptPad: Zero Knowledge, Colaborare n timp real"; + out.main_slogan = "Puterea stă n cooperare - Colaborarea este cheia"; - out.main_title = ""; // "CryptPad: Zero Knowledge, Collaborative Real Time Editing" - out.main_slogan = ""; // "Unity is Strength - Collaboration is Key" - out.type = ""; // {"pad":"Rich text","code":"Code","poll":"Poll","slide":"Presentation","drive":"Drive","whiteboard":"Whiteboard","file":"File","media":"Media"} - out.button_newpad = ""; // "New Rich Text pad" - out.button_newcode = ""; // "New Code pad" - out.button_newpoll = ""; // "New Poll" - out.button_newslide = ""; // "New Presentation" - out.button_newwhiteboard = ""; // "New Whiteboard" - 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; // TODO: Key updated --> make sure the updated key "out.updated_0_common_connectionLost" exists and is translated before that one. - 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.wrongApp = ""; // "Unable to display the content of that realtime session in your browser. Please try to reload that page." - out.loading = ""; // "Loading..." - out.error = ""; // "Error" - out.saved = ""; // "Saved" - out.synced = ""; // "Everything is saved" - out.deleted = ""; // "Pad deleted from your CryptDrive" - out.disconnected = ""; // "Disconnected" - out.synchronizing = ""; // "Synchronizing" - out.reconnecting = ""; // "Reconnecting..." - out.lag = ""; // "Lag" - out.readonly = ""; // "Read only" - out.anonymous = ""; // "Anonymous" - out.yourself = ""; // "Yourself" - out.anonymousUsers = ""; // "anonymous editors" - out.anonymousUser = ""; // "anonymous editor" - out.users = ""; // "Users" - out.and = ""; // "And" - out.viewer = ""; // "viewer" - out.viewers = ""; // "viewers" - out.editor = ""; // "editor" - out.editors = ""; // "editors" - out.language = ""; // "Language" - out.upgrade = ""; // "Upgrade" - out.upgradeTitle = ""; // "Upgrade your account to increase the storage limit" - out.MB = ""; // "MB" - out.greenLight = ""; // "Everything is working fine" - out.orangeLight = ""; // "Your slow connection may impact your experience" - out.redLight = ""; // "You are disconnected from the session" - out.pinLimitReached = ""; // "You've reached your storage limit" - out.pinLimitReachedAlert = ""; // "You've reached your storage limit. New pads won't be stored in your CryptDrive.
To fix this problem, you can either remove pads from your CryptDrive (including the trash) or subscribe to a premium offer to increase your limit." - out.pinLimitNotPinned = ""; // "You've reached your storage limit.
This pad is not stored in your CryptDrive." - out.pinLimitDrive = ""; // "You've reached your storage limit.
You can't create new pads." - out.importButtonTitle = ""; // "Import a pad from a local file" - out.exportButtonTitle = ""; // "Export this pad to a local file" - out.exportPrompt = ""; // "What would you like to name your file?" - out.changeNamePrompt = ""; // "Change your name (leave empty to be anonymous): " - out.user_rename = ""; // "Change display name" - out.user_displayName = ""; // "Display name" - out.user_accountName = ""; // "Account name" - out.clickToEdit = ""; // "Click to edit" - out.forgetButtonTitle = ""; // "Move this pad to the trash" - out.forgetPrompt = ""; // "Clicking OK will move this pad to your trash. Are you sure?" - out.movedToTrash = ""; // "That pad has been moved to the trash.
Access my Drive" - out.shareButton = ""; // "Share" - out.shareSuccess = ""; // "Copied link to clipboard" - out.newButton = ""; // "New" - out.newButtonTitle = ""; // "Create a new pad" - out.saveTemplateButton = ""; // "Save as template" - out.saveTemplatePrompt = ""; // "Choose a title for the template" - out.templateSaved = ""; // "Template saved!" - out.selectTemplate = ""; // "Select a template or press escape" - 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.printButton = ""; // "Print (enter)" - out.printButtonTitle = ""; // "Print your slides or export them as a PDF file" - out.printOptions = ""; // "Layout options" - out.printSlideNumber = ""; // "Display the slide number" - out.printDate = ""; // "Display the date" - out.printTitle = ""; // "Display the pad title" - out.printCSS = ""; // "Custom style rules (CSS):" - out.printTransition = ""; // "Enable transition animations" - out.slideOptionsTitle = ""; // "Customize your slides" - out.slideOptionsButton = ""; // "Save (enter)" - out.editShare = ""; // "Editing link" - out.editShareTitle = ""; // "Copy the editing link to clipboard" - out.editOpen = ""; // "Open editing link in a new tab" - out.editOpenTitle = ""; // "Open this pad in editing mode in a new tab" - out.viewShare = ""; // "Read-only link" - 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.notifyJoined = ""; // "{0} has joined the collaborative session" - out.notifyRenamed = ""; // "{0} is now known as {1}" - out.notifyLeft = ""; // "{0} has left the collaborative session" - out.okButton = ""; // "OK (enter)" - out.cancel = ""; // "Cancel" - out.cancelButton = ""; // "Cancel (esc)" - out.historyButton = ""; // "Display the document history" - out.history_next = ""; // "Go to the next version" - out.history_prev = ""; // "Go to the previous version" - out.history_goTo = ""; // "Go to the selected version" - out.history_close = ""; // "Back" - out.history_closeTitle = ""; // "Close the history" - out.history_restore = ""; // "Restore" - out.history_restoreTitle = ""; // "Restore the selected version of the document" - out.history_restorePrompt = ""; // "Are you sure you want to replace the current version of the document by the displayed one?" - out.history_restoreDone = ""; // "Document restored" - out.history_version = ""; // "Version:" - out.poll_title = ""; // "Zero Knowledge Date Picker" - out.poll_subtitle = ""; // "Zero Knowledge, realtime scheduling" - out.poll_p_save = ""; // "Your settings are updated instantly, so you never need to save." - out.poll_p_encryption = ""; // "All your input is encrypted so only people who have the link can access it. Even the server cannot see what you change." - out.wizardLog = ""; // "Click the button in the top left to return to your poll" - out.wizardTitle = ""; // "Use the wizard to create your poll" - out.wizardConfirm = ""; // "Are you really ready to add these options to your poll?" - out.poll_publish_button = ""; // "Publish" - 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_closeWizardButton = ""; // "Close wizard" - out.poll_closeWizardButtonTitle = ""; // "Close wizard" - out.poll_wizardComputeButton = ""; // "Compute Options" - out.poll_wizardClearButton = ""; // "Clear Table" - out.poll_wizardDescription = ""; // "Automatically create a number of options by entering any number of dates and times segments" - out.poll_wizardAddDateButton = ""; // "+ Dates" - out.poll_wizardAddTimeButton = ""; // "+ Times" - out.poll_optionPlaceholder = ""; // "Option" - out.poll_userPlaceholder = ""; // "Your name" - out.poll_removeOption = ""; // "Are you sure you'd like to remove this option?" - 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.canvas_clear = ""; // "Clear" - out.canvas_delete = ""; // "Delete selection" - out.canvas_disable = ""; // "Disable draw" - out.canvas_enable = ""; // "Enable draw" - out.canvas_width = ""; // "Width" - out.canvas_opacity = ""; // "Opacity" - out.fm_rootName = ""; // "Documents" - out.fm_trashName = ""; // "Trash" - out.fm_unsortedName = ""; // "Unsorted files" - out.fm_filesDataName = ""; // "All files" - out.fm_templateName = ""; // "Templates" - out.fm_searchName = ""; // "Search" - out.fm_searchPlaceholder = ""; // "Search..." - out.fm_newButton = ""; // "New" - out.fm_newButtonTitle = ""; // "Create a new pad or folder" - out.fm_newFolder = ""; // "New folder" - out.fm_newFile = ""; // "New pad" - out.fm_folder = ""; // "Folder" - out.fm_folderName = ""; // "Folder name" - out.fm_numberOfFolders = ""; // "# of folders" - out.fm_numberOfFiles = ""; // "# of files" - out.fm_fileName = ""; // "File name" - out.fm_title = ""; // "Title" - out.fm_type = ""; // "Type" - out.fm_lastAccess = ""; // "Last access" - out.fm_creation = ""; // "Creation" - out.fm_forbidden = ""; // "Forbidden action" - out.fm_originalPath = ""; // "Original path" - 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_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_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." - out.fm_selectError = ""; // "Unable to select the targetted element. If the problem persist, try to reload the page." - out.fm_categoryError = ""; // "Unable to open the selected category, displaying root." - out.fm_info_root = ""; // "Create as many nested folders here as you want to sort your files." - out.fm_info_unsorted = ""; // "Contains all the files you've visited that are not yet sorted in \"Documents\" or moved to the \"Trash\"." - out.fm_info_template = ""; // "Contains all the pads stored as templates and that you can re-use when you create a new pad." - out.fm_info_trash = ""; // "Files deleted from the trash are also removed from \"All files\" and it is impossible to recover them from the file manager." - out.fm_info_allFiles = ""; // "Contains all the files from \"Documents\", \"Unsorted\" and \"Trash\". You can't move or remove files from here." - out.fm_info_login = ""; // "Log in" - out.fm_info_register = ""; // "Sign up" - out.fm_info_anonymous = ""; // "You are not logged in so these pads may be deleted (find out why). Sign up or Log in to keep them alive." - out.fm_alert_backupUrl = ""; // "Backup link for this drive.
It is highly recommended that you keep ip for yourself only.
You can use it to retrieve all your files in case your browser memory got erased.
Anybody with that link can edit or remove all the files in your file manager.
" - out.fm_alert_anonymous = ""; // "Hello there, you are currently using CryptPad anonymously, that's ok but your pads may be deleted after a period of inactivity. We have disabled advanced features of the drive for anonymous users because we want to be clear that it is not a safe place to store things. You can read more about why we are doing this and why you really should Sign up and Log in." - out.fm_backup_title = ""; // "Backup link" - out.fm_nameFile = ""; // "How would you like to name that file?" - 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_restore = ""; // "Restore" - out.fc_remove = ""; // "Delete permanently" - out.fc_empty = ""; // "Empty the trash" - out.fc_prop = ""; // "Properties" - out.fc_sizeInKilobytes = ""; // "Size in Kilobytes" - out.fo_moveUnsortedError = ""; // "You can't move a folder to the list of unsorted pads" - out.fo_existingNameError = ""; // "Name already used in that directory. Please choose another one." - out.fo_moveFolderToChildError = ""; // "You can't move a folder into one of its descendants" - out.fo_unableToRestore = ""; // "Unable to restore that file to its original location. You can try to move it to a new location." - out.fo_unavailableName = ""; // "A file or a folder with the same name already exist at the new location. Rename the element and try again." - out.login_login = ""; // "Log in" - out.login_makeAPad = ""; // "Create a pad anonymously" - out.login_nologin = ""; // "Browse local pads" - out.login_register = ""; // "Sign up" - out.logoutButton = ""; // "Log out" - out.settingsButton = ""; // "Settings" - out.login_username = ""; // "Username" - out.login_password = ""; // "Password" - out.login_confirm = ""; // "Confirm your password" - out.login_remember = ""; // "Remember me" - out.login_hashing = ""; // "Hashing your password, this might take some time." - out.login_hello = ""; // "Hello {0}," - out.login_helloNoName = ""; // "Hello," - out.login_accessDrive = ""; // "Access your drive" - out.login_orNoLogin = ""; // "or" - out.login_noSuchUser = ""; // "Invalid username or password. Try again, or sign up" - out.login_invalUser = ""; // "Username required" - out.login_invalPass = ""; // "Password required" - out.login_unhandledError = ""; // "An unexpected error occurred :(" - out.register_importRecent = ""; // "Import pad history (Recommended)" - out.register_acceptTerms = ""; // "I accept the terms of service" - out.register_passwordsDontMatch = ""; // "Passwords do not match!" - 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_header = ""; // "Welcome to CryptPad" - out.register_explanation = ""; // "

Lets go over a couple things first

  • Your password is your secret key which encrypts all of your pads. If you lose it there is no way we can recover your data.
  • You can import pads which were recently viewed in your browser so you have them in your account.
  • If you are using a shared computer, you need to log out when you are done, closing the tab is not enough.
" - out.register_writtenPassword = ""; // "I have written down my username and password, proceed" - out.register_cancel = ""; // "Go back" - out.register_warning = ""; // "Zero Knowledge means that we can't recover your data if you lose your password." - out.register_alreadyRegistered = ""; // "This user already exists, do you want to log in?" - out.settings_title = ""; // "Settings" - out.settings_save = ""; // "Save" - out.settings_backupTitle = ""; // "Backup or restore all your data" - out.settings_backup = ""; // "Backup" - out.settings_restore = ""; // "Restore" - out.settings_resetTitle = ""; // "Clean your drive" - out.settings_reset = ""; // "Remove all the files and folders from your CryptDrive" - out.settings_resetPrompt = ""; // "This action will remove all the pads from your drive.
Are you sure you want to continue?
Type “I love CryptPad” to confirm." - out.settings_resetDone = ""; // "Your drive is now empty!" - out.settings_resetError = ""; // "Incorrect verification text. Your CryptDrive has not been changed." - out.settings_resetTips = ""; // "Tips in CryptDrive" - out.settings_resetTipsButton = ""; // "Reset the available tips in CryptDrive" - out.settings_resetTipsDone = ""; // "All the tips are now visible again." - out.settings_importTitle = ""; // "Import this browser's recent pads in my 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?" - out.settings_importDone = ""; // "Import completed" - out.settings_userFeedbackHint1 = ""; // "CryptPad provides some very basic feedback to the server, to let us know how to improve your experience." - out.settings_userFeedbackHint2 = ""; // "Your pad's content will never be shared with the server." - out.settings_userFeedback = ""; // "Enable user feedback" - out.settings_anonymous = ""; // "You are not logged in. Settings here are specific to this browser." - out.settings_publicSigningKey = ""; // "Public Signing Key" - out.settings_usage = ""; // "Usage" - out.settings_usageTitle = ""; // "See the total size of your pinned pads in MB" - out.settings_pinningNotAvailable = ""; // "Pinned pads are only available to registered users." - out.settings_pinningError = ""; // "Something went wrong" - out.settings_usageAmount = ""; // "Your pinned pads occupy {0}MB" - out.settings_logoutEverywhereTitle = ""; // "Log out everywhere" - out.settings_logoutEverywhere = ""; // "Log out of all other web sessions" - out.settings_logoutEverywhereConfirm = ""; // "Are you sure? You will need to log in with all your devices." - 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.main_p2 = ""; // "This project uses the CKEditor Visual Editor, CodeMirror, and the ChainPad realtime engine." - out.main_howitworks_p1 = ""; // "CryptPad uses a variant of the Operational transformation algorithm which is able to find distributed consensus using a Nakamoto Blockchain, a construct popularized by Bitcoin. This way the algorithm can avoid the need for a central server to resolve Operational Transform Edit Conflicts and without the need for resolving conflicts, the server can be kept unaware of the content which is being edited on the pad." - out.main_about_p2 = ""; // "If you have any questions or comments, you can tweet us, open an issue on github, come say hi on irc (irc.freenode.net), or send us an email." - 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_howitworks = ""; // "How It Works" - out.main_zeroKnowledge = ""; // "Zero Knowledge" - out.main_zeroKnowledge_p = ""; // "You don't have to trust that we won't look at your pads, with CryptPad's revolutionary Zero Knowledge Technology we can't. Learn more about how we protect your Privacy and Security." - out.main_writeItDown = ""; // "Write it down" - out.main_writeItDown_p = ""; // "The greatest projects come from the smallest ideas. Take down the moments of inspiration and unexpected ideas because you never know which one might be a breakthrough." - out.main_share = ""; // "Share the link, share the pad" - out.main_share_p = ""; // "Grow your ideas together: conduct efficient meetings, collaborate on TODO lists and make quick presentations with all your friends and all your devices." - out.main_organize = ""; // "Get organized" - out.main_organize_p = ""; // "With CryptPad Drive, you can keep your sights on what's important. Folders allow you to keep track of your projects and have a global vision of where things are going." - out.tryIt = ""; // "Try it out!" - out.main_richText = ""; // "Rich Text editor" - out.main_richText_p = ""; // "Edit rich text pads collaboratively with our realtime Zero Knowledge CkEditor application." - out.main_code = ""; // "Code editor" - out.main_code_p = ""; // "Edit code from your software collaboratively with our realtime Zero Knowledge CodeMirror application." - out.main_slide = ""; // "Slide editor" - out.main_slide_p = ""; // "Create your presentations using the Markdown syntax, and display them in your browser." - out.main_poll = ""; // "Polls" - out.main_poll_p = ""; // "Plan your meeting or your event, or vote for the best solution regarding your problem." - out.main_drive = ""; // "CryptDrive" - out.footer_applications = ""; // "Applications" - out.footer_contact = ""; // "Contact" - out.footer_aboutUs = ""; // "About us" - out.about = ""; // "About" - out.privacy = ""; // "Privacy" - out.contact = ""; // "Contact" - out.terms = ""; // "ToS" - out.blog = ""; // "Blog" - out.policy_title = ""; // "CryptPad Privacy Policy" - out.policy_whatweknow = ""; // "What we know about you" - out.policy_whatweknow_p1 = ""; // "As an application that is hosted on the web, CryptPad has access to metadata exposed by the HTTP protocol. This includes your IP address, and various other HTTP headers that can be used to identify your particular browser. You can see what information your browser is sharing by visiting WhatIsMyBrowser.com." - out.policy_whatweknow_p2 = ""; // "We use Kibana, an open source analytics platform, to learn more about our users. Kibana tells us about how you found CryptPad, via direct entry, through a search engine, or via a referral from another web service like Reddit or Twitter." - out.policy_howweuse = ""; // "How we use what we learn" - out.policy_howweuse_p1 = ""; // "We use this information to make better decisions about promoting CryptPad, by evaluating which of our past efforts were successful. Information about your location lets us know whether we should consider providing better support for languages other than English." - out.policy_howweuse_p2 = ""; // "Information about your browser (whether it's a desktop or mobile operating system) helps us make decisions when prioritizing feature improvements. Our development team is small, and we try to make choices that will improve as many users' experience as possible." - out.policy_whatwetell = ""; // "What we tell others about you" - out.policy_whatwetell_p1 = ""; // "We do not furnish to third parties the information that we gather or that you provide to us unless we are legally required to do so." - out.policy_links = ""; // "Links to other sites" - out.policy_links_p1 = ""; // "This site contains links to other sites, including those produced by other organizations. We are not responsible for the privacy practices or the contents of any outside sites. As a general rule, links to outside sites are launched in a new browser window, to make clear that you are leaving CryptPad.fr." - out.policy_ads = ""; // "Advertisement" - out.policy_ads_p1 = ""; // "We do not display any online advertising, though we may link to the bodies which are financing our research." - out.policy_choices = ""; // "Choices you have" - out.policy_choices_open = ""; // "Our code is open source, so you always have the option of hosting your own instance of CryptPad." - 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." - out.tos_title = ""; // "CryptPad Terms of Service" - out.tos_legal = ""; // "Please don't be malicious, abusive, or do anything illegal." - out.tos_availability = ""; // "We hope you find this service useful, but availability or performance cannot be guaranteed. Please export your data regularly." - out.tos_e2ee = ""; // "CryptPad contents can be read or modified by anyone who can guess or otherwise obtain the pad's fragment identifier. We recommend that you use end-to-end-encrypted (e2ee) messaging technology to share links, and assume no liability in the event that such a link is leaked." - 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." - out.bottom_france = ""; // "Made with \"love\" in \"France\"" - out.bottom_support = ""; // "An \"XWiki Labs Project with the support of \"OpenPaaS-ng\"" - out.header_france = ""; // "With \"love\" from \"France\"/ by \"XWiki" - out.header_support = ""; // " \"OpenPaaS-ng\"" - out.header_logoTitle = ""; // "Go to the main page" - 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.

Go ahead, just start typing...

 

" - out.codeInitialState = ""; // "/*\n This is the CryptPad Zero Knowledge collaborative code editor.\n What you type here is encrypted so only people who have the link can access it.\n You can choose the programming language to highlight and the UI color scheme in the upper right.\n*/" - 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\n1. Write your slides content using markdown syntax\n - Learn more about markdown syntax [here](http://www.markdowntutorial.com/)\n2. Separate your slides with ---\n3. Click on the \"Play\" button to see the result - Your slides are updated in realtime" - out.driveReadmeTitle = ""; // "What is CryptDrive?" - out.readme_welcome = ""; // "Welcome to CryptPad !" - out.readme_p1 = ""; // "Welcome to CryptPad, this is where you can take note of things alone and with friends." - out.readme_p2 = ""; // "This pad will give you a quick walk through of how you can use CryptPad to take notes, keep them organized and work together on them." - out.readme_cat1 = ""; // "Get to know your CryptDrive" - out.readme_cat1_l1 = ""; // "Make a pad: In your CryptDrive, click {0} then {1} and you can make a pad." - out.readme_cat1_l2 = ""; // "Open Pads from your CryptDrive: double-click on a pad icon to open it." - out.readme_cat1_l3 = ""; // "Organize your pads: When you are logged in, every pad you access will be shown as in the {0} section of your drive." - out.readme_cat1_l3_l1 = ""; // "You can click and drag files into folders in the {0} section of your drive and make new folders." - out.readme_cat1_l3_l2 = ""; // "Remember to try right clicking on icons because there are often additional menus." - out.readme_cat1_l4 = ""; // "Put old pads in the trash: You can click and drag your pads into the {0} the same way you drag them into folders." - out.readme_cat2 = ""; // "Make pads like a pro" - out.edit = ""; // "edit" - out.view = ""; // "view" - out.readme_cat2_l1 = ""; // "The {0} button in your pad allows you to give access to collaborators to either {1} or to {2} the pad." - out.readme_cat2_l2 = ""; // "Change the title of the pad by clicking on the pencil" - out.readme_cat3 = ""; // "Discover CryptPad apps" - out.readme_cat3_l1 = ""; // "With CryptPad code editor, you can collaborate on code like Javascript and markdown like HTML and Markdown" - out.readme_cat3_l2 = ""; // "With CryptPad slide editor, you can make quick presentations using Markdown" - out.readme_cat3_l3 = ""; // "With CryptPoll you can take quick votes, especially for scheduling meetings which fit with everybody's calendar" - out.tips = ""; // {"lag":"The green icon in the upper right shows the quality of your internet connection to the CryptPad server.","shortcuts":"`ctrl+b`, `ctrl+i` and `ctrl+u` are quick shortcuts for bold, italic and underline.","indent":"In numbered and bulleted lists, you can use tab or shift+tab to quickly increase or decrease indentation.","title":"You can set the title of your pad by clicking the top center.","store":"Every time you visit a pad, if you're logged in it will be saved to your CryptDrive.","marker":"You can highlight text in a pad using the \"marker\" item in the styles dropdown menu."} - 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" + 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"; + out.button_newslide = "Prezentare Noua"; + out.button_newwhiteboard = "Fila Desen Noua"; + out.updated_0_common_connectionLost = "Conexiunea la server este pierdută
Până la revenirea conexiunii, vei fi n modul citire"; + 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.wrongApp = "Momentan nu putem arăta conținutul sesiunii n timp real n fereastra ta. Te rugăm rencarcă pagina."; + out.loading = "Încarcă..." + out.error = "Eroare"; + + out.saved = "Salvat"; + out.synced = "Totul a fost salvat"; + out.deleted = "Pad șters din CryptDrive-ul tău"; + out.disconnected = "Deconectat"; + out.synchronizing = "Se sincronizează"; + out.reconnecting = "Reconectare..."; + out.lag = "Decalaj"; + out.readonly = "Mod citire"; + out.anonymous = "Anonim"; + out.yourself = "Tu"; + out.anonymousUsers = "editori anonimi"; + out.anonymousUser = "editor anonim"; + out.users = "Utilizatori"; + out.and = "Și"; + out.viewer = "privitor"; + out.viewers = "privitori"; + out.editor = "editor"; + out.editors = "editori"; + out.language = "Limba"; + out.upgrade = "Actualizare"; + out.upgradeTitle = "Actualizează-ți contul pentru a mări limita de stocare"; + out.MB = "MB"; + out.greenLight = "Totul funcționează corespunzător"; + out.orangeLight = "Conexiunea lentă la internet ți poate afecta experiența"; + out.redLight = "Ai fost deconectat de la sesiune"; + out.pinLimitReached = "Ai atins limita de stocare"; + out.pinLimitReachedAlert = "Ai atins limita de stocare. Noile pad-uri nu vor mai fi stocate n CryptDrive.
Pentru a rezolva această problemă, poți să nlături pad-uri din CryptDrive-ul tău (incluzând gunoiul) sau să subscrii la un pachet premium pentru a ți extinde spațiul de stocare."; + out.pinLimitNotPinned = "Ai atins limita de stocare.
Acest pad nu va fi stocat n CryptDrive-ul tău."; + out.pinLimitDrive = "Ai atins limita de stocare.
Nu poți să creezi alte pad-uri."; + out.importButtonTitle = "Importă un pad dintr-un fișier local"; + out.exportButtonTitle = "Exportă pad-ul acesta către un fișier local"; + out.exportPrompt = "Cum ai vrea să ți denumești fișierul?"; + out.changeNamePrompt = "Schimbă-ți numele (lasă necompletat dacă vrei să fii anonim): "; + out.user_rename = "Schimbă numele afișat"; + out.user_displayName = "Nume afișat"; + out.user_accountName = "Nume cont"; + out.clickToEdit = "Click pentru editare"; + out.forgetButtonTitle = "Mută acest pad la gunoi"; + out.forgetPrompt = "Click-ul pe OK va muta acest pad la gunoi. Ești sigur?"; + out.movedToTrash = "Acest pad a fost mutat la gunoi.
Acesează-mi Drive-ul"; + out.shareButton = "Distribuie"; + out.shareSuccess = "Link copiat n clipboard"; + out.newButton = "Nou"; + out.newButtonTitle = "Crează un nou pad"; + out.saveTemplateButton = "Salvează ca șablon"; + out.saveTemplatePrompt = "Alege un titlu pentru șablon"; + 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)"; + out.printButtonTitle = "Printează-ți slide-urile sau exportă-le ca fișier PDF"; + out.printOptions = "Opțiuni schemă"; + out.printSlideNumber = "Afișează numărul slide-ului"; + out.printDate = "Afișează data"; + out.printTitle = "Afișează titlul pad-ului"; + out.printCSS = "Reguli de stil personalizate (CSS):"; + out.printTransition = "Permite tranziția animațiilor"; + out.slideOptionsTitle = "Personalizează-ți slide-urile"; + out.slideOptionsButton = "Salvează (enter)"; + out.editShare = "Editează link-ul"; + out.editShareTitle = "Copiază link-ul de editare n clipboard"; + out.editOpen = "Deschide link-ul de editare ntr-o nouă filă"; + out.editOpenTitle = "Deschide acest pad n modul de editare ntr-o nouă filă"; + out.viewShare = "Link n modul citire"; + out.viewShareTitle = "Copiază link-ul n modul de citire n clipboard"; + out.viewOpen = "Deschide link-ul n modul de citire ntr-o filă nouă"; + out.viewOpenTitle = "Deschide acest pad n modul de citire ntr-o nouă filă"; + out.notifyJoined = "{0} s-au alăturat sesiunii colaborative"; + out.notifyRenamed = "{0} e cunoscut ca {1}"; + out.notifyLeft = "{0} au părăsit sesiunea colaborativă"; + out.okButton = "OK (enter)"; + out.cancel = "Anulează"; + out.cancelButton = "Anulează (esc)"; + out.historyButton = "Afișează istoricul documentului"; + out.history_next = "Mergi la versiunea următoare"; + out.history_prev = "Mergi la versiunea trecută"; + out.history_goTo = "Mergi la sesiunea selectată"; + out.history_close = "Înapoi"; + out.history_closeTitle = "Închide istoricul"; + out.history_restore = "Restabilește"; + out.history_restoreTitle = "Restabilește versiunea selectată a documentului"; + out.history_restorePrompt = "Ești sigur că vrei să nlocuiești versiunea curentă a documentului cu cea afișată?"; + out.history_restoreDone = ""; "Document restabilit"; + out.history_version = "Versiune:"; + out.poll_title = "Zero Knowledge Selector Dată"; + out.poll_subtitle = "Zero Knowledge, realtime programare"; + out.poll_p_save = "Setările tale sunt actualizate instant, așa că tu nu trebuie să salvezi."; + out.poll_p_encryption = "Tot conținutul tău este criptat ca doar persoanele cărora tu le dai link-ul să aibă acces. Nici chiar serverul nu poate să vadă ce modifici."; + out.wizardLog = "Click pe butonul din dreapta sus pentru a te ntoarce la sondajul tău"; + out.wizardTitle = "Folosește wizard-ul pentru a crea sondajul tău"; + out.wizardConfirm = "Ești pregătit să adaugi aceste opțiuni la sondajul tău?"; + out.poll_publish_button = "Publică"; + out.poll_admin_button = "Admin"; + out.poll_create_user = "Adaugă un nou utilizator"; + out.poll_create_option = "Adaugă o nouă opțiune"; + out.poll_commit = "Comite"; + out.poll_closeWizardButton = "Închide wizard-ul"; + out.poll_closeWizardButtonTitle = "Închide wizard-ul"; + out.poll_wizardComputeButton = "Calculează Opțiunile"; + out.poll_wizardClearButton = "Curăță Tabelul"; + out.poll_wizardDescription = "Crează automat un număr de opțiuni introducând orice număr de zile sau intervale orare"; + + out.poll_wizardAddDateButton = "+ Zi"; + out.poll_wizardAddTimeButton = "+ Ore"; + out.poll_optionPlaceholder = "Opțiune"; + out.poll_userPlaceholder = "Numele tău"; + out.poll_removeOption = "Ești sigur că vrei să ndepărtezi această opțiune?"; + out.poll_removeUser = "Ești sigur că vrei să ndepărtezi aceast utilizator?"; + out.poll_titleHint = "Titlu"; + out.poll_descriptionHint = "Descrie sondajul, și apoi folosește butonul 'publică' când ai terminat. Orice utilizator care are link-ul poate modifica descrierea, dar descurajăm această practică."; + out.canvas_clear = "Curăță"; + out.canvas_delete = "Curăță selecția"; + out.canvas_disable = "Dezactivează modul desen"; + out.canvas_enable = "Activează modul desen"; + out.canvas_width = "Lățime"; + out.canvas_opacity = "Opacitate"; + out.fm_rootName = "Documente"; + out.fm_trashName = "Gunoi"; + out.fm_unsortedName = "Fișiere nesortate"; + out.fm_filesDataName = "Toate fișierele"; + out.fm_templateName = "Șabloane"; + out.fm_searchName = "Caută"; + out.fm_searchPlaceholder = "Caută..."; + out.fm_newButton = "Nou"; + out.fm_newButtonTitle = "Crează un nou pad sau folder"; + out.fm_newFolder = "Folder nou"; + out.fm_newFile = "Pad nou"; + out.fm_folder = "Folder"; + out.fm_folderName = "Numele folderului"; + out.fm_numberOfFolders = "# de foldere"; + out.fm_numberOfFiles = "# of files"; + out.fm_fileName = "File name"; + out.fm_title = "Titlu"; + out.fm_type = "Tip"; + out.fm_lastAccess = "Ultima accesare"; + out.fm_creation = "Creare"; + out.fm_forbidden = "Acțiune interzisă"; + out.fm_originalPath = "Ruta inițială"; + out.fm_openParent = "Arată n folder"; + out.fm_noname = "Document nedenumit"; + out.fm_emptyTrashDialog = "Ești sigur că vrei să golești coșul de gunoi?"; + out.fm_removeSeveralPermanentlyDialog = "Ești sigur că vrei să ștergi pentru totdeauna aceste {0} elements din coșul de gunoi?"; + out.fm_removePermanentlyDialog = "Ești sigur că vrei să ștergi acest element pentru totdeauna?"; + out.fm_removeSeveralDialog = "Ești sigur că vrei să muți aceste {0} elemente la coșul de gunoi?"; + out.fm_removeDialog = "Ești sigur că vrei să muți {0} la gunoi?"; + out.fm_restoreDialog = "Ești sigur că vrei să restabilești {0} n locația trecută?"; + out.fm_unknownFolderError = "Ultima locație vizitată sau cea selectată nu mai există. Deschidem fișierul părinte..."; + out.fm_contextMenuError = "Nu putem deschide meniul de context pentru acest element. Dacă problema persistă, rencarcă pagina."; + out.fm_selectError = "Nu putem selecta elementul vizat. Dacă Unable to select the targeted element. Dacă problema persistă, rencarcă pagina."; + out.fm_categoryError = "Nu putem deschide categoria selectată, afișează sursa."; + out.fm_info_root = "Crează câte foldere tip cuib ai nevoie pentru a ți sorta fișierele."; + out.fm_info_unsorted = "Conține toate fișierele pe care le-ai vizitat și nu sunt sortate n \"Documente\" sau mutate n \"Gunoi\"."; + out.fm_info_template = "Conține toate pad-urile stocate ca șabloane și pe care le poți refolosi atunci când creezi un nou pad."; + out.fm_info_trash = "Fișierele șterse din gunoi vor fi șterse și din \"Toate fișierele\", făcând imposibilă recuperarea fișierelor din managerul de fișiere."; + out.fm_info_allFiles = "Conține toate fișierele din \"Documente\", \"Nesortate\" și \"Gunoi\". Poți să muți sau să ștergi fișierele aici."; + out.fm_info_login = ""; "Loghează-te"; + out.fm_info_register = "Înscrie-te"; + out.fm_info_anonymous = "Nu ești logat cu un cont valid așa că aceste pad-uri vor fi șterse (află de ce). Înscrie-te sau Loghează-te pentru a le salva."; + out.fm_alert_backupUrl = "Link copie de rezervă pentru acest drive.
Este foarte recomandat să o păstrezi pentru tine.
Poți să o folosești pentru a recupera toate fișierele n cazul n care memoria browserului tău este șterge..
Oricine are linkul poate să editeze sau să ndepărteze toate fișierele din managerul tău de documente.
"; + out.fm_alert_anonymous = "Salut, momentan folosești CryptPad n mod anonim. Este ok, doar că fișierele tale vor fi șterse după o perioadă de inactivitate. Am dezactivat caracteristicile avansate ale drive-ului pentru utilizatorii anonimi pentru a face clar faptul că stocare documentelor acolo nu este o metodă sigură. Poți să citești mai multe despre motivarea noastră și despre ce de trebuie să te Înregistrezi and Loghezi."; + out.fm_backup_title = "Link de backup"; + out.fm_nameFile = "Cum ai vrea să numești fișierul?"; + out.fc_newfolder = "Folder nou"; + out.fc_rename = "Redenumește"; + out.fc_open = "Deschide"; + out.fc_open_ro = "Deschide (modul citire)"; + out.fc_delete = "Șterge"; + out.fc_restore = "Restaurează"; + out.fc_remove = "Șterge permanent"; + out.fc_empty = "Curăță coșul"; + out.fc_prop = "Proprietăți"; + out.fc_sizeInKilobytes = "Dimensiune n Kilobytes"; + out.fo_moveUnsortedError = "Nu poți să muți un folder la lista de pad-uri nesortate"; + out.fo_existingNameError = "Numele ales este deja folosit n acest director. Te rugăm să alegi altul."; + out.fo_moveFolderToChildError = "Nu poți să muți un folder ntr-unul dintre descendenții săi"; + out.fo_unableToRestore = "Nu am reușit să restaurăm fișierul n locația de origine. Poți să ncerci să l muți ntr-o nouă locație."; + out.fo_unavailableName = "Un fișier sau un folder cu același nume există deja n locația nouă. Redenumește elementul și ncearcă din nou."; + out.login_login = ""; "Loghează-te"; + out.login_makeAPad = "Crează un pad n modul anonim"; + out.login_nologin = "Răsfoiește pad-urile locale"; + out.login_register = "Înscrie-te"; + out.logoutButton = "Deloghează-te"; + out.settingsButton = "Setări"; + out.login_username = "Nume utilizator"; + out.login_password = "Parolă"; + out.login_confirm = "Confirmă parola"; + out.login_remember = "Ține-mă minte"; + out.login_hashing = "Încriptăm parola, o să mai dureze."; + out.login_hello = "Salut {0},"; + out.login_helloNoName = "Salut,"; + out.login_accessDrive = "Acesează-ți drive-ul"; + out.login_orNoLogin = "sau"; + out.login_noSuchUser = "Nume de utilizator sau parolă invalide. Încearcă din nou sau nscrie-te."; + out.login_invalUser = "Nume utilizator cerut"; + out.login_invalPass = "Parolă cerută"; + out.login_unhandledError = "O eroare neașteptată a avut loc emoticon_unhappy"; + out.register_importRecent = "Importă istoricul pad-ului (Recomandat)"; + out.register_acceptTerms = "Accept termenii serviciului"; + out.register_passwordsDontMatch = "Parolele nu se potrivesc!"; + out.register_mustAcceptTerms = "Trebuie să accepți termenii serviciului"; + out.register_mustRememberPass = "Nu putem să ți resetăm parola dacă o uiți. Este foarte important să o ții minte! Bifează căsuța pentru a confirma."; + out.register_header = "Bine ai venit n CryptPad"; + out.register_explanation = "

Hai să stabilim câteva lucruri, mai ntâi

  • Parola ta este cheia secretă care criptează toate pad-urile tale. Dacă pierzi/uiți parola nu există nici-o metodă prin care ți putem recupera datele.
  • Poți importa pad-uri care au fost vizionate recent n browser pentru a le avea n cont.
  • Dacă folosești un computer mpărțit, trebuie să te deloghezi, nchiderea taburilor nu este de ajuns.
"; + out.register_writtenPassword = "Mi-am notat numele de utilizator și parola, naintează."; + out.register_cancel = "Întoarce-te"; + out.register_warning = "Zero Knowledge nseamnă că noi nu ți putem recupera datele dacă ți pierzi parola."; + out.register_alreadyRegistered = "Acest user există deja, vrei să te loghezi?"; + out.settings_title = "Setări"; + out.settings_save = "Salvează"; + out.settings_backupTitle = "Fă o copie de rezervă sau restaurează toate datele"; + out.settings_backup = "Copie de rezervă"; + out.settings_restore = "Restaurează"; + out.settings_resetTitle = "Curăță-ți drive-ul"; + out.settings_reset = "Îndepărtează toate fișierele și folderele din CryptPad-ul tău."; + out.settings_resetPrompt = "Această acțiune o să ndepărteze toate pad-urile din drive-ul tău.
Ești sigur că vrei să continui?
Type “Iubesc CryptPad” pentru a confirma."; + out.settings_resetDone = "Drive-ul tău este acum gol!"; + out.settings_resetError = "Text de verificare incorrect. CryptPad-ul tău nu a fost schimbat."; + out.settings_resetTips = "Sfaturi n CryptDrive"; + out.settings_resetTipsButton = "Resetează sfaturile disponibile n CryptDrive"; + out.settings_resetTipsDone = "Toate sfaturile sunt vizibile din nou."; + out.settings_importTitle = "Importă pad-urile recente ale acestui browser n CryptDrive-ul meu"; + out.settings_import = "Importă"; + out.settings_importConfirm = "Ești sigur că vrei să imporți pad-urile recente ale acestui browser n contul tău de CryptDrive?"; + out.settings_importDone = "Import complet"; + out.settings_userFeedbackHint1 = "CryptPad oferă niște feedback foarte simplu serverului, pentru a ne informa cum putem să ți mbunătățim experiența voastră."; + out.settings_userFeedbackHint2 = "Conținutul pad-ului tău nu va fi mpărțit cu serverele."; + out.settings_userFeedback = "Activează feedback"; + out.settings_anonymous = "Nu ești logat. Setările sunt specifice browser-ului."; + out.settings_publicSigningKey = "Cheia de semnătură publică"; + out.settings_usage = "Uzaj"; + out.settings_usageTitle = "Vezi dimensiunea totală a pad-urilor fixate n MB"; + out.settings_pinningNotAvailable = "Pad-urile fixate sunt disponibile doar utilizatorilor nregistrați."; + out.settings_pinningError = "Ceva nu a funcționat"; + out.settings_usageAmount = "Pad-urile tale fixate ocupă {0}MB"; + out.settings_logoutEverywhereTitle = "Deloghează-te peste tot"; + out.settings_logoutEverywhere = "Deloghează-te din toate sesiunile web"; + out.settings_logoutEverywhereConfirm = "Ești sigur? Va trebui să te loghezi, din nou, pe toate device-urile tale."; + out.upload_serverError = "Eroare de server: fișierele tale nu pot fi ncărcate la momentul acesta."; + out.upload_uploadPending = "Ai deja o ncărcare n desfășurare. Anulezi si ncarci noul fișier?"; + out.upload_success = "Fișierul tău ({0}) a fost ncărcat și adăugat la drive-ul tău cu succes."; + out.main_p2 = "Acest proiect folosește CKEditor Visual Editor, CodeMirror, și ChainPad un motor n timp real."; + out.main_howitworks_p1 = "CryptPad folosește o variantă a algoritmului de Operational transformation care este capabil să găsescă consens distribuit folosind Nakamoto Blockchain, o construcție popularizată de Bitcoin. Astfel algoritmul poate evita nevoia ca serverul central să rezove conflicte, iar serverul nu este interesat de conținutul care este editat n pad."; + out.main_about_p2 = "Dacă ai orice fel de ntrebare sau comentariu, poți să ne dai un tweet, semnalezi o problemă on github, spui salut pe IRC (irc.freenode.net), sau trimiți un email."; + out.main_info = "

Colaborează n siguranță


Dezvoltă-ți ideile mpreună cu documente partajate n timp ce tehnologia Zero Knowledge ți păstrează securitatea; chiar și de noi."; + out.main_howitworks = "Cum funcționează"; + out.main_zeroKnowledge = "Zero Knowledge"; + out.main_zeroKnowledge_p = "Nu trebuie să ne crezi că nu ne uităm la pad-urile tale, cu tehnologia revoluționară Zero Knowledge a CryptPad nu putem. Învață mai multe despre cum ți protejăm Learn more about how we protect your Intimitate și Securitate."; + out.main_writeItDown = "Notează"; + out.main_writeItDown_p = "Cele mai importante proiecte vin din idei mici. Notează-ți momentele de inspirație și ideile neașteptate pentru că nu știi niciodată care ar putea fi noua mare descoperire."; + out.main_share = "Partajează link-ul, partajează pad-ul"; + out.main_share_p = "Dezvoltă-ți ideile mpreună: organizează ntâlniri eficiente, colaborează pe liste TODO și fă prezentări scurte cu toți prietenii tăi și device-urile tale."; + out.main_organize = "Organizează-te"; + out.main_organize_p = "Cu CryptPad Drive, poți să stai cu ochii pe ce este important. Folderele ți permit să ții evidența proiectelor tale și să ai o viziune globală asupra evoluției lucrurilor."; + out.tryIt = "Testează!"; + out.main_richText = "Rich Text editor"; + out.main_richText_p = "Editează texte complexe n mod colaborativ cu Zero Knowledge n timp real. CkEditor application."; + out.main_code = "Editor cod"; + out.main_code_p = "Editează cod din softul tău, n mod colaborativ, cu Zero Knowledge n timp real.CodeMirror application."; + out.main_slide = "Editor slide-uri"; + out.main_slide_p = "Crează-ți prezentări folosind sintaxa Markdown, și afișează-le n browser-ul tău."; + out.main_poll = "Sondaj"; + out.main_poll_p = "Plănuiește ntâlniri sau evenimente, sau votează pentru cea mai bună soluție pentru problema ta."; + out.main_drive = "CryptDrive"; + out.footer_applications = "Aplicații"; + out.footer_contact = "Contact"; + out.footer_aboutUs = "Despre noi"; + out.about = "Despre"; + out.privacy = "Privacy"; + out.contact = "Contact"; + out.terms = "ToS"; + out.blog = "Blog"; + out.policy_title = "Politica de confidențialitate CryptPad"; + out.policy_whatweknow = "Ce știm despre tine"; + out.policy_whatweknow_p1 = "Ca o aplicație care este găzduită online, CryptPad are acces la metadatele expuse de protocolul HTTP. Asta include adresa IP-ului tău, și alte titluri HTTP care pot fi folosite ca să identifice un browser. Poți să vezi ce informații mpărtășește browser-ul tău vizitând WhatIsMyBrowser.com."; + out.policy_whatweknow_p2 = "Folosim Kibana, o platformă open source, pentru a afla mai multe despre utilizatorii noștri. Kibana ne spune despre cum ai găsit CryptPad, căutare directă, printr-un motor de căutare, sau prin recomandare de la un alt serviciu online ca Reddit sau Twitter."; + out.policy_howweuse = "Cum folosim ce aflăm"; + out.policy_howweuse_p1 = "Folosim aceste informații pentru a lua decizii mai bune n promovarea CryptPad, prin evaluarea eforturilor trecute care au fost de succes. Informațiile despre locația ta ne ajută să aflăm dacă ar trebui să oferim suport pentru alte limbi, pe lângă engleză."; + out.policy_howweuse_p2 = ""; "Informațiile despre browser-ul tău (dacă este bazat pe un sistem de operare desktop sau mobil) ne ajută să luăm decizii când prioritizăm viitoare mbunătățiri. Echipa noastră de development este mică, și ncercăm să facem alegeri care să mbunătățească experiența câtor mai mulți utilizatori."; + + out.policy_whatwetell = "Ce le spunem altora despre tine"; + out.policy_whatwetell_p1 = "Nu furnizăm informațiile obținute terților, decât dacă ne este cerut n mod legal."; + out.policy_links = "Link-uri către alte site-uri"; + out.policy_links_p1 = "Acest site conține link-uri către alte site-uri, incluzându-le pe cele produse de alte organizații. Nu suntem responsabili pentru practicile de intimitate sau pentru conținutul site-urilor externe. Ca regulă generală, link-urile către site-uri externe sunt deschise ntr-o fereastră noup, pentru a face clar faptul că părăsiți CryptPad.fr."; + out.policy_ads = "Reclame"; + out.policy_ads_p1 = "Nu afișăm nici o formă de publicitate online, dar s-ar putea să atașăm link-uri către instituțiile care ne finanțează cerecetarea."; + out.policy_choices = "Ce alegeri ai"; + out.policy_choices_open = "Codul nostru este open source, așa că tu ai mereu posibilitatea de a-ți găzdui propria instanță de CryptPad."; + out.policy_choices_vpn = "Dacă vrei să folosești instanța găzduită de noi, dar nu vrei să ți expui IP-ul, poți să l protejezi folosind Tor browser bundle, sau VPN."; + out.policy_choices_ads = "Dacă vrei doar să blochezi platforma noastră de analiză, poți folosi soluții de adblocking ca Privacy Badger."; + out.tos_title = "CryptPad Termeni de Utilizare"; + out.tos_legal = "Te rugăm să nu fii rău intenționat, abuziv, sau să faci orice ilegal."; + out.tos_availability = "Sperăm că o să găsești acest serviciu util, dar disponibilitatea sau performanța nu poate fi garantată. Te rugăm să ți exporți datele n mod regulat."; + out.tos_e2ee = "Conținutul CryptPad poate fi citit sau modificat de oricine care poate ghici sau obține fragmentul identificator al pad-ului. Recomandăm să folosești soluții de comunicare criptate end-to-end-encrypted (e2ee) pentru a partaja link-uri, evitând orice risc n cazul unei scurgeri de informații."; + out.tos_logs = "Metadatele oferite de browser-ul tău serverului ar putea fi nscrise n scopul de a menține serviciul."; + out.tos_3rdparties = "Nu oferim date personale terților, decât dacă ne sunt solicitate prin lege."; + out.bottom_france = "Realizat cu \"love\" n \"Franța\""; + out.bottom_support = "Un proiect al \"XWiki Labs Project cu susținerea \"OpenPaaS-ng\""; + 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.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, lasă 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.driveReadmeTitle = "Ce este CryptDrive?"; + 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."; + out.readme_p2 = "Acest pad o să ți ofere un scurt ghid n cum poți să folosești CryptPad pentru a lua notițe, a le ține organizate și a colabora pe ele."; + out.readme_cat1 = "Descoperă-ți CryptDrive-ul"; + out.readme_cat1_l1 = "Crează un pad: În CryptDrive-ul tău, dă click {0} apoi {1} și poți să creezi un pad."; + out.readme_cat1_l2 = "Deschide pad-urile din CryptDrive-ul tău: doublu-click pe iconița unui pad pentru a-l deschide."; + out.readme_cat1_l3 = "Organizează-ți pad-urile: Când ești logat, orice pad accesezi va fi afișat ca n secțiunea {0} a drive-ului tău."; + out.readme_cat1_l3_l1 = "Poți să folosești funcția click and drag pentru a muta fișierele n folderele secțiunii {0} a drive-ului tău și pentru a crea noi foldere."; + out.readme_cat1_l3_l2 = ""; "Ține minte să ncerci click-dreapta pe iconițe pentru că există și meniuri adiționale."; + out.readme_cat1_l4 = "Pune pad-urile vechi n gunoi. Poți să folosești funcția click and drag pe pad-uri n categoria {0} la fel ca și n cazul folderelor."; + out.readme_cat2 = ""; "Crează pad-uri ca un profesionist"; + out.edit = "editează"; + out.view = "vezi"; + out.readme_cat2_l1 = "Butonul {0} din pad-ul tău dă accesul colaboratorilor tăi să {1} sau să {2} pad-ul."; + out.readme_cat2_l2 = "Schimbă titlul pad-ului dând click pe creion"; + out.readme_cat3 = "Descoperă aplicațiile CryptPad"; + out.readme_cat3_l1 = "Cu editorul de cod CryptPad, poți colabora pe cod ca Javascript și markdown ca HTML și Markdown"; + out.readme_cat3_l2 = "Cu editorul de slide-uri CryptPad, poți să faci prezentări scurte folosind Markdown"; + out.readme_cat3_l3 = "Cu CryptPoll poți să organizezi votări rapide, mai ales pentru a programa ntâlniri care se potrivesc calendarelor tuturor"; + out.tips = ""; {"lag":"Iconița verde din dreapta-sus arată calitatea conexiunii internetului tău la serverele CryptPad.","scurtături":"`ctrl+b`, `ctrl+i` and `ctrl+u` sunt scurtături pentru bold, italic și underline.","indentare":"În listele cu bulină sau cele numerotate, poți folosi tab sau shift+tab pentru a mări sau micșora indentarea.","titlu":"Poți seta titlul pad-urilor tale prin click pe centru sus.","stocare":"De fiecare dată când vizitezi un pad, dacă ești logat va fi salvat pe CryptDrive-ul tău.","marker":"Poți sublinia text ntr-un pad folosind itemul \"marker\" n meniul de stiluri."} out.feedback_about = "Dacă citești asta, probabil că ești curios de ce CryptPad cere pagini web atunci când ntreprinzi anumite acțiuni"; + out.feedback_privacy = "Ne pasă de intimitatea ta, si n același timp vrem să păstrăm CryptPad ușor de folosit. Folosim acest fișier pentru a ne da seama care beneficii UI contează cel mai mult pentru utilizatori, cerându-l alături de un parametru specific atunci când acțiunea se desfășoară"; + out.feedback_optout = "Dacă vrei să ieși, vizitează setările de pe pagina ta de user, unde vei găsi o căsuță pentru a activa sau dezactiva feedback-ul de la user"; + + return out; +}); return out; }); From 70c1061f4712825dc2b081e49c0b60f72b51e0b0 Mon Sep 17 00:00:00 2001 From: Caleb James DeLisle Date: Wed, 24 May 2017 14:49:00 +0200 Subject: [PATCH 302/306] Fix romanian translation --- customize.dist/translations/messages.ro.js | 30 +++++++++++++--------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/customize.dist/translations/messages.ro.js b/customize.dist/translations/messages.ro.js index 162603908..4f8579ba0 100644 --- a/customize.dist/translations/messages.ro.js +++ b/customize.dist/translations/messages.ro.js @@ -25,7 +25,7 @@ define(function () { 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.wrongApp = "Momentan nu putem arăta conținutul sesiunii n timp real n fereastra ta. Te rugăm rencarcă pagina."; - out.loading = "Încarcă..." + out.loading = "Încarcă..."; out.error = "Eroare"; out.saved = "Salvat"; @@ -113,7 +113,7 @@ define(function () { out.history_restore = "Restabilește"; out.history_restoreTitle = "Restabilește versiunea selectată a documentului"; out.history_restorePrompt = "Ești sigur că vrei să nlocuiești versiunea curentă a documentului cu cea afișată?"; - out.history_restoreDone = ""; "Document restabilit"; + out.history_restoreDone = "Document restabilit"; out.history_version = "Versiune:"; out.poll_title = "Zero Knowledge Selector Dată"; out.poll_subtitle = "Zero Knowledge, realtime programare"; @@ -186,7 +186,7 @@ define(function () { out.fm_info_template = "Conține toate pad-urile stocate ca șabloane și pe care le poți refolosi atunci când creezi un nou pad."; out.fm_info_trash = "Fișierele șterse din gunoi vor fi șterse și din \"Toate fișierele\", făcând imposibilă recuperarea fișierelor din managerul de fișiere."; out.fm_info_allFiles = "Conține toate fișierele din \"Documente\", \"Nesortate\" și \"Gunoi\". Poți să muți sau să ștergi fișierele aici."; - out.fm_info_login = ""; "Loghează-te"; + out.fm_info_login = "Loghează-te"; out.fm_info_register = "Înscrie-te"; out.fm_info_anonymous = "Nu ești logat cu un cont valid așa că aceste pad-uri vor fi șterse (află de ce). Înscrie-te sau Loghează-te pentru a le salva."; out.fm_alert_backupUrl = "Link copie de rezervă pentru acest drive.
Este foarte recomandat să o păstrezi pentru tine.
Poți să o folosești pentru a recupera toate fișierele n cazul n care memoria browserului tău este șterge..
Oricine are linkul poate să editeze sau să ndepărteze toate fișierele din managerul tău de documente.
"; @@ -208,7 +208,7 @@ define(function () { out.fo_moveFolderToChildError = "Nu poți să muți un folder ntr-unul dintre descendenții săi"; out.fo_unableToRestore = "Nu am reușit să restaurăm fișierul n locația de origine. Poți să ncerci să l muți ntr-o nouă locație."; out.fo_unavailableName = "Un fișier sau un folder cu același nume există deja n locația nouă. Redenumește elementul și ncearcă din nou."; - out.login_login = ""; "Loghează-te"; + out.login_login = "Loghează-te"; out.login_makeAPad = "Crează un pad n modul anonim"; out.login_nologin = "Răsfoiește pad-urile locale"; out.login_register = "Înscrie-te"; @@ -308,7 +308,7 @@ define(function () { out.policy_whatweknow_p2 = "Folosim Kibana, o platformă open source, pentru a afla mai multe despre utilizatorii noștri. Kibana ne spune despre cum ai găsit CryptPad, căutare directă, printr-un motor de căutare, sau prin recomandare de la un alt serviciu online ca Reddit sau Twitter."; out.policy_howweuse = "Cum folosim ce aflăm"; out.policy_howweuse_p1 = "Folosim aceste informații pentru a lua decizii mai bune n promovarea CryptPad, prin evaluarea eforturilor trecute care au fost de succes. Informațiile despre locația ta ne ajută să aflăm dacă ar trebui să oferim suport pentru alte limbi, pe lângă engleză."; - out.policy_howweuse_p2 = ""; "Informațiile despre browser-ul tău (dacă este bazat pe un sistem de operare desktop sau mobil) ne ajută să luăm decizii când prioritizăm viitoare mbunătățiri. Echipa noastră de development este mică, și ncercăm să facem alegeri care să mbunătățească experiența câtor mai mulți utilizatori."; + out.policy_howweuse_p2 = "Informațiile despre browser-ul tău (dacă este bazat pe un sistem de operare desktop sau mobil) ne ajută să luăm decizii când prioritizăm viitoare mbunătățiri. Echipa noastră de development este mică, și ncercăm să facem alegeri care să mbunătățească experiența câtor mai mulți utilizatori."; out.policy_whatwetell = "Ce le spunem altora despre tine"; out.policy_whatwetell_p1 = "Nu furnizăm informațiile obținute terților, decât dacă ne este cerut n mod legal."; @@ -343,9 +343,9 @@ define(function () { out.readme_cat1_l2 = "Deschide pad-urile din CryptDrive-ul tău: doublu-click pe iconița unui pad pentru a-l deschide."; out.readme_cat1_l3 = "Organizează-ți pad-urile: Când ești logat, orice pad accesezi va fi afișat ca n secțiunea {0} a drive-ului tău."; out.readme_cat1_l3_l1 = "Poți să folosești funcția click and drag pentru a muta fișierele n folderele secțiunii {0} a drive-ului tău și pentru a crea noi foldere."; - out.readme_cat1_l3_l2 = ""; "Ține minte să ncerci click-dreapta pe iconițe pentru că există și meniuri adiționale."; + out.readme_cat1_l3_l2 = "Ține minte să ncerci click-dreapta pe iconițe pentru că există și meniuri adiționale."; out.readme_cat1_l4 = "Pune pad-urile vechi n gunoi. Poți să folosești funcția click and drag pe pad-uri n categoria {0} la fel ca și n cazul folderelor."; - out.readme_cat2 = ""; "Crează pad-uri ca un profesionist"; + out.readme_cat2 = "Crează pad-uri ca un profesionist"; out.edit = "editează"; out.view = "vezi"; out.readme_cat2_l1 = "Butonul {0} din pad-ul tău dă accesul colaboratorilor tăi să {1} sau să {2} pad-ul."; @@ -354,12 +354,18 @@ define(function () { out.readme_cat3_l1 = "Cu editorul de cod CryptPad, poți colabora pe cod ca Javascript și markdown ca HTML și Markdown"; out.readme_cat3_l2 = "Cu editorul de slide-uri CryptPad, poți să faci prezentări scurte folosind Markdown"; out.readme_cat3_l3 = "Cu CryptPoll poți să organizezi votări rapide, mai ales pentru a programa ntâlniri care se potrivesc calendarelor tuturor"; - out.tips = ""; {"lag":"Iconița verde din dreapta-sus arată calitatea conexiunii internetului tău la serverele CryptPad.","scurtături":"`ctrl+b`, `ctrl+i` and `ctrl+u` sunt scurtături pentru bold, italic și underline.","indentare":"În listele cu bulină sau cele numerotate, poți folosi tab sau shift+tab pentru a mări sau micșora indentarea.","titlu":"Poți seta titlul pad-urilor tale prin click pe centru sus.","stocare":"De fiecare dată când vizitezi un pad, dacă ești logat va fi salvat pe CryptDrive-ul tău.","marker":"Poți sublinia text ntr-un pad folosind itemul \"marker\" n meniul de stiluri."} out.feedback_about = "Dacă citești asta, probabil că ești curios de ce CryptPad cere pagini web atunci când ntreprinzi anumite acțiuni"; + + out.tips = { }; + out.tips.lag = "Iconița verde din dreapta-sus arată calitatea conexiunii internetului tău la serverele CryptPad."; + out.tips.shortcuts = "`ctrl+b`, `ctrl+i` and `ctrl+u` sunt scurtături pentru bold, italic și underline."; + out.tips.indentare = "În listele cu bulină sau cele numerotate, poți folosi tab sau shift+tab pentru a mări sau micșora indentarea."; + out.tips.titlu = "Poți seta titlul pad-urilor tale prin click pe centru sus."; + out.tips.stocare = "De fiecare dată când vizitezi un pad, dacă ești logat va fi salvat pe CryptDrive-ul tău."; + out.tips.marker = "Poți sublinia text ntr-un pad folosind itemul \"marker\" n meniul de stiluri."; + + out.feedback_about = "Dacă citești asta, probabil că ești curios de ce CryptPad cere pagini web atunci când ntreprinzi anumite acțiuni"; out.feedback_privacy = "Ne pasă de intimitatea ta, si n același timp vrem să păstrăm CryptPad ușor de folosit. Folosim acest fișier pentru a ne da seama care beneficii UI contează cel mai mult pentru utilizatori, cerându-l alături de un parametru specific atunci când acțiunea se desfășoară"; out.feedback_optout = "Dacă vrei să ieși, vizitează setările de pe pagina ta de user, unde vei găsi o căsuță pentru a activa sau dezactiva feedback-ul de la user"; return out; -}); - - return out; -}); +}); \ No newline at end of file From 70fbbe0078ce29a68c7312a9d599af9ac6f1ea92 Mon Sep 17 00:00:00 2001 From: superniko Date: Thu, 25 May 2017 10:23:27 +0300 Subject: [PATCH 303/306] Update messages.ro.js --- customize.dist/translations/messages.ro.js | 180 ++++++++++----------- 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/customize.dist/translations/messages.ro.js b/customize.dist/translations/messages.ro.js index 4f8579ba0..2c09b9a76 100644 --- a/customize.dist/translations/messages.ro.js +++ b/customize.dist/translations/messages.ro.js @@ -1,8 +1,8 @@ define(function () { var out = {}; - out.main_title = "CryptPad: Zero Knowledge, Colaborare n timp real"; - out.main_slogan = "Puterea stă n cooperare - Colaborarea este cheia"; + 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"; @@ -17,14 +17,14 @@ define(function () { out.button_newpad = "Filă Text Nouă"; out.button_newcode = "Filă Cod Nouă"; out.button_newpoll = "Sondaj Nou"; - out.button_newslide = "Prezentare Noua"; - out.button_newwhiteboard = "Fila Desen Noua"; - out.updated_0_common_connectionLost = "Conexiunea la server este pierdută
Până la revenirea conexiunii, vei fi n modul citire"; + out.button_newslide = "Prezentare Nouă"; + out.button_newwhiteboard = "Fila Desen Nouă"; + out.updated_0_common_connectionLost = "Conexiunea la server este pierdută
Până la revenirea conexiunii, vei fi în modul citire"; 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.wrongApp = "Momentan nu putem arăta conținutul sesiunii n timp real n fereastra ta. Te rugăm rencarcă pagina."; + out.onLogout = "Nu mai ești autentificat, apasă aici 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"; @@ -46,20 +46,20 @@ define(function () { out.viewers = "privitori"; out.editor = "editor"; out.editors = "editori"; - out.language = "Limba"; + out.language = "Limbă"; out.upgrade = "Actualizare"; out.upgradeTitle = "Actualizează-ți contul pentru a mări limita de stocare"; out.MB = "MB"; out.greenLight = "Totul funcționează corespunzător"; - out.orangeLight = "Conexiunea lentă la internet ți poate afecta experiența"; + out.orangeLight = "Conexiunea lentă la internet îți poate afecta experiența"; out.redLight = "Ai fost deconectat de la sesiune"; out.pinLimitReached = "Ai atins limita de stocare"; - out.pinLimitReachedAlert = "Ai atins limita de stocare. Noile pad-uri nu vor mai fi stocate n CryptDrive.
Pentru a rezolva această problemă, poți să nlături pad-uri din CryptDrive-ul tău (incluzând gunoiul) sau să subscrii la un pachet premium pentru a ți extinde spațiul de stocare."; + out.pinLimitReachedAlert = "Ai atins limita de stocare. Noile pad-uri nu vor mai fi stocate în CryptDrive.
Pentru a rezolva această problemă, poți să nlături pad-uri din CryptDrive-ul tău (incluzând gunoiul) sau să subscrii la un pachet premium pentru a-ți extinde spațiul de stocare."; out.pinLimitNotPinned = "Ai atins limita de stocare.
Acest pad nu va fi stocat n CryptDrive-ul tău."; out.pinLimitDrive = "Ai atins limita de stocare.
Nu poți să creezi alte pad-uri."; out.importButtonTitle = "Importă un pad dintr-un fișier local"; out.exportButtonTitle = "Exportă pad-ul acesta către un fișier local"; - out.exportPrompt = "Cum ai vrea să ți denumești fișierul?"; + out.exportPrompt = "Cum ai vrea să îți denumești fișierul?"; out.changeNamePrompt = "Schimbă-ți numele (lasă necompletat dacă vrei să fii anonim): "; out.user_rename = "Schimbă numele afișat"; out.user_displayName = "Nume afișat"; @@ -69,19 +69,19 @@ define(function () { out.forgetPrompt = "Click-ul pe OK va muta acest pad la gunoi. Ești sigur?"; out.movedToTrash = "Acest pad a fost mutat la gunoi.
Acesează-mi Drive-ul"; out.shareButton = "Distribuie"; - out.shareSuccess = "Link copiat n clipboard"; + out.shareSuccess = "Link copiat în clipboard"; out.newButton = "Nou"; out.newButtonTitle = "Crează un nou pad"; out.saveTemplateButton = "Salvează ca șablon"; out.saveTemplatePrompt = "Alege un titlu pentru șablon"; out.templateSaved = "Șablon salvat!"; out.selectTemplate = "Selectează un șablon sau apasă escape"; - out.presentButtonTitle = "Intră n modul de prezentare"; + 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.colorButtonTitle = "Schimbă culoarea textului în modul de prezentare"; out.printButton = "Printează (enter)"; - out.printButtonTitle = "Printează-ți slide-urile sau exportă-le ca fișier PDF"; + out.printButtonTitle = "Printează-ți slide-urile sau exportă-le ca fișier PDF"; out.printOptions = "Opțiuni schemă"; out.printSlideNumber = "Afișează numărul slide-ului"; out.printDate = "Afișează data"; @@ -91,13 +91,13 @@ define(function () { out.slideOptionsTitle = "Personalizează-ți slide-urile"; out.slideOptionsButton = "Salvează (enter)"; out.editShare = "Editează link-ul"; - out.editShareTitle = "Copiază link-ul de editare n clipboard"; - out.editOpen = "Deschide link-ul de editare ntr-o nouă filă"; - out.editOpenTitle = "Deschide acest pad n modul de editare ntr-o nouă filă"; - out.viewShare = "Link n modul citire"; - out.viewShareTitle = "Copiază link-ul n modul de citire n clipboard"; - out.viewOpen = "Deschide link-ul n modul de citire ntr-o filă nouă"; - out.viewOpenTitle = "Deschide acest pad n modul de citire ntr-o nouă filă"; + out.editShareTitle = "Copiază link-ul de editare în clipboard"; + out.editOpen = "Deschide link-ul de editare într-o nouă filă"; + out.editOpenTitle = "Deschide acest pad în modul de editare într-o nouă filă"; + out.viewShare = "Link în modul citire"; + out.viewShareTitle = "Copiază link-ul în modul de citire în clipboard"; + out.viewOpen = "Deschide link-ul în modul de citire într-o filă nouă"; + out.viewOpenTitle = "Deschide acest pad în modul de citire într-o nouă filă"; out.notifyJoined = "{0} s-au alăturat sesiunii colaborative"; out.notifyRenamed = "{0} e cunoscut ca {1}"; out.notifyLeft = "{0} au părăsit sesiunea colaborativă"; @@ -112,13 +112,13 @@ define(function () { out.history_closeTitle = "Închide istoricul"; out.history_restore = "Restabilește"; out.history_restoreTitle = "Restabilește versiunea selectată a documentului"; - out.history_restorePrompt = "Ești sigur că vrei să nlocuiești versiunea curentă a documentului cu cea afișată?"; + out.history_restorePrompt = "Ești sigur că vrei să înlocuiești versiunea curentă a documentului cu cea afișată?"; out.history_restoreDone = "Document restabilit"; out.history_version = "Versiune:"; out.poll_title = "Zero Knowledge Selector Dată"; out.poll_subtitle = "Zero Knowledge, realtime programare"; out.poll_p_save = "Setările tale sunt actualizate instant, așa că tu nu trebuie să salvezi."; - out.poll_p_encryption = "Tot conținutul tău este criptat ca doar persoanele cărora tu le dai link-ul să aibă acces. Nici chiar serverul nu poate să vadă ce modifici."; + out.poll_p_encryption = "Tot conținutul tău este criptat ca doar persoanele cărora tu le dai link-ul să aibă acces. Nici serverul nu poate să vadă ce modifici."; out.wizardLog = "Click pe butonul din dreapta sus pentru a te ntoarce la sondajul tău"; out.wizardTitle = "Folosește wizard-ul pentru a crea sondajul tău"; out.wizardConfirm = "Ești pregătit să adaugi aceste opțiuni la sondajul tău?"; @@ -131,14 +131,14 @@ define(function () { out.poll_closeWizardButtonTitle = "Închide wizard-ul"; out.poll_wizardComputeButton = "Calculează Opțiunile"; out.poll_wizardClearButton = "Curăță Tabelul"; - out.poll_wizardDescription = "Crează automat un număr de opțiuni introducând orice număr de zile sau intervale orare"; + out.poll_wizardDescription = "Crează automat un număr de opțiuni întroducând orice număr de zile sau intervale orare"; out.poll_wizardAddDateButton = "+ Zi"; out.poll_wizardAddTimeButton = "+ Ore"; out.poll_optionPlaceholder = "Opțiune"; out.poll_userPlaceholder = "Numele tău"; - out.poll_removeOption = "Ești sigur că vrei să ndepărtezi această opțiune?"; - out.poll_removeUser = "Ești sigur că vrei să ndepărtezi aceast utilizator?"; + out.poll_removeOption = "Ești sigur că vrei să îndepărtezi această opțiune?"; + out.poll_removeUser = "Ești sigur că vrei să îndepărtezi aceast utilizator?"; out.poll_titleHint = "Titlu"; out.poll_descriptionHint = "Descrie sondajul, și apoi folosește butonul 'publică' când ai terminat. Orice utilizator care are link-ul poate modifica descrierea, dar descurajăm această practică."; out.canvas_clear = "Curăță"; @@ -162,35 +162,35 @@ define(function () { out.fm_folderName = "Numele folderului"; out.fm_numberOfFolders = "# de foldere"; out.fm_numberOfFiles = "# of files"; - out.fm_fileName = "File name"; + out.fm_fileName = "Nume filă"; out.fm_title = "Titlu"; out.fm_type = "Tip"; out.fm_lastAccess = "Ultima accesare"; out.fm_creation = "Creare"; out.fm_forbidden = "Acțiune interzisă"; out.fm_originalPath = "Ruta inițială"; - out.fm_openParent = "Arată n folder"; + out.fm_openParent = "Arată în folder"; out.fm_noname = "Document nedenumit"; out.fm_emptyTrashDialog = "Ești sigur că vrei să golești coșul de gunoi?"; - out.fm_removeSeveralPermanentlyDialog = "Ești sigur că vrei să ștergi pentru totdeauna aceste {0} elements din coșul de gunoi?"; + out.fm_removeSeveralPermanentlyDialog = "Ești sigur că vrei să ștergi pentru totdeauna aceste {0} elemente din coșul de gunoi?"; out.fm_removePermanentlyDialog = "Ești sigur că vrei să ștergi acest element pentru totdeauna?"; out.fm_removeSeveralDialog = "Ești sigur că vrei să muți aceste {0} elemente la coșul de gunoi?"; out.fm_removeDialog = "Ești sigur că vrei să muți {0} la gunoi?"; - out.fm_restoreDialog = "Ești sigur că vrei să restabilești {0} n locația trecută?"; + out.fm_restoreDialog = "Ești sigur că vrei să restabilești {0} în locația trecută?"; out.fm_unknownFolderError = "Ultima locație vizitată sau cea selectată nu mai există. Deschidem fișierul părinte..."; - out.fm_contextMenuError = "Nu putem deschide meniul de context pentru acest element. Dacă problema persistă, rencarcă pagina."; - out.fm_selectError = "Nu putem selecta elementul vizat. Dacă Unable to select the targeted element. Dacă problema persistă, rencarcă pagina."; + out.fm_contextMenuError = "Nu putem deschide meniul de context pentru acest element. Dacă problema persistă, reîncarcă pagina."; + out.fm_selectError = "Nu putem selecta elementul vizat. Dacă problema persistă, reîncarcă pagina."; out.fm_categoryError = "Nu putem deschide categoria selectată, afișează sursa."; - out.fm_info_root = "Crează câte foldere tip cuib ai nevoie pentru a ți sorta fișierele."; - out.fm_info_unsorted = "Conține toate fișierele pe care le-ai vizitat și nu sunt sortate n \"Documente\" sau mutate n \"Gunoi\"."; + out.fm_info_root = "Crează câte foldere tip cuib ai nevoie pentru a-ți sorta fișierele."; + out.fm_info_unsorted = "Conține toate fișierele pe care le-ai vizitat și nu sunt sortate în \"Documente\" sau mutate în \"Gunoi\"."; out.fm_info_template = "Conține toate pad-urile stocate ca șabloane și pe care le poți refolosi atunci când creezi un nou pad."; out.fm_info_trash = "Fișierele șterse din gunoi vor fi șterse și din \"Toate fișierele\", făcând imposibilă recuperarea fișierelor din managerul de fișiere."; out.fm_info_allFiles = "Conține toate fișierele din \"Documente\", \"Nesortate\" și \"Gunoi\". Poți să muți sau să ștergi fișierele aici."; out.fm_info_login = "Loghează-te"; out.fm_info_register = "Înscrie-te"; out.fm_info_anonymous = "Nu ești logat cu un cont valid așa că aceste pad-uri vor fi șterse (află de ce). Înscrie-te sau Loghează-te pentru a le salva."; - out.fm_alert_backupUrl = "Link copie de rezervă pentru acest drive.
Este foarte recomandat să o păstrezi pentru tine.
Poți să o folosești pentru a recupera toate fișierele n cazul n care memoria browserului tău este șterge..
Oricine are linkul poate să editeze sau să ndepărteze toate fișierele din managerul tău de documente.
"; - out.fm_alert_anonymous = "Salut, momentan folosești CryptPad n mod anonim. Este ok, doar că fișierele tale vor fi șterse după o perioadă de inactivitate. Am dezactivat caracteristicile avansate ale drive-ului pentru utilizatorii anonimi pentru a face clar faptul că stocare documentelor acolo nu este o metodă sigură. Poți să citești mai multe despre motivarea noastră și despre ce de trebuie să te Înregistrezi and Loghezi."; + out.fm_alert_backupUrl = "Link copie de rezervă pentru acest drive.
Este foarte recomandat să o păstrezi pentru tine.
Poți să o folosești pentru a recupera toate fișierele în cazul în care memoria browserului tău este șterge..
Oricine are linkul poate să editeze sau să îndepărteze toate fișierele din managerul tău de documente.
"; + out.fm_alert_anonymous = "Salut, momentan folosești CryptPad în mod anonim. Este ok, doar că fișierele tale vor fi șterse după o perioadă de inactivitate. Am dezactivat caracteristicile avansate ale drive-ului pentru utilizatorii anonimi pentru a face clar faptul că stocare documentelor acolo nu este o metodă sigură. Poți să citești mai multe despre motivarea noastră și despre ce de trebuie să te Înregistrezi si sa te Loghezi."; out.fm_backup_title = "Link de backup"; out.fm_nameFile = "Cum ai vrea să numești fișierul?"; out.fc_newfolder = "Folder nou"; @@ -204,12 +204,12 @@ define(function () { out.fc_prop = "Proprietăți"; out.fc_sizeInKilobytes = "Dimensiune n Kilobytes"; out.fo_moveUnsortedError = "Nu poți să muți un folder la lista de pad-uri nesortate"; - out.fo_existingNameError = "Numele ales este deja folosit n acest director. Te rugăm să alegi altul."; - out.fo_moveFolderToChildError = "Nu poți să muți un folder ntr-unul dintre descendenții săi"; - out.fo_unableToRestore = "Nu am reușit să restaurăm fișierul n locația de origine. Poți să ncerci să l muți ntr-o nouă locație."; - out.fo_unavailableName = "Un fișier sau un folder cu același nume există deja n locația nouă. Redenumește elementul și ncearcă din nou."; + out.fo_existingNameError = "Numele ales este deja folosit în acest director. Te rugăm să alegi altul."; + out.fo_moveFolderToChildError = "Nu poți să muți un folder într-unul dintre descendenții săi"; + out.fo_unableToRestore = "Nu am reușit să restaurăm fișierul în locația de origine. Poți să ncerci să îl muți într-o nouă locație."; + out.fo_unavailableName = "Un fișier sau un folder cu același nume există deja în locația nouă. Redenumește elementul și încearcă din nou."; out.login_login = "Loghează-te"; - out.login_makeAPad = "Crează un pad n modul anonim"; + out.login_makeAPad = "Crează un pad în modul anonim"; out.login_nologin = "Răsfoiește pad-urile locale"; out.login_register = "Înscrie-te"; out.logoutButton = "Deloghează-te"; @@ -223,7 +223,7 @@ define(function () { out.login_helloNoName = "Salut,"; out.login_accessDrive = "Acesează-ți drive-ul"; out.login_orNoLogin = "sau"; - out.login_noSuchUser = "Nume de utilizator sau parolă invalide. Încearcă din nou sau nscrie-te."; + out.login_noSuchUser = "Nume de utilizator sau parolă invalide. Încearcă din nou sau înscrie-te."; out.login_invalUser = "Nume utilizator cerut"; out.login_invalPass = "Parolă cerută"; out.login_unhandledError = "O eroare neașteptată a avut loc emoticon_unhappy"; @@ -231,12 +231,12 @@ define(function () { out.register_acceptTerms = "Accept termenii serviciului"; out.register_passwordsDontMatch = "Parolele nu se potrivesc!"; out.register_mustAcceptTerms = "Trebuie să accepți termenii serviciului"; - out.register_mustRememberPass = "Nu putem să ți resetăm parola dacă o uiți. Este foarte important să o ții minte! Bifează căsuța pentru a confirma."; - out.register_header = "Bine ai venit n CryptPad"; - out.register_explanation = "

Hai să stabilim câteva lucruri, mai ntâi

  • Parola ta este cheia secretă care criptează toate pad-urile tale. Dacă pierzi/uiți parola nu există nici-o metodă prin care ți putem recupera datele.
  • Poți importa pad-uri care au fost vizionate recent n browser pentru a le avea n cont.
  • Dacă folosești un computer mpărțit, trebuie să te deloghezi, nchiderea taburilor nu este de ajuns.
"; - out.register_writtenPassword = "Mi-am notat numele de utilizator și parola, naintează."; + out.register_mustRememberPass = "Nu putem să îți resetăm parola dacă o uiți. Este foarte important să o ții minte! Bifează căsuța pentru a confirma."; + out.register_header = "Bine ai venit în CryptPad"; + out.register_explanation = "

Hai să stabilim câteva lucruri, mai întâi

  • Parola ta este cheia secretă care criptează toate pad-urile tale. Dacă pierzi/uiți parola nu există nici-o metodă prin care îți putem recupera datele.
  • Poți importa pad-uri care au fost vizionate recent în browser pentru a le avea în cont.
  • Dacă folosești un computer împărțit, trebuie să te deloghezi, închiderea taburilor nu este de ajuns.
"; + out.register_writtenPassword = "Mi-am notat numele de utilizator și parola, înaintează."; out.register_cancel = "Întoarce-te"; - out.register_warning = "Zero Knowledge nseamnă că noi nu ți putem recupera datele dacă ți pierzi parola."; + out.register_warning = "Zero Knowledge înseamnă că noi nu îți putem recupera datele dacă îți pierzi parola."; out.register_alreadyRegistered = "Acest user există deja, vrei să te loghezi?"; out.settings_title = "Setări"; out.settings_save = "Salvează"; @@ -245,54 +245,54 @@ define(function () { out.settings_restore = "Restaurează"; out.settings_resetTitle = "Curăță-ți drive-ul"; out.settings_reset = "Îndepărtează toate fișierele și folderele din CryptPad-ul tău."; - out.settings_resetPrompt = "Această acțiune o să ndepărteze toate pad-urile din drive-ul tău.
Ești sigur că vrei să continui?
Type “Iubesc CryptPad” pentru a confirma."; + out.settings_resetPrompt = "Această acțiune va indepărta toate pad-urile din drive-ul tău.
Ești sigur că vrei să continui?
Tastează “Iubesc CryptPad” pentru a confirma."; out.settings_resetDone = "Drive-ul tău este acum gol!"; - out.settings_resetError = "Text de verificare incorrect. CryptPad-ul tău nu a fost schimbat."; - out.settings_resetTips = "Sfaturi n CryptDrive"; - out.settings_resetTipsButton = "Resetează sfaturile disponibile n CryptDrive"; + out.settings_resetError = "Text de verificare incorect. CryptPad-ul tău nu a fost schimbat."; + out.settings_resetTips = "Sfaturi în CryptDrive"; + out.settings_resetTipsButton = "Resetează sfaturile disponibile în CryptDrive"; out.settings_resetTipsDone = "Toate sfaturile sunt vizibile din nou."; out.settings_importTitle = "Importă pad-urile recente ale acestui browser n CryptDrive-ul meu"; out.settings_import = "Importă"; - out.settings_importConfirm = "Ești sigur că vrei să imporți pad-urile recente ale acestui browser n contul tău de CryptDrive?"; + out.settings_importConfirm = "Ești sigur că vrei să imporți pad-urile recente ale acestui browser în contul tău de CryptDrive?"; out.settings_importDone = "Import complet"; - out.settings_userFeedbackHint1 = "CryptPad oferă niște feedback foarte simplu serverului, pentru a ne informa cum putem să ți mbunătățim experiența voastră."; - out.settings_userFeedbackHint2 = "Conținutul pad-ului tău nu va fi mpărțit cu serverele."; + out.settings_userFeedbackHint1 = "CryptPad oferă niște feedback foarte simplu serverului, pentru a ne informa cum putem să îți îmbunătățim experiența voastră."; + out.settings_userFeedbackHint2 = "Conținutul pad-ului tău nu va fi împărțit cu serverele."; out.settings_userFeedback = "Activează feedback"; out.settings_anonymous = "Nu ești logat. Setările sunt specifice browser-ului."; out.settings_publicSigningKey = "Cheia de semnătură publică"; out.settings_usage = "Uzaj"; - out.settings_usageTitle = "Vezi dimensiunea totală a pad-urilor fixate n MB"; - out.settings_pinningNotAvailable = "Pad-urile fixate sunt disponibile doar utilizatorilor nregistrați."; + out.settings_usageTitle = "Vezi dimensiunea totală a pad-urilor fixate în MB"; + out.settings_pinningNotAvailable = "Pad-urile fixate sunt disponibile doar utilizatorilor înregistrați."; out.settings_pinningError = "Ceva nu a funcționat"; out.settings_usageAmount = "Pad-urile tale fixate ocupă {0}MB"; out.settings_logoutEverywhereTitle = "Deloghează-te peste tot"; out.settings_logoutEverywhere = "Deloghează-te din toate sesiunile web"; out.settings_logoutEverywhereConfirm = "Ești sigur? Va trebui să te loghezi, din nou, pe toate device-urile tale."; - out.upload_serverError = "Eroare de server: fișierele tale nu pot fi ncărcate la momentul acesta."; - out.upload_uploadPending = "Ai deja o ncărcare n desfășurare. Anulezi si ncarci noul fișier?"; + out.upload_serverError = "Eroare de server: fișierele tale nu pot fi încărcate la momentul acesta."; + out.upload_uploadPending = "Ai deja o încărcare în desfășurare. Anulezi și încarci noul fișier?"; out.upload_success = "Fișierul tău ({0}) a fost ncărcat și adăugat la drive-ul tău cu succes."; - out.main_p2 = "Acest proiect folosește CKEditor Visual Editor, CodeMirror, și ChainPad un motor n timp real."; - out.main_howitworks_p1 = "CryptPad folosește o variantă a algoritmului de Operational transformation care este capabil să găsescă consens distribuit folosind Nakamoto Blockchain, o construcție popularizată de Bitcoin. Astfel algoritmul poate evita nevoia ca serverul central să rezove conflicte, iar serverul nu este interesat de conținutul care este editat n pad."; - out.main_about_p2 = "Dacă ai orice fel de ntrebare sau comentariu, poți să ne dai un tweet, semnalezi o problemă on github, spui salut pe IRC (irc.freenode.net), sau trimiți un email."; - out.main_info = "

Colaborează n siguranță


Dezvoltă-ți ideile mpreună cu documente partajate n timp ce tehnologia Zero Knowledge ți păstrează securitatea; chiar și de noi."; + out.main_p2 = "Acest proiect folosește CKEditor Visual Editor, CodeMirror, și ChainPad un motor în timp real."; + out.main_howitworks_p1 = "CryptPad folosește o variantă a algoritmului de Operational transformation care este capabil să găsescă consens distribuit folosind Nakamoto Blockchain, o construcție popularizată de Bitcoin. Astfel algoritmul poate evita nevoia ca serverul central să rezove conflicte, iar serverul nu este interesat de conținutul care este editat în pad."; + out.main_about_p2 = "Dacă ai orice fel de întrebare sau comentariu, poți să ne dai un tweet, semnalezi o problemă on github, spui salut pe IRC (irc.freenode.net), sau trimiți un email."; + out.main_info = "

Colaborează n siguranță


Dezvoltă-ți ideile împreună cu documente partajate în timp ce tehnologia Zero Knowledge îți păstrează securitatea; chiar și de noi."; out.main_howitworks = "Cum funcționează"; out.main_zeroKnowledge = "Zero Knowledge"; - out.main_zeroKnowledge_p = "Nu trebuie să ne crezi că nu ne uităm la pad-urile tale, cu tehnologia revoluționară Zero Knowledge a CryptPad nu putem. Învață mai multe despre cum ți protejăm Learn more about how we protect your Intimitate și Securitate."; + out.main_zeroKnowledge_p = "Nu trebuie să ne crezi că nu ne uităm la pad-urile tale, cu tehnologia revoluționară Zero Knowledge a CryptPad nu putem. Învață mai multe despre cum îți protejăm Intimitate și Securitate."; out.main_writeItDown = "Notează"; out.main_writeItDown_p = "Cele mai importante proiecte vin din idei mici. Notează-ți momentele de inspirație și ideile neașteptate pentru că nu știi niciodată care ar putea fi noua mare descoperire."; out.main_share = "Partajează link-ul, partajează pad-ul"; - out.main_share_p = "Dezvoltă-ți ideile mpreună: organizează ntâlniri eficiente, colaborează pe liste TODO și fă prezentări scurte cu toți prietenii tăi și device-urile tale."; + out.main_share_p = "Dezvoltă-ți ideile împreună: organizează întâlniri eficiente, colaborează pe liste TODO și fă prezentări scurte cu toți prietenii tăi și device-urile tale."; out.main_organize = "Organizează-te"; - out.main_organize_p = "Cu CryptPad Drive, poți să stai cu ochii pe ce este important. Folderele ți permit să ții evidența proiectelor tale și să ai o viziune globală asupra evoluției lucrurilor."; + out.main_organize_p = "Cu CryptPad Drive, poți să stai cu ochii pe ce este important. Folderele îți permit să ții evidența proiectelor tale și să ai o viziune globală asupra evoluției lucrurilor."; out.tryIt = "Testează!"; out.main_richText = "Rich Text editor"; - out.main_richText_p = "Editează texte complexe n mod colaborativ cu Zero Knowledge n timp real. CkEditor application."; + out.main_richText_p = "Editează texte complexe în mod colaborativ cu Zero Knowledge în timp real. CkEditor application."; out.main_code = "Editor cod"; - out.main_code_p = "Editează cod din softul tău, n mod colaborativ, cu Zero Knowledge n timp real.CodeMirror application."; + out.main_code_p = "Editează cod din softul tău, în mod colaborativ, cu Zero Knowledge în timp real.CodeMirror application."; out.main_slide = "Editor slide-uri"; - out.main_slide_p = "Crează-ți prezentări folosind sintaxa Markdown, și afișează-le n browser-ul tău."; + out.main_slide_p = "Crează-ți prezentări folosind sintaxa Markdown, și afișează-le în browser-ul tău."; out.main_poll = "Sondaj"; - out.main_poll_p = "Plănuiește ntâlniri sau evenimente, sau votează pentru cea mai bună soluție pentru problema ta."; + out.main_poll_p = "Plănuiește întâlniri sau evenimente, sau votează pentru cea mai bună soluție pentru problema ta."; out.main_drive = "CryptDrive"; out.footer_applications = "Aplicații"; out.footer_contact = "Contact"; @@ -304,47 +304,47 @@ define(function () { out.blog = "Blog"; out.policy_title = "Politica de confidențialitate CryptPad"; out.policy_whatweknow = "Ce știm despre tine"; - out.policy_whatweknow_p1 = "Ca o aplicație care este găzduită online, CryptPad are acces la metadatele expuse de protocolul HTTP. Asta include adresa IP-ului tău, și alte titluri HTTP care pot fi folosite ca să identifice un browser. Poți să vezi ce informații mpărtășește browser-ul tău vizitând WhatIsMyBrowser.com."; + out.policy_whatweknow_p1 = "Ca o aplicație care este găzduită online, CryptPad are acces la metadatele expuse de protocolul HTTP. Asta include adresa IP-ului tău, și alte titluri HTTP care pot fi folosite ca să identifice un browser. Poți să vezi ce informații împărtășește browser-ul tău vizitând WhatIsMyBrowser.com."; out.policy_whatweknow_p2 = "Folosim Kibana, o platformă open source, pentru a afla mai multe despre utilizatorii noștri. Kibana ne spune despre cum ai găsit CryptPad, căutare directă, printr-un motor de căutare, sau prin recomandare de la un alt serviciu online ca Reddit sau Twitter."; out.policy_howweuse = "Cum folosim ce aflăm"; - out.policy_howweuse_p1 = "Folosim aceste informații pentru a lua decizii mai bune n promovarea CryptPad, prin evaluarea eforturilor trecute care au fost de succes. Informațiile despre locația ta ne ajută să aflăm dacă ar trebui să oferim suport pentru alte limbi, pe lângă engleză."; - out.policy_howweuse_p2 = "Informațiile despre browser-ul tău (dacă este bazat pe un sistem de operare desktop sau mobil) ne ajută să luăm decizii când prioritizăm viitoare mbunătățiri. Echipa noastră de development este mică, și ncercăm să facem alegeri care să mbunătățească experiența câtor mai mulți utilizatori."; + out.policy_howweuse_p1 = "Folosim aceste informații pentru a lua decizii mai bune în promovarea CryptPad, prin evaluarea eforturilor trecute care au fost de succes. Informațiile despre locația ta ne ajută să aflăm dacă ar trebui să oferim suport pentru alte limbi, pe lângă engleză."; + out.policy_howweuse_p2 = "Informațiile despre browser-ul tău (dacă este bazat pe un sistem de operare desktop sau mobil) ne ajută să luăm decizii când prioritizăm viitoarele îmbunătățiri. Echipa noastră de dezvoltare este mică, și încercăm să facem alegeri care să îmbunătățească experiența câtor mai mulți utilizatori."; out.policy_whatwetell = "Ce le spunem altora despre tine"; - out.policy_whatwetell_p1 = "Nu furnizăm informațiile obținute terților, decât dacă ne este cerut n mod legal."; + out.policy_whatwetell_p1 = "Nu furnizăm informațiile obținute terților, decât dacă ne este cerut în mod legal."; out.policy_links = "Link-uri către alte site-uri"; out.policy_links_p1 = "Acest site conține link-uri către alte site-uri, incluzându-le pe cele produse de alte organizații. Nu suntem responsabili pentru practicile de intimitate sau pentru conținutul site-urilor externe. Ca regulă generală, link-urile către site-uri externe sunt deschise ntr-o fereastră noup, pentru a face clar faptul că părăsiți CryptPad.fr."; out.policy_ads = "Reclame"; out.policy_ads_p1 = "Nu afișăm nici o formă de publicitate online, dar s-ar putea să atașăm link-uri către instituțiile care ne finanțează cerecetarea."; out.policy_choices = "Ce alegeri ai"; out.policy_choices_open = "Codul nostru este open source, așa că tu ai mereu posibilitatea de a-ți găzdui propria instanță de CryptPad."; - out.policy_choices_vpn = "Dacă vrei să folosești instanța găzduită de noi, dar nu vrei să ți expui IP-ul, poți să l protejezi folosind Tor browser bundle, sau VPN."; + out.policy_choices_vpn = "Dacă vrei să folosești instanța găzduită de noi, dar nu vrei să îți expui IP-ul, poți să îl protejezi folosind Tor browser bundle, sau VPN."; out.policy_choices_ads = "Dacă vrei doar să blochezi platforma noastră de analiză, poți folosi soluții de adblocking ca Privacy Badger."; out.tos_title = "CryptPad Termeni de Utilizare"; out.tos_legal = "Te rugăm să nu fii rău intenționat, abuziv, sau să faci orice ilegal."; - out.tos_availability = "Sperăm că o să găsești acest serviciu util, dar disponibilitatea sau performanța nu poate fi garantată. Te rugăm să ți exporți datele n mod regulat."; - out.tos_e2ee = "Conținutul CryptPad poate fi citit sau modificat de oricine care poate ghici sau obține fragmentul identificator al pad-ului. Recomandăm să folosești soluții de comunicare criptate end-to-end-encrypted (e2ee) pentru a partaja link-uri, evitând orice risc n cazul unei scurgeri de informații."; - out.tos_logs = "Metadatele oferite de browser-ul tău serverului ar putea fi nscrise n scopul de a menține serviciul."; + out.tos_availability = "Sperăm că o să găsești acest serviciu util, dar disponibilitatea sau performanța nu poate fi garantată. Te rugăm să îți exporți datele n mod regulat."; + out.tos_e2ee = "Conținutul CryptPad poate fi citit sau modificat de oricine care poate ghici sau obține fragmentul identificator al pad-ului. Recomandăm să folosești soluții de comunicare criptate end-to-end-encrypted (e2ee) pentru a partaja link-uri, evitând orice risc în cazul unei scurgeri de informații."; + out.tos_logs = "Metadatele oferite de browser-ul tău serverului ar putea fi înscrise în scopul de a menține serviciul."; out.tos_3rdparties = "Nu oferim date personale terților, decât dacă ne sunt solicitate prin lege."; out.bottom_france = "Realizat cu \"love\" n \"Franța\""; out.bottom_support = "Un proiect al \"XWiki Labs Project cu susținerea \"OpenPaaS-ng\""; 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  Share  butonul pentru a partaja read-only link permițând vizualizarea dar nu și editarea.

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

 

"; 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, lasă 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\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.driveReadmeTitle = "Ce este CryptDrive?"; 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."; - out.readme_p2 = "Acest pad o să ți ofere un scurt ghid n cum poți să folosești CryptPad pentru a lua notițe, a le ține organizate și a colabora pe ele."; + out.readme_p1 = "Bine ai venit în CryptPad, acesta este locul unde îți poți lua notițe, singur sau cu prietenii."; + out.readme_p2 = "Acest pad o să îți ofere un scurt ghid în cum poți să folosești CryptPad pentru a lua notițe, a le ține organizate și a colabora pe ele."; out.readme_cat1 = "Descoperă-ți CryptDrive-ul"; out.readme_cat1_l1 = "Crează un pad: În CryptDrive-ul tău, dă click {0} apoi {1} și poți să creezi un pad."; out.readme_cat1_l2 = "Deschide pad-urile din CryptDrive-ul tău: doublu-click pe iconița unui pad pentru a-l deschide."; - out.readme_cat1_l3 = "Organizează-ți pad-urile: Când ești logat, orice pad accesezi va fi afișat ca n secțiunea {0} a drive-ului tău."; - out.readme_cat1_l3_l1 = "Poți să folosești funcția click and drag pentru a muta fișierele n folderele secțiunii {0} a drive-ului tău și pentru a crea noi foldere."; - out.readme_cat1_l3_l2 = "Ține minte să ncerci click-dreapta pe iconițe pentru că există și meniuri adiționale."; - out.readme_cat1_l4 = "Pune pad-urile vechi n gunoi. Poți să folosești funcția click and drag pe pad-uri n categoria {0} la fel ca și n cazul folderelor."; + out.readme_cat1_l3 = "Organizează-ți pad-urile: Când ești logat, orice pad accesezi va fi afișat ca în secțiunea {0} a drive-ului tău."; + out.readme_cat1_l3_l1 = "Poți să folosești funcția click and drag pentru a muta fișierele în folderele secțiunii {0} a drive-ului tău și pentru a crea noi foldere."; + out.readme_cat1_l3_l2 = "Ține minte să încerci click-dreapta pe iconițe pentru că există și meniuri adiționale."; + out.readme_cat1_l4 = "Pune pad-urile vechi în gunoi. Poți să folosești funcția click and drag pe pad-uri în categoria {0} la fel ca și în cazul folderelor."; out.readme_cat2 = "Crează pad-uri ca un profesionist"; out.edit = "editează"; out.view = "vezi"; @@ -361,11 +361,11 @@ define(function () { out.tips.indentare = "În listele cu bulină sau cele numerotate, poți folosi tab sau shift+tab pentru a mări sau micșora indentarea."; out.tips.titlu = "Poți seta titlul pad-urilor tale prin click pe centru sus."; out.tips.stocare = "De fiecare dată când vizitezi un pad, dacă ești logat va fi salvat pe CryptDrive-ul tău."; - out.tips.marker = "Poți sublinia text ntr-un pad folosind itemul \"marker\" n meniul de stiluri."; + out.tips.marker = "Poți sublinia text într-un pad folosind itemul \"marker\" n meniul de stiluri."; - out.feedback_about = "Dacă citești asta, probabil că ești curios de ce CryptPad cere pagini web atunci când ntreprinzi anumite acțiuni"; - out.feedback_privacy = "Ne pasă de intimitatea ta, si n același timp vrem să păstrăm CryptPad ușor de folosit. Folosim acest fișier pentru a ne da seama care beneficii UI contează cel mai mult pentru utilizatori, cerându-l alături de un parametru specific atunci când acțiunea se desfășoară"; + out.feedback_about = "Dacă citești asta, probabil că ești curios de ce CryptPad cere pagini web atunci când întreprinzi anumite acțiuni"; + out.feedback_privacy = "Ne pasă de intimitatea ta, si în același timp vrem să păstrăm CryptPad ușor de folosit. Folosim acest fișier pentru a ne da seama care beneficii UI contează cel mai mult pentru utilizatori, cerându-l alături de un parametru specific atunci când acțiunea se desfășoară"; out.feedback_optout = "Dacă vrei să ieși, vizitează setările de pe pagina ta de user, unde vei găsi o căsuță pentru a activa sau dezactiva feedback-ul de la user"; return out; -}); \ No newline at end of file +}); From cf658c1b3ab3df508a16731906e291cc80be8530 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 26 May 2017 12:41:30 +0200 Subject: [PATCH 304/306] backport live changes --- www/code/main.js | 17 ++++++++++++++--- www/common/common-util.js | 9 +++++++++ www/common/cryptpad-common.js | 1 + 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/www/code/main.js b/www/code/main.js index fa6ecf12b..d82d73273 100644 --- a/www/code/main.js +++ b/www/code/main.js @@ -110,6 +110,18 @@ define([ return stringify(obj); }; + var forceDrawPreview = function () { + try { + DiffMd.apply(DiffMd.render(editor.getValue()), $preview); + } catch (e) { console.error(e); } + }; + + var drawPreview = Cryptpad.throttle(function () { + if (CodeMirror.highlightMode !== 'markdown') { return; } + if (!$previewContainer.is(':visible')) { return; } + forceDrawPreview(); + }, 150); + var onLocal = config.onLocal = function () { if (initializing) { return; } if (isHistoryMode) { return; } @@ -117,7 +129,7 @@ define([ editor.save(); - DiffMd.apply(DiffMd.render(editor.getValue()), $preview); + drawPreview(); var textValue = canonicalize(CodeMirror.$textarea.val()); var shjson = stringifyInner(textValue); @@ -324,14 +336,13 @@ define([ var hjson = JSON.parse(shjson); var remoteDoc = hjson.content; - DiffMd.apply(DiffMd.render(remoteDoc), $preview); - var highlightMode = hjson.highlightMode; if (highlightMode && highlightMode !== APP.highlightMode) { CodeMirror.setMode(highlightMode, onModeChanged); } CodeMirror.setValueAndCursor(oldDoc, remoteDoc, TextPatcher); + drawPreview(); if (!readOnly) { var textValue = canonicalize(CodeMirror.$textarea.val()); diff --git a/www/common/common-util.js b/www/common/common-util.js index 6fb2ad7bb..debbd8e2d 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -122,5 +122,14 @@ define([], function () { xhr.send(null); }; + Util.throttle = function (f, ms) { + var to; + var g = function () { + window.clearTimeout(to); + to = window.setTimeout(f, ms); + }; + return g; + }; + return Util; }); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 730a7984f..20927dc04 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -71,6 +71,7 @@ define([ common.bytesToMegabytes = Util.bytesToMegabytes; common.bytesToKilobytes = Util.bytesToKilobytes; common.fetch = Util.fetch; + common.throttle = Util.throttle; // import hash utilities for export var createRandomHash = common.createRandomHash = Hash.createRandomHash; From 57017c68fa2169644ff721fb4c9af253ba9112b2 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 26 May 2017 12:42:12 +0200 Subject: [PATCH 305/306] hacks for cross-domain safari --- www/common/fsStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/fsStore.js b/www/common/fsStore.js index b20c38c48..08a1a8fa3 100644 --- a/www/common/fsStore.js +++ b/www/common/fsStore.js @@ -172,7 +172,7 @@ define([ if (typeof(proxy.loginToken) !== 'number') { proxy[tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); } - + if (sessionStorage) { sessionStorage.setItem('User_hash', localStorage.getItem('User_hash')); } var localToken = tryParsing(localStorage.getItem(tokenKey)); if (localToken === null) { // if that number hasn't been set to localStorage, do so. From 2ecf257fcdcadd65ea17c0e9f7b3678fb2b39c03 Mon Sep 17 00:00:00 2001 From: Caleb James DeLisle Date: Fri, 26 May 2017 15:30:01 +0200 Subject: [PATCH 306/306] Sync localStorage with sessionStorage in auth because safari... --- www/auth/main.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/www/auth/main.js b/www/auth/main.js index 488165911..747434c23 100644 --- a/www/auth/main.js +++ b/www/auth/main.js @@ -18,6 +18,9 @@ define([ /^http(s)?:\/\/localhost\:/ ]; + // Safari is weird about localStorage in iframes but seems to let sessionStorage slide. + localStorage.User_hash = localStorage.User_hash || sessionStorage.User_hash; + Cryptpad.ready(function () { console.log('IFRAME READY'); $(window).on("message", function (jqe) {