').append(getIcon('link')).html() + Messages.fm_link_new
+ });
+ options.push({tag: 'hr'});
}
getNewPadTypes().forEach(function (type) {
var attributes = {
@@ -3073,6 +3194,13 @@ define([
$elementFolderUpload.append($('
', {'class': 'cp-app-drive-new-name'})
.text(Messages.uploadFolderButton));
}
+ // Link
+ var $elementLink = $('', {
+ 'class': 'cp-app-drive-new-link cp-app-drive-element-row ' +
+ 'cp-app-drive-element-grid'
+ }).prepend(getIcon('link')).appendTo($container);
+ $elementLink.append($('', {'class': 'cp-app-drive-new-name'})
+ .text(Messages.fm_link_type));
}
// Pads
getNewPadTypes().forEach(function (type) {
@@ -3479,6 +3607,7 @@ define([
var path = paths[0];
if (manager.isPathIn(path, [TRASH])) { return; }
+ if (!file.channel) { file.channel = id; }
if (channels.indexOf(file.channel) !== -1) { return; }
channels.push(file.channel);
@@ -4482,8 +4611,9 @@ define([
password: data.password
},
isTemplate: paths[0].path[0] === 'template',
- title: data.title,
+ title: data.title || data.name,
sharedFolder: sf,
+ static: data.static ? data.href : undefined,
common: common
};
if (padType === 'file') {
@@ -4501,6 +4631,20 @@ define([
data = manager.getSharedFolderData(el);
}
if (!data) { return; }
+ if (data.static) {
+ sframeChan.query("Q_DRIVE_USEROBJECT", {
+ cmd: "addLink",
+ teamId: -1,
+ data: {
+ name: data.name,
+ href: data.href,
+ path: ['root']
+ }
+ }, function () {
+ UI.log(Messages.saved);
+ });
+ return;
+ }
sframeChan.query('Q_STORE_IN_TEAM', {
href: data.href || data.rohref,
password: data.password,
@@ -4539,6 +4683,9 @@ define([
}
else if ($this.hasClass("cp-app-drive-context-newdoc")) {
var ntype = $this.data('type') || 'pad';
+ if (ntype === 'link') {
+ return void showLinkModal();
+ }
var path2 = manager.isPathIn(currentPath, [TRASH]) ? '' : currentPath;
openIn(ntype, path2, APP.team);
}
diff --git a/www/common/inner/share.js b/www/common/inner/share.js
index 9ba75f867..4bcf0d621 100644
--- a/www/common/inner/share.js
+++ b/www/common/inner/share.js
@@ -90,7 +90,11 @@ define([
setTimeout(w);
});
if (res && /^http/.test(res)) {
- href = Hash.getRelativeHref(res);
+ var _href = Hash.getRelativeHref(res);
+ if (_href) { href = _href; }
+ else {
+ href = res;
+ }
setTimeout(w);
return;
}
@@ -109,6 +113,7 @@ define([
if (mailbox.notifications && mailbox.curvePublic) {
common.mailbox.sendTo("SHARE_PAD", {
href: href,
+ isStatic: Boolean(config.static),
password: config.password,
isTemplate: config.isTemplate,
name: myName,
@@ -119,6 +124,9 @@ define([
channel: mailbox.notifications,
curvePublic: mailbox.curvePublic
});
+ if (config.static) {
+ Feedback.send("LINK_SHARED_WITH_CONTACT");
+ }
return;
}
}
@@ -137,6 +145,21 @@ define([
});
return;
}
+ if (config.static) {
+ common.getSframeChannel().query("Q_DRIVE_USEROBJECT", {
+ cmd: "addLink",
+ teamId: team.id,
+ data: {
+ name: title,
+ href: href,
+ path: ['root']
+ }
+ }, function () {
+ UI.log(Messages.saved);
+ });
+ Feedback.send("LINK_ADDED_TO_DRIVE");
+ return;
+ }
sframeChan.query('Q_STORE_IN_TEAM', {
href: href,
password: config.password,
@@ -346,6 +369,9 @@ define([
] : [
UI.createCheckbox('cp-share-embed', Messages.share_linkEmbed, false, { mark: {tabindex:1} }),
];
+
+ if (opts.static) { linkContent = []; }
+
linkContent.push(h('div.cp-spacer'));
linkContent.push(UI.dialog.selectableArea('', { id: 'cp-share-link-preview', tabindex: 1, rows:3}));
@@ -361,7 +387,7 @@ define([
// warning about sharing links
// when sharing a version hash, there is a similar warning and we want
// to avoid alert fatigue
- if (!opts.versionHash) {
+ if (!opts.versionHash && !opts.static) {
var localStore = window.cryptpadStore;
var dismissButton = h('span.fa.fa-times');
var shareLinkWarning = h('div.alert.alert-warning.dismissable',
@@ -405,6 +431,10 @@ define([
var v = opts.getLinkValue({
embed: Util.isChecked($link.find('#cp-share-embed'))
});
+ if (opts.static) {
+ common.openUnsafeURL(v);
+ return true;
+ }
window.open(v);
return true;
},
@@ -562,6 +592,7 @@ define([
});
};
opts.getLinkValue = function (initValue, cb) {
+ if (opts.static) { return opts.static; }
var val = initValue || {};
var edit = val.edit !== undefined ? val.edit : Util.isChecked($rights.find('#cp-share-editable-true'));
var embed = val.embed;
@@ -686,7 +717,7 @@ define([
opts.access = true; // Allow the use of the modal even if the pad is not stored
var hashes = opts.hashes;
- if (!hashes || (!hashes.editHash && !hashes.viewHash)) { return; }
+ if (!hashes || (!hashes.editHash && !hashes.viewHash && !opts.static)) { return; }
var teams = getEditableTeams(common, opts);
opts.teams = teams;
@@ -705,19 +736,23 @@ define([
var $rights = opts.$rights = getRightsHeader(common, opts);
var resetTab = function () {
+ if (opts.static) { return; }
$rights.show();
$rights.find('label.cp-radio').show();
};
var onShowEmbed = function () {
+ if (opts.static) { return; }
$rights.find('#cp-share-bar').closest('label').hide();
$rights.find('input[type="radio"]:enabled').first().prop('checked', 'checked');
$rights.find('input[type="radio"]').trigger('change');
};
var onShowContacts = function () {
+ if (opts.static) { return; }
if (!hasFriends || priv.offline) {
$rights.hide();
}
};
+ if (opts.static) { $rights.hide(); }
var contactsActive = hasFriends && !priv.offline;
var tabs = [{
@@ -732,13 +767,16 @@ define([
title: Messages.share_linkCategory,
icon: "fa fa-link",
active: !contactsActive,
- }, {
- getTab: getEmbedTab,
- title: Messages.share_embedCategory,
- icon: "fa fa-code",
- onShow: onShowEmbed,
- onHide: resetTab
}];
+ if (!opts.static) {
+ tabs.push({
+ getTab: getEmbedTab,
+ title: Messages.share_embedCategory,
+ icon: "fa fa-code",
+ onShow: onShowEmbed,
+ onHide: resetTab
+ });
+ }
Modal.getModal(common, opts, tabs, function (err, modal) {
// Hide the burn-after-reading option by default
var $modal = $(modal);
diff --git a/www/common/notifications.js b/www/common/notifications.js
index 20738e237..83052ff68 100644
--- a/www/common/notifications.js
+++ b/www/common/notifications.js
@@ -92,6 +92,10 @@ define([
(type === 'file' ? 'notification_fileShared' : // Msg.notification_fileSharedTeam
'notification_padShared'); // Msg.notification_padSharedTeam
+ if (msg.content.isStatic) {
+ key = 'notification_linkShared'; // Msg.notification_linkShared;
+ }
+
var teamNotification = /^team-/.test(data.type) && Number(data.type.slice(5));
var teamName = '';
if (teamNotification) {
@@ -109,6 +113,15 @@ define([
return Messages._getKey(key, [name, title, teamName]);
};
content.handler = function() {
+ if (msg.content.isStatic) {
+ UIElements.displayOpenLinkModal(common, {
+ curve: msg.author,
+ href: msg.content.href,
+ name: name,
+ title: title
+ }, defaultDismiss(common, data));
+ return;
+ }
var obj = {
p: msg.content.isTemplate ? ['template'] : undefined,
t: teamNotification || undefined,
diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js
index 9f779a0a8..d8e558dad 100644
--- a/www/common/outer/async-store.js
+++ b/www/common/outer/async-store.js
@@ -1306,9 +1306,14 @@ define([
getAllStores().forEach(function (s) {
s.manager.getSecureFilesList(where).forEach(function (obj) {
var data = obj.data;
- if (channels.indexOf(data.channel) !== -1) { return; }
+ if (channels.indexOf(data.channel || data.id) !== -1) { return; }
var id = obj.id;
- if (data.channel) { channels.push(data.channel); }
+ if (data.channel) { channels.push(data.channel || data.id); }
+ // Only include static links if "link" is requested
+ if (data.static) {
+ if (types.indexOf('link') !== -1) { list[id] = data; }
+ return;
+ }
var parsed = Hash.parsePadUrl(data.href || data.roHref);
if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) &&
!isFiltered(parsed.type, data)) {
@@ -2053,8 +2058,17 @@ define([
} catch (e) {
console.error(e);
}
+
// Tell all the owners that the pad was deleted from the server
- var curvePublic = store.proxy.curvePublic;
+ var curvePublic;
+ try {
+ // users in noDrive mode don't have a proxy and
+ // unregistered users don't have a curvePublic
+ curvePublic = store.proxy.curvePublic;
+ } catch (err) {
+ console.error(err);
+ return;
+ }
m.forEach(function (obj) {
var mb = JSON.parse(obj);
if (mb.curvePublic === curvePublic) { return; }
diff --git a/www/common/outer/cache-store.js b/www/common/outer/cache-store.js
index 514e18ab7..93bc0d0c6 100644
--- a/www/common/outer/cache-store.js
+++ b/www/common/outer/cache-store.js
@@ -97,7 +97,7 @@ define([
var checkCheckpoints = function (array) {
if (!Array.isArray(array)) { return; }
// Keep the last 100 messages
- if (array.length > 100) { // XXX
+ if (array.length > 100) { // XXX 4.10.0
array.splice(0, array.length - 100);
}
// Remove every message before the first checkpoint
diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js
index 7f7e20d2a..88e43352b 100644
--- a/www/common/outer/mailbox-handlers.js
+++ b/www/common/outer/mailbox-handlers.js
@@ -238,8 +238,9 @@ define([
// content.name, content.title, content.href, content.password
if (isMuted(ctx, data)) { return void cb(true); }
-
- var channel = Hash.hrefToHexChannelId(content.href, content.password);
+ // if the shared content is a 'link' then we can't use the channel to deduplicate notifications
+ // use href instead.
+ var channel = content.isStatic ? content.href : Hash.hrefToHexChannelId(content.href, content.password);
var parsed = Hash.parsePadUrl(content.href);
var mode = parsed.hashData && parsed.hashData.mode || 'n/a';
diff --git a/www/common/outer/team.js b/www/common/outer/team.js
index 4184b8482..94f91c807 100644
--- a/www/common/outer/team.js
+++ b/www/common/outer/team.js
@@ -195,7 +195,7 @@ define([
Pinpad.create(ctx.store.network, data, function (e, call) {
if (e) { return void cb(e); }
team.rpc = call;
- team.onRpcReadyEvt.fire();
+ if (team && team.onRpcReadyEvt) { team.onRpcReadyEvt.fire(); }
cb();
}, Cache);
});
diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js
index e7c55da67..7108da936 100644
--- a/www/common/outer/userObject.js
+++ b/www/common/outer/userObject.js
@@ -20,6 +20,7 @@ define([
var ROOT = exp.ROOT;
var FILES_DATA = exp.FILES_DATA;
+ var STATIC_DATA = exp.STATIC_DATA;
var OLD_FILES_DATA = exp.OLD_FILES_DATA;
var UNSORTED = exp.UNSORTED;
var TRASH = exp.TRASH;
@@ -78,6 +79,14 @@ define([
files[FILES_DATA][id] = data;
cb(null, id);
};
+ exp.pushLink = function (_data, cb) {
+ if (typeof cb !== "function") { cb = function () {}; }
+ if (readOnly) { return void cb('EFORBIDDEN'); }
+ var id = Util.createRandomInteger();
+ var data = clone(_data);
+ files[STATIC_DATA][id] = data;
+ cb(null, id);
+ };
exp.pushSharedFolder = function (_data, cb) {
if (typeof cb !== "function") { cb = function () {}; }
@@ -136,7 +145,7 @@ define([
var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
var toClean = [];
- exp.getFiles([FILES_DATA, SHARED_FOLDERS]).forEach(function (id) {
+ exp.getFiles([FILES_DATA, SHARED_FOLDERS, STATIC_DATA]).forEach(function (id) {
if (filesList.indexOf(id) === -1) {
var fd = exp.isSharedFolder(id) ? files[SHARED_FOLDERS][id] : exp.getFileData(id);
var channelId = fd.channel;
@@ -146,6 +155,8 @@ define([
if (exp.isSharedFolder(id)) {
delete files[SHARED_FOLDERS][id];
if (config.removeProxy) { config.removeProxy(id); }
+ } else if (files[STATIC_DATA][id]) {
+ delete files[STATIC_DATA][id];
} else {
spliceFileData(id);
}
@@ -242,6 +253,12 @@ define([
id = Number(id);
// Find and maybe update existing pads with the same channel id
var d = data[id];
+ // If we were given a static link, copy to STATIC_DATA
+ if (d.static) {
+ delete d.static;
+ files[STATIC_DATA][id] = d;
+ return;
+ }
// If we were given an edit link, encrypt its value if needed
if (d.href) { d.href = exp.cryptor.encrypt(d.href); }
var found = false;
@@ -398,7 +415,7 @@ define([
if (!loggedIn && !config.testMode) { return; }
id = Number(id);
- var data = files[FILES_DATA][id] || files[SHARED_FOLDERS][id];
+ var data = files[FILES_DATA][id] || files[STATIC_DATA][id] || files[SHARED_FOLDERS][id];
if (!data || typeof(data) !== "object") { return; }
var newPath = path, parentEl;
if (path && !Array.isArray(path)) {
@@ -599,13 +616,18 @@ define([
var element = elem || files[ROOT];
if (!element) { return console.error("Invalid element in root"); }
var nbMetadataFolders = 0;
+ // caching this variables saves a lot of hashmap lookups in this loop
+ var static_data = files[STATIC_DATA];
+ var files_data = files[FILES_DATA];
+ var element_el;
for (var el in element) {
- if (element[el] === null) {
+ element_el = element[el];
+ if (element_el === null) {
console.error('element[%s] is null', el);
delete element[el];
continue;
}
- if (exp.isFolderData(element[el])) {
+ if (exp.isFolderData(element_el)) {
if (nbMetadataFolders !== 0) {
debug("Multiple metadata files in folder");
delete element[el];
@@ -613,30 +635,30 @@ define([
nbMetadataFolders++;
continue;
}
- if (!exp.isFile(element[el], true) && !exp.isFolder(element[el])) {
- debug("An element in ROOT was not a folder nor a file. ", element[el]);
+ if (!exp.isFile(element_el, true) && !exp.isFolder(element_el)) {
+ debug("An element in ROOT was not a folder nor a file. ", element_el);
delete element[el];
continue;
}
- if (exp.isFolder(element[el])) {
- fixRoot(element[el]);
+ if (exp.isFolder(element_el)) {
+ fixRoot(element_el);
continue;
}
- if (typeof element[el] === "string") {
+ if (typeof element_el === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger();
var key = Hash.createChannelId();
- files[FILES_DATA][id] = {
- href: exp.cryptor.encrypt(element[el]),
+ files_data[id] = {
+ href: exp.cryptor.encrypt(element_el),
filename: el
};
element[key] = id;
delete element[el];
}
- if (typeof element[el] === "number") {
- var data = files[FILES_DATA][element[el]];
+ if (typeof element_el === "number") {
+ var data = files_data[element_el] || static_data[element_el];
if (!data) {
- debug("An element in ROOT doesn't have associated data", element[el], el);
+ debug("An element in ROOT doesn't have associated data", element_el, el);
delete element[el];
}
}
@@ -845,6 +867,26 @@ define([
toClean.forEach(function (id) {
spliceFileData(id);
});
+ // make sure that links are displayed at least once in your drive if you are going to keep them
+ var sd = files[STATIC_DATA];
+ var toCleanSD = [];
+ for (var id2 in sd) {
+ id2 = Number(id2);
+ var el2 = sd[id2];
+ if (!el2 || typeof(el2) !== "object" || !el2.href) {
+ toCleanSD.push(id2);
+ continue;
+ }
+ if ((loggedIn || config.testMode) && rootFiles.indexOf(id2) === -1) {
+ toCleanSD.push(id2);
+ continue;
+ }
+ }
+ var spliceSD = function (id) {
+ if (readOnly) { return; }
+ delete files[STATIC_DATA][id];
+ };
+ toCleanSD.forEach(spliceSD);
};
var fixSharedFolders = function () {
if (sharedFolder) { return; }
@@ -892,6 +934,12 @@ define([
}
}
};
+ var fixStaticData = function () {
+ if (!Util.isObject(files[STATIC_DATA])) {
+ debug("STATIC_DATA was not an object");
+ files[STATIC_DATA] = {};
+ }
+ };
var fixDrive = function () {
@@ -900,6 +948,7 @@ define([
});
};
+ fixStaticData();
fixRoot();
fixTrashRoot();
fixTemplate();
diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js
index 0e799de56..d6492c68c 100644
--- a/www/common/proxy-manager.js
+++ b/www/common/proxy-manager.js
@@ -22,11 +22,11 @@ define([
// a cached version
if (Env.folders[id].offline && !lm.cache) {
Env.folders[id].offline = false;
+ if (Env.folders[id].userObject.fixFiles) { Env.folders[id].userObject.fixFiles(); }
Env.Store.refreshDriveUI();
}
return;
}
- if (Env.folders[id]) { console.warn(Env.folders[id]); }
var cfg = getConfig(Env);
cfg.sharedFolder = true;
cfg.id = id;
@@ -584,6 +584,24 @@ define([
});
});
};
+ // Add a link
+ var _addLink = function (Env, data, cb) {
+ data = data || {};
+ var resolved = _resolvePath(Env, data.path);
+ if (!resolved || !resolved.userObject) { return void cb({error: 'E_NOTFOUND'}); }
+ var uo = resolved.userObject;
+ var now = +new Date();
+ uo.pushLink({
+ name: data.name,
+ href: data.href,
+ atime: now,
+ ctime: now
+ }, function (e, id) {
+ if (e) { return void cb({error: e}); }
+ uo.add(id, resolved.path);
+ Env.onSync(cb);
+ });
+ };
var _restoreSharedFolder = function (Env, _data, cb) {
var fId = _data.id;
@@ -1019,35 +1037,40 @@ define([
});
};
+
+ var _updateStaticAccess = function (Env, id, cb) {
+ var uo = _getUserObjectFromId(Env, id);
+ var sd = uo.getFileData(id, true);
+ sd.atime = +new Date();
+ Env.onSync(cb);
+ };
+
+ var COMMANDS = {
+ move: _move,
+ restore: _restore,
+ addFolder: _addFolder,
+ addSharedFolder: _addSharedFolder,
+ addLink: _addLink,
+ restoreSharedFolder: _restoreSharedFolder,
+ convertFolderToSharedFolder: _convertFolderToSharedFolder,
+ delete: _delete,
+ deleteOwned: _deleteOwned,
+ emptyTrash: _emptyTrash,
+ rename: _rename,
+ setFolderData: _setFolderData,
+ updateStaticAccess: _updateStaticAccess,
+ };
+
var onCommand = function (Env, cmdData, cb) {
var cmd = cmdData.cmd;
var data = cmdData.data || {};
- switch (cmd) {
- case 'move':
- _move(Env, data, cb); break;
- case 'restore':
- _restore(Env, data, cb); break;
- case 'addFolder':
- _addFolder(Env, data, cb); break;
- case 'addSharedFolder':
- _addSharedFolder(Env, data, cb); break;
- case 'restoreSharedFolder':
- _restoreSharedFolder(Env, data, cb); break;
- case 'convertFolderToSharedFolder':
- _convertFolderToSharedFolder(Env, data, cb); break;
- case 'delete':
- _delete(Env, data, cb); break;
- case 'deleteOwned':
- _deleteOwned(Env, data, cb); break;
- case 'emptyTrash':
- _emptyTrash(Env, data, cb); break;
- case 'rename':
- _rename(Env, data, cb); break;
- case 'setFolderData':
- _setFolderData(Env, data, cb); break;
- default:
- cb();
+ var method = COMMANDS[cmd];
+
+ if (typeof(method) === 'function') {
+ return void method(Env, data, cb);
}
+ // if the command was not handled then call back
+ cb();
};
// Set the value everywhere the given pad is stored (main and shared folders)
@@ -1129,8 +1152,8 @@ define([
data: uo.getFileData(id)
};
}).filter(function (d) {
- if (channels.indexOf(d.data.channel) === -1) {
- channels.push(d.data.channel);
+ if (channels.indexOf(d.data.channel || d.id) === -1) {
+ channels.push(d.data.channel || d.id);
return true;
}
});
@@ -1233,7 +1256,10 @@ define([
Array.prototype.push.apply(result, sfChannels);
}
- return result;
+ return result.filter(function (channel) {
+ if (typeof(channel) !== 'string') { return; }
+ return [32, 48].indexOf(channel.length) !== -1;
+ });
};
var addPad = function (Env, path, pad, cb) {
@@ -1383,6 +1409,16 @@ define([
}
}, cb);
};
+ var addLinkInner = function (Env, path, data, cb) {
+ return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", {
+ cmd: "addLink",
+ data: {
+ path: path,
+ name: data.name,
+ href: data.url
+ }
+ }, cb);
+ };
var restoreSharedFolderInner = function (Env, fId, password, cb) {
return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "restoreSharedFolder",
@@ -1433,6 +1469,14 @@ define([
}, cb);
};
+ var updateStaticAccessInner = function (Env, id, cb) {
+ return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", {
+ cmd: "updateStaticAccess",
+ data: id
+ }, cb);
+
+ };
+
/* Tools */
var findChannels = _findChannels;
@@ -1450,6 +1494,11 @@ define([
return String(uo.getTitle(id, type));
};
+ var isStaticFile = function (Env, id) {
+ var uo = _getUserObjectFromId(Env, id);
+ return uo.isStaticFile(id);
+ };
+
var isReadOnlyFile = function (Env, id) {
var uo = _getUserObjectFromId(Env, id);
return uo.isReadOnlyFile(id);
@@ -1491,7 +1540,7 @@ define([
var files = [];
var userObjects = _getUserObjects(Env);
userObjects.forEach(function (uo) {
- var data = uo.getFiles([UserObject.FILES_DATA]).map(function (id) {
+ var data = uo.getFiles([UserObject.FILES_DATA, UserObject.STATIC_DATA]).map(function (id) {
return [Number(id), uo.getFileData(id)];
});
Array.prototype.push.apply(files, data);
@@ -1608,17 +1657,20 @@ define([
emptyTrash: callWithEnv(emptyTrashInner),
addFolder: callWithEnv(addFolderInner),
addSharedFolder: callWithEnv(addSharedFolderInner),
+ addLink: callWithEnv(addLinkInner),
restoreSharedFolder: callWithEnv(restoreSharedFolderInner),
convertFolderToSharedFolder: callWithEnv(convertFolderToSharedFolderInner),
delete: callWithEnv(deleteInner),
deleteOwned: callWithEnv(deleteOwnedInner),
restore: callWithEnv(restoreInner),
setFolderData: callWithEnv(setFolderDataInner),
+ updateStaticAccess: callWithEnv(updateStaticAccessInner),
// Tools
getFileData: callWithEnv(getFileData),
find: callWithEnv(find),
getTitle: callWithEnv(getTitle),
isReadOnlyFile: callWithEnv(isReadOnlyFile),
+ isStaticFile: callWithEnv(isStaticFile),
getFiles: callWithEnv(getFiles),
search: callWithEnv(search),
getRecentPads: callWithEnv(getRecentPads),
diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js
index 0fd250f80..96997cd06 100644
--- a/www/common/sframe-app-framework.js
+++ b/www/common/sframe-app-framework.js
@@ -733,11 +733,20 @@ define([
if (!common.isLoggedIn()) { return; }
$embedButton = common.createButton('mediatag', true).click(function () {
var cfg = {
- types: ['file'],
+ types: ['file', 'link'],
where: ['root']
};
if ($embedButton.data('filter')) { cfg.filter = $embedButton.data('filter'); }
common.openFilePicker(cfg, function (data) {
+ // Embed links
+ if (data.static) {
+ var a = h('a', {
+ href: data.href
+ }, data.name);
+ mediaTagEmbedder($(a), data);
+ return;
+ }
+ // Embed files
if (data.type !== 'file') {
console.log("Unexpected data type picked " + data.type);
return;
diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js
index b0759b5ec..575971368 100644
--- a/www/common/sframe-common-outer.js
+++ b/www/common/sframe-common-outer.js
@@ -1925,7 +1925,6 @@ define([
var cryptputCfg = $.extend(true, {}, rtConfig, {password: password});
if (data.templateContent) {
Cryptget.put(currentPad.hash, JSON.stringify(data.templateContent), function () {
- console.error(arguments);
startRealtime();
cb();
}, cryptputCfg);
@@ -2004,6 +2003,8 @@ define([
sframeChan.on('EV_BURN_AFTER_READING', function () {
startRealtime();
+ // feedback fails for users in noDrive mode
+ Utils.Feedback.send("BURN_AFTER_READING", Boolean(cfg.noDrive));
});
sframeChan.ready();
diff --git a/www/common/sframe-common-title.js b/www/common/sframe-common-title.js
index 2539d1542..d1b06df73 100644
--- a/www/common/sframe-common-title.js
+++ b/www/common/sframe-common-title.js
@@ -23,6 +23,12 @@ define([
var $title;
exp.setToolbar = function (toolbar) {
$title = toolbar && (toolbar.title || toolbar.pageTitle);
+ if ($title && exp.defaultTitle) {
+ var md = metadataMgr.getMetadata();
+ $title.find('input').prop('placeholder', md.defaultTitle);
+ $title.find('span.cp-toolbar-title-value').text(md.title || md.defaultTitle);
+ $title.find('input').val(md.title || md.defaultTitle);
+ }
};
exp.getTitle = function () { return exp.title; };
diff --git a/www/common/toolbar.js b/www/common/toolbar.js
index 785d1bc7e..5b53aa9a1 100644
--- a/www/common/toolbar.js
+++ b/www/common/toolbar.js
@@ -867,10 +867,6 @@ MessengerUI, Messages, Pages) {
'class': "cp-toolbar-link-logo"
}).append(UIElements.getSvgLogo());
- /*.append($('', {
- //src: '/customize/images/logo_white.png?' + ApiConfig.requireConf.urlArgs
- src: '/customize/favicon/main-favicon.png?' + ApiConfig.requireConf.urlArgs
- }));*/
var onClick = function (e) {
e.preventDefault();
if (e.ctrlKey) {
diff --git a/www/common/userObject.js b/www/common/userObject.js
index ca716f25f..9de3ea918 100644
--- a/www/common/userObject.js
+++ b/www/common/userObject.js
@@ -17,6 +17,7 @@ define([
var SHARED_FOLDERS_TEMP = module.SHARED_FOLDERS_TEMP = "sharedFoldersTemp"; // Maybe deleted or new password
var FILES_DATA = module.FILES_DATA = Constants.storageKey;
var OLD_FILES_DATA = module.OLD_FILES_DATA = Constants.oldStorageKey;
+ var STATIC_DATA = module.STATIC_DATA = 'static';
// Create untitled documents when no name is given
var getLocaleDate = function () {
@@ -138,6 +139,7 @@ define([
var NEW_FILE_NAME = Messages.fm_newFile || 'New file';
exp.ROOT = ROOT;
+ exp.STATIC_DATA = STATIC_DATA;
exp.UNSORTED = UNSORTED;
exp.TRASH = TRASH;
exp.TEMPLATE = TEMPLATE;
@@ -236,6 +238,10 @@ define([
return Boolean(data.roHref && !data.href);
};
+ exp.isStaticFile = function (element) {
+ return Boolean(files[STATIC_DATA] && files[STATIC_DATA][element]);
+ };
+
var isFolder = exp.isFolder = function (element) {
if (isFolderData(element)) { return false; }
return typeof(element) === "object" || isSharedFolder(element);
@@ -310,6 +316,12 @@ define([
// Get data from AllFiles (Cryptpad_RECENTPADS)
var getFileData = exp.getFileData = function (file, editable) {
if (!file) { return; }
+ var link = (files[STATIC_DATA] || {})[file];
+ if (link) {
+ var _link = editable ? link : Util.clone(link);
+ if (!editable) { _link.static = true; }
+ return _link;
+ }
var data = files[FILES_DATA][file] || {};
if (!editable) {
data = JSON.parse(JSON.stringify(data));
@@ -344,6 +356,7 @@ define([
return '??';
}
var data = getFileData(file);
+ if (data.static) { return data.name; }
if (!file || !data || !(data.href || data.roHref)) {
error("getTitle called with a non-existing file id: ", file, data);
return;
@@ -475,6 +488,11 @@ define([
});
return ret;
};
+ _getFiles[STATIC_DATA] = function () {
+ var ret = [];
+ if (!files[STATIC_DATA]) { return ret; }
+ return Object.keys(files[STATIC_DATA]).map(Number).filter(Boolean);
+ };
_getFiles[FILES_DATA] = function () {
var ret = [];
if (!files[FILES_DATA]) { return ret; }
@@ -854,6 +872,7 @@ define([
// RENAME
exp.rename = function (path, newName, cb) {
+ cb = cb || function () {};
if (sframeChan) {
return void sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "rename",
@@ -891,9 +910,15 @@ define([
if (isSharedFolder(element)) {
data = files[SHARED_FOLDERS][element];
} else {
- data = files[FILES_DATA][element];
+ data = files[FILES_DATA][element] || files[STATIC_DATA][element];
}
if (!data) { return; }
+ if (files[STATIC_DATA][element]) {
+ if (!newName || !newName.trim()) { return void cb(); }
+ data.name = newName;
+ cb();
+ return;
+ }
if (!newName || newName.trim() === "") {
delete data.filename;
if (typeof cb === "function") { cb(); }
diff --git a/www/convert/inner.js b/www/convert/inner.js
index cdcf745ca..ba9870ccf 100644
--- a/www/convert/inner.js
+++ b/www/convert/inner.js
@@ -192,8 +192,8 @@ define([
},
};
- Messages.convertPage = "Convert"; // XXX
- Messages.convert_hint = "Pick the file you want to convert. The list of output format will be visible afterward."; // XXX
+ Messages.convertPage = "Convert"; // XXX 4.10.0
+ Messages.convert_hint = "Pick the file you want to convert. The list of output format will be visible afterward."; // XXX 4.10.0
var createToolbar = function () {
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle', 'notifications'];
diff --git a/www/form/app-form.less b/www/form/app-form.less
index 95802e18b..60da1daff 100644
--- a/www/form/app-form.less
+++ b/www/form/app-form.less
@@ -247,6 +247,15 @@
.cp-form-anon-answer {
text-align: center;
margin: 20px 0 30px 0;
+ .cp-form-anon-answer-input {
+ margin-top: 20px;
+ display: flex;
+ white-space: nowrap;
+ align-items: center;
+ input {
+ margin-left: 10px;
+ }
+ }
}
}
@@ -464,6 +473,16 @@
display: flex;
flex-flow: column;
position: relative;
+
+ #cp-form-response-msg {
+ background: @cp_form-bg1;
+ margin-bottom: 20px;
+ padding: 10px;
+ p:last-child {
+ margin-bottom: 0;
+ }
+ }
+
.cp-form-creator-results-controls {
margin-bottom: 20px;
//background: @cp_form-bg1;
@@ -529,6 +548,7 @@
.cp-form-individual {
background: @cp_form-bg1;
padding: 10px;
+ margin-bottom: 20px;
& > *:not(:last-child) {
margin-right: 10px;
}
@@ -659,6 +679,16 @@
.avatar_main(30px);
margin-right: 10px;
}
+ &.cp-clickable {
+ cursor: pointer;
+ &:hover {
+ color: @cryptpad_color_link;
+ &::after {
+ font-family: FontAwesome;
+ content: "\00a0\f06e";
+ }
+ }
+ }
}
.cp-poll-time-day {
flex-basis: 100px;
@@ -677,6 +707,16 @@
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
+ .cp-form-poll-option, .cp-poll-time-day {
+ flex-flow: column;
+ text-align: center;
+ .cp-form-weekday-separator {
+ display: none;
+ }
+ }
+ .cp-poll-time-day {
+ height: 50px;
+ }
}
&.cp-form-poll-switch {
display: flex;
@@ -706,12 +746,21 @@
}
.cp-form-poll-option, .cp-poll-switch {
width: 200px;
+ .cp-form-weekday-separator {
+ margin-right: 5px;
+ margin-left: 5px;
+ }
}
.cp-poll-time-day {
flex-basis: 40px;
border-right: none;
border-right: 1px solid @cryptpad_text_col;
border-bottom: 0px;
+ flex-flow: column;
+ text-align: center;
+ .cp-form-weekday-separator {
+ display: none;
+ }
}
}
.cp-form-poll-choice, .cp-form-poll-answer {
diff --git a/www/form/inner.js b/www/form/inner.js
index 2a1501ecb..1b555be5d 100644
--- a/www/form/inner.js
+++ b/www/form/inner.js
@@ -298,6 +298,7 @@ define([
del
]);
$(del).click(function () {
+ var $block = $(el).closest('.cp-form-edit-block');
$(el).remove();
// We've just deleted an item/option so we should be under the MAX limit and
// we can show the "add" button again
@@ -306,6 +307,13 @@ define([
$add.show();
if (v.type === "time") { $(addMultiple).show(); }
}
+ // decrement the max choices input when there are fewer options than the current maximum
+ if (maxInput) {
+ var inputs = $block.find('input').length;
+ var $maxInput = $(maxInput);
+ var currentMax = Number($maxInput.val());
+ $maxInput.val(Math.min(inputs, currentMax));
+ }
});
return el;
};
@@ -575,7 +583,22 @@ define([
];
};
- var makePollTable = function (answers, opts) {
+ var getWeekDays = function (large) {
+ var baseDate = new Date(2017, 0, 1); // just a Sunday
+ var weekDays = [];
+ for(var i = 0; i < 7; i++) {
+ weekDays.push(baseDate.toLocaleDateString(undefined, { weekday: 'long' }));
+ baseDate.setDate(baseDate.getDate() + 1);
+ }
+ if (!large) {
+ weekDays = weekDays.map(function (day) { return day.slice(0,3); });
+ }
+ return weekDays.map(function (day) { return day.replace(/^./, function (str) { return str.toUpperCase(); }); });
+ };
+
+ // "resultsPageObj" is an object with "content" and "answers"
+ // only available when viewing the Responses page
+ var makePollTable = function (answers, opts, resultsPageObj) {
// Sort date values
if (opts.type !== "text") {
opts.values.sort(function (a, b) {
@@ -583,18 +606,25 @@ define([
});
}
// Create first line with options
+ var allDays = getWeekDays(true);
var els = opts.values.map(function (data) {
+ var _date;
if (opts.type === "day") {
- var _date = new Date(data);
+ _date = new Date(data);
data = _date.toLocaleDateString();
}
if (opts.type === "time") {
- var _dateT = new Date(data);
- data = Flatpickr.formatDate(_dateT, timeFormat);
+ _date = new Date(data);
+ data = Flatpickr.formatDate(_date, timeFormat);
}
+ var day = _date && allDays[_date.getDay()];
return h('div.cp-poll-cell.cp-form-poll-option', {
title: Util.fixHTML(data)
- }, data);
+ }, [
+ opts.type === 'day' ? h('span.cp-form-weekday', day) : undefined,
+ opts.type === 'day' ? h('span.cp-form-weekday-separator', ' - ') : undefined,
+ h('span', data)
+ ]);
});
// Insert axis switch button
var switchAxis = h('button.btn.btn-default', [
@@ -611,13 +641,20 @@ define([
opts.values.forEach(function (d) {
var date = new Date(d);
var day = date.toLocaleDateString();
- _days[day] = _days[day] || 0;
- _days[day]++;
+ _days[day] = {
+ n: (_days[day] && _days[day].n) || 0,
+ name: allDays[date.getDay()]
+ };
+ _days[day].n++;
});
Object.keys(_days).forEach(function (day) {
days.push(h('div.cp-poll-cell.cp-poll-time-day', {
- style: 'flex-grow:'+(_days[day]-1)+';'
- }, day));
+ style: 'flex-grow:'+(_days[day].n - 1)+';'
+ }, [
+ h('span.cp-form-weekday', _days[day].name),
+ h('span.cp-form-weekday-separator', ' - '),
+ h('span', day)
+ ]));
});
lines.unshift(h('div', days));
}
@@ -640,13 +677,19 @@ define([
}, v);
return cell;
});
- els.unshift(h('div.cp-poll-cell.cp-poll-answer-name', {
+ var nameCell;
+ els.unshift(nameCell = h('div.cp-poll-cell.cp-poll-answer-name', {
title: Util.fixHTML(name)
}, [
avatar,
h('span', name)
]));
bodyEls.push(h('div', els));
+ if (resultsPageObj && (APP.isEditor || APP.isAuditor)) {
+ $(nameCell).addClass('cp-clickable').click(function () {
+ APP.renderResults(resultsPageObj.content, resultsPageObj.answers, answerObj.curve);
+ });
+ }
});
}
var body = h('div.cp-form-poll-body', bodyEls);
@@ -788,6 +831,7 @@ define([
if (filterCurve && user === filterCurve) { return; }
try {
return {
+ curve: user,
user: answers[user].msg._userdata,
results: answers[user].msg[uid]
};
@@ -950,7 +994,7 @@ define([
printResults: function (answers, uid) {
var results = [];
var empty = 0;
- Object.keys(answers).forEach(function (author) {
+ Object.keys(answers).forEach(function (author) { // TODO deduplicate these?
var obj = answers[author];
var answer = obj.msg[uid];
if (!answer || !answer.trim()) { return empty++; }
@@ -1016,7 +1060,7 @@ define([
printResults: function (answers, uid) {
var results = [];
var empty = 0;
- Object.keys(answers).forEach(function (author) {
+ Object.keys(answers).forEach(function (author) { // TODO deduplicate these
var obj = answers[author];
var answer = obj.msg[uid];
if (!answer || !answer.trim()) { return empty++; }
@@ -1090,8 +1134,7 @@ define([
var obj = answers[author];
var answer = obj.msg[uid];
if (!answer || !answer.trim()) { return empty++; }
- count[answer] = count[answer] || 0;
- count[answer]++;
+ Util.inc(count, answer);
});
Object.keys(count).forEach(function (value) {
results.push(h('div.cp-form-results-type-radio-data', [
@@ -1193,8 +1236,7 @@ define([
var c = count[q_uid] = count[q_uid] || {};
var res = answer[q_uid];
if (!res || !res.trim()) { return; }
- c[res] = c[res] || 0;
- c[res]++;
+ Util.inc(c, res);
});
});
Object.keys(count).forEach(function (q_uid) {
@@ -1304,8 +1346,7 @@ define([
var answer = obj.msg[uid];
if (!Array.isArray(answer) || !answer.length) { return empty++; }
answer.forEach(function (val) {
- count[val] = count[val] || 0;
- count[val]++;
+ Util.inc(count, val);
});
});
Object.keys(count).forEach(function (value) {
@@ -1420,8 +1461,7 @@ define([
var res = answer[q_uid];
if (!Array.isArray(res) || !res.length) { return; }
res.forEach(function (v) {
- c[v] = c[v] || 0;
- c[v]++;
+ Util.inc(c, v);
});
});
});
@@ -1470,7 +1510,9 @@ define([
if (!Array.isArray(opts.values)) { return; }
var map = {};
var invMap = {};
- var els = opts.values.map(function (data, i) {
+ var sorted = false;
+ Util.shuffleArray(opts.values);
+ var els = opts.values.map(function (data) {
var uid = Util.uid();
map[uid] = data;
invMap[data] = uid;
@@ -1479,7 +1521,7 @@ define([
h('i.fa.fa-ellipsis-v'),
h('i.fa.fa-ellipsis-v'),
]),
- h('span.cp-form-sort-order', (i+1)),
+ h('span.cp-form-sort-order', '?'),
h('span', data)
]);
$(div).data('val', data);
@@ -1490,10 +1532,11 @@ define([
els
]);
var $tag = $(tag);
- var reorder = function () {
+ var reorder = function (reset) {
$tag.find('.cp-form-type-sort').each(function (i, el) {
- $(el).find('.cp-form-sort-order').text(i+1);
+ $(el).find('.cp-form-sort-order').text(reset ? '?' : i+1);
});
+ sorted = !reset;
};
var cursorGetter;
var setCursorGetter = function (f) { cursorGetter = f; };
@@ -1516,16 +1559,18 @@ define([
return {
tag: tag,
getValue: function () {
+ if (!sorted) { return; }
return sortable.toArray().map(function (id) {
return map[id];
});
},
reset: function () {
+ Util.shuffleArray(opts.values);
var toSort = (opts.values).map(function (val) {
return invMap[val];
});
sortable.sort(toSort);
- reorder();
+ reorder(true);
},
edit: function (cb, tmp) {
var v = Util.clone(opts);
@@ -1554,7 +1599,7 @@ define([
if (!Array.isArray(answer) || !answer.length) { return empty++; }
answer.forEach(function (el, i) {
var score = l - i;
- count[el] = (count[el] || 0) + score;
+ Util.inc(count, el, score);
});
});
var sorted = Object.keys(count).sort(function (a, b) {
@@ -1583,7 +1628,7 @@ define([
if (!opts) { opts = TYPES.poll.defaultOpts; }
if (!Array.isArray(opts.values)) { return; }
- var lines = makePollTable(answers, opts);
+ var lines = makePollTable(answers, opts, false);
// Add form
var addLine = opts.values.map(function (data) {
@@ -1667,31 +1712,53 @@ define([
};
},
- printResults: function (answers, uid, form) {
+ printResults: function (answers, uid, form, content) {
var opts = form[uid].opts || TYPES.poll.defaultOpts;
var _answers = getBlockAnswers(answers, uid);
- var lines = makePollTable(_answers, opts);
+
+ // If content is defined, we'll be able to click on a row to display
+ // all the answers of this user
+ var lines = makePollTable(_answers, opts, content && {
+ content: content,
+ answers: answers
+ });
var total = makePollTotal(_answers, opts);
if (total) { lines.push(h('div', total)); }
return h('div.cp-form-type-poll', lines);
},
- exportCSV: function (answer) {
- if (answer === false) { return; }
- if (!answer || !answer.values) { return ['']; }
+ exportCSV: function (answer, form) {
+ var opts = form.opts || TYPES.poll.defaultOpts;
+ var q = form.q || Messages.form_default;
+ if (answer === false) {
+ var cols = opts.values.map(function (key) {
+ return q + ' | ' + key;
+ });
+ cols.unshift(q);
+ return cols;
+ }
+ if (!answer || !answer.values) {
+ var empty = opts.values.map(function () { return ''; });
+ empty.unshift('');
+ return empty;
+ }
var str = '';
Object.keys(answer.values).sort().forEach(function (k, i) {
if (i !== 0) { str += ';'; }
str += k.replace(';', '').replace(':', '') + ':' + answer.values[k];
});
- return [str];
+ var res = opts.values.map(function (key) {
+ return answer.values[key] || '';
+ });
+ res.unshift(str);
+ return res;
},
icon: h('i.cptools.cptools-form-poll')
},
};
- var renderResults = function (content, answers) {
+ var renderResults = APP.renderResults = function (content, answers, showUser) {
var $container = $('div.cp-form-creator-results').empty();
if (!Object.keys(answers || {}).length) {
@@ -1699,9 +1766,15 @@ define([
return;
}
+ if (content.answers.msg) {
+ var description = h('div.cp-form-creator-results-description#cp-form-response-msg');
+ var $desc = $(description).appendTo($container);
+ DiffMd.apply(DiffMd.render(content.answers.msg), $desc, APP.common);
+ }
+
var controls = h('div.cp-form-creator-results-controls');
var $controls = $(controls).appendTo($container);
- var exportButton = h('button.btn.btn-secondary', Messages.exportButton); // XXX form_exportCSV;
+ var exportButton = h('button.btn.btn-secondary', Messages.form_exportCSV);
var exportCSV = h('div.cp-form-creator-results-export', exportButton);
$(exportCSV).appendTo($container);
var results = h('div.cp-form-creator-results-content');
@@ -1729,7 +1802,9 @@ define([
var type = block.type;
var model = TYPES[type];
if (!model || !model.printResults) { return; }
- var print = model.printResults(answers, uid, form);
+
+ // Only use content if we're not viewing individual answers
+ var print = model.printResults(answers, uid, form, !header && content);
var q = h('div.cp-form-block-question', block.q || Messages.form_default);
@@ -1823,14 +1898,23 @@ define([
e.preventDefault();
APP.common.openURL(Hash.hashToHref(ud.profile, 'profile'));
});
+ if (showUser === curve) {
+ setTimeout(function () {
+ showUser = undefined;
+ $(viewButton).click();
+ });
+ }
return div;
});
$results.append(els);
});
+ if (showUser) {
+ $s.click();
+ }
};
var addResultsButton = function (framework, content) {
- var $res = $(h('button.cp-toolbar-appmenu', [
+ var $res = $(h('button.cp-toolbar-appmenu.cp-toolbar-form-button', [
h('i.fa.fa-bar-chart'),
h('span.cp-button-name', Messages.form_results)
]));
@@ -1872,25 +1956,42 @@ define([
var makeFormControls = function (framework, content, update, evOnChange) {
var loggedIn = framework._.sfCommon.isLoggedIn();
var metadataMgr = framework._.cpNfInner.metadataMgr;
+ var user = metadataMgr.getUserData();
if (!loggedIn && !content.answers.anonymous) { return; }
var cbox;
+ var anonName, $anonName;
cbox = UI.createCheckbox('cp-form-anonymous',
Messages.form_anonymousBox, true, { mark: { tabindex:1 } });
+ var $anonBox = $(cbox).find('input');
if (loggedIn) {
if (!content.answers.anonymous || APP.cantAnon) {
$(cbox).hide().find('input').attr('disabled', 'disabled').prop('checked', false);
}
+ } else {
+ anonName = h('div.cp-form-anon-answer-input', [
+ Messages.form_answerAs,
+ h('input', {
+ value: user.name || '',
+ placeholder: Messages.form_anonName
+ })
+ ]);
+ $anonName = $(anonName).hide();
+ $anonBox.on('change', function () {
+ if (Util.isChecked($anonBox)) { $anonName.hide(); }
+ else { $anonName.show(); }
+ });
}
var send = h('button.cp-open.btn.btn-primary', update ? Messages.form_update : Messages.form_submit);
- var reset = h('button.cp-open.btn.btn-danger-alt', Messages.form_reset);
+ var reset = h('button.cp-open.cp-reset-button.btn.btn-danger-alt', Messages.form_reset);
$(reset).click(function () {
if (!Array.isArray(APP.formBlocks)) { return; }
APP.formBlocks.forEach(function (data) {
if (typeof(data.reset) === "function") { data.reset(); }
});
+ $(reset).attr('disabled', 'disabled');
});
var $send = $(send).click(function () {
$send.attr('disabled', 'disabled');
@@ -1898,14 +1999,16 @@ define([
if (!results) { return; }
var user = metadataMgr.getUserData();
- if (!Util.isChecked($(cbox).find('input'))) {
+ if (!Util.isChecked($anonBox)) {
results._userdata = loggedIn ? {
avatar: user.avatar,
name: user.name,
notifications: user.notifications,
curvePublic: user.curvePublic,
profile: user.profile
- } : { name: user.name };
+ } : {
+ name: $anonName ? $anonName.find('input').val() : user.name
+ };
}
var sframeChan = framework._.sfCommon.getSframeChannel();
@@ -1985,7 +2088,10 @@ define([
return h('div.cp-form-send-container', [
invalid,
- cbox ? h('div.cp-form-anon-answer', cbox) : undefined,
+ cbox ? h('div.cp-form-anon-answer', [
+ cbox,
+ anonName
+ ]) : undefined,
reset, send
]);
};
@@ -1997,6 +2103,18 @@ define([
APP.formBlocks = [];
+ if (APP.isClosed && content.answers.privateKey && !APP.isEditor) {
+ var sframeChan = framework._.sfCommon.getSframeChannel();
+ sframeChan.query("Q_FORM_FETCH_ANSWERS", content.answers, function (err, obj) {
+ var answers = obj && obj.results;
+ if (answers) { APP.answers = answers; }
+ $('body').addClass('cp-app-form-results');
+ $('.cp-toolbar-form-button').remove();
+ renderResults(content, answers);
+ });
+ return;
+ }
+
var evOnChange = Util.mkEvent();
if (!APP.isEditor) {
var _answers = Util.clone(answers || {});
@@ -2004,6 +2122,7 @@ define([
delete _answers._userdata;
evOnChange.reg(function (noBeforeUnload, isSave) {
if (noBeforeUnload) { return; }
+ $container.find('.cp-reset-button').removeAttr('disabled');
var results = getFormResults();
if (isSave) {
answers = Util.clone(results || {});
@@ -2356,6 +2475,9 @@ define([
// In view mode, add "Submit" and "reset" buttons
$container.append(makeFormControls(framework, content, Boolean(answers), evOnChange));
+ if (!answers) {
+ $container.find('.cp-reset-button').attr('disabled', 'disabled');
+ }
};
var getTempFields = function () {
@@ -2443,6 +2565,72 @@ define([
};
refreshPublic();
+ var responseMsg = h('div.cp-form-response-msg-container');
+ var $responseMsg = $(responseMsg);
+ var refreshResponse = function () {
+ if (true) { return; } // XXX 4.10.0
+ $responseMsg.empty();
+ Messages.form_updateMsg = "Update response message"; // XXX 4.10.0
+ Messages.form_addMsg = "Add response message"; // XXX 4.10.0
+ Messages.form_responseMsg = "Add a message that will be displayed in the response page."; // XXX 4.10.0
+ var text = content.answers.msg ? Messages.form_updateMsg : Messages.form_addMsg;
+ var btn = h('button.btn.btn-secondary', text);
+ $(btn).click(function () {
+ var editor;
+ if (!APP.responseModal) {
+ var t = h('textarea');
+ var div = h('div', [
+ h('p', Messages.form_responseMsg),
+ t
+ ]);
+ var cm = SFCodeMirror.create("gfm", CMeditor, t);
+ editor = APP.responseEditor = cm.editor;
+ editor.setOption('lineNumbers', true);
+ editor.setOption('lineWrapping', true);
+ editor.setOption('styleActiveLine', true);
+ editor.setOption('readOnly', false);
+ setTimeout(function () {
+ editor.setValue(content.answers.msg || '');
+ editor.refresh();
+ editor.save();
+ editor.focus();
+ });
+
+ var buttons = [{
+ className: 'primary',
+ name: Messages.settings_save,
+ onClick: function () {
+ var v = editor.getValue();
+ content.answers.msg = v.trim(0, 2000); // XXX 4.10.0 max length?
+ framework.localChange();
+ framework._.cpNfInner.chainpad.onSettle(function () {
+ UI.log(Messages.saved);
+ refreshResponse();
+ });
+ },
+ //keys: []
+ }, {
+ className: 'cancel',
+ name: Messages.cancel,
+ onClick: function () {},
+ keys: [27]
+ }];
+ APP.responseModal = UI.dialog.customModal(div, { buttons: buttons });
+ } else {
+ editor = APP.responseEditor;
+ setTimeout(function () {
+ editor.setValue(content.answers.msg || '');
+ editor.refresh();
+ editor.save();
+ editor.focus();
+ });
+ }
+ UI.openCustomModal(APP.responseModal);
+ });
+ // $responseMsg.append(btn); // XXX 4.10.0
+ };
+ //refreshResponse();
+
// Allow anonymous answers
var privacyContainer = h('div.cp-form-privacy-container');
var $privacy = $(privacyContainer);
@@ -2538,11 +2726,13 @@ define([
evOnChange.reg(refreshPublic);
evOnChange.reg(refreshPrivacy);
evOnChange.reg(refreshEndDate);
+ //evOnChange.reg(refreshResponse);
return [
endDateContainer,
privacyContainer,
resultsType,
+ responseMsg
];
};
diff --git a/www/form/main.js b/www/form/main.js
index 3b2c160a4..de2c311ef 100644
--- a/www/form/main.js
+++ b/www/form/main.js
@@ -176,7 +176,7 @@ define([
validateKey: keys.secondaryValidateKey,
owners: [myKeys.edPublic],
crypto: crypto,
- //Cache: Utils.Cache // XXX
+ //Cache: Utils.Cache // XXX 4.10.0
};
var results = {};
config.onError = function (info) {
@@ -265,6 +265,7 @@ define([
}, function (obj) {
if (obj && obj.error) { return void cb(obj); }
var messages = obj.messages;
+ if (!messages.length) { return void cb(); }
var res = Utils.Crypto.Mailbox.openOwnSecretLetter(messages[0].msg, {
validateKey: data.validateKey,
ephemeral_private: Nacl.util.decodeBase64(answer.curvePrivate),
diff --git a/www/pad/inner.js b/www/pad/inner.js
index 304e9abe4..95e7c1bff 100644
--- a/www/pad/inner.js
+++ b/www/pad/inner.js
@@ -1326,6 +1326,11 @@ define([
}));
$(waitFor());
}).nThen(function(waitFor) {
+ // TODO this breaks users' ability to tab out of the editor
+ // but that's a problem in other editors and nobody has complained so far
+ // so we'll include this as-is for now while we search for a good pattern
+ // addresses this issue more generally
+ Ckeditor.config.tabSpaces = 4;
Ckeditor.config.toolbarCanCollapse = true;
Ckeditor.config.language = Messages._getLanguage();
if (screen.height < 800) {
diff --git a/www/secureiframe/inner.js b/www/secureiframe/inner.js
index 4226c7f82..18970c47b 100644
--- a/www/secureiframe/inner.js
+++ b/www/secureiframe/inner.js
@@ -127,6 +127,7 @@ define([
sframeChan.event("EV_SECURE_ACTION", {
type: parsed.type,
password: data.password,
+ static: data.static,
href: data.url,
name: data.name
});
@@ -214,20 +215,21 @@ define([
$container.html('');
Object.keys(list).forEach(function (id) {
var data = list[id];
- var name = data.filename || data.title || '?';
+ var name = data.filename || data.title || data.name || '?';
if (filter && name.toLowerCase().indexOf(filter.toLowerCase()) === -1) {
return;
}
var $span = $('', {
'class': 'cp-filepicker-content-element',
- 'title': name,
+ 'title': Util.fixHTML(name),
}).appendTo($container);
$span.append(UI.getFileIcon(data));
$('', {'class': 'cp-filepicker-content-element-name'}).text(name)
.appendTo($span);
+ if (data.static) { $span.attr('title', Util.fixHTML(data.href)); }
$span.click(function () {
if (typeof onFilePicked === "function") {
- onFilePicked({url: data.href, name: name, password: data.password});
+ onFilePicked({url: data.href, name: name, static: data.static, password: data.password});
}
});
diff --git a/www/support/ui.js b/www/support/ui.js
index 90fd43fa5..f61ddac4b 100644
--- a/www/support/ui.js
+++ b/www/support/ui.js
@@ -345,7 +345,7 @@ define([
var senderKey = content.sender && content.sender.edPublic;
var fromMe = senderKey === privateData.edPublic;
var fromAdmin = ctx.adminKeys.indexOf(senderKey) !== -1;
- var fromPremium = Boolean(content.sender.plan);
+ var fromPremium = Boolean(content.sender.plan || Util.find(content, ['sender', 'quota', 'plan']));
var userData = h('div.cp-support-showdata', [
Messages.support_showData,
diff --git a/www/teams/main.js b/www/teams/main.js
index ea48f72a1..7d12126eb 100644
--- a/www/teams/main.js
+++ b/www/teams/main.js
@@ -24,7 +24,10 @@ define([
sframeChan.on('Q_DRIVE_USEROBJECT', function (data, cb) {
if (!teamId) { return void cb({error: 'EINVAL'}); }
- data.teamId = teamId;
+ // a teamId of -1 bypasses guards against modifying your drive
+ // from the team app
+ if (data.teamId !== -1) { data.teamId = teamId; }
+ else { delete data.teamId; }
Cryptpad.userObjectCommand(data, cb);
});
sframeChan.on('Q_DRIVE_GETOBJECT', function (data, cb) {