').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/onlyoffice/inner.js b/www/common/onlyoffice/inner.js
index 4a0a56be0..2afef4978 100644
--- a/www/common/onlyoffice/inner.js
+++ b/www/common/onlyoffice/inner.js
@@ -72,8 +72,31 @@ define([
return JSONSortify(obj);
};
+ /* Chrome 92 dropped support for SharedArrayBuffer in cross-origin contexts
+ where window.crossOriginIsolated is false.
+
+ Their blog (https://blog.chromium.org/2021/02/restriction-on-sharedarraybuffers.html)
+ isn't clear about why they're doing this, but since it's related to site-isolation
+ it seems they're trying to do vague security things.
+
+ In any case, there seems to be a workaround where you can still create them
+ by using `new WebAssembly.Memory({shared: true, ...})` instead of `new SharedArrayBuffer`.
+
+ This seems unreliable, but it's better than not being able to export, since
+ we actively rely on postMessage between iframes and therefore can't afford
+ to opt for full isolation.
+ */
+ var supportsSharedArrayBuffers = function () {
+ try {
+ return Object.prototype.toString.call(new window.WebAssembly.Memory({shared: true, initial: 0, maximum: 0}).buffer) === '[object SharedArrayBuffer]';
+ } catch (err) {
+ console.error(err);
+ }
+ return false;
+ };
+
var supportsXLSX = function () {
- return !(typeof(Atomics) === "undefined" || typeof (SharedArrayBuffer) === "undefined" || typeof(WebAssembly) === 'undefined');
+ return !(typeof(Atomics) === "undefined" || !supportsSharedArrayBuffers() /* || typeof (SharedArrayBuffer) === "undefined" */ || typeof(WebAssembly) === 'undefined');
};
@@ -1915,7 +1938,9 @@ define([
var exportXLSXFile = function() {
var text = getContent();
var suggestion = Title.suggestTitle(Title.defaultTitle);
- var ext = ['.xlsx', '.ods', '.bin', '.csv', '.pdf'];
+ var ext = ['.xlsx', '.ods', '.bin',
+ //'.csv', // XXX
+ '.pdf'];
var type = common.getMetadataMgr().getPrivateData().ooType;
var warning = '';
if (type==="presentation") {
diff --git a/www/common/onlyoffice/x2t/x2t.js b/www/common/onlyoffice/x2t/x2t.js
index 181b2db8f..9c1289c56 100644
--- a/www/common/onlyoffice/x2t/x2t.js
+++ b/www/common/onlyoffice/x2t/x2t.js
@@ -1,3 +1,8 @@
+function SUPPORTS_SHARED_MEMORY() {
+ return typeof(SharedArrayBuffer) !== 'undefined';
+}
+
+
// Support for growable heap + pthreads, where the buffer may change, so JS views
// must be updated.
function GROWABLE_HEAP_STORE_I8(ptr, value) {
@@ -1030,7 +1035,7 @@ if (ENVIRONMENT_IS_PTHREAD) {
"maximum": 1073741824 / WASM_PAGE_SIZE,
"shared": true
});
- if (!(wasmMemory.buffer instanceof SharedArrayBuffer)) {
+ if (Object.prototype.toString.call(wasmMemory.buffer) !== '[object SharedArrayBuffer]') {
err("requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag");
if (ENVIRONMENT_HAS_NODE) {
console.log("(on node you may need: --experimental-wasm-threads --experimental-wasm-bulk-memory and also use a recent version)");
@@ -2161,7 +2166,7 @@ var PThread = {
}),
receiveObjectTransfer: (function(data) {}),
allocateUnusedWorkers: (function(numWorkers, onFinishedLoading) {
- if (typeof SharedArrayBuffer === "undefined") return;
+ if (!SUPPORTS_SHARED_MEMORY()) return;
var workers = [];
var numWorkersToCreate = numWorkers;
if (PThread.preallocatedWorkers.length > 0) {
@@ -2276,7 +2281,7 @@ var PThread = {
}
}),
createNewWorkers: (function(numWorkers) {
- if (typeof SharedArrayBuffer === "undefined") return [];
+ if (!SUPPORTS_SHARED_MEMORY()) return [];
var pthreadMainJs = "x2t.worker.js";
pthreadMainJs = locateFile(pthreadMainJs);
var newWorkers = [];
@@ -5683,7 +5688,7 @@ function _emscripten_get_sbrk_ptr() {
}
Module["_emscripten_get_sbrk_ptr"] = _emscripten_get_sbrk_ptr;
function _emscripten_has_threading_support() {
- return typeof SharedArrayBuffer !== "undefined";
+ return SUPPORTS_SHARED_MEMORY();
}
Module["_emscripten_has_threading_support"] = _emscripten_has_threading_support;
function _emscripten_is_main_browser_thread() {
@@ -6761,7 +6766,7 @@ function _pthread_self() {
}
Module["_pthread_self"] = _pthread_self;
function _pthread_create(pthread_ptr, attr, start_routine, arg) {
- if (typeof SharedArrayBuffer === "undefined") {
+ if (!SUPPORTS_SHARED_MEMORY()) {
err("Current environment does not support SharedArrayBuffer, pthreads are not available!");
return 6;
}
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/sframe-common.js b/www/common/sframe-common.js
index 5d3738cbb..6a7e3effd 100644
--- a/www/common/sframe-common.js
+++ b/www/common/sframe-common.js
@@ -921,7 +921,7 @@ define([
});
ctx.sframeChan.on('EV_WORKER_TIMEOUT', function () {
- UI.errorLoadingScreen(Messages.timeoutError, false, function () {
+ UI.errorLoadingScreen(Messages.timeoutError, false, function () { // XXX mobile users can't necessarily hit 'ESC' as this message suggests. provice a click option
funcs.gotoURL('');
});
});
diff --git a/www/common/toolbar.js b/www/common/toolbar.js
index bcb6c1683..d3e2ae658 100644
--- a/www/common/toolbar.js
+++ b/www/common/toolbar.js
@@ -878,10 +878,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/translations/messages.de.json b/www/common/translations/messages.de.json
index 226e57c7c..259a19928 100644
--- a/www/common/translations/messages.de.json
+++ b/www/common/translations/messages.de.json
@@ -1332,7 +1332,7 @@
"form_cantFindAnswers": "Deine vorigen Antworten für dieses Formular konnten nicht geladen werden.",
"form_updateWarning": "Trotzdem aktualisieren",
"form_submitWarning": "Trotzdem absenden",
- "form_sent": "Gesendet",
+ "form_sent": "Deine Antwort wurde gesendet",
"form_update": "Aktualisieren",
"form_submit": "Absenden",
"form_type_checkbox": "Mehrfachauswahl",
@@ -1368,5 +1368,16 @@
"admin_purpose_public": "Zur Bereitstellung eines kostenlosen Dienstes für die Allgemeinheit",
"resources_learnWhy": "Mehr über die Gründe erfahren",
"team_leaveOwner": "Bitte entferne dich von der Rolle des Eigentümers, bevor du das Teams verlässt. Beachte, dass Teams mindestens einen Eigentümer haben müssen. Bitte füge daher zunächst einen weiteren Eigentümer hinzu, sofern du derzeit der alleinige Eigentümer bist.",
- "form_exportCSV": "Als CSV exportieren"
+ "form_exportCSV": "Als CSV exportieren",
+ "form_answerAs": "Antworten als",
+ "notification_openLink": "Du hast einen Link {0} von {1} erhalten:",
+ "fm_link_warning": "Warnung: URL ist länger als 200 Zeichen",
+ "notification_linkShared": "{0} hat einen Link mit dir geteilt: {1}",
+ "fm_link_name": "Bezeichnung des Links",
+ "fm_link_invalid": "Ungültige URL",
+ "form_anonName": "Dein Name",
+ "fm_link_name_placeholder": "Mein Link",
+ "fm_link_url": "URL",
+ "fm_link_type": "Link",
+ "fm_link_new": "Neuer Link"
}
diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json
index 1ea01009b..5974401e7 100644
--- a/www/common/translations/messages.fr.json
+++ b/www/common/translations/messages.fr.json
@@ -1315,7 +1315,7 @@
"form_updateWarning": "Mettre à jour avec erreurs",
"form_submitWarning": "Envoyer avec erreurs",
"form_delete": "Supprimer",
- "form_sent": "Envoyé",
+ "form_sent": "Votre réponse a été envoyée",
"form_reset": "Effacer",
"form_update": "Mettre à jour",
"form_submit": "Envoyer",
@@ -1368,5 +1368,16 @@
"admin_purpose_business": "Usage en entreprise",
"admin_instancePurposeHint": "À quel usage cette instance est-elle destinée ? Votre réponse sera utilisée pour définir la planification de nouvelles fonctionnalités (si votre télémétrie est activée).",
"team_leaveOwner": "Veuillez vous rétrograder de votre rôle de propriétaire avant de quitter l'équipe. Notez que les équipes doivent avoir au moins un propriétaire, veuillez en ajouter un autre avant de poursuivre si vous êtes actuellement le seul propriétaire.",
- "form_exportCSV": "Exporter en CSV"
+ "form_exportCSV": "Exporter en CSV",
+ "fm_link_invalid": "URL invalide",
+ "fm_link_warning": "Attention : l'URL dépasse 200 caractères",
+ "form_answerAs": "Répondre en tant que",
+ "form_anonName": "Votre nom",
+ "notification_linkShared": "{0} a partagé un lien avec vous : {1}",
+ "fm_link_name_placeholder": "Nouveau lien",
+ "fm_link_url": "URL",
+ "fm_link_name": "Titre du lien",
+ "fm_link_type": "Lien",
+ "fm_link_new": "Nouveau Lien",
+ "notification_openLink": "Vous avez reçu un lien {0} de {1} :"
}
diff --git a/www/common/translations/messages.ja.json b/www/common/translations/messages.ja.json
index 742a137d6..84fe9a3ae 100644
--- a/www/common/translations/messages.ja.json
+++ b/www/common/translations/messages.ja.json
@@ -1,7 +1,7 @@
{
"common_connectionLost": "サーバーとの接続が切断しました
再接続するまで閲覧モードになります。",
"button_newsheet": "新規スプレッドシート",
- "button_newkanban": "新規のカンバン",
+ "button_newkanban": "新規カンバン",
"button_newwhiteboard": "新規ホワイトボード",
"button_newslide": "新規プレゼンテーション",
"button_newpoll": "新規投票・アンケート",
@@ -111,7 +111,7 @@
"settings_backupCategory": "バックアップ",
"settings_backup": "バックアップ",
"settings_title": "設定",
- "settings_cat_subscription": "サブスクリプション",
+ "settings_cat_subscription": "定額利用",
"settings_cat_drive": "CryptDrive",
"settings_cat_account": "アカウント",
"settings_save": "保存",
@@ -125,7 +125,7 @@
"login_invalUser": "ユーザー名を入力してください",
"register_importRecent": "匿名セッションのドキュメントをインポート",
"importButton": "インポート",
- "main_catch_phrase": "コラボレーションスイート
暗号化されかつオープンソース",
+ "main_catch_phrase": "コラボレーションスイート
端末間暗号化とオープンソース",
"tos_3rdparties": "私たちは、法令に基づく場合を除き、個人情報を第三者に提供しません。",
"tos_logs": "あなたのブラウザからサーバーに送信されたメタデータは、サービスを維持するために記録される場合があります。",
"tos_availability": "私たちはこのサービスがあなたの役に立つことを願っていますが、可用性や性能は保証できません。定期的にデータをエクスポートしてください。",
@@ -150,7 +150,7 @@
"logoutButton": "ログアウト",
"login_login": "ログイン",
"autostore_hide": "保存しない",
- "autostore_store": "保存する",
+ "autostore_store": "保存",
"autostore_notstored": "この{0}はあなたのCryptDriveに保存されていません。今すぐ保存しますか?",
"user_displayName": "表示名",
"exportButton": "エクスポート",
@@ -166,13 +166,13 @@
"profile_upload": " 新しいアバターをアップロード",
"teams_table_generic_edit": "編集: フォルダとパッドの作成、変更、削除が可能。",
"teams_table_generic_view": "表示: フォルダとパッドへのアクセス(閲覧のみ)。",
- "teams_table_generic_own": "チームの管理: チーム名とチームのアバターの変更、オーナーの追加または削除、チームのサブスクリプションの変更、チームの削除が可能。",
+ "teams_table_generic_own": "チームの管理: チーム名とチームのアバターの変更、オーナーの追加または削除、チームの定額利用に関する変更、チームの削除が可能。",
"teams_table_owners": "チームの管理",
"teams_table_generic_admin": "メンバーの管理: メンバーの招待および取り消し、メンバーに管理者までの権限の付与が可能。",
"teams_table_admins": "メンバーの管理",
"teams_table_generic": "権限一覧",
"teams_table": "権限",
- "contacts_fetchHistory": "古い履歴を取得する",
+ "contacts_fetchHistory": "古い履歴を取得",
"contacts_warning": "ここに入力した全てのメッセージは永続的であり、このパッドの現在および将来の全てのユーザーが確認できます。機密情報の入力には注意してください!",
"contacts_typeHere": "ここにメッセージを入力...",
"team_cat_drive": "ドライブ",
@@ -228,14 +228,14 @@
"notifications_cat_friends": "連絡先リクエスト",
"notifications_dismiss": "確認済みにする",
"settings_autostoreMaybe": "手動 (確認しない)",
- "settings_autostoreNo": "手動 (常に確認する)",
+ "settings_autostoreNo": "手動(常に確認)",
"settings_autostoreHint": "自動 アクセスした全てのパッドをCryptDriveに保存します。
手動(常に確認) 保存していないパッドにアクセスした際、CryptDriveに保存するかどうかを確認します。
手動(確認しない) アクセス先のパッドはCryptDriveに自動で保存されません。保存オプションは表示されません。",
"settings_userFeedback": "ユーザーフィードバックを有効にする",
"settings_userFeedbackHint2": "あなたのパッドのコンテンツがサーバーと共有されることはありません。",
"settings_userFeedbackHint1": "CryptPadは、ユーザーエクスペリエンスの向上のため、いくつかの非常に基本的なフィードバックを、サーバーに提供します。 ",
"settings_userFeedbackTitle": "フィードバック",
"settings_autostoreYes": "自動",
- "settings_importConfirm": "このブラウザで最近使用したパッドを、あなたのユーザーアカウントのCryptDriveにインポートしますか?",
+ "settings_importConfirm": "このブラウザで最近利用したパッドを、あなたのユーザーアカウントのCryptDriveにインポートしますか?",
"settings_importDone": "インポートが完了しました",
"settings_import": "インポート",
"settings_importTitle": "このブラウザでの最近のパッドをあなたのCryptDriveにインポートします",
@@ -246,7 +246,7 @@
"fc_delete": "ごみ箱へ移動",
"fc_remove": "削除",
"fc_restore": "復元",
- "fc_delete_owned": "完全削除",
+ "fc_delete_owned": "破棄",
"creation_create": "作成",
"creation_password": "パスワード\n",
"creation_expireMonths": "か月",
@@ -266,7 +266,7 @@
"features_f_cryptdrive1": "CryptDriveの全機能",
"features_f_anon_note": "追加機能あり",
"features_f_anon": "匿名ユーザーの全機能",
- "features_f_storage0_note": "ドキュメントは{0}日以上使用されないと削除されます",
+ "features_f_storage0_note": "ドキュメントは{0}日以上利用されないと削除されます",
"features_f_storage0": "一時的な保存",
"features_f_cryptdrive0_note": "アクセスしたパッドをブラウザに保存して、後で開くことができます",
"features_f_cryptdrive0": "CryptDriveへの限定的なアクセス",
@@ -322,7 +322,7 @@
"properties_passwordSuccessFile": "パスワードは正常に変更されました。",
"drive_sfPasswordError": "誤ったパスワードです",
"team_title": "チーム: {0}",
- "password_error": "ドキュメントが存在しません!
このエラーは、誤ったパスワードが入力された場合、またはドキュメントがサーバーから完全削除された場合に発生します。",
+ "password_error": "ドキュメントが存在しません!
このエラーは、誤ったパスワードが入力された場合、またはドキュメントがサーバーから破棄された場合に発生します。",
"password_error_seed": "パッドが存在しません!
このエラーは「パスワードが追加・変更された」場合、または「パッドがサーバーから削除された」場合に発生します。",
"password_submit": "送信",
"password_placeholder": "パスワードを入力...",
@@ -332,7 +332,7 @@
"properties_addPassword": "パスワードを設定",
"history_close": "閉じる",
"history_restore": "復元",
- "fm_emptyTrashOwned": "ごみ箱に、あなたが所有しているドキュメントが入っています。あなたのドライブからのみ削除するか、全てのユーザーから完全削除するかを選択できます。",
+ "fm_emptyTrashOwned": "ごみ箱に、あなたの所有するドキュメントが入っています。あなたのドライブからのみ削除するか、全てのユーザーから破棄するかを選択できます。",
"access_destroyPad": "このドキュメントまたはフォルダを完全に削除する",
"accessButton": "アクセス",
"access_allow": "リスト",
@@ -417,7 +417,7 @@
"fm_noname": "無題のドキュメント",
"fm_openParent": "フォルダに表示",
"fm_newButtonTitle": "新しいドキュメントやフォルダを作成したり、ファイルを現在のフォルダにインポートしたりできます。",
- "contacts_info4": "チャットの参加者のどちらも履歴を削除できます",
+ "contacts_info4": "チャットの参加者は誰でも履歴を消去できます",
"contacts_info2": "連絡先のアイコンをクリックしてチャットを開始",
"contacts_confirmRemove": "{0}をあなたの連絡先から削除してよろしいですか?",
"profile_error": "プロフィールの作成時にエラーが発生しました: {0}",
@@ -475,7 +475,7 @@
"poll_create_option": "新しいオプションを追加",
"poll_create_user": "新しいユーザーを追加",
"pad_mediatagImport": "あなたのCryptDriveに保存",
- "admin_listMyInstanceLabel": "このインスタンスをリストに表示する",
+ "admin_listMyInstanceLabel": "このインスタンスをリストに表示",
"admin_checkupTitle": "インスタンスの設定を検証",
"cba_disable": "消去して無効にする",
"upload_pending": "保留中",
@@ -560,7 +560,7 @@
"form_form": "フォーム",
"form_editor": "エディタ",
"form_delete": "削除",
- "form_sent": "送信しました",
+ "form_sent": "回答を送信しました",
"form_reset": "リセット",
"form_update": "更新",
"form_submit": "送信",
@@ -595,7 +595,7 @@
"toolbar_file": "ファイル",
"drive_treeButton": "ファイル",
"toolbar_insert": "挿入",
- "fm_sort": "並び替え",
+ "fm_sort": "並び替える",
"comments_comment": "コメント",
"comments_resolve": "解決",
"comments_reply": "返信",
@@ -765,25 +765,25 @@
"errorState": "重大なエラー: {0}",
"realtime_unrecoverableError": "回復不能なエラーが発生しました。OKをクリックして再読み込みを行ってください。",
"disabledApp": "このアプリケーションは無効になっています。詳細については、このCryptPadの管理者にお問い合わせください。",
- "deletedFromServer": "パッドは完全削除されました",
+ "deletedFromServer": "ドキュメントが破棄されました",
"newVersionError": "新しいバージョンのCryptPadがあります。
リロードすると新しいバージョンを読み込みます。Escキーを押すとオフラインモードでコンテンツにアクセスします。",
"errorRedirectToHome": "Escキーを押すとCryptDriveにリダイレクトします。",
"errorCopy": " Escキーを押すと、閲覧モードで引き続きコンテンツにアクセスできます。",
"invalidHashError": "要求したドキュメントのURLが無効です。",
"chainpadError": "コンテンツを更新する際に重大なエラーが発生しました。コンテンツが失われないよう、閲覧モードで表示されています。
このパッドを表示し続けるにはEscキーを押し、再度編集を試みるにはリロードをしてください。",
- "inactiveError": "このパッドは使用されていなかったため削除されました。Escキーを押して新しいパッドを作成します。",
- "deletedError": "このドキュメントは削除されたため、使用できなくなりました。",
- "expiredError": "このパッドは使用期限が過ぎてしまったため、使用できなくなりました。",
+ "inactiveError": "このパッドは利用されていなかったため削除されました。Escキーを押すと新しいパッドを作成します。",
+ "deletedError": "このドキュメントは削除されたため、利用できなくなりました。",
+ "expiredError": "このパッドは利用期限を過ぎてしまったため、利用できなくなりました。",
"anonymousStoreDisabled": "このCryptPadのインスタンスの管理者は、匿名ユーザーによる保存を無効に設定しています。CryptDriveを使用するにはログインする必要があります。",
- "padNotPinnedVariable": "このパッドは{4}日使用しないと期限切れになります。{0}ログイン{1}または{2}登録{3}し保存してください。",
- "padNotPinned": "このパッドは3ヶ月間使用しないと有効期限が切れます。{0}ログイン{1}するか{2}登録{3}して保存してください。",
+ "padNotPinnedVariable": "このパッドは{4}日間利用しないと有効期限が切れます。{0}ログイン{1}するか{2}登録{3}して保存してください。",
+ "padNotPinned": "このパッドは3か月間利用しないと有効期限が切れます。{0}ログイン{1}するか{2}登録{3}して保存してください。",
"onLogout": "ログアウトしました。{0}ここをクリック{1}するか
Escapeキーを押すと、閲覧モードでパッドにアクセスできます。",
"typeError": "このパッドは選択したアプリケーションと互換性がありません",
"form_type_page": "ページ分割",
"form_description_default": "ここにテキストを入力",
"team_pcsSelectHelp": "所有するパッドをチームのドライブに作成すると、そのパッドのオーナー権はチームに与えられます。",
"sharedFolders_create_owned": "所有するフォルダ",
- "creation_owned1": "所有している項目は、オーナーの望むときにいつでも完全削除できます。完全削除すると、他のユーザーのCryptDriveからも削除されます。",
+ "creation_owned1": "所有している項目は、オーナーの望むときにいつでも破棄できます。破棄すると、他のユーザーのCryptDriveからも削除されます。",
"creation_owned": "パッドを所有",
"uploadFolder_modal_owner": "所有するファイル",
"upload_modal_owner": "所有するファイル",
@@ -824,7 +824,7 @@
"pad_goToAnchor": "アンカーに移動",
"oo_cantMigrate": "この表はアップロードの最大のサイズを超えているため、移行することができません。",
"footer_roadmap": "ロードマップ",
- "settings_deleteSubscription": "サブスクリプションを管理",
+ "settings_deleteSubscription": "定額利用を管理",
"broadcast_translations": "翻訳",
"admin_broadcastCancel": "メッセージを削除",
"admin_broadcastButton": "送信",
@@ -1252,7 +1252,7 @@
"owner_removeMeConfirm": "オーナー権を放棄しようとしています。これは取り消せません。よろしいですか?",
"requestEdit_confirm": "{1}がパッド「{0}」の編集権を要求しました。編集権を与えますか?",
"admin_supportInitHelp": "サーバーはサポートメールボックスを使用するように設定されていません。サポートメールボックスを有効にし、ユーザーからメッセージを受け取るためには、サーバーの管理者に連絡し、「./scripts/generate-admin-keys.js」のスクリプトを実行してもらい、生成された公開鍵を「config.js」に保存して、秘密鍵をあなたに送信してもらうよう依頼する必要があります。",
- "feedback_privacy": "私たちはプライバシーを配慮すると同時に、CryptPadを使いやすくしたいと望んでいます。このファイルは、実行されたアクションを特定するパラメーターと共に要求され、ユーザーにとって重要なUI機能を特定するために使用されます。",
+ "feedback_privacy": "私たちはプライバシーに配慮すると同時に、CryptPadを使いやすくしたいと望んでいます。このファイルは、実行されたアクションを特定するパラメーターと共に要求され、ユーザーにとって重要なUI機能を特定するために使用されます。",
"register_warning_note": "暗号化を行うCryptPadの性質上、サービス管理者は、ユーザー名とパスワードを忘れた場合にデータを回復することができません。ユーザー名とパスワードを安全な場所に保管してください。",
"history_restoreDriveTitle": "選択したバージョンのCryptDriveを復元",
"errorPopupBlocked": "新しいタブを開く許可が必要です。お使いのブラウザのアドレスバーから、ポップアップウィンドウを許可してください。これらのウィンドウが広告の表示に使用されることはありません。",
@@ -1308,8 +1308,8 @@
"share_linkPasswordAlert": "この項目はパスワードで保護されています。リンクを受け取った相手はパスワードを入力する必要があります。",
"register_notes_title": "重要な注意事項",
"teams_table_specificHint": "以前のバージョンの共有フォルダでは、閲覧者は既存のパッドを変更することができます。 これらのフォルダで作成またはコピーしたパッドには、標準の権限が与えられます。",
- "broadcast_maintenance": "{0}から{1}の間でメンテナンスを予定しています。その間CryptPadは使用できません。",
- "admin_archiveHint": "ドキュメントを完全削除することなく、利用できないよう設定できます。「アーカイブ」フォルダに移動し、数日後に削除します(期間については設定ファイルより設定できます)。",
+ "broadcast_maintenance": "{0}から{1}の間でメンテナンスを予定しています。その間CryptPadは利用できません。",
+ "admin_archiveHint": "ドキュメントを破棄することなく、利用できないよう設定できます。「アーカイブ」フォルダに移動し、数日後に削除します(期間については設定ファイルより設定できます)。",
"admin_unarchiveHint": "アーカイブされたドキュメントを復元できます",
"admin_registrationTitle": "登録を締め切る",
"admin_defaultlimitHint": "ユーザー定義のルールが適用されていない際の最大のストレージ容量(ユーザーとチームについて)を設定できます",
@@ -1368,5 +1368,16 @@
"reminder_time": "{0}が今日の{1}にあります",
"form_answerWarning": "本人であることが確認されていません",
"team_leaveOwner": "チームから退出する前に、オーナーの役割から降格してください。チームには最低1人以上のオーナーが必要です。あなたが唯一のオーナーの場合は、別のオーナーを追加してください。",
- "form_exportCSV": "CSVにエクスポート"
+ "form_exportCSV": "CSVにエクスポート",
+ "fm_link_new": "新しいリンク",
+ "notification_openLink": "{1}からリンク「 {0}」を受け取りました:",
+ "fm_link_type": "リンク",
+ "fm_link_url": "URL",
+ "fm_link_name": "リンク名",
+ "form_anonName": "あなたの名前",
+ "notification_linkShared": "{0}があなたとリンクを共有しました: {1}",
+ "fm_link_name_placeholder": "あなたのリンク",
+ "fm_link_warning": "注意:URLが200字を超えています",
+ "fm_link_invalid": "URLが無効です",
+ "form_answerAs": "名前を記入してください"
}
diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json
index 29f415755..035275177 100644
--- a/www/common/translations/messages.json
+++ b/www/common/translations/messages.json
@@ -1230,7 +1230,7 @@
"genericCopySuccess": "Copied to clipboard",
"mediatag_defaultImageName": "image",
"register_registrationIsClosed": "Registration is closed.",
- "oo_conversionSupport": "Your browser cannot handle conversion to and from Microsoft Office formats. We suggest using a recent version of Firefox or Chrome.",
+ "oo_conversionSupport": "Your browser cannot handle conversion to and from office formats. We suggest using a recent version of Firefox or Chrome.",
"oo_importBin": "Click OK to import CryptPad's internal .bin format.",
"admin_emailTitle": "Admin contact email",
"admin_emailHint": "Set the contact email for your instance here",
@@ -1278,7 +1278,7 @@
"form_submit": "Submit",
"form_update": "Update",
"form_reset": "Reset",
- "form_sent": "Sent",
+ "form_sent": "Your response was submitted",
"form_delete": "Delete",
"form_submitWarning": "Submit anyway",
"form_updateWarning": "Update anyway",
@@ -1368,5 +1368,16 @@
"admin_purpose_business": "For a business or commercial organization",
"admin_instancePurposeHint": "Why do you run this instance? Your answer will be used to inform the development roadmap if your telemetry is enabled.",
"team_leaveOwner": "Please demote yourself from the owner role before leaving the team. Note that teams must have at least one owner, please add one before proceeding if you are currently the only owner.",
- "form_exportCSV": "Export to CSV"
+ "form_exportCSV": "Export to CSV",
+ "notification_openLink": "You've received a link {0} from {1}:",
+ "fm_link_new": "New Link",
+ "fm_link_type": "Link",
+ "fm_link_name": "Link name",
+ "fm_link_url": "URL",
+ "fm_link_name_placeholder": "My link",
+ "notification_linkShared": "{0} has shared a link with you: {1}",
+ "form_anonName": "Your name",
+ "form_answerAs": "Answer as",
+ "fm_link_warning": "Warning: the URL exceeds 200 characters",
+ "fm_link_invalid": "Invalid URL"
}
diff --git a/www/common/translations/messages.lt.json b/www/common/translations/messages.lt.json
index bd3a3a86b..9d252e196 100644
--- a/www/common/translations/messages.lt.json
+++ b/www/common/translations/messages.lt.json
@@ -182,5 +182,27 @@
"tags_add": "Atnaujinkite pažymėtų padų žymas",
"tags_title": "Žymos (tik jums)",
"filePicker_filter": "Filtruokite failus pagal pavadinimą",
- "filePicker_description": "Pasirinkite failą iš „CryptDrive“, kad jį įdėtumėte arba įkeltumėte naują"
+ "filePicker_description": "Pasirinkite failą iš „CryptDrive“, kad jį įdėtumėte arba įkeltumėte naują",
+ "oo_cantUpload": "Įkelti negalima, kol dalyvauja kiti vartotojai.",
+ "oo_reconnect": "Serverio ryšys atkurtas. Spustelėkite Gerai, jei norite iš naujo įkelti ir tęsti leidimą.",
+ "poll_comment_disabled": "Paskelbkite šią apklausą naudodami mygtuką ✓, kad įgalintumėte komentarus.",
+ "poll_comment_placeholder": "Jūsų komentaras",
+ "poll_comment_remove": "Ištrinti šį komentarą",
+ "poll_comment_submit": "Siųsti",
+ "poll_comment_add": "Pridėti komentarą",
+ "poll_comment_list": "Komentarai",
+ "poll_total": "Bendras",
+ "poll_bookmarked_col": "Tai yra jūsų pažymėtas stulpelis. Jums visada bus atrakinta ir rodoma pradžioje.",
+ "poll_bookmark_col": "Pažymėkite šį stulpelį taip, kad jis visada būtų atrakintas ir rodomas pradžioje",
+ "poll_unlocked": "Atrakinta",
+ "poll_locked": "Užrakinta",
+ "poll_edit": "Redaguoti",
+ "poll_remove": "Pašalinti",
+ "poll_descriptionHint": "Apibūdinkite apklausą ir, kai baigsite, naudokite mygtuką ✓ (paskelbti).\nAprašymas gali būti parašytas naudojant žymėjimo sintaksę ir galite įdėti laikmenos elementus iš savo „CryptDrive“.\nVisi, turintys nuorodą, gali pakeisti aprašą, tačiau tai nerekomenduojama.",
+ "poll_removeUser": "Ar tikrai norite pašalinti šį vartotoją?",
+ "poll_removeOption": "Ar tikrai norite pašalinti šią parinktį?",
+ "poll_userPlaceholder": "Vardas",
+ "poll_optionPlaceholder": "Pasirinkimai",
+ "poll_commit": "Pateikti",
+ "poll_create_option": "Pridėti naują parinktį"
}
diff --git a/www/common/translations/messages.zh.json b/www/common/translations/messages.zh.json
index 49331f9a5..ca0b85c73 100644
--- a/www/common/translations/messages.zh.json
+++ b/www/common/translations/messages.zh.json
@@ -13,7 +13,8 @@
"todo": "待办事项",
"teams": "团队",
"sheet": "工作表",
- "contacts": "联系我们"
+ "contacts": "联系我们",
+ "form": "从"
},
"button_newpad": "富文件檔案",
"button_newcode": "新代碼檔案",
diff --git a/www/common/userObject.js b/www/common/userObject.js
index ca716f25f..9e033f5c0 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,7 +316,24 @@ define([
// Get data from AllFiles (Cryptpad_RECENTPADS)
var getFileData = exp.getFileData = function (file, editable) {
if (!file) { return; }
- var data = files[FILES_DATA][file] || {};
+ var link;
+ try {
+ link = (files[STATIC_DATA] || {})[file];
+ } catch (err) {
+ console.error(err);
+ }
+ if (link) {
+ var _link = editable ? link : Util.clone(link);
+ if (!editable) { _link.static = true; }
+ return _link;
+ }
+ var data;
+ try {
+ data = files[FILES_DATA][file] || {};
+ } catch (err) {
+ console.error(err);
+ data = {};
+ }
if (!editable) {
data = JSON.parse(JSON.stringify(data));
if (data.href && data.href.indexOf('#') === -1) {
@@ -344,7 +367,13 @@ define([
return '??';
}
var data = getFileData(file);
- if (!file || !data || !(data.href || data.roHref)) {
+ if (!data) {
+ error("unable to retrieve data about the requested file: ", file, data);
+ return;
+ }
+ // handle links
+ if (data.static) { return data.name; }
+ if (!file || !(data.href || data.roHref)) {
error("getTitle called with a non-existing file id: ", file, data);
return;
}
@@ -475,6 +504,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 +888,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 +926,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 b0e38f27c..ad26863bb 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;
+ }
+ }
}
}
@@ -539,6 +548,7 @@
.cp-form-individual {
background: @cp_form-bg1;
padding: 10px;
+ margin-bottom: 20px;
& > *:not(:last-child) {
margin-right: 10px;
}
@@ -551,6 +561,9 @@
margin-right: 5px;
}
}
+ a, a:visited {
+ color: @cryptpad_color_link;
+ }
}
}
}
@@ -669,6 +682,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;
diff --git a/www/form/inner.js b/www/form/inner.js
index f8dfe5ab8..410918787 100644
--- a/www/form/inner.js
+++ b/www/form/inner.js
@@ -68,13 +68,9 @@ define([
var APP = window.APP = {
};
- var is24h = false;
+ var is24h = UIElements.is24h();
var dateFormat = "Y-m-d H:i";
var timeFormat = "H:i";
- try {
- is24h = !new Intl.DateTimeFormat(navigator.language, { hour: 'numeric' }).format(0).match(/AM/);
- } catch (e) {}
- is24h = false;
if (!is24h) {
dateFormat = "Y-m-d h:i K";
timeFormat = "h:i K";
@@ -298,6 +294,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 +303,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;
};
@@ -588,7 +592,9 @@ define([
return weekDays.map(function (day) { return day.replace(/^./, function (str) { return str.toUpperCase(); }); });
};
- var makePollTable = function (answers, opts) {
+ // "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) {
@@ -607,7 +613,7 @@ define([
_date = new Date(data);
data = Flatpickr.formatDate(_date, timeFormat);
}
- var day = allDays[_date.getDay()];
+ var day = _date && allDays[_date.getDay()];
return h('div.cp-poll-cell.cp-form-poll-option', {
title: Util.fixHTML(data)
}, [
@@ -667,13 +673,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);
@@ -815,6 +827,7 @@ define([
if (filterCurve && user === filterCurve) { return; }
try {
return {
+ curve: user,
user: answers[user].msg._userdata,
results: answers[user].msg[uid]
};
@@ -977,7 +990,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++; }
@@ -1043,7 +1056,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++; }
@@ -1117,8 +1130,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', [
@@ -1220,8 +1232,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) {
@@ -1331,8 +1342,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) {
@@ -1447,8 +1457,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);
});
});
});
@@ -1586,7 +1595,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) {
@@ -1615,7 +1624,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) {
@@ -1699,31 +1708,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) {
@@ -1739,7 +1770,7 @@ define([
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');
@@ -1767,7 +1798,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);
@@ -1861,10 +1894,19 @@ 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) {
@@ -1910,16 +1952,32 @@ 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);
@@ -1937,14 +1995,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();
@@ -2024,7 +2084,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
]);
};
@@ -2501,10 +2564,11 @@ define([
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
- Messages.form_addMsg = "Add response message";
- Messages.form_responseMsg = "Add a message that will be displayed in the response page.";
+ 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 () {
@@ -2533,7 +2597,7 @@ define([
name: Messages.settings_save,
onClick: function () {
var v = editor.getValue();
- content.answers.msg = v.trim(0, 2000); // XXX max length?
+ content.answers.msg = v.trim(0, 2000); // XXX 4.10.0 max length?
framework.localChange();
framework._.cpNfInner.chainpad.onSettle(function () {
UI.log(Messages.saved);
@@ -2559,9 +2623,9 @@ define([
}
UI.openCustomModal(APP.responseModal);
});
- $responseMsg.append(btn);
+ // $responseMsg.append(btn); // XXX 4.10.0
};
- refreshResponse();
+ //refreshResponse();
// Allow anonymous answers
var privacyContainer = h('div.cp-form-privacy-container');
@@ -2658,7 +2722,7 @@ define([
evOnChange.reg(refreshPublic);
evOnChange.reg(refreshPrivacy);
evOnChange.reg(refreshEndDate);
- evOnChange.reg(refreshResponse);
+ //evOnChange.reg(refreshResponse);
return [
endDateContainer,
@@ -2716,6 +2780,11 @@ define([
var endDateEl = h('div.alert.alert-warning.cp-burn-after-reading');
var endDate;
var endDateTo;
+
+ // numbers greater than this overflow the maximum delay for a setTimeout
+ // which results in it being executed immediately (oops)
+ // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#maximum_delay_value
+ var MAX_TIMEOUT_DELAY = 2147483647;
var refreshEndDateBanner = function (force) {
if (APP.isEditor) { return; }
var _endDate = content.answers.endDate;
@@ -2736,10 +2805,21 @@ define([
APP.isClosed = endDate && endDate < (+new Date());
clearTimeout(endDateTo);
if (!APP.isClosed && endDate) {
- setTimeout(function () {
+ // calculate how many ms in the future the poll will be closed
+ var diff = (endDate - +new Date() + 100);
+ // if that value would overflow, then check again in a day
+ // (if the tab is still open)
+ if (diff > MAX_TIMEOUT_DELAY) {
+ endDateTo = setTimeout(function () {
+ refreshEndDateBanner(true);
+ }, 1000 * 3600 * 24);
+ return;
+ }
+
+ endDateTo = setTimeout(function () {
refreshEndDateBanner(true);
- $('.cp-form-send-container').find('.cp-open').remove();
- },(endDate - +new Date() + 100));
+ $('.cp-form-send-container').find('.cp-open').hide();
+ }, diff);
}
};
diff --git a/www/form/main.js b/www/form/main.js
index b25a4b21a..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) {
diff --git a/www/kanban/app-kanban.less b/www/kanban/app-kanban.less
index 9fd06a510..f0aa82d62 100644
--- a/www/kanban/app-kanban.less
+++ b/www/kanban/app-kanban.less
@@ -545,7 +545,7 @@
#kanban-addboard {
order: 2;
- width: 300px;
+ width: 50px;
margin: 10px 5px;
border: 1px solid @cp_kanban-fg;
color: @cp_kanban-fg;
diff --git a/www/kanban/inner.js b/www/kanban/inner.js
index 4832adcca..7d3c5dfe5 100644
--- a/www/kanban/inner.js
+++ b/www/kanban/inner.js
@@ -1006,6 +1006,14 @@ define([
]);
$container.before(container);
+ var common = framework._.sfCommon;
+ var $button = common.createButton('toggle', true, {
+ element: $(container),
+ }, function () {
+
+ });
+ framework._.toolbar.$bottomL.append($button);
+
onRedraw.reg(function () {
// Redraw if new tags have been added to items
var old = Sortify(existing);
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) {