diff --git a/www/assert/main.js b/www/assert/main.js index d47ec0210..7124f58d9 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -4,8 +4,9 @@ define([ '/bower_components/textpatcher/TextPatcher.amd.js', 'json.sortify', '/common/cryptpad-common.js', + '/drive/tests.js', '/common/test.js' -], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad, Test) { +], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad, Drive, Test) { window.Hyperjson = Hyperjson; window.TextPatcher = TextPatcher; window.Sortify = Sortify; @@ -206,6 +207,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/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 d90cb327d..fcefbd0fe 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -42,7 +42,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'; + common.storageKey = 'filesData'; var PINNING_ENABLED = AppConfig.enablePinning; var store; @@ -327,7 +328,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) { @@ -347,7 +349,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'); @@ -361,23 +363,30 @@ 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) { + common.isDefaultName = function (parsed, title) { var name = getDefaultName(parsed); 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)), }; }; @@ -428,8 +437,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) { @@ -472,13 +483,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, {}); }); }; @@ -502,50 +513,13 @@ define([ // STORAGE 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); @@ -561,7 +535,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; } @@ -592,7 +567,6 @@ 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 @@ -603,14 +577,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') { common.alert(Messages.pinLimitNotPinned, null, true); @@ -618,12 +591,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); @@ -677,7 +652,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 35bd9a048..855d8b4f5 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 = []; @@ -144,76 +141,81 @@ define([ var onReady = function (f, proxy, Cryptpad, exp) { var fo = exp.fo = FO.init(proxy.drive, { - Cryptpad: Cryptpad + Cryptpad: Cryptpad, + rt: exp.realtime }); + var todo = function () { + fo.fixFiles(); - //storeObj = proxy; - store = initStore(fo, proxy, exp); - if (typeof(f) === 'function') { - f(void 0, store); - } + //storeObj = proxy; + store = initStore(fo, proxy, exp); + if (typeof(f) === 'function') { + f(void 0, store); + } - var requestLogin = function () { - // log out so that you don't go into an endless loop... - Cryptpad.logout(); + 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/'; - }; + // redirect them to log in, and come back when they're done. + sessionStorage.redirectTo = window.location.href; + window.location.href = '/login/'; + }; - var tokenKey = 'loginToken'; - 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'; + 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. */ - // every user object should have a persistent, random number - if (typeof(proxy.loginToken) !== 'number') { - proxy[tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); - } + // every user object should have a persistent, random number + if (typeof(proxy.loginToken) !== 'number') { + proxy[tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); + } - // copy User_hash into sessionStorage because cross-domain iframes - // on safari replaces localStorage with sessionStorage or something - 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. - 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(); + // copy User_hash into sessionStorage because cross-domain iframes + // on safari replaces localStorage with sessionStorage or something + 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. + 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; - } - if (typeof(proxy.uid) !== 'string' || proxy.uid.length !== 32) { - // even anonymous users should have a persistent, unique-ish id - console.log('generating a persistent identifier'); - proxy.uid = Cryptpad.createChannelId(); - } + if (typeof(proxy.allowUserFeedback) !== 'boolean') { + proxy.allowUserFeedback = true; + } - // if the user is logged in, but does not have signing keys... - if (Cryptpad.isLoggedIn() && !Cryptpad.hasSigningKeys(proxy)) { - return void requestLogin(); - } + if (typeof(proxy.uid) !== 'string' || proxy.uid.length !== 32) { + // even anonymous users should have a persistent, unique-ish id + console.log('generating a persistent identifier'); + proxy.uid = Cryptpad.createChannelId(); + } - proxy.on('change', [Cryptpad.displayNameKey], function (o, n) { - if (typeof(n) !== "string") { return; } - Cryptpad.changeDisplayName(n); - }); - proxy.on('change', [tokenKey], function () { - console.log('wut'); - var localToken = tryParsing(localStorage.getItem(tokenKey)); - if (localToken !== proxy[tokenKey]) { + // if the user is logged in, but does not have signing keys... + if (Cryptpad.isLoggedIn() && !Cryptpad.hasSigningKeys(proxy)) { return void requestLogin(); } - }); + + proxy.on('change', [Cryptpad.displayNameKey], function (o, n) { + if (typeof(n) !== "string") { return; } + Cryptpad.changeDisplayName(n); + }); + proxy.on('change', [tokenKey], function () { + console.log('wut'); + var localToken = tryParsing(localStorage.getItem(tokenKey)); + if (localToken !== proxy[tokenKey]) { + return void requestLogin(); + } + }); + }; + fo.migrate(todo); }; var initialized = false; @@ -263,6 +265,7 @@ define([ var rt = window.rt = Listmap.create(listmapConfig); + exp.realtime = rt.realtime; exp.proxy = rt.proxy; rt.proxy.on('create', function (info) { exp.info = info; @@ -274,9 +277,10 @@ 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; onReady(f, rt.proxy, Cryptpad, exp); }); return; diff --git a/www/common/mergeDrive.js b/www/common/mergeDrive.js index 2f4632544..fb9416f7c 100644 --- a/www/common/mergeDrive.js +++ b/www/common/mergeDrive.js @@ -2,8 +2,7 @@ define([ '/common/cryptpad-common.js', '/common/cryptget.js', '/common/userObject.js', - 'json.sortify' -], function (Cryptpad, Crypt, FO, Sortify) { +], function (Cryptpad, Crypt, FO) { var exp = {}; var getType = function (el) { @@ -22,61 +21,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); @@ -425,27 +415,24 @@ define([ }; // FILES DATA - var pushFileData = exp.pushData = function (data, cb) { + 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 @@ -492,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); @@ -522,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); } }); @@ -538,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(','); @@ -550,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(id) === -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); @@ -600,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]); }; @@ -634,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); }); }; @@ -673,30 +618,26 @@ define([ var allFilesPaths = paths.filter(function(x) { return isPathIn(x, [FILES_DATA]); }); if (!Cryptpad.isLoggedIn()) { - 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(); @@ -727,7 +668,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) { @@ -746,65 +687,69 @@ 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]; + if (getTitle(element, 'name') === 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); } }; @@ -812,12 +757,109 @@ define([ * INTEGRITY CHECK */ + exp.migrate = function (cb) { + // Make sure unsorted doesn't exist anymore + // 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[OLD_FILES_DATA]) { return; } + debug("UNSORTED still exists in the object, removing it..."); + var us = files[UNSORTED]; + if (us.length === 0) { + delete files[UNSORTED]; + return; + } + us.forEach(function (el) { + if (typeof el !== "string") { + return; + } + var data = files[OLD_FILES_DATA].filter(function (x) { + return x.href === el; + }); + if (data.length === 0) { + files[OLD_FILES_DATA].push({ + href: el + }); + } + return; + }); + delete files[UNSORTED]; + }; + // mergeDrive... + var migrateToNewFormat = function (todo) { + if (!files[OLD_FILES_DATA]) { + return void todo(); + } + try { + debug("Migrating file system..."); + files.migrate = 1; + var next = function () { + 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 = obj; + 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]; + }); + }); + files[OLD_FILES_DATA] = undefined; + delete files[OLD_FILES_DATA]; + files.migrate = undefined; + delete files.migrate; + console.log('done'); + todo(); + }; + if (exp.rt) { + exp.rt.sync(); + Cryptpad.whenRealtimeSyncs(exp.rt, next); + } else { + window.setTimeout(next, 1000); + } + } catch(e) { + console.error(e); + todo(); + } + }; + + fixUnsorted(); + migrateToNewFormat(cb); + }; + 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. + // * 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 @@ -829,12 +871,23 @@ 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]; - } 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[FILES_DATA][id] = {href: element[el], filename: el}; + element[key] = id; + delete element[el]; } } }; @@ -842,47 +895,38 @@ 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 (!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[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(addToClean); + for (var j=0; j=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()); @@ -890,51 +934,54 @@ 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[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] = []; } + 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 = []; - 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; + toClean.push(id); + continue; } if (!el.href) { - debug("Rmoving an element in filesData with a missing href.", el); - toClean.push(el); - return; + debug("Removing an element in filesData with a missing href.", el); + toClean.push(id); + 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; - } - }); - toClean.forEach(function (el) { - var idx = fd.indexOf(el); - if (idx !== -1) { - spliceFileData(idx); + 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 (id) { + spliceFileData(id); }); }; fixRoot(); fixTrashRoot(); if (!workgroup) { - fixUnsorted(); fixTemplate(); fixFilesData(); } @@ -948,6 +995,5 @@ define([ return exp; }; - return module; }); diff --git a/www/drive/main.js b/www/drive/main.js index a5f17bf73..dc5ae5842 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -485,6 +485,7 @@ define([ }; var getDate = function (sDate) { + if (!sDate) { return ''; } var ret = sDate.toString(); try { var date = new Date(sDate); @@ -501,12 +502,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 () { @@ -529,7 +533,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 @@ -934,19 +939,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 @@ -966,8 +961,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) { @@ -1094,30 +1090,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); @@ -1136,9 +1129,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(); } @@ -1154,7 +1153,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 { @@ -1179,7 +1178,7 @@ define([ if (isFolder) { addFolderData(element, key, $element); } else { - addFileData(element, key, $element, true); + addFileData(element, $element); } $element.prepend($icon).dblclick(function () { if (isFolder) { @@ -1187,7 +1186,7 @@ define([ return; } if (isTrash) { return; } - openFile(root[key], key); + openFile(root[key]); }); $element.addClass(liClass); $element.data('path', newPath); @@ -1252,10 +1251,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) { @@ -1266,9 +1268,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(' > '); } @@ -1414,24 +1417,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'; - // We can't create a hash for files before uploading the file - if (type === 'file') { // TODO: remove when filename are gone? - sessionStorage[Cryptpad.newPadPathKey] = filesOp.isPathIn(currentPath, [TRASH]) ? '' : currentPath; - window.open('/' + type + '/'); - return; - } - 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; }; @@ -1521,67 +1512,44 @@ define([ } }; // _WORKGROUP_ : do not display title, atime and ctime in workgroups since we don't have files data - var getFileListHeader = function (displayTitle) { + var getFileListHeader = function () { var $fihElement = $('
  • ', {'class': 'file-header header listElement element'}); //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; + } + if (prop === 'atime' || prop === 'ctime') { + return new Date(data[prop]); } - return useData ? el.title.toLowerCase() : el.toLowerCase(); + return filesOp.getTitle(id).toLowerCase(); }; keys.sort(function(a, b) { if (getProp(a, prop) < getProp(b, prop)) { return mult * -1; } @@ -1591,7 +1559,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); @@ -1617,7 +1584,6 @@ define([ if (prop === 'atime' || prop === 'ctime') { return new Date(e[prop]); } - return e.title.toLowerCase(); } return el.name.toLowerCase(); }; @@ -1649,27 +1615,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); @@ -1691,20 +1657,19 @@ 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 $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(); @@ -1720,24 +1685,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 }); }); }); @@ -1763,8 +1722,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); @@ -2152,29 +2112,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++; @@ -2182,11 +2125,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); @@ -2210,16 +2157,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) { $('