diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 3d8ad6060..d8e6f73d2 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -107,6 +107,7 @@ define(function () { out.printDate = "Afficher la date"; out.printTitle = "Afficher le titre du pad"; out.printCSS = "Personnaliser l'apparence (CSS):"; + out.printTransition = "Activer les animations de transition"; out.slideOptionsTitle = "Personnaliser la présentation"; out.slideOptionsButton = "Enregistrer (Entrée)"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 9219ab457..afdbdc6b2 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -109,6 +109,7 @@ define(function () { out.printDate = "Display the date"; out.printTitle = "Display the pad title"; out.printCSS = "Custom style rules (CSS):"; + out.printTransition = "Enable transition animations"; out.slideOptionsTitle = "Customize your slides"; out.slideOptionsButton = "Save (enter)"; diff --git a/package.json b/package.json index 696091528..83653bd7b 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,12 @@ "description": "realtime collaborative visual editor with zero knowlege server", "version": "1.6.0", "dependencies": { + "chainpad-server": "^1.0.1", "express": "~4.10.1", - "ws": "^1.0.1", "nthen": "~0.1.0", + "saferphore": "0.0.1", "tweetnacl": "~0.12.2", - "chainpad-server": "^1.0.1" + "ws": "^1.0.1" }, "devDependencies": { "jshint": "~2.9.1", @@ -15,9 +16,9 @@ "less": "2.7.1" }, "scripts": { - "lint": "jshint --config .jshintrc --exclude-path .jshintignore .", - "test": "node TestSelenium.js", - "style": "lessc ./customize.dist/src/less/cryptpad.less > ./customize.dist/main.css && lessc ./customize.dist/src/less/toolbar.less > ./customize.dist/toolbar.css && lessc ./www/drive/file.less > ./www/drive/file.css && lessc ./www/settings/main.less > ./www/settings/main.css && lessc ./www/slide/slide.less > ./www/slide/slide.css && lessc ./www/whiteboard/whiteboard.less > ./www/whiteboard/whiteboard.css && lessc ./www/poll/poll.less > ./www/poll/poll.css", - "template": "cd customize.dist/src && node build.js" + "lint": "jshint --config .jshintrc --exclude-path .jshintignore .", + "test": "node TestSelenium.js", + "style": "lessc ./customize.dist/src/less/cryptpad.less > ./customize.dist/main.css && lessc ./customize.dist/src/less/toolbar.less > ./customize.dist/toolbar.css && lessc ./www/drive/file.less > ./www/drive/file.css && lessc ./www/settings/main.less > ./www/settings/main.css && lessc ./www/slide/slide.less > ./www/slide/slide.css && lessc ./www/whiteboard/whiteboard.less > ./www/whiteboard/whiteboard.css && lessc ./www/poll/poll.less > ./www/poll/poll.css", + "template": "cd customize.dist/src && node build.js" } } diff --git a/pinneddata.js b/pinneddata.js new file mode 100644 index 000000000..0bf9be75f --- /dev/null +++ b/pinneddata.js @@ -0,0 +1,101 @@ +/* jshint esversion: 6 */ +const Fs = require('fs'); +const Semaphore = require('saferphore'); +const nThen = require('nthen'); + +const hashesFromPinFile = (pinFile, fileName) => { + var pins = {}; + pinFile.split('\n').filter((x)=>(x)).map((l) => JSON.parse(l)).forEach((l) => { + switch (l[0]) { + case 'RESET': { + pins = {}; + //jshint -W086 + // fallthrough + } + case 'PIN': { + l[1].forEach((x) => { pins[x] = 1; }); + break; + } + case 'UNPIN': { + l[1].forEach((x) => { delete pins[x]; }); + break; + } + default: throw new Error(JSON.stringify(l) + ' ' + fileName); + } + }); + return Object.keys(pins); +}; + +const sizeForHashes = (hashes, dsFileSizes) => { + let sum = 0; + hashes.forEach((h) => { + const s = dsFileSizes[h]; + if (typeof(s) !== 'number') { + //console.log('missing ' + h + ' ' + typeof(s)); + } else { + sum += s; + } + }); + return sum; +}; + +const sema = Semaphore.create(20); + +let dirList; +const fileList = []; +const dsFileSizes = {}; +const out = []; + +nThen((waitFor) => { + Fs.readdir('./datastore', waitFor((err, list) => { + if (err) { throw err; } + dirList = list; + })); +}).nThen((waitFor) => { + dirList.forEach((f) => { + sema.take((returnAfter) => { + Fs.readdir('./datastore/' + f, waitFor(returnAfter((err, list2) => { + if (err) { throw err; } + list2.forEach((ff) => { fileList.push('./datastore/' + f + '/' + ff); }); + }))); + }); + }); +}).nThen((waitFor) => { + fileList.forEach((f) => { + sema.take((returnAfter) => { + Fs.stat(f, waitFor(returnAfter((err, st) => { + if (err) { throw err; } + dsFileSizes[f.replace(/^.*\/([^\/]*)\.ndjson$/, (all, a) => (a))] = st.size; + }))); + }); + }); +}).nThen((waitFor) => { + Fs.readdir('./pins', waitFor((err, list) => { + if (err) { throw err; } + dirList = list; + })); +}).nThen((waitFor) => { + fileList.splice(0, fileList.length); + dirList.forEach((f) => { + sema.take((returnAfter) => { + Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => { + if (err) { throw err; } + list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); }); + }))); + }); + }); +}).nThen((waitFor) => { + fileList.forEach((f) => { + sema.take((returnAfter) => { + Fs.readFile(f, waitFor(returnAfter((err, content) => { + if (err) { throw err; } + const hashes = hashesFromPinFile(content.toString('utf8'), f); + const size = sizeForHashes(hashes, dsFileSizes); + out.push([f, Math.floor(size / (1024 * 1024))]); + }))); + }); + }); +}).nThen(() => { + out.sort((a,b) => (a[1] - b[1])); + out.forEach((x) => { console.log(x[0] + ' ' + x[1] + ' MB'); }); +}); diff --git a/www/common/common-interface.js b/www/common/common-interface.js index fb965a5d9..b2b5dad10 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -143,7 +143,7 @@ define([ return { show: function () { - $target.show(); + $target.css('display', 'inline'); return this; }, hide: function () { diff --git a/www/common/userObject.js b/www/common/userObject.js index a7c21f3f3..0972b6943 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -840,21 +840,17 @@ define([ return; } var rootFiles = getFiles([ROOT, TEMPLATE]).slice(); - //var toClean = []; var root = find([ROOT]); us.forEach(function (el) { if (!isFile(el) || rootFiles.indexOf(el) !== -1) { return; - //toClean.push(idx); } - var name = getFileData(el).title || NEW_FILE_NAME; + var data = getFileData(el); + var name = data ? data.title : NEW_FILE_NAME; var newName = getAvailableName(root, name); root[newName] = el; }); delete files[UNSORTED]; - /*toClean.forEach(function (idx) { - us.splice(idx, 1); - });*/ }; var fixTemplate = function () { if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; } diff --git a/www/drive/main.js b/www/drive/main.js index d4ac6cce2..47868bde4 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -371,6 +371,7 @@ define([ e.stopPropagation(); }); + // Arrow keys to modify the selection $(ifrw).keydown(function (e) { var $searchBar = $tree.find('#searchInput'); if ($searchBar.is(':focus') && $searchBar.val()) { return; } @@ -381,6 +382,7 @@ define([ if (e.ctrlKey) { ev.ctrlKey = true; } if (e.shiftKey) { ev.shiftKey = true; } var click = function (el) { + if (!el) { return; } module.onElementClick(ev, $(el)); }; @@ -402,6 +404,7 @@ define([ // [Left, Up, Right, Down] if ([37, 38, 39, 40].indexOf(e.which) === -1) { return; } + e.preventDefault(); var $selection = $content.find('.element.selected'); if ($selection.length === 0) { return void click($elements.first()[0]); } @@ -715,6 +718,21 @@ define([ updatePathSize(); }; + var scrollTo = function ($element) { + // Current scroll position + var st = $content.scrollTop(); + // Block height + var h = $content.height(); + // Current top position of the element relative to the scroll position + var pos = Math.round($element.offset().top - $content.position().top); + // Element height + var eh = $element.outerHeight(); + // New scroll value + var v = st + pos + eh - h; + // If the element is completely visile, don't change the scroll position + if (pos+eh <= h && pos >= 0) { return; } + $content.scrollTop(v); + }; // Add the "selected" class to the "li" corresponding to the clicked element var onElementClick = module.onElementClick = function (e, $element) { // If "Ctrl" is pressed, do not remove the current selection @@ -730,6 +748,7 @@ define([ log(Messages.fm_selectError); return; } + scrollTo($element); // Add the selected class to the clicked / right-clicked element // Remove the class if it already has it // If ctrlKey, add to the selection @@ -1793,7 +1812,11 @@ define([ module.resetTree(); // in history mode we want to focus the version number input - if (!history.isHistoryMode && !APP.mobile()) { $tree.find('#searchInput').focus(); } + if (!history.isHistoryMode && !APP.mobile()) { + var st = $tree.scrollTop() || 0; + $tree.find('#searchInput').focus(); + $tree.scrollTop(st); + } $tree.find('#searchInput')[0].selectionStart = getSearchCursor(); $tree.find('#searchInput')[0].selectionEnd = getSearchCursor(); @@ -2080,12 +2103,14 @@ define([ }; module.resetTree = function () { + var s = $tree.scrollTop() || 0; $tree.html(''); if (displayedCategories.indexOf(SEARCH) !== -1) { createSearch($tree); } if (displayedCategories.indexOf(ROOT) !== -1) { createTree($tree, [ROOT]); } if (displayedCategories.indexOf(TEMPLATE) !== -1) { createTemplate($tree, [TEMPLATE]); } if (displayedCategories.indexOf(FILES_DATA) !== -1) { createAllFiles($tree, [FILES_DATA]); } if (displayedCategories.indexOf(TRASH) !== -1) { createTrash($tree, [TRASH]); } + $tree.scrollTop(s); }; module.hideMenu = function () { @@ -2665,12 +2690,16 @@ define([ var $usage = $('', {'class': 'usage'}).css('width', width+'px'); if (quota >= 0.8) { + var origin = encodeURIComponent(window.location.origin); + var $upgradeLink = $('', { + href: "https://account.cryptpad.fr/#!on=" + origin, + rel: "noreferrer noopener", + target: "_blank", + }).appendTo($leftside); $('