From df2b00122ac8f67712fbc0d7e270632a8b96d3e6 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 24 May 2017 18:59:44 +0200 Subject: [PATCH 01/12] New drive structure without file names --- www/assert/main.js | 6 +- www/common/userObject.js | 180 ++++++++++++++++++++++++++++++--------- www/drive/tests.js | 178 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 321 insertions(+), 43 deletions(-) create mode 100644 www/drive/tests.js diff --git a/www/assert/main.js b/www/assert/main.js index ffab3f5a9..9c7c1f413 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -4,7 +4,8 @@ define([ '/bower_components/textpatcher/TextPatcher.amd.js', 'json.sortify', '/common/cryptpad-common.js', -], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad) { + '/drive/tests.js' +], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad, Drive) { window.Hyperjson = Hyperjson; window.TextPatcher = TextPatcher; window.Sortify = Sortify; @@ -204,6 +205,9 @@ define([ return cb(true); }, "version 2 hash failed to parse correctly"); + + Drive.test(assert); + var swap = function (str, dict) { return str.replace(/\{\{(.*?)\}\}/g, function (all, key) { return typeof dict[key] !== 'undefined'? dict[key] : all; diff --git a/www/common/userObject.js b/www/common/userObject.js index 1559397d0..1ce3d60d5 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -8,6 +8,7 @@ define([ var UNSORTED = module.UNSORTED = "unsorted"; var TRASH = module.TRASH = "trash"; var TEMPLATE = module.TEMPLATE = "template"; + var NEW_FILES_DATA = 'filesData'; module.init = function (files, config) { var exp = {}; @@ -54,7 +55,7 @@ define([ var compareFiles = function (fileA, fileB) { return fileA === fileB; }; var isFile = exp.isFile = function (element) { - return typeof(element) === "string"; + return typeof(element) === "number" || typeof(element) === "string"; }; exp.isReadOnlyFile = function (element) { @@ -247,6 +248,7 @@ define([ }; _getFiles[FILES_DATA] = function () { var ret = []; + if (!files[FILES_DATA]) { return ret; } files[FILES_DATA].forEach(function (el) { if (el.href && ret.indexOf(el.href) === -1) { ret.push(el.href); @@ -254,10 +256,21 @@ define([ }); return ret; }; + _getFiles[NEW_FILES_DATA] = function () { + var ret = []; + if (!files[NEW_FILES_DATA]) { return ret; } + for (var k in files[NEW_FILES_DATA]) { + var el = files[NEW_FILES_DATA][k]; + if (el.href && ret.indexOf(el.href) === -1) { + ret.push(el.href); + } + }; + return ret; + }; var getFiles = exp.getFiles = function (categories) { var ret = []; if (!categories || !categories.length) { - categories = [ROOT, 'hrefArray', TRASH, FILES_DATA]; + categories = [ROOT, 'hrefArray', TRASH, FILES_DATA, NEW_FILES_DATA]; } categories.forEach(function (c) { if (typeof _getFiles[c] === "function") { @@ -298,6 +311,7 @@ define([ return _findFileInRoot([ROOT], href); }; var _findFileInHrefArray = function (rootName, href) { + if (!files[rootName]) { return []; } var unsorted = files[rootName].slice(); var ret = []; var i = -1; @@ -833,8 +847,19 @@ define([ 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])) { + continue; + } + if (isFolder(element[el])) { fixRoot(element[el]); + continue; + } + if (typeof element[el] === "string") { + // We have an old file (href) which is not in filesData: add it + var id = Cryptpad.createRandomInteger(); + var key = Cryptpad.createChannelId(); + files[NEW_FILES_DATA][id] = {href: element[el], filename: el}; + element[key] = id; + delete element[el]; } } }; @@ -842,10 +867,17 @@ define([ if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; } var tr = files[TRASH]; var toClean; - var addToClean = function (obj, idx) { + var addToClean = function (obj, idx, el) { 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; } + if (typeof obj.element === "string") { + // We have an old file (href) which is not in filesData: add it + var id = Cryptpad.createRandomInteger(); + files[NEW_FILES_DATA][id] = {href: obj.element, filename: el}; + obj.element = id; + } + if (isFolder(obj.element)) { fixRoot(obj.element); } }; for (var el in tr) { if (!$.isArray(tr[el])) { @@ -854,35 +886,13 @@ define([ delete tr[el]; } else { toClean = []; - tr[el].forEach(addToClean); + tr[el].forEach(function (obj, idx) { addToClean(obj, idx, el); }); for (var i = toClean.length-1; i>=0; i--) { tr[el].splice(toClean[i], 1); } } } }; - // Make sure unsorted doesn't exist anymore - var fixUnsorted = function () { - 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 root = find([ROOT]); - us.forEach(function (el) { - if (!isFile(el) || rootFiles.indexOf(el) !== -1) { - return; - } - var data = getFileData(el); - var name = data ? data.title : NEW_FILE_NAME; - var newName = getAvailableName(root, name); - root[newName] = el; - }); - delete files[UNSORTED]; - }; var fixTemplate = function () { if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; } files[TEMPLATE] = Cryptpad.deduplicateString(files[TEMPLATE].slice()); @@ -893,36 +903,43 @@ define([ if (!isFile(el) || rootFiles.indexOf(el) !== -1) { toClean.push(idx); } + if (typeof el === "string") { + // We have an old file (href) which is not in filesData: add it + var id = Cryptpad.createRandomInteger(); + files[NEW_FILES_DATA][id] = {href: el}; + us[idx] = id; + } }); 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]; + if (typeof files[NEW_FILES_DATA] !== "object") { debug("FILES_DATA was not an object"); files[NEW_FILES_DATA] = {}; } + var fd = files[NEW_FILES_DATA]; var rootFiles = getFiles([ROOT, TRASH, 'hrefArray']); var root = find([ROOT]); var toClean = []; - fd.forEach(function (el) { + for (var id in fd) { + id = Number(id); + var el = fd[id]; if (!el || typeof(el) !== "object") { debug("An element in filesData was not an object.", el); toClean.push(el); - return; + continue; } if (!el.href) { - debug("Rmoving an element in filesData with a missing href.", el); + debug("Removing an element in filesData with a missing href.", el); toClean.push(el); - return; + continue; } - if (Cryptpad.isLoggedIn() && 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; - var newName = getAvailableName(root, name); - root[newName] = el.href; - return; + if (Cryptpad.isLoggedIn() && rootFiles.indexOf(id) === -1) { + debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el); + var newName = Cryptpad.createChannelId(); + root[newName] = id; + continue; } - }); + }; toClean.forEach(function (el) { var idx = fd.indexOf(el); if (idx !== -1) { @@ -931,10 +948,90 @@ define([ }); }; + // Make sure unsorted doesn't exist anymore + // Note: Unsorted only works with ld structure where pads are href + // It should be called before the migration code + var fixUnsorted = function () { + if (!files[UNSORTED] || !files[FILES_DATA]) { return; } + debug("UNSORTED still exists in the object, removing it..."); + var us = files[UNSORTED]; + if (us.length === 0) { + delete files[UNSORTED]; + return; + } + var root = find([ROOT]); + us.forEach(function (el) { + if (typeof el !== "string") { + return; + } + var data = files[FILES_DATA].filter(function (x) { + return x.href === el; + }); + if (data.length === 0) { + files[FILES_DATA].push({ + href: el + }); + } + return; + /* + TODO remove + var name = data.length !== 0 ? data[0].title : NEW_FILE_NAME; + var newName = getAvailableName(root, name); + root[newName] = el; + */ + }); + delete files[UNSORTED]; + }; + // TODO: getRecentPads in cryptpad-common + // TODO: in fixRoot, trash and template, make sure we have a filedata associated to the id + var migrateToNewFormat = function () { + if (!files[FILES_DATA]) { return; } + try { + var oldData = files[FILES_DATA].slice(); + var newData = files[NEW_FILES_DATA] = {}; + //var oldFiles = oldData.map(function (o) { return o.href; }); + oldData.forEach(function (obj) { + if (!obj || !obj.href) { return; } + var href = obj.href; + var id = Cryptpad.createRandomInteger(); + var paths = findFile(href); + var data = getFileData(href); + var key = Cryptpad.createChannelId(); + if (data) { + newData[id] = data; + } else { + newData[id] = {href: href}; + } + paths.forEach(function (p) { + var parentPath = p.slice(); + var okey = parentPath.pop(); // get the parent + var parent = find(parentPath); + if (isInTrashRoot(p)) { + parent.element = id; + newData[id].filename = p[1]; + return; + } + if (isPathIn(p, ['hrefArray'])) { + parent[okey] = id; + return; + } + // else root or trash (not trashroot) + parent[key] = id; + newData[id].filename = okey; + delete parent[okey]; + }); + }); + delete files[FILES_DATA]; + } catch(e) { + console.error(e); + } + }; + + fixUnsorted(); + migrateToNewFormat(); fixRoot(); fixTrashRoot(); if (!workgroup) { - fixUnsorted(); fixTemplate(); fixFilesData(); } @@ -948,6 +1045,5 @@ define([ return exp; }; - return module; }); diff --git a/www/drive/tests.js b/www/drive/tests.js new file mode 100644 index 000000000..922e23a57 --- /dev/null +++ b/www/drive/tests.js @@ -0,0 +1,178 @@ +define([ + '/common/cryptpad-common.js', + '/common/userObject.js', + 'json.sortify', +],function (Cryptpad, FO, sortify) { + var module = {}; + + var href1 = "/pad/#/1/edit/a798u+miu2tg5b-QaP9SvA/UIPoGUPewZscBUFhNIi+eBBM/"; + var href2 = "/poll/#/1/edit/uFJTXjQUEwV2bl-y3cKVpP/LJ-4qPnpR5iY0HVdwLcnjLsx/"; + var href3 = "/code/#/1/edit/R1kZC1mY9khSsrLCyJT+CA/jtQrCxbTiqQJ4HyUxbFBnmG8/"; + var href4 = "/slide/#/1/edit/R2bZC1mY9khSsrLCyJT+CA/mlQrCxbTiqQJ4HyUxbFBnmG8/"; + + module.test = function (assert) { + var config = {Cryptpad: Cryptpad, workgroup: false}; + assert(function (cb) { + var files = { + "root": { + "Folder": {}, + "Folder2": { + "FileName": href1 + } + }, + "template": [href3], + "trash": { + "DeletedF": [{ + "path": ["root"], + "element": {} + }, { + "path": ["root", "Folder"], + "element": href2 + }] + }, + "CryptPad_RECENTPADS": [{ + "atime": 23456783456, + "ctime": 12345678901, + "href": href3, + "title": "pewcode" + }, { + "atime": 23456789012, + "ctime": 12345789235, + "href": href2, + "title": "pewpoll" + }, { + "atime": 23456789012, + "ctime": 12345789235, + "href": href1, + "title": "pewpad" + }] + }; + var fo = FO.init(files, config); + fo.fixFiles(); + if (files['CryptPad_RECENTPADS'] || !files.filesData) { + console.log("DRIVE1: migration from RECENTPADS to filesData failed"); + return cb(); + } + var fileKey = Object.keys(files.root.Folder2)[0]; + if (!fileKey) { return cb(); } + var fileId = files.root.Folder2[fileKey]; + var res = typeof fileId === "number" + && typeof files.filesData[fileId] === "object" + && files.filesData[fileId].filename === "FileName" + && typeof files.trash.DeletedF[1].element === "number" + && typeof files.filesData[files.trash.DeletedF[1].element] === "object" + && files.filesData[files.trash.DeletedF[1].element].filename === "DeletedF" + && typeof files.template[0] === "number" + && typeof files.filesData[files.template[0]] === "object" + && !files.filesData[files.template[0]].filename + return cb(res); + }, "DRIVE1: migration and fixFiles without unsorted"); + + assert(function (cb) { + var files = { + "root": { + "Folder": {}, + "Folder2": { + "FileName": "/pad/#/1/edit/a798u+miu2tg5b-QaP9SvA/UIPoGUPewZscBUFhNIi+eBBM/" + } + }, + "unsorted": ["/code/#/1/edit/R1kZC1mY9khSsrLCyJT+CA/jtQrCxbTiqQJ4HyUxbFBnmG8/"], + "trash": {}, + "CryptPad_RECENTPADS": [{ + "atime": 23456783456, + "ctime": 12345678901, + "href": "/code/#/1/edit/R1kZC1mY9khSsrLCyJT+CA/jtQrCxbTiqQJ4HyUxbFBnmG8/", + "title": "pewcode" + }, { + "atime": 23456789012, + "ctime": 12345789235, + "href": "/pad/#/1/edit/a798u+miu2tg5b-QaP9SvA/UIPoGUPewZscBUFhNIi+eBBM/", + "title": "pewpad" + }] + }; + var fo = FO.init(files, config); + fo.fixFiles(); + if (files['CryptPad_RECENTPADS'] || !files.filesData) { + console.log("DRIVE2: migration from RECENTPADS to filesData failed"); + return cb(); + } + if (!files.template) { + console.log("DRIVE2: template is missing"); + return cb(); + } + if (files.unsorted) { + console.log("DRIVE2: unsorted not removed"); + return cb(); + } + var fileKey = Object.keys(files.root.Folder2)[0]; + var fileKey2 = Object.keys(files.root).filter(function (x) { + return typeof files.root[x] === "number" + })[0]; + if (!fileKey || !fileKey2) { return cb(); } + var fileId = files.root.Folder2[fileKey]; + var fileId2 = files.root[fileKey2]; + var res = typeof fileId === "number" + && typeof files.filesData[fileId] === "object" + && files.filesData[fileId].filename === "FileName" + && typeof fileId2 === "number" + && typeof files.filesData[fileId2] === "object" + && !files.filesData[fileId2].filename + return cb(res); + }, "DRIVE2: migration and fixFiles with unsorted"); + + assert(function (cb) { + var files = { + "root": { + "Folder": {}, + "Folder2": { + "FileName": href1 + } + }, + "template": [href3], + "trash": { + "DeletedF": [{ + "path": ["root"], + "element": { "Trash": href4 } + }, { + "path": ["root", "Folder"], + "element": href2 + }] + }, + "CryptPad_RECENTPADS": [] + }; + var fo = FO.init(files, config); + fo.fixFiles(); + if (files['CryptPad_RECENTPADS'] || !files.filesData) { + console.log("DRIVE2: migration from RECENTPADS to filesData failed"); + return cb(); + } + var fileKey = Object.keys(files.root.Folder2)[0]; + var fileKey2 = Object.keys(files.trash.DeletedF[0].element)[0]; + if (!fileKey || !fileKey2) { return cb(); } + var fileId = files.root.Folder2[fileKey]; + var fileId2 = files.trash.DeletedF[0].element[fileKey2]; + var res = typeof fileId === "number" + && typeof files.filesData[fileId] === "object" + && files.filesData[fileId].filename === "FileName" + && files.filesData[fileId].href === href1 + && typeof files.trash.DeletedF[1].element === "number" + && typeof files.filesData[files.trash.DeletedF[1].element] === "object" + && files.filesData[files.trash.DeletedF[1].element].filename === "DeletedF" + && files.filesData[files.trash.DeletedF[1].element].href === href2 + && typeof files.template[0] === "number" + && typeof files.filesData[files.template[0]] === "object" + && !files.filesData[files.template[0]].filename + && files.filesData[files.template[0]].href === href3 + && typeof fileId2 === "number" + && typeof files.filesData[fileId2] === "object" + && files.filesData[fileId2].filename === "Trash" + && files.filesData[fileId2].href === href4; + return cb(res); + }, "DRIVE4: migration and fixFiles with a pad in trash not root"); + + }; + + //TODO test with a file not in RECENTPADS + + return module; +}); From 1c2ea04f113e82e9c69c62c2b7c60b14aad5df7c Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 8 Jun 2017 17:52:00 +0200 Subject: [PATCH 02/12] Drive refactoring --- www/common/common-hash.js | 6 +- www/common/cryptpad-common.js | 90 +++---- www/common/fsStore.js | 17 +- www/common/mergeDrive.js | 79 ++----- www/common/userObject.js | 432 ++++++++++++++-------------------- www/drive/main.js | 284 ++++++++++------------ www/drive/tests.js | 204 +++++++++++++++- 7 files changed, 570 insertions(+), 542 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 5de76ab99..c4f452dc7 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -238,7 +238,8 @@ Version 1 var parsed = parsePadUrl(rHref); if (!parsed.hash) { return false; } var weaker; - recents.some(function (pad) { + Object.keys(recents).some(function (id) { + var pad = recents[id]; 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 @@ -264,7 +265,8 @@ Version 1 var parsed = parsePadUrl(rHref); if (!parsed.hash) { return false; } var stronger; - recents.some(function (pad) { + Object.keys(recents).some(function (id) { + var pad = recents[id]; 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 diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 5214fcc3b..50e99547b 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -35,7 +35,8 @@ define([ common.displayNameKey = 'cryptpad.username'; var newPadNameKey = common.newPadNameKey = "newPadName"; var newPadPathKey = common.newPadPathKey = "newPadPath"; - var storageKey = common.storageKey = 'CryptPad_RECENTPADS'; + var oldStorageKey = common.oldStorageKey = 'CryptPad_RECENTPADS'; + var storageKey = common.storageKey = 'filesData'; var PINNING_ENABLED = AppConfig.enablePinning; var store; @@ -314,7 +315,8 @@ define([ }; // Remove everything from RecentPads that is not an object and check the objects var checkRecentPads = common.checkRecentPads = function (pads) { - pads.forEach(function (pad, i) { + Object.keys(pads).forEach(function (id, i) { + var pad = pads[id]; if (pad && typeof(pad) === 'object') { var parsedHash = checkObjectData(pad); if (!parsedHash || !parsedHash.type) { @@ -334,7 +336,7 @@ define([ require(['/customize/store.js'], function(Legacy) { // TODO DEPRECATE_F Legacy.ready(function (err, legacy) { if (err) { cb(err, null); return; } - legacy.get(storageKey, function (err2, recentPads) { + legacy.get(oldStorageKey, function (err2, recentPads) { if (err2) { cb(err2, null); return; } if (Array.isArray(recentPads)) { feedback('MIGRATE_LEGACY_STORE'); @@ -348,9 +350,16 @@ define([ }; // Create untitled documents when no name is given + var getLocaleDate = common.getLocaleDate = function () { + if (window.Intl && window.Intl.DateTimeFormat) { + var options = {weekday: "short", year: "numeric", month: "long", day: "numeric"}; + return new window.Intl.DateTimeFormat(undefined, options).format(new Date()); + } + return new Date().toString().split(' ').slice(0,4).join(' '); + }; var getDefaultName = common.getDefaultName = function (parsed) { var type = parsed.type; - var name = (Messages.type)[type] + ' - ' + new Date().toString().split(' ').slice(0,4).join(' '); + var name = (Messages.type)[type] + ' - ' + getLocaleDate(); return name; }; var isDefaultName = common.isDefaultName = function (parsed, title) { @@ -358,13 +367,13 @@ define([ return title === name; }; - var makePad = function (href, title) { + var makePad = common.makePad = function (href, title) { var now = +new Date(); return { href: href, atime: now, ctime: now, - title: title || window.location.hash.slice(1, 9), + title: title || getDefaultName(parsePadUrl(href)), }; }; @@ -415,8 +424,10 @@ define([ return templates; }; common.addTemplate = function (data) { - getStore().pushData(data); - getStore().addPad(data.href, ['template']); + getStore().pushData(data, function (e, id) { + if (e) { return void console.error("Error while adding a template:", e); } // TODO LIMIT + getStore().addPad(id, ['template']); + }); }; common.isTemplate = function (href) { @@ -459,13 +470,13 @@ define([ // STORAGE /* fetch and migrate your pad history from the store */ var getRecentPads = common.getRecentPads = function (cb) { - getStore().getDrive(storageKey, function (err, recentPads) { - if (Array.isArray(recentPads)) { + getStore().getDrive('filesData', function (err, recentPads) { + if (typeof(recentPads) === "object") { checkRecentPads(recentPads); cb(void 0, recentPads); return; } - cb(void 0, []); + cb(void 0, {}); }); }; @@ -491,48 +502,13 @@ define([ common.forgetPad = function (href, cb) { var parsed = parsePadUrl(href); - var callback = function (err) { - if (err) { - cb(err); - return; - } - - getStore().keys(function (err, keys) { - if (err) { - cb(err); - return; - } - var toRemove = keys.filter(function (k) { - return k.indexOf(parsed.hash) === 0; - }); - - if (!toRemove.length) { - cb(); - return; - } - getStore().removeBatch(toRemove, function (err, data) { - cb(err, data); - }); - }); - }; - if (typeof(getStore().forgetPad) === "function") { - getStore().forgetPad(common.getRelativeHref(href), callback); + getStore().forgetPad(common.getRelativeHref(href), cb); + return; } + cb ("store.forgetPad is not a function"); }; - 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); - } - }); - }; common.setPadTitle = function (name, cb) { var href = window.location.href; var parsed = parsePadUrl(href); @@ -548,7 +524,8 @@ define([ var updateWeaker = []; var contains; - recent.forEach(function (pad) { + Object.keys(recent).forEach(function (id) { + var pad = recent[id]; var p = parsePadUrl(pad.href); if (p.type !== parsed.type) { return pad; } @@ -590,14 +567,13 @@ define([ }); } pad.href = href; - updateFileName(href, old, name); } return pad; }); if (!contains && href) { var data = makePad(href, name); - getStore().pushData(data, function (e) { + getStore().pushData(data, function (e, id) { if (e) { if (e === 'E_OVER_LIMIT' && AppConfig.enablePinLimit) { common.alert(Messages.pinLimitNotPinned, null, true); @@ -605,12 +581,14 @@ define([ } else { throw new Error("Cannot push this pad to CryptDrive", e); } } - getStore().addPad(data, common.initialPath); + getStore().addPad(id, common.initialPath); }); } if (updateWeaker.length > 0) { updateWeaker.forEach(function (obj) { - getStore().replaceHref(obj.o, obj.n); + // If we have a stronger url, and if all the occurences of the weaker were + // in the trash, add remove them from the trash and add the stronger in root + getStore().restoreHref(obj.n); }); } cb(err, recent); @@ -664,7 +642,9 @@ define([ var userChannel = userParsedHash && userParsedHash.channel; if (!userChannel) { return null; } - var list = fo.getFiles([fo.FILES_DATA]).map(hrefToHexChannelId) + var list = fo.getFiles([fo.FILES_DATA]).map(function (id) { + return hrefToHexChannelId(fo.getFileData(id).href); + }) .filter(function (x) { return x; }); list.push(common.base64ToHex(userChannel)); diff --git a/www/common/fsStore.js b/www/common/fsStore.js index b20c38c48..4e5f2f056 100644 --- a/www/common/fsStore.js +++ b/www/common/fsStore.js @@ -87,10 +87,7 @@ define([ ret.removeData = filesOp.removeData; ret.pushData = filesOp.pushData; - - ret.addPad = function (data, path) { - filesOp.add(data, path); - }; + ret.addPad = filesOp.add; ret.forgetPad = function (href, cb) { filesOp.forget(href); @@ -123,9 +120,9 @@ define([ return filesOp.getStructure(); }; - ret.replaceHref = function (o, n) { - return filesOp.replace(o, n); - }; + ret.replace = filesOp.replace; + + ret.restoreHref = filesOp.restoreHref; ret.changeHandlers = []; @@ -270,9 +267,11 @@ define([ if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; } var drive = rt.proxy.drive; // Creating a new anon drive: import anon pads from localStorage - if (!drive[Cryptpad.storageKey] || !Cryptpad.isArray(drive[Cryptpad.storageKey])) { + if ((!drive[Cryptpad.oldStorageKey] || !Cryptpad.isArray(drive[Cryptpad.oldStorageKey])) + && !drive['filesData']) { Cryptpad.getLegacyPads(function (err, data) { - drive[Cryptpad.storageKey] = data; + drive[Cryptpad.oldStorageKey] = data; + console.log(drive); onReady(f, rt.proxy, Cryptpad, exp); }); return; diff --git a/www/common/mergeDrive.js b/www/common/mergeDrive.js index 2f4632544..05603f56c 100644 --- a/www/common/mergeDrive.js +++ b/www/common/mergeDrive.js @@ -22,61 +22,17 @@ define([ return nkey; }; - var copy = function (el) { - if (typeof (el) !== "object") { return el; } - return JSON.parse(JSON.stringify(el)); - }; - - var deduplicate = function (array) { - var a = array.slice(); - for(var i=0; i= 4) { - _findFileInRoot(path, href).forEach(addPaths); + _findFileInRoot(path, file).forEach(addPaths); } return paths; }; - var findFile = exp.findFile = function (href) { - var rootpaths = _findFileInRoot([ROOT], href); - var templatepaths = _findFileInHrefArray(TEMPLATE, href); - var trashpaths = _findFileInTrash([TRASH], href); + var findFile = exp.findFile = function (file) { + var rootpaths = _findFileInRoot([ROOT], file); + var templatepaths = _findFileInHrefArray(TEMPLATE, file); + var trashpaths = _findFileInTrash([TRASH], file); return rootpaths.concat(templatepaths, trashpaths); }; 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); + var allFilesList = files[FILES_DATA]; + var lValue = value.toLowerCase(); + getFiles([FILES_DATA]).forEach(function (id) { + var data = allFilesList[id]; + if ((data.title && data.title.toLowerCase().indexOf(lValue) !== -1) || + (data.filename && data.filename.toLowerCase().indexOf(lValue) !== -1)) { + res.push(id); } }); // Search Href var href = Cryptpad.getRelativeHref(value); if (href) { - res.push(href); + var id = getIdFromHref(href); + if (id) { res.push(id); } } res = Cryptpad.deduplicateString(res); @@ -442,24 +418,21 @@ define([ var pushFileData = exp.pushData = function (data, cb) { if (typeof cb !== "function") { cb = function () {}; } var todo = function () { - files[FILES_DATA].push(data); - cb(); + var id = Cryptpad.createRandomInteger(); + files[FILES_DATA][id] = data; + cb(null, id); }; - if (!Cryptpad.isLoggedIn() || !AppConfig.enablePinning) { return void todo(); } + if (!Cryptpad.isLoggedIn() || !AppConfig.enablePinning || config.testMode) { + return void todo(); + } Cryptpad.pinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e) { if (e) { return void cb(e); } todo(); }); }; - var spliceFileData = exp.removeData = function (idx) { - var data = files[FILES_DATA][idx]; - if (typeof data === "object" && Cryptpad.isLoggedIn() && AppConfig.enablePinning) { - Cryptpad.unpinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e, hash) { - if (e) { return void logError(e); } - debug('UNPIN', hash); - }); - } - files[FILES_DATA].splice(idx, 1); + var spliceFileData = exp.removeData = function (id) { + files[FILES_DATA][id] = undefined; + delete files[FILES_DATA][id]; }; // MOVE @@ -506,16 +479,10 @@ define([ } } // 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 = !isPathIn(elementPath, [ROOT]) ? getAvailableName(newParent, name) : name; + var newName = isFile(element) ? + getAvailableName(newParent, Cryptpad.createChannelId()) : + isInTrashRoot(elementPath) ? + elementPath[1] : elementPath.pop(); if (typeof(newParent[newName]) !== "undefined") { log(Messages.fo_unavailableName); @@ -536,7 +503,7 @@ define([ return; } // Try to copy, and if success, remove the element from the old location - if (copyElement(p, newPath)) { + if (copyElement(p.slice(), newPath)) { toRemove.push(p); } }); @@ -552,11 +519,10 @@ define([ // ADD - var add = exp.add = function (data, path) { + var add = exp.add = function (id, path) { if (!Cryptpad.isLoggedIn()) { return; } + var data = files[FILES_DATA][id]; 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(','); @@ -564,43 +530,20 @@ define([ // Add to href array if (path && isPathIn(newPath, ['hrefArray'])) { parentEl = find(newPath); - parentEl.push(href); + parentEl.push(id); return; } // 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) { + if (path && isPathIn(newPath, [ROOT]) || filesList.indexOf(href) === -1) { parentEl = find(newPath || [ROOT]); if (parentEl) { - var newName = getAvailableName(parentEl, name); - parentEl[newName] = href; + var newName = getAvailableName(parentEl, Cryptpad.createChannelId()); + parentEl[newName] = id; return; } } }; - 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(); - - pushFileData({ - href: href, - title: fileName, - atime: +new Date(), - ctime: +new Date() - }, function (err) { - if (err) { - logError(err); - return void cb(err); - } - parentEl[fileName] = href; - var newPath = filePath.slice(); - newPath.push(fileName); - cb(void 0, { - newPath: newPath - }); - }); - }; exp.addFolder = function (folderPath, name, cb) { var parentEl = find(folderPath); var folderName = getAvailableName(parentEl, name || NEW_FOLDER_NAME); @@ -614,19 +557,15 @@ define([ // FORGET (move with href not path) exp.forget = function (href) { + var id = getIdFromHref(href); + if (!id) { return; } if (!Cryptpad.isLoggedIn()) { // delete permanently - var data = getFileData(href); - if (data) { - var i = find([FILES_DATA]).indexOf(data); - if (i !== -1) { - exp.removePadAttribute(href); - spliceFileData(i); - } - } + exp.removePadAttribute(href); + spliceFileData(id); return; } - var paths = findFile(href); + var paths = findFile(id); move(paths, [TRASH]); }; @@ -648,29 +587,21 @@ define([ }); }; var checkDeletedFiles = function () { - // Nothing in FILES_DATA for workgroups + // Nothing in OLD_FILES_DATA for workgroups if (workgroup || !Cryptpad.isLoggedIn()) { 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 fData = files[FILES_DATA]; + getFiles([FILES_DATA]).forEach(function (id) { + if (filesList.indexOf(id) === -1) { + removePadAttribute(fData[id].href); + spliceFileData(id); } }); }; - var deleteHrefs = function (hrefs) { - hrefs.forEach(function (obj) { - var idx = files[obj.root].indexOf(obj.href); + var deleteHrefs = function (ids) { + ids.forEach(function (obj) { + var idx = files[obj.root].indexOf(obj.id); files[obj.root].splice(idx, 1); }); }; @@ -690,27 +621,24 @@ define([ var toSplice = []; allFilesPaths.forEach(function (path) { var el = find(path); - toSplice.push(el); - }); - toSplice.forEach(function (el) { - var i = find([FILES_DATA]).indexOf(el); - if (i === -1) { return; } + if (!el) { return; } + var id = getIdFromHref(el.href); + if (!id) { return; } + spliceFileData(id); removePadAttribute(el.href); - console.log(el.href); - spliceFileData(i); }); return; } - var hrefs = []; + var ids = []; hrefPaths.forEach(function (path) { - var href = find(path); - hrefs.push({ + var id = find(path); + ids.push({ root: path[0], - href: href + id: id }); }); - deleteHrefs(hrefs); + deleteHrefs(ids); rootPaths.forEach(function (path) { var parentPath = path.slice(); @@ -741,7 +669,7 @@ define([ deleteMultipleTrashRoot(trashRoot); // In some cases, we want to remove pads from a location without removing them from - // FILES_DATA (replaceHref) + // OLD_FILES_DATA (replaceHref) if (!nocheck) { checkDeletedFiles(); } }; exp.delete = function (paths, cb, nocheck) { @@ -760,65 +688,70 @@ define([ 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) { + + // Folders + if (isFolder(element)) { + var parentPath = path.slice(); + var oldName = parentPath.pop(); + if (!newName || !newName.trim() || 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]; + if (typeof cb === "function") { cb(); } return; } - var parentEl = find(parentPath); - if (typeof(parentEl[newName]) !== "undefined") { - log(Messages.fo_existingNameError); + + // Files + var data = files[FILES_DATA][element]; + if (!data) { return; } + if (!newName || newName.trim() === "") { + data.filename = undefined; + delete data.filename; + if (typeof cb === "function") { cb(); } return; } - parentEl[newName] = element; - parentEl[oldName] = undefined; - delete parentEl[oldName]; + var oldName = getTitle(element, 'name'); + if (oldName === newName) { return; } + data.filename = newName; if (typeof cb === "function") { 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) exp.replace = function (o, n) { - if (!isFile(o) || !isFile(n)) { return; } - var paths = findFile(o); + var idO = getIdFromHref(o); + if (!idO || !isFile(idO)) { return; } + var data = getFileData(idO); + if (!data) { return; } + data.href = n; + }; + // If all the occurences of an href are in the trash, remvoe them and add the file in root. + // This is use with setPadTitle when we open a stronger version of a deleted pad + exp.restoreHref = function (href) { + var idO = getIdFromHref(href); + + if (!idO || !isFile(idO)) { return; } + + var paths = findFile(idO); // 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 + // If all the occurences are in the trash or no occurence, add the pad to root var allInTrash = true; paths.forEach(function (p) { if (p[0] === TRASH) { exp.delete(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; } + allInTrash = false; }); if (allInTrash) { - add(n); + add(idO); } }; @@ -831,7 +764,7 @@ define([ // * '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. + // * OLD_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' // * TEMPLATE: Contains only files (href), and does not contains files that are in ROOT @@ -843,7 +776,7 @@ define([ 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])) { + if (!isFile(element[el], true) && !isFolder(element[el])) { debug("An element in ROOT was not a folder nor a file. ", element[el]); element[el] = undefined; delete element[el]; @@ -857,7 +790,7 @@ define([ // We have an old file (href) which is not in filesData: add it var id = Cryptpad.createRandomInteger(); var key = Cryptpad.createChannelId(); - files[NEW_FILES_DATA][id] = {href: element[el], filename: el}; + files[FILES_DATA][id] = {href: element[el], filename: el}; element[key] = id; delete element[el]; } @@ -869,21 +802,25 @@ define([ var toClean; var addToClean = function (obj, idx, el) { if (typeof(obj) !== "object") { toClean.push(idx); return; } - if (!isFile(obj.element) && !isFolder(obj.element)) { toClean.push(idx); return; } + if (!isFile(obj.element, true) && !isFolder(obj.element)) { toClean.push(idx); return; } if (!$.isArray(obj.path)) { toClean.push(idx); return; } if (typeof obj.element === "string") { // We have an old file (href) which is not in filesData: add it var id = Cryptpad.createRandomInteger(); - files[NEW_FILES_DATA][id] = {href: obj.element, filename: el}; + files[FILES_DATA][id] = {href: obj.element, filename: el}; obj.element = id; } if (isFolder(obj.element)) { fixRoot(obj.element); } }; for (var el in tr) { - if (!$.isArray(tr[el])) { + if (!Array.isArray(tr[el])) { debug("An element in TRASH root is not an array. ", tr[el]); tr[el] = undefined; delete tr[el]; + } else if (tr[el].length === 0) { + debug("Empty array in TRASH root. ", tr[el]); + tr[el] = undefined; + delete tr[el]; } else { toClean = []; tr[el].forEach(function (obj, idx) { addToClean(obj, idx, el); }); @@ -900,13 +837,13 @@ define([ var rootFiles = getFiles([ROOT]).slice(); var toClean = []; us.forEach(function (el, idx) { - if (!isFile(el) || rootFiles.indexOf(el) !== -1) { + if (!isFile(el, true) || rootFiles.indexOf(el) !== -1) { toClean.push(idx); } if (typeof el === "string") { // We have an old file (href) which is not in filesData: add it var id = Cryptpad.createRandomInteger(); - files[NEW_FILES_DATA][id] = {href: el}; + files[FILES_DATA][id] = {href: el}; us[idx] = id; } }); @@ -915,8 +852,8 @@ define([ }); }; var fixFilesData = function () { - if (typeof files[NEW_FILES_DATA] !== "object") { debug("FILES_DATA was not an object"); files[NEW_FILES_DATA] = {}; } - var fd = files[NEW_FILES_DATA]; + if (typeof files[FILES_DATA] !== "object") { debug("OLD_FILES_DATA was not an object"); files[FILES_DATA] = {}; } + var fd = files[FILES_DATA]; var rootFiles = getFiles([ROOT, TRASH, 'hrefArray']); var root = find([ROOT]); var toClean = []; @@ -925,12 +862,12 @@ define([ var el = fd[id]; if (!el || typeof(el) !== "object") { debug("An element in filesData was not an object.", el); - toClean.push(el); + toClean.push(id); continue; } if (!el.href) { debug("Removing an element in filesData with a missing href.", el); - toClean.push(el); + toClean.push(id); continue; } if (Cryptpad.isLoggedIn() && rootFiles.indexOf(id) === -1) { @@ -940,19 +877,16 @@ define([ continue; } }; - toClean.forEach(function (el) { - var idx = fd.indexOf(el); - if (idx !== -1) { - spliceFileData(idx); - } + toClean.forEach(function (id) { + spliceFileData(id); }); }; // Make sure unsorted doesn't exist anymore - // Note: Unsorted only works with ld structure where pads are href + // Note: Unsorted only works with the old structure where pads are href // It should be called before the migration code var fixUnsorted = function () { - if (!files[UNSORTED] || !files[FILES_DATA]) { return; } + if (!files[UNSORTED] || !files[OLD_FILES_DATA]) { return; } debug("UNSORTED still exists in the object, removing it..."); var us = files[UNSORTED]; if (us.length === 0) { @@ -964,38 +898,34 @@ define([ if (typeof el !== "string") { return; } - var data = files[FILES_DATA].filter(function (x) { + var data = files[OLD_FILES_DATA].filter(function (x) { return x.href === el; }); if (data.length === 0) { - files[FILES_DATA].push({ + files[OLD_FILES_DATA].push({ href: el }); } return; - /* - TODO remove - var name = data.length !== 0 ? data[0].title : NEW_FILE_NAME; - var newName = getAvailableName(root, name); - root[newName] = el; - */ }); delete files[UNSORTED]; }; - // TODO: getRecentPads in cryptpad-common - // TODO: in fixRoot, trash and template, make sure we have a filedata associated to the id + // mergeDrive... var migrateToNewFormat = function () { - if (!files[FILES_DATA]) { return; } + if (!files[OLD_FILES_DATA]) { return; } try { - var oldData = files[FILES_DATA].slice(); - var newData = files[NEW_FILES_DATA] = {}; + var oldData = files[OLD_FILES_DATA].slice(); + if (!files[FILES_DATA]) { + files[FILES_DATA] = {}; + } + var newData = files[FILES_DATA]; //var oldFiles = oldData.map(function (o) { return o.href; }); oldData.forEach(function (obj) { if (!obj || !obj.href) { return; } var href = obj.href; var id = Cryptpad.createRandomInteger(); var paths = findFile(href); - var data = getFileData(href); + var data = obj; var key = Cryptpad.createChannelId(); if (data) { newData[id] = data; @@ -1021,7 +951,7 @@ define([ delete parent[okey]; }); }); - delete files[FILES_DATA]; + delete files[OLD_FILES_DATA]; } catch(e) { console.error(e); } diff --git a/www/drive/main.js b/www/drive/main.js index 5343bd829..1a6c77bf2 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -484,6 +484,7 @@ define([ }; var getDate = function (sDate) { + if (!sDate) { return ''; } var ret = sDate.toString(); try { var date = new Date(sDate); @@ -500,12 +501,15 @@ define([ return ret; }; - var openFile = function (fileEl, name) { - if (name) { - sessionStorage[Cryptpad.newPadNameKey] = name; + var openFile = function (el, href) { + if (!href) { + var data = filesOp.getFileData(el); + if (!data || !data.href) { + return void logError("Missing data for the file", el, data); + } + href = data.href; } - window.open(fileEl); - delete sessionStorage[Cryptpad.newPadNameKey]; + window.open(href); }; var refresh = APP.refresh = function () { @@ -528,7 +532,8 @@ define([ $name = $element.find('> .element'); } $name.hide(); - var name = path[path.length - 1]; + var el = filesOp.find(path); + var name = filesOp.isFile(el) ? filesOp.getTitle(el) : path[path.length - 1]; var $input = $('', { placeholder: name, value: name @@ -933,19 +938,9 @@ define([ }; var getElementName = function (path) { - // Trash root - if (filesOp.isInTrashRoot(path)) { return path[0]; } - // Root or trash - if (filesOp.isPathIn(path, [ROOT, TRASH])) { return path[path.length - 1]; } - // Unsorted or template - if (filesOp.isPathIn(path, ['hrefArray'])) { - var file = filesOp.find(path); - if (filesOp.isFile(file) && filesOp.getTitle(file)) { - return filesOp.getTitle(file); - } - } - // default - return "???"; + var file = filesOp.find(path); + if (!file || !filesOp.isFile(file)) { return '???'; } + return filesOp.getTitle(file); }; // filesOp.moveElements is able to move several paths to a new location, including // the Trash or the "Unsorted files" folder @@ -965,8 +960,9 @@ define([ } var msg = Messages._getKey('fm_removeSeveralDialog', [paths.length]); if (paths.length === 1) { - var path = paths[0]; - var name = path[0] === TEMPLATE ? filesOp.getTitle(filesOp.find(path)) : path[path.length - 1]; + var path = paths[0].slice(); + var el = filesOp.find(path); + var name = filesOp.isFile(el) ? getElementName(path) : path.pop(); msg = Messages._getKey('fm_removeDialog', [name]); } Cryptpad.confirm(msg, function (res) { @@ -1093,30 +1089,27 @@ define([ // In list mode, display metadata from the filesData object // _WORKGROUP_ : Do not display title, atime and ctime columns since we don't have files data - var addFileData = function (element, key, $span, displayTitle) { + var addFileData = function (element, $span) { if (!filesOp.isFile(element)) { return; } + var data = filesOp.getFileData(element); + if (!data) { return void logError("No data for the file", element); } + + var name = filesOp.getTitle(element); + // The element with the class '.name' is underlined when the 'li' is hovered - var $name = $('', {'class': 'name', title: key}).text(key); + var $name = $('', {'class': 'name', title: name}).text(name); $span.html(''); $span.append($name); - if (!filesOp.getFileData(element)) { - return; - } - var hrefData = Cryptpad.parsePadUrl(element); - var data = filesOp.getFileData(element); + var hrefData = Cryptpad.parsePadUrl(data.href); var type = Messages.type[hrefData.type] || hrefData.type; - var $title = $('', {'class': 'title listElement', title: data.title}).text(data.title); var $type = $('', {'class': 'type listElement', title: type}).text(type); if (hrefData.hashData && hrefData.hashData.mode === 'view') { $type.append(' (' + Messages.readonly+ ')'); } var $adate = $('', {'class': 'atime listElement', title: getDate(data.atime)}).text(getDate(data.atime)); var $cdate = $('', {'class': 'ctime listElement', title: getDate(data.ctime)}).text(getDate(data.ctime)); - if (displayTitle && !isWorkgroup()) { - $span.append($title); - } $span.append($type); if (!isWorkgroup()) { $span.append($adate).append($cdate); @@ -1135,9 +1128,15 @@ define([ $span.append($name).append($subfolders).append($files); }; - var getFileIcon = function (href) { + var getFileIcon = function (id) { var $icon = $fileIcon.clone(); + var data = filesOp.getFileData(id); + if (!data) { return $icon; } + + var href = data.href; + if (!href) { return $icon; } + if (href.indexOf('/pad/') !== -1) { $icon = $padIcon.clone(); } else if (href.indexOf('/code/') !== -1) { $icon = $codeIcon.clone(); } else if (href.indexOf('/slide/') !== -1) { $icon = $slideIcon.clone(); } @@ -1153,7 +1152,7 @@ define([ var isTrash = path[0] === TRASH; var newPath = path.slice(); var key; - if (isTrash && $.isArray(elPath)) { + if (isTrash && Array.isArray(elPath)) { key = elPath[0]; elPath.forEach(function (k) { newPath.push(k); }); } else { @@ -1178,7 +1177,7 @@ define([ if (isFolder) { addFolderData(element, key, $element); } else { - addFileData(element, key, $element, true); + addFileData(element, $element); } $element.prepend($icon).dblclick(function () { if (isFolder) { @@ -1186,7 +1185,7 @@ define([ return; } if (isTrash) { return; } - openFile(root[key], key); + openFile(root[key]); }); $element.addClass(liClass); $element.data('path', newPath); @@ -1251,10 +1250,13 @@ define([ if (APP.mobile()) { return $title; } + var el = path[0] === SEARCH ? undefined : filesOp.find(path); path = path[0] === SEARCH ? path.slice(0,1) : path; path.forEach(function (p, idx) { if (isTrash && [2,3].indexOf(idx) !== -1) { return; } + var name = p; + var $span = $('', {'class': 'element'}); if (idx < path.length - 1) { if (!noStyle) { @@ -1265,9 +1267,10 @@ define([ module.displayDirectory(path.slice(0, sliceEnd)); }); } + } else if (idx > 0 && filesOp.isFile(el)) { + name = getElementName(path); } - var name = p; if (idx === 0) { name = getPrettyName(p); } else { $title.append(' > '); } @@ -1409,18 +1412,12 @@ define([ $block.find('a.newFolder').click(function () { filesOp.addFolder(currentPath, null, onCreated); }); - $block.find('a.newdoc').click(function () { - var type = $(this).attr('data-type') || 'pad'; - var name = Cryptpad.getDefaultName({type: type}); - filesOp.addFile(currentPath, name, type, onCreated); - }); - } else { - $block.find('a.newdoc').click(function () { - var type = $(this).attr('data-type') || 'pad'; - sessionStorage[Cryptpad.newPadPathKey] = filesOp.isPathIn(currentPath, [TRASH]) ? '' : currentPath; - window.open('/' + type + '/'); - }); } + $block.find('a.newdoc').click(function () { + var type = $(this).attr('data-type') || 'pad'; + sessionStorage[Cryptpad.newPadPathKey] = filesOp.isPathIn(currentPath, [TRASH]) ? '' : currentPath; + window.open('/' + type + '/'); + }); return $block; }; @@ -1515,62 +1512,39 @@ define([ //var $fihElement = $('', {'class': 'element'}).appendTo($fileHeader); var $fhIcon = $('', {'class': 'icon'}); var $fhName = $('', {'class': 'name filename clickable'}).text(Messages.fm_fileName).click(onSortByClick); - var $fhTitle = $('', {'class': 'title clickable'}).text(Messages.fm_title).click(onSortByClick); var $fhType = $('', {'class': 'type clickable'}).text(Messages.fm_type).click(onSortByClick); var $fhAdate = $('', {'class': 'atime clickable'}).text(Messages.fm_lastAccess).click(onSortByClick); var $fhCdate = $('', {'class': 'ctime clickable'}).text(Messages.fm_creation).click(onSortByClick); // If displayTitle is false, it means the "name" is the title, so do not display the "name" header - $fihElement.append($fhIcon); - if (displayTitle || isWorkgroup()) { - $fihElement.append($fhName); - } else { - $fhTitle.width('auto'); - } - if (!isWorkgroup()) { - $fihElement.append($fhTitle); - } - $fihElement.append($fhType); + $fihElement.append($fhIcon).append($fhName).append($fhType); if (!isWorkgroup()) { $fihElement.append($fhAdate).append($fhCdate); } addFileSortIcon($fihElement); return $fihElement; - //return $fileHeader; }; - var sortElements = function (folder, path, oldkeys, prop, asc, useHref, useData) { + var sortElements = function (folder, path, oldkeys, prop, asc, useId) { var root = filesOp.find(path); var test = folder ? filesOp.isFolder : filesOp.isFile; - var keys; - if (!useData) { - keys = oldkeys.filter(function (e) { - return useHref ? test(e) : test(root[e]); - }); - } else { keys = oldkeys.slice(); } + var keys = oldkeys.filter(function (e) { + return useId ? test(e) : test(root[e]); + }); if (keys.length < 2) { return keys; } var mult = asc ? 1 : -1; var getProp = function (el, prop) { - if (prop) { - var element = useHref || useData ? el : root[el]; - var e = useData ? element : filesOp.getFileData(element); - if (!e) { - e = { - href : element, - title : Messages.fm_noname, - atime : 0, - ctime : 0 - }; - } - if (prop === 'type') { - var hrefData = Cryptpad.parsePadUrl(e.href); - return hrefData.type; - } - if (prop === 'atime' || prop === 'ctime') { - return new Date(e[prop]); - } - return e && e.title ? e.title.toLowerCase() : ''; + if (folder) { return el.toLowerCase(); } + var id = useId ? el : root[el]; + var data = filesOp.getFileData(id); + if (!data) { return ''; } + if (prop === 'type') { + var hrefData = Cryptpad.parsePadUrl(data.href); + return hrefData.type; } - return useData ? el.title.toLowerCase() : el.toLowerCase(); + if (prop === 'atime' || prop === 'ctime') { + return new Date(data[prop]); + } + return filesOp.getTitle(id).toLowerCase(); }; keys.sort(function(a, b) { if (getProp(a, prop) < getProp(b, prop)) { return mult * -1; } @@ -1580,7 +1554,6 @@ define([ return keys; }; var sortTrashElements = function (folder, oldkeys, prop, asc) { - //var root = files[TRASH]; var test = folder ? filesOp.isFolder : filesOp.isFile; var keys = oldkeys.filter(function (e) { return test(e.element); @@ -1606,7 +1579,6 @@ define([ if (prop === 'atime' || prop === 'ctime') { return new Date(e[prop]); } - return e.title.toLowerCase(); } return el.name.toLowerCase(); }; @@ -1638,27 +1610,27 @@ define([ $container.append($fileHeader); var keys = unsorted; var sortBy = Cryptpad.getLSAttribute(SORT_FILE_BY); - sortBy = sortBy === "" ? sortBy = 'title' : sortBy; + sortBy = sortBy === "" ? sortBy = 'name' : sortBy; var sortedFiles = sortElements(false, [rootName], keys, sortBy, !getSortFileDesc(), true); - sortedFiles.forEach(function (href) { - var file = filesOp.getFileData(href); + sortedFiles.forEach(function (id) { + var file = filesOp.getFileData(id); if (!file) { //debug("Unsorted or template returns an element not present in filesData: ", href); file = { title: Messages.fm_noname }; //return; } - var idx = files[rootName].indexOf(href); - var $icon = getFileIcon(href); - var ro = filesOp.isReadOnlyFile(href); + var idx = files[rootName].indexOf(id); + var $icon = getFileIcon(id); + var ro = filesOp.isReadOnlyFile(id); // ro undefined mens it's an old hash which doesn't support read-only var roClass = typeof(ro) === 'undefined' ? ' noreadonly' : ro ? ' readonly' : ''; var $element = $('
  • ', { 'class': 'file-element element element-row' + roClass, draggable: draggable }); - addFileData(href, file.title, $element, false); + addFileData(id, $element); $element.prepend($icon).dblclick(function () { - openFile(href); + openFile(id); }); var path = [rootName, idx]; $element.data('path', path); @@ -1680,20 +1652,20 @@ define([ if (allfiles.length === 0) { return; } var $fileHeader = getFileListHeader(false); $container.append($fileHeader); - var keys = allfiles; - - var sortedFiles = sortElements(false, [FILES_DATA], keys, Cryptpad.getLSAttribute(SORT_FILE_BY), !getSortFileDesc(), false, true); - sortedFiles.forEach(function (file) { - var $icon = getFileIcon(file.href); - var ro = filesOp.isReadOnlyFile(file.href); - // ro undefined mens it's an old hash which doesn't support read-only + var keys = filesOp.getFiles([FILES_DATA]); + var sortedFiles = sortElements(false, [FILES_DATA], keys, Cryptpad.getLSAttribute(SORT_FILE_BY), !getSortFileDesc(), true); + sortedFiles.forEach(function (id) { + var file = filesOp.getFileData(id); + var $icon = getFileIcon(id); + var ro = filesOp.isReadOnlyFile(id); + // ro undefined maens it's an old hash which doesn't support read-only var roClass = typeof(ro) === 'undefined' ? ' noreadonly' : ro ? ' readonly' : ''; var $element = $('
  • ', { 'class': 'file-element element element-row' + roClass }); - addFileData(file.href, file.title, $element, false); - $element.data('path', [FILES_DATA, allfiles.indexOf(file)]); - $element.data('element', file.href); + addFileData(id, $element); + $element.data('path', [FILES_DATA, id]); + $element.data('element', id); $element.prepend($icon).dblclick(function () { - openFile(file.href); + openFile(id); }); $element.click(function(e) { e.stopPropagation(); @@ -1709,24 +1681,18 @@ define([ var filesList = []; var root = files[TRASH]; // Elements in the trash are JS arrays (several elements can have the same name) - [true,false].forEach(function (folder) { - var testElement = filesOp.isFile; - if (!folder) { - testElement = filesOp.isFolder; + Object.keys(root).forEach(function (key) { + if (!Array.isArray(root[key])) { + logError("Trash element has a wrong type", root[key]); + return; } - Object.keys(root).forEach(function (key) { - if (!$.isArray(root[key])) { - logError("Trash element has a wrong type", root[key]); - return; - } - root[key].forEach(function (el, idx) { - if (testElement(el.element)) { return; } - var spath = [key, idx, 'element']; - filesList.push({ - element: el.element, - spath: spath, - name: key - }); + root[key].forEach(function (el, idx) { + if (!filesOp.isFile(el.element) && !filesOp.isFolder(el.element)) { return; } + var spath = [key, idx, 'element']; + filesList.push({ + element: el.element, + spath: spath, + name: key }); }); }); @@ -1752,8 +1718,9 @@ define([ var parsed = Cryptpad.parsePadUrl(href); var $table = $(''); var $icon = $('
    ', {'rowspan': '3', 'class': 'icon'}).append(getFileIcon(href)); - var $title = $('', {'class': 'col1 title'}).text(r.data.title).click(function () { - openFile(r.data.href); + var $title = $('', {'class': 'col1 title'}).text(r.data.title) + .click(function () { + openFile(null, r.data.href); }); var $typeName = $('', {'class': 'label2'}).text(Messages.fm_type); var $type = $('', {'class': 'col2'}).text(Messages.type[parsed.type] || parsed.type); @@ -2141,29 +2108,12 @@ define([ }; var stringifyPath = function (path) { - if (!$.isArray(path)) { return; } - var rootName = function (s) { - var prettyName; - switch (s) { - case ROOT: - prettyName = ROOT_NAME; - break; - case FILES_DATA: - prettyName = FILES_DATA_NAME; - break; - case TRASH: - prettyName = TRASH_NAME; - break; - default: - prettyName = s; - } - return prettyName; - }; + if (!Array.isArray(path)) { return; } var $div = $('
    '); var i = 0; var space = 10; path.forEach(function (s) { - if (i === 0) { s = rootName(s); } + if (i === 0) { s = getPrettyName(s); } $div.append($('', {'style': 'margin: 0 0 0 ' + i * space + 'px;'}).text(s)); $div.append($('
    ')); i++; @@ -2171,11 +2121,15 @@ define([ return $div.html(); }; - var getReadOnlyUrl = APP.getRO = function (href) { - if (!filesOp.isFile(href)) { return; } - var i = href.indexOf('#') + 1; - var parsed = Cryptpad.parsePadUrl(href); - var base = href.slice(0, i); + var getReadOnlyUrl = APP.getRO = function (id) { + if (!filesOp.isFile(id)) { return; } + var data = filesOp.getFileData(id); + if (!data) { return; } + var parsed = Cryptpad.parsePadUrl(data.href); + if (parsed.hashData.type !== "pad") { return; } + var origin = window.location.origin; + var i = data.href.indexOf('#') + 1; + var base = origin + data.href.slice(0, i); var hrefsecret = Cryptpad.getSecrets(parsed.type, parsed.hash); if (!hrefsecret.keys) { return; } var viewHash = Cryptpad.getViewHashFromKeys(hrefsecret.channel, hrefsecret.keys); @@ -2199,16 +2153,21 @@ define([ var base = window.location.origin; var $d = $('
    '); $('').text(Messages.fc_prop).appendTo($d); + + var data = filesOp.getFileData(el); + if (!data || !data.href) { return void cb(void 0, $d); } + $('
    ').appendTo($d); if (!ro) { $('