Merge branch 'link' into staging

pull/1/head
ansuz 3 years ago
commit 8200f579a8

@ -368,7 +368,7 @@ define([
var mkFilePicker = function (framework, editor, evModeChange) { var mkFilePicker = function (framework, editor, evModeChange) {
evModeChange.reg(function (mode) { evModeChange.reg(function (mode) {
if (MEDIA_TAG_MODES.indexOf(mode) !== -1) { if (MEDIA_TAG_MODES.indexOf(mode) !== -1) {
// Embedding is endabled // Embedding is enabled
framework.setMediaTagEmbedder(function (mt) { framework.setMediaTagEmbedder(function (mt) {
editor.focus(); editor.focus();
editor.replaceSelection($(mt)[0].outerHTML); editor.replaceSelection($(mt)[0].outerHTML);

@ -119,6 +119,7 @@ define(function() {
file: 'cptools-file', file: 'cptools-file',
fileupload: 'cptools-file-upload', fileupload: 'cptools-file-upload',
folderupload: 'cptools-folder-upload', folderupload: 'cptools-folder-upload',
link: 'fa-link',
pad: 'cptools-richtext', pad: 'cptools-richtext',
code: 'cptools-code', code: 'cptools-code',
slide: 'cptools-slide', slide: 'cptools-slide',

@ -1050,6 +1050,7 @@ define([
var font = icon.indexOf('cptools') === 0 ? 'cptools' : 'fa'; var font = icon.indexOf('cptools') === 0 ? 'cptools' : 'fa';
if (type === 'fileupload') { type = 'file'; } if (type === 'fileupload') { type = 'file'; }
if (type === 'folderupload') { type = 'file'; } if (type === 'folderupload') { type = 'file'; }
if (type === 'link') { type = 'drive'; }
var appClass = ' cp-icon cp-icon-color-'+type; var appClass = ' cp-icon cp-icon-color-'+type;
$icon = $('<span>', {'class': font + ' ' + icon + appClass}); $icon = $('<span>', {'class': font + ' ' + icon + appClass});
} }
@ -1061,6 +1062,7 @@ define([
if (!data) { return $icon; } if (!data) { return $icon; }
var href = data.href || data.roHref; var href = data.href || data.roHref;
var type = data.type; var type = data.type;
if (data.static) { type = 'link'; }
if (!href && !type) { return $icon; } if (!href && !type) { return $icon; }
if (!type) { type = Hash.parsePadUrl(href).type; } if (!type) { type = Hash.parsePadUrl(href).type; }

@ -1032,10 +1032,19 @@ define([
icon: 'fa-picture-o', icon: 'fa-picture-o',
action: function () { action: function () {
var _cfg = { var _cfg = {
types: ['file'], types: ['file', 'link'],
where: ['root'] where: ['root']
}; };
common.openFilePicker(_cfg, function (data) { common.openFilePicker(_cfg, function (data) {
// Embed links
if (data.static) {
var a = h('a', {
href: data.href
}, data.name);
cfg.embed(a, data);
return;
}
// Embed files
if (data.type !== 'file') { if (data.type !== 'file') {
console.log("Unexpected data type picked " + data.type); console.log("Unexpected data type picked " + data.type);
return; return;
@ -3021,6 +3030,63 @@ define([
UI.proposal(content, todo); UI.proposal(content, todo);
}; };
UIElements.displayOpenLinkModal = function (common, data, dismiss) {
var name = Util.fixHTML(data.title);
var url = data.href;
var user = data.name;
Messages.notification_openLink = "You've received a link <b>{0}</b> from {1}:"; // XXX
Messages.link_open = "Open URL";
Messages.link_store = "Store link in drive";
var content = h('div', [
UI.setHTML(h('p'), Messages._getKey('notification_openLink', [name, user])),
h('pre', url),
UIElements.getVerifiedFriend(common, data.curve, user)
]);
var clicked = false;
var modal;
var buttons = [{
name: Messages.friendRequest_later,
onClick: function () {
if (clicked) { return true; }
clicked = true;
},
keys: [27]
}, {
className: 'primary',
name: Messages.link_open,
onClick: function () {
if (clicked) { return true; }
clicked = true;
common.openUnsafeURL(url);
},
keys: [13]
}, {
className: 'primary',
name: Messages.link_store,
onClick: function () {
if (clicked) { return; }
clicked = true;
common.getSframeChannel().query("Q_DRIVE_USEROBJECT", {
cmd: "addLink",
data: {
name: name,
href: url,
path: ['root']
}
}, function () {
modal.closeModal();
dismiss();
});
return true;
},
keys: [[13, 'ctrl']]
}];
var _modal = UI.dialog.customModal(content, {buttons: buttons});
modal = UI.openCustomModal(_modal);
return modal;
};
UIElements.displayAddOwnerModal = function (common, data) { UIElements.displayAddOwnerModal = function (common, data) {
var priv = common.getMetadataMgr().getPrivateData(); var priv = common.getMetadataMgr().getPrivateData();
var sframeChan = common.getSframeChannel(); var sframeChan = common.getSframeChannel();

@ -42,6 +42,9 @@ define([
Pages) Pages)
{ {
Messages.fm_link = "Link"; // XXX "New Link" ?
// XXX check for all occurrences of `fm_link` before changing it
var APP = window.APP = { var APP = window.APP = {
editable: false, editable: false,
online: false, online: false,
@ -443,6 +446,11 @@ define([
'data-icon': AppConfig.applicationsIcon.poll, 'data-icon': AppConfig.applicationsIcon.poll,
'data-type': 'poll' 'data-type': 'poll'
}, Messages.button_newpoll)), }, Messages.button_newpoll)),
h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
'data-icon': AppConfig.applicationsIcon.link,
'data-type': 'link'
}, Messages.fm_link)),
]), ]),
]), ]),
$separator.clone()[0], $separator.clone()[0],
@ -1106,11 +1114,24 @@ define([
common.getMediaTagPreview(mts, idx); common.getMediaTagPreview(mts, idx);
}; };
var refresh = APP.refresh = function () {
APP.displayDirectory(currentPath);
};
// `app`: true (force open wiht the app), false (force open in preview), // `app`: true (force open wiht the app), false (force open in preview),
// falsy (open in preview if default is not using the app) // falsy (open in preview if default is not using the app)
var defaultInApp = ['application/pdf']; var defaultInApp = ['application/pdf'];
var openFile = function (el, isRo, app) { var openFile = function (el, isRo, app) {
var data = manager.getFileData(el); var data = manager.getFileData(el);
if (data.static) {
if (data.href) {
common.openUnsafeURL(data.href);
manager.updateStaticAccess(el, refresh);
}
return;
}
if (!data || (!data.href && !data.roHref)) { if (!data || (!data.href && !data.roHref)) {
return void logError("Missing data for the file", el, data); return void logError("Missing data for the file", el, data);
} }
@ -1147,10 +1168,6 @@ define([
common.openURL(Hash.getNewPadURL(href, obj)); common.openURL(Hash.getNewPadURL(href, obj));
}; };
var refresh = APP.refresh = function () {
APP.displayDirectory(currentPath);
};
var pickFolderColor = function ($element, currentColor, cb) { var pickFolderColor = function ($element, currentColor, cb) {
var colors = ["", "#f23c38", "#ff0073", "#da0eba", "#9d00ac", "#6c19b3", "#4a42b1", "#3d8af0", "#30a0f1", "#1fb9d1", "#009686", "#45b354", "#84c750", "#c6e144", "#faf147", "#fbc423", "#fc9819", "#fd5227", "#775549", "#9c9c9c", "#607a89"]; var colors = ["", "#f23c38", "#ff0073", "#da0eba", "#9d00ac", "#6c19b3", "#4a42b1", "#3d8af0", "#30a0f1", "#1fb9d1", "#009686", "#45b354", "#84c750", "#c6e144", "#faf147", "#fbc423", "#fc9819", "#fd5227", "#775549", "#9c9c9c", "#607a89"];
@ -1263,6 +1280,9 @@ define([
if ($element.is('.cp-border-color-sheet')) { if ($element.is('.cp-border-color-sheet')) {
hide.push('download'); hide.push('download');
} }
if ($element.is('.cp-app-drive-static')) {
hide.push('access', 'hashtag', 'properties', 'download');
}
if ($element.is('.cp-app-drive-element-file')) { if ($element.is('.cp-app-drive-element-file')) {
// No folder in files // No folder in files
hide.push('color'); hide.push('color');
@ -1899,6 +1919,27 @@ define([
// In list mode, display metadata from the filesData object // In list mode, display metadata from the filesData object
var addStaticData = function (element, $element, data) {
$element.addClass('cp-border-color-drive');
var name = data.name;
var $name = $('<span>', {'class': 'cp-app-drive-element-name'}).text(name);
$element.append($name);
if (getViewMode() === 'grid') {
$element.attr('title', name); // XXX Util.fixHTML
}
var type = Messages.fm_link; // XXX new translation key ("Link")
var $type = $('<span>', {
'class': 'cp-app-drive-element-type cp-app-drive-element-list'
}).text(type);
var $adate = $('<span>', {
'class': 'cp-app-drive-element-atime cp-app-drive-element-list'
}).text(getDate(data.atime));
var $cdate = $('<span>', {
'class': 'cp-app-drive-element-ctime cp-app-drive-element-list'
}).text(getDate(data.ctime));
$element.append($type).append($adate).append($cdate);
};
var _addOwnership = function ($span, $state, data) { var _addOwnership = function ($span, $state, data) {
if (data && Array.isArray(data.owners) && data.owners.indexOf(edPublic) !== -1) { if (data && Array.isArray(data.owners) && data.owners.indexOf(edPublic) !== -1) {
var $owned = $ownedIcon.clone().appendTo($state); var $owned = $ownedIcon.clone().appendTo($state);
@ -1914,6 +1955,9 @@ define([
if (!manager.isFile(element)) { return; } if (!manager.isFile(element)) { return; }
var data = manager.getFileData(element); var data = manager.getFileData(element);
if (data.static) {
return addStaticData(element, $element, data);
}
if (!Object.keys(data).length) { if (!Object.keys(data).length) {
return true; return true;
@ -2124,7 +2168,9 @@ define([
$icon = manager.isFolderEmpty(root[key]) ? $folderEmptyIcon.clone() : $folderIcon.clone(); $icon = manager.isFolderEmpty(root[key]) ? $folderEmptyIcon.clone() : $folderIcon.clone();
$icon.css("color", getFolderColor(path.concat(elPath))); $icon.css("color", getFolderColor(path.concat(elPath)));
} }
var classes = restrictedClass + roClass + liClass;
var staticClass = manager.isStaticFile(element) ? '.cp-app-drive-static' : '';
var classes = restrictedClass + roClass + liClass + staticClass;
var $element = $(h('li.cp-app-drive-element.cp-app-drive-element-row' + classes, { var $element = $(h('li.cp-app-drive-element.cp-app-drive-element-row' + classes, {
draggable: true draggable: true
})); }));
@ -2459,8 +2505,7 @@ define([
setViewMode(viewMode || 'grid'); setViewMode(viewMode || 'grid');
showMode(viewMode); showMode(viewMode);
$button.click(function (e) { $button.click(function () {
console.error(e);
var viewMode = getViewMode(); var viewMode = getViewMode();
var newViewMode = getOppositeViewMode(viewMode); var newViewMode = getOppositeViewMode(viewMode);
setViewMode(newViewMode); setViewMode(newViewMode);
@ -2686,6 +2731,63 @@ define([
}); });
$input.click(); $input.click();
}; };
var showLinkModal = function () {
Messages.fm_link_name = "Link name"; // XXX
Messages.fm_link_url = "URL";
Messages.fm_link_warning = "Warning: URL size...";
var name, url;
var warning = h('div.alert.alert-warning', [
h('i.fa.fa-exclamation-triangle'),
h('span', Messages.fm_link_warning)
]);
var content = h('p', [
warning,
h('label', {for: 'cp-app-drive-link-name'}, Messages.fm_link_name),
name = h('input#cp-app-drive-link-name', { autocomplete: 'off' }),
h('label', {for: 'cp-app-drive-link-url'}, Messages.fm_link_url),
url = h('input#cp-app-drive-link-url', { type: 'url', autocomplete: 'off' })
]);
var $warning = $(warning).hide();
var $url = $(url).on('change keypress keyup keydown', function () {
var v = $url.val().trim();
if (v.length > 200) {
$warning.show();
return;
}
$warning.hide();
});
var buttons = [{
className: 'cancel',
name: Messages.cancelButton,
onClick: function () {},
keys: [27]
}];
buttons.push({
className: 'primary',
// We may want to use a new key here
iconClass: '.fa.fa-plus',
name: Messages.tag_add,
onClick: function () {
var n = $(name).val().trim();
var u = $url.val().trim();
if (!n || !u) { return true; }
if (!Util.isValidURL(u)) {
// XXX add style for invalid input? input:invalid
UI.warn(Messages.error);
return true;
}
manager.addLink(currentPath, {
name: n,
url: u
}, refresh);
},
keys: [13]
});
var m = UI.dialog.customModal(content, {
buttons: buttons
});
UI.openCustomModal(m);
};
var addNewPadHandlers = function ($block, isInRoot) { var addNewPadHandlers = function ($block, isInRoot) {
// Handlers // Handlers
if (isInRoot) { if (isInRoot) {
@ -2714,6 +2816,7 @@ define([
} }
$block.find('a.cp-app-drive-new-fileupload, li.cp-app-drive-new-fileupload').click(showUploadFilesModal); $block.find('a.cp-app-drive-new-fileupload, li.cp-app-drive-new-fileupload').click(showUploadFilesModal);
$block.find('a.cp-app-drive-new-folderupload, li.cp-app-drive-new-folderupload').click(showUploadFolderModal); $block.find('a.cp-app-drive-new-folderupload, li.cp-app-drive-new-folderupload').click(showUploadFolderModal);
$block.find('a.cp-app-drive-new-link, li.cp-app-drive-new-link').click(showLinkModal);
} }
$block.find('a.cp-app-drive-new-doc, li.cp-app-drive-new-doc') $block.find('a.cp-app-drive-new-doc, li.cp-app-drive-new-doc')
.click(function () { .click(function () {
@ -2757,6 +2860,12 @@ define([
}); });
} }
options.push({tag: 'hr'}); options.push({tag: 'hr'});
options.push({
tag: 'a',
attributes: {'class': 'cp-app-drive-new-link'},
content: $('<div>').append(getIcon('link')).html() + Messages.fm_link
});
options.push({tag: 'hr'});
} }
getNewPadTypes().forEach(function (type) { getNewPadTypes().forEach(function (type) {
var attributes = { var attributes = {
@ -3073,6 +3182,13 @@ define([
$elementFolderUpload.append($('<span>', {'class': 'cp-app-drive-new-name'}) $elementFolderUpload.append($('<span>', {'class': 'cp-app-drive-new-name'})
.text(Messages.uploadFolderButton)); .text(Messages.uploadFolderButton));
} }
// Link
var $elementLink = $('<li>', {
'class': 'cp-app-drive-new-link cp-app-drive-element-row ' +
'cp-app-drive-element-grid'
}).prepend(getIcon('link')).appendTo($container);
$elementLink.append($('<span>', {'class': 'cp-app-drive-new-name'})
.text(Messages.fm_link));
} }
// Pads // Pads
getNewPadTypes().forEach(function (type) { getNewPadTypes().forEach(function (type) {
@ -3479,6 +3595,7 @@ define([
var path = paths[0]; var path = paths[0];
if (manager.isPathIn(path, [TRASH])) { return; } if (manager.isPathIn(path, [TRASH])) { return; }
if (!file.channel) { file.channel = id; }
if (channels.indexOf(file.channel) !== -1) { return; } if (channels.indexOf(file.channel) !== -1) { return; }
channels.push(file.channel); channels.push(file.channel);
@ -4482,8 +4599,9 @@ define([
password: data.password password: data.password
}, },
isTemplate: paths[0].path[0] === 'template', isTemplate: paths[0].path[0] === 'template',
title: data.title, title: data.title || data.name,
sharedFolder: sf, sharedFolder: sf,
static: data.static ? data.href : undefined,
common: common common: common
}; };
if (padType === 'file') { if (padType === 'file') {
@ -4501,6 +4619,20 @@ define([
data = manager.getSharedFolderData(el); data = manager.getSharedFolderData(el);
} }
if (!data) { return; } 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', { sframeChan.query('Q_STORE_IN_TEAM', {
href: data.href || data.rohref, href: data.href || data.rohref,
password: data.password, password: data.password,
@ -4539,6 +4671,9 @@ define([
} }
else if ($this.hasClass("cp-app-drive-context-newdoc")) { else if ($this.hasClass("cp-app-drive-context-newdoc")) {
var ntype = $this.data('type') || 'pad'; var ntype = $this.data('type') || 'pad';
if (ntype === 'link') {
return void showLinkModal();
}
var path2 = manager.isPathIn(currentPath, [TRASH]) ? '' : currentPath; var path2 = manager.isPathIn(currentPath, [TRASH]) ? '' : currentPath;
openIn(ntype, path2, APP.team); openIn(ntype, path2, APP.team);
} }

@ -90,7 +90,11 @@ define([
setTimeout(w); setTimeout(w);
}); });
if (res && /^http/.test(res)) { if (res && /^http/.test(res)) {
href = Hash.getRelativeHref(res); var _href = Hash.getRelativeHref(res);
if (_href) { href = _href; }
else {
href = res;
}
setTimeout(w); setTimeout(w);
return; return;
} }
@ -109,6 +113,7 @@ define([
if (mailbox.notifications && mailbox.curvePublic) { if (mailbox.notifications && mailbox.curvePublic) {
common.mailbox.sendTo("SHARE_PAD", { common.mailbox.sendTo("SHARE_PAD", {
href: href, href: href,
isStatic: Boolean(config.static),
password: config.password, password: config.password,
isTemplate: config.isTemplate, isTemplate: config.isTemplate,
name: myName, name: myName,
@ -137,6 +142,20 @@ define([
}); });
return; 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);
});
return;
}
sframeChan.query('Q_STORE_IN_TEAM', { sframeChan.query('Q_STORE_IN_TEAM', {
href: href, href: href,
password: config.password, password: config.password,
@ -346,6 +365,9 @@ define([
] : [ ] : [
UI.createCheckbox('cp-share-embed', Messages.share_linkEmbed, false, { mark: {tabindex:1} }), UI.createCheckbox('cp-share-embed', Messages.share_linkEmbed, false, { mark: {tabindex:1} }),
]; ];
if (opts.static) { linkContent = []; }
linkContent.push(h('div.cp-spacer')); linkContent.push(h('div.cp-spacer'));
linkContent.push(UI.dialog.selectableArea('', { id: 'cp-share-link-preview', tabindex: 1, rows:3})); linkContent.push(UI.dialog.selectableArea('', { id: 'cp-share-link-preview', tabindex: 1, rows:3}));
@ -361,7 +383,7 @@ define([
// warning about sharing links // warning about sharing links
// when sharing a version hash, there is a similar warning and we want // when sharing a version hash, there is a similar warning and we want
// to avoid alert fatigue // to avoid alert fatigue
if (!opts.versionHash) { if (!opts.versionHash && !opts.static) {
var localStore = window.cryptpadStore; var localStore = window.cryptpadStore;
var dismissButton = h('span.fa.fa-times'); var dismissButton = h('span.fa.fa-times');
var shareLinkWarning = h('div.alert.alert-warning.dismissable', var shareLinkWarning = h('div.alert.alert-warning.dismissable',
@ -405,6 +427,10 @@ define([
var v = opts.getLinkValue({ var v = opts.getLinkValue({
embed: Util.isChecked($link.find('#cp-share-embed')) embed: Util.isChecked($link.find('#cp-share-embed'))
}); });
if (opts.static) {
common.openUnsafeURL(v);
return true;
}
window.open(v); window.open(v);
return true; return true;
}, },
@ -562,6 +588,7 @@ define([
}); });
}; };
opts.getLinkValue = function (initValue, cb) { opts.getLinkValue = function (initValue, cb) {
if (opts.static) { return opts.static; }
var val = initValue || {}; var val = initValue || {};
var edit = val.edit !== undefined ? val.edit : Util.isChecked($rights.find('#cp-share-editable-true')); var edit = val.edit !== undefined ? val.edit : Util.isChecked($rights.find('#cp-share-editable-true'));
var embed = val.embed; var embed = val.embed;
@ -686,7 +713,7 @@ define([
opts.access = true; // Allow the use of the modal even if the pad is not stored opts.access = true; // Allow the use of the modal even if the pad is not stored
var hashes = opts.hashes; 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); var teams = getEditableTeams(common, opts);
opts.teams = teams; opts.teams = teams;
@ -705,19 +732,23 @@ define([
var $rights = opts.$rights = getRightsHeader(common, opts); var $rights = opts.$rights = getRightsHeader(common, opts);
var resetTab = function () { var resetTab = function () {
if (opts.static) { return; }
$rights.show(); $rights.show();
$rights.find('label.cp-radio').show(); $rights.find('label.cp-radio').show();
}; };
var onShowEmbed = function () { var onShowEmbed = function () {
if (opts.static) { return; }
$rights.find('#cp-share-bar').closest('label').hide(); $rights.find('#cp-share-bar').closest('label').hide();
$rights.find('input[type="radio"]:enabled').first().prop('checked', 'checked'); $rights.find('input[type="radio"]:enabled').first().prop('checked', 'checked');
$rights.find('input[type="radio"]').trigger('change'); $rights.find('input[type="radio"]').trigger('change');
}; };
var onShowContacts = function () { var onShowContacts = function () {
if (opts.static) { return; }
if (!hasFriends || priv.offline) { if (!hasFriends || priv.offline) {
$rights.hide(); $rights.hide();
} }
}; };
if (opts.static) { $rights.hide(); }
var contactsActive = hasFriends && !priv.offline; var contactsActive = hasFriends && !priv.offline;
var tabs = [{ var tabs = [{
@ -732,13 +763,16 @@ define([
title: Messages.share_linkCategory, title: Messages.share_linkCategory,
icon: "fa fa-link", icon: "fa fa-link",
active: !contactsActive, active: !contactsActive,
}, { }];
if (!opts.static) {
tabs.push({
getTab: getEmbedTab, getTab: getEmbedTab,
title: Messages.share_embedCategory, title: Messages.share_embedCategory,
icon: "fa fa-code", icon: "fa fa-code",
onShow: onShowEmbed, onShow: onShowEmbed,
onHide: resetTab onHide: resetTab
}]; });
}
Modal.getModal(common, opts, tabs, function (err, modal) { Modal.getModal(common, opts, tabs, function (err, modal) {
// Hide the burn-after-reading option by default // Hide the burn-after-reading option by default
var $modal = $(modal); var $modal = $(modal);

@ -92,6 +92,11 @@ define([
(type === 'file' ? 'notification_fileShared' : // Msg.notification_fileSharedTeam (type === 'file' ? 'notification_fileShared' : // Msg.notification_fileSharedTeam
'notification_padShared'); // Msg.notification_padSharedTeam 'notification_padShared'); // Msg.notification_padSharedTeam
Messages.notification_linkShared = "{0} has shared a link with you: <b>{1}</b>"; // XXX
if (msg.content.isStatic) {
key = 'notification_linkShared'; // Msg.notification_linkShared;
}
var teamNotification = /^team-/.test(data.type) && Number(data.type.slice(5)); var teamNotification = /^team-/.test(data.type) && Number(data.type.slice(5));
var teamName = ''; var teamName = '';
if (teamNotification) { if (teamNotification) {
@ -109,6 +114,15 @@ define([
return Messages._getKey(key, [name, title, teamName]); return Messages._getKey(key, [name, title, teamName]);
}; };
content.handler = function() { 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 = { var obj = {
p: msg.content.isTemplate ? ['template'] : undefined, p: msg.content.isTemplate ? ['template'] : undefined,
t: teamNotification || undefined, t: teamNotification || undefined,

@ -1306,9 +1306,14 @@ define([
getAllStores().forEach(function (s) { getAllStores().forEach(function (s) {
s.manager.getSecureFilesList(where).forEach(function (obj) { s.manager.getSecureFilesList(where).forEach(function (obj) {
var data = obj.data; var data = obj.data;
if (channels.indexOf(data.channel) !== -1) { return; } if (channels.indexOf(data.channel || data.id) !== -1) { return; }
var id = obj.id; 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); var parsed = Hash.parsePadUrl(data.href || data.roHref);
if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) && if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) &&
!isFiltered(parsed.type, data)) { !isFiltered(parsed.type, data)) {

@ -238,8 +238,9 @@ define([
// content.name, content.title, content.href, content.password // content.name, content.title, content.href, content.password
if (isMuted(ctx, data)) { return void cb(true); } if (isMuted(ctx, data)) { return void cb(true); }
// if the shared content is a 'link' then we can't use the channel to deduplicate notifications
var channel = Hash.hrefToHexChannelId(content.href, content.password); // use href instead.
var channel = content.isStatic ? content.href : Hash.hrefToHexChannelId(content.href, content.password);
var parsed = Hash.parsePadUrl(content.href); var parsed = Hash.parsePadUrl(content.href);
var mode = parsed.hashData && parsed.hashData.mode || 'n/a'; var mode = parsed.hashData && parsed.hashData.mode || 'n/a';

@ -20,6 +20,7 @@ define([
var ROOT = exp.ROOT; var ROOT = exp.ROOT;
var FILES_DATA = exp.FILES_DATA; var FILES_DATA = exp.FILES_DATA;
var STATIC_DATA = exp.STATIC_DATA;
var OLD_FILES_DATA = exp.OLD_FILES_DATA; var OLD_FILES_DATA = exp.OLD_FILES_DATA;
var UNSORTED = exp.UNSORTED; var UNSORTED = exp.UNSORTED;
var TRASH = exp.TRASH; var TRASH = exp.TRASH;
@ -78,6 +79,14 @@ define([
files[FILES_DATA][id] = data; files[FILES_DATA][id] = data;
cb(null, id); 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) { exp.pushSharedFolder = function (_data, cb) {
if (typeof cb !== "function") { cb = function () {}; } if (typeof cb !== "function") { cb = function () {}; }
@ -136,7 +145,7 @@ define([
var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]); var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
var toClean = []; 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) { if (filesList.indexOf(id) === -1) {
var fd = exp.isSharedFolder(id) ? files[SHARED_FOLDERS][id] : exp.getFileData(id); var fd = exp.isSharedFolder(id) ? files[SHARED_FOLDERS][id] : exp.getFileData(id);
var channelId = fd.channel; var channelId = fd.channel;
@ -146,6 +155,8 @@ define([
if (exp.isSharedFolder(id)) { if (exp.isSharedFolder(id)) {
delete files[SHARED_FOLDERS][id]; delete files[SHARED_FOLDERS][id];
if (config.removeProxy) { config.removeProxy(id); } if (config.removeProxy) { config.removeProxy(id); }
} else if (files[STATIC_DATA][id]) {
delete files[STATIC_DATA][id];
} else { } else {
spliceFileData(id); spliceFileData(id);
} }
@ -242,6 +253,12 @@ define([
id = Number(id); id = Number(id);
// Find and maybe update existing pads with the same channel id // Find and maybe update existing pads with the same channel id
var d = data[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 we were given an edit link, encrypt its value if needed
if (d.href) { d.href = exp.cryptor.encrypt(d.href); } if (d.href) { d.href = exp.cryptor.encrypt(d.href); }
var found = false; var found = false;
@ -398,7 +415,7 @@ define([
if (!loggedIn && !config.testMode) { return; } if (!loggedIn && !config.testMode) { return; }
id = Number(id); 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; } if (!data || typeof(data) !== "object") { return; }
var newPath = path, parentEl; var newPath = path, parentEl;
if (path && !Array.isArray(path)) { if (path && !Array.isArray(path)) {
@ -599,13 +616,18 @@ define([
var element = elem || files[ROOT]; var element = elem || files[ROOT];
if (!element) { return console.error("Invalid element in root"); } if (!element) { return console.error("Invalid element in root"); }
var nbMetadataFolders = 0; 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) { for (var el in element) {
if (element[el] === null) { element_el = element[el];
if (element_el === null) {
console.error('element[%s] is null', el); console.error('element[%s] is null', el);
delete element[el]; delete element[el];
continue; continue;
} }
if (exp.isFolderData(element[el])) { if (exp.isFolderData(element_el)) {
if (nbMetadataFolders !== 0) { if (nbMetadataFolders !== 0) {
debug("Multiple metadata files in folder"); debug("Multiple metadata files in folder");
delete element[el]; delete element[el];
@ -613,30 +635,30 @@ define([
nbMetadataFolders++; nbMetadataFolders++;
continue; continue;
} }
if (!exp.isFile(element[el], true) && !exp.isFolder(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]); debug("An element in ROOT was not a folder nor a file. ", element_el);
delete element[el]; delete element[el];
continue; continue;
} }
if (exp.isFolder(element[el])) { if (exp.isFolder(element_el)) {
fixRoot(element[el]); fixRoot(element_el);
continue; continue;
} }
if (typeof element[el] === "string") { if (typeof element_el === "string") {
// We have an old file (href) which is not in filesData: add it // We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger(); var id = Util.createRandomInteger();
var key = Hash.createChannelId(); var key = Hash.createChannelId();
files[FILES_DATA][id] = { files_data[id] = {
href: exp.cryptor.encrypt(element[el]), href: exp.cryptor.encrypt(element_el),
filename: el filename: el
}; };
element[key] = id; element[key] = id;
delete element[el]; delete element[el];
} }
if (typeof element[el] === "number") { if (typeof element_el === "number") {
var data = files[FILES_DATA][element[el]]; var data = files_data[element_el] || static_data[element_el];
if (!data) { 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]; delete element[el];
} }
} }
@ -845,6 +867,26 @@ define([
toClean.forEach(function (id) { toClean.forEach(function (id) {
spliceFileData(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 () { var fixSharedFolders = function () {
if (sharedFolder) { return; } 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 () { var fixDrive = function () {
@ -900,6 +948,7 @@ define([
}); });
}; };
fixStaticData();
fixRoot(); fixRoot();
fixTrashRoot(); fixTrashRoot();
fixTemplate(); fixTemplate();

@ -22,11 +22,11 @@ define([
// a cached version // a cached version
if (Env.folders[id].offline && !lm.cache) { if (Env.folders[id].offline && !lm.cache) {
Env.folders[id].offline = false; Env.folders[id].offline = false;
if (Env.folders[id].userObject.fixFiles) { Env.folders[id].userObject.fixFiles(); }
Env.Store.refreshDriveUI(); Env.Store.refreshDriveUI();
} }
return; return;
} }
if (Env.folders[id]) { console.warn(Env.folders[id]); }
var cfg = getConfig(Env); var cfg = getConfig(Env);
cfg.sharedFolder = true; cfg.sharedFolder = true;
cfg.id = id; 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 _restoreSharedFolder = function (Env, _data, cb) {
var fId = _data.id; var fId = _data.id;
@ -1019,6 +1037,14 @@ 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 onCommand = function (Env, cmdData, cb) { var onCommand = function (Env, cmdData, cb) {
var cmd = cmdData.cmd; var cmd = cmdData.cmd;
var data = cmdData.data || {}; var data = cmdData.data || {};
@ -1031,6 +1057,8 @@ define([
_addFolder(Env, data, cb); break; _addFolder(Env, data, cb); break;
case 'addSharedFolder': case 'addSharedFolder':
_addSharedFolder(Env, data, cb); break; _addSharedFolder(Env, data, cb); break;
case 'addLink':
_addLink(Env, data, cb); break;
case 'restoreSharedFolder': case 'restoreSharedFolder':
_restoreSharedFolder(Env, data, cb); break; _restoreSharedFolder(Env, data, cb); break;
case 'convertFolderToSharedFolder': case 'convertFolderToSharedFolder':
@ -1045,6 +1073,8 @@ define([
_rename(Env, data, cb); break; _rename(Env, data, cb); break;
case 'setFolderData': case 'setFolderData':
_setFolderData(Env, data, cb); break; _setFolderData(Env, data, cb); break;
case 'updateStaticAccess':
_updateStaticAccess(Env, data, cb); break;
default: default:
cb(); cb();
} }
@ -1129,8 +1159,8 @@ define([
data: uo.getFileData(id) data: uo.getFileData(id)
}; };
}).filter(function (d) { }).filter(function (d) {
if (channels.indexOf(d.data.channel) === -1) { if (channels.indexOf(d.data.channel || d.id) === -1) {
channels.push(d.data.channel); channels.push(d.data.channel || d.id);
return true; return true;
} }
}); });
@ -1383,6 +1413,16 @@ define([
} }
}, cb); }, 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) { var restoreSharedFolderInner = function (Env, fId, password, cb) {
return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", { return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "restoreSharedFolder", cmd: "restoreSharedFolder",
@ -1433,6 +1473,14 @@ define([
}, cb); }, cb);
}; };
var updateStaticAccessInner = function (Env, id, cb) {
return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "updateStaticAccess",
data: id
}, cb);
};
/* Tools */ /* Tools */
var findChannels = _findChannels; var findChannels = _findChannels;
@ -1450,6 +1498,11 @@ define([
return String(uo.getTitle(id, type)); 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 isReadOnlyFile = function (Env, id) {
var uo = _getUserObjectFromId(Env, id); var uo = _getUserObjectFromId(Env, id);
return uo.isReadOnlyFile(id); return uo.isReadOnlyFile(id);
@ -1491,7 +1544,7 @@ define([
var files = []; var files = [];
var userObjects = _getUserObjects(Env); var userObjects = _getUserObjects(Env);
userObjects.forEach(function (uo) { 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)]; return [Number(id), uo.getFileData(id)];
}); });
Array.prototype.push.apply(files, data); Array.prototype.push.apply(files, data);
@ -1608,17 +1661,20 @@ define([
emptyTrash: callWithEnv(emptyTrashInner), emptyTrash: callWithEnv(emptyTrashInner),
addFolder: callWithEnv(addFolderInner), addFolder: callWithEnv(addFolderInner),
addSharedFolder: callWithEnv(addSharedFolderInner), addSharedFolder: callWithEnv(addSharedFolderInner),
addLink: callWithEnv(addLinkInner),
restoreSharedFolder: callWithEnv(restoreSharedFolderInner), restoreSharedFolder: callWithEnv(restoreSharedFolderInner),
convertFolderToSharedFolder: callWithEnv(convertFolderToSharedFolderInner), convertFolderToSharedFolder: callWithEnv(convertFolderToSharedFolderInner),
delete: callWithEnv(deleteInner), delete: callWithEnv(deleteInner),
deleteOwned: callWithEnv(deleteOwnedInner), deleteOwned: callWithEnv(deleteOwnedInner),
restore: callWithEnv(restoreInner), restore: callWithEnv(restoreInner),
setFolderData: callWithEnv(setFolderDataInner), setFolderData: callWithEnv(setFolderDataInner),
updateStaticAccess: callWithEnv(updateStaticAccessInner),
// Tools // Tools
getFileData: callWithEnv(getFileData), getFileData: callWithEnv(getFileData),
find: callWithEnv(find), find: callWithEnv(find),
getTitle: callWithEnv(getTitle), getTitle: callWithEnv(getTitle),
isReadOnlyFile: callWithEnv(isReadOnlyFile), isReadOnlyFile: callWithEnv(isReadOnlyFile),
isStaticFile: callWithEnv(isStaticFile),
getFiles: callWithEnv(getFiles), getFiles: callWithEnv(getFiles),
search: callWithEnv(search), search: callWithEnv(search),
getRecentPads: callWithEnv(getRecentPads), getRecentPads: callWithEnv(getRecentPads),

@ -733,11 +733,20 @@ define([
if (!common.isLoggedIn()) { return; } if (!common.isLoggedIn()) { return; }
$embedButton = common.createButton('mediatag', true).click(function () { $embedButton = common.createButton('mediatag', true).click(function () {
var cfg = { var cfg = {
types: ['file'], types: ['file', 'link'],
where: ['root'] where: ['root']
}; };
if ($embedButton.data('filter')) { cfg.filter = $embedButton.data('filter'); } if ($embedButton.data('filter')) { cfg.filter = $embedButton.data('filter'); }
common.openFilePicker(cfg, function (data) { 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') { if (data.type !== 'file') {
console.log("Unexpected data type picked " + data.type); console.log("Unexpected data type picked " + data.type);
return; return;

@ -17,6 +17,7 @@ define([
var SHARED_FOLDERS_TEMP = module.SHARED_FOLDERS_TEMP = "sharedFoldersTemp"; // Maybe deleted or new password var SHARED_FOLDERS_TEMP = module.SHARED_FOLDERS_TEMP = "sharedFoldersTemp"; // Maybe deleted or new password
var FILES_DATA = module.FILES_DATA = Constants.storageKey; var FILES_DATA = module.FILES_DATA = Constants.storageKey;
var OLD_FILES_DATA = module.OLD_FILES_DATA = Constants.oldStorageKey; 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 // Create untitled documents when no name is given
var getLocaleDate = function () { var getLocaleDate = function () {
@ -138,6 +139,7 @@ define([
var NEW_FILE_NAME = Messages.fm_newFile || 'New file'; var NEW_FILE_NAME = Messages.fm_newFile || 'New file';
exp.ROOT = ROOT; exp.ROOT = ROOT;
exp.STATIC_DATA = STATIC_DATA;
exp.UNSORTED = UNSORTED; exp.UNSORTED = UNSORTED;
exp.TRASH = TRASH; exp.TRASH = TRASH;
exp.TEMPLATE = TEMPLATE; exp.TEMPLATE = TEMPLATE;
@ -236,6 +238,10 @@ define([
return Boolean(data.roHref && !data.href); 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) { var isFolder = exp.isFolder = function (element) {
if (isFolderData(element)) { return false; } if (isFolderData(element)) { return false; }
return typeof(element) === "object" || isSharedFolder(element); return typeof(element) === "object" || isSharedFolder(element);
@ -310,6 +316,12 @@ define([
// Get data from AllFiles (Cryptpad_RECENTPADS) // Get data from AllFiles (Cryptpad_RECENTPADS)
var getFileData = exp.getFileData = function (file, editable) { var getFileData = exp.getFileData = function (file, editable) {
if (!file) { return; } 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] || {}; var data = files[FILES_DATA][file] || {};
if (!editable) { if (!editable) {
data = JSON.parse(JSON.stringify(data)); data = JSON.parse(JSON.stringify(data));
@ -344,6 +356,7 @@ define([
return '??'; return '??';
} }
var data = getFileData(file); var data = getFileData(file);
if (data.static) { return data.name; }
if (!file || !data || !(data.href || data.roHref)) { if (!file || !data || !(data.href || data.roHref)) {
error("getTitle called with a non-existing file id: ", file, data); error("getTitle called with a non-existing file id: ", file, data);
return; return;
@ -475,6 +488,11 @@ define([
}); });
return ret; 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 () { _getFiles[FILES_DATA] = function () {
var ret = []; var ret = [];
if (!files[FILES_DATA]) { return ret; } if (!files[FILES_DATA]) { return ret; }
@ -854,6 +872,7 @@ define([
// RENAME // RENAME
exp.rename = function (path, newName, cb) { exp.rename = function (path, newName, cb) {
cb = cb || function () {};
if (sframeChan) { if (sframeChan) {
return void sframeChan.query("Q_DRIVE_USEROBJECT", { return void sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "rename", cmd: "rename",
@ -891,9 +910,15 @@ define([
if (isSharedFolder(element)) { if (isSharedFolder(element)) {
data = files[SHARED_FOLDERS][element]; data = files[SHARED_FOLDERS][element];
} else { } else {
data = files[FILES_DATA][element]; data = files[FILES_DATA][element] || files[STATIC_DATA][element];
} }
if (!data) { return; } if (!data) { return; }
if (files[STATIC_DATA][element]) {
if (!newName || !newName.trim()) { return void cb(); }
data.name = newName;
cb();
return;
}
if (!newName || newName.trim() === "") { if (!newName || newName.trim() === "") {
delete data.filename; delete data.filename;
if (typeof cb === "function") { cb(); } if (typeof cb === "function") { cb(); }

@ -127,6 +127,7 @@ define([
sframeChan.event("EV_SECURE_ACTION", { sframeChan.event("EV_SECURE_ACTION", {
type: parsed.type, type: parsed.type,
password: data.password, password: data.password,
static: data.static,
href: data.url, href: data.url,
name: data.name name: data.name
}); });
@ -214,20 +215,21 @@ define([
$container.html(''); $container.html('');
Object.keys(list).forEach(function (id) { Object.keys(list).forEach(function (id) {
var data = list[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) { if (filter && name.toLowerCase().indexOf(filter.toLowerCase()) === -1) {
return; return;
} }
var $span = $('<span>', { var $span = $('<span>', {
'class': 'cp-filepicker-content-element', 'class': 'cp-filepicker-content-element',
'title': name, 'title': Util.fixHTML(name),
}).appendTo($container); }).appendTo($container);
$span.append(UI.getFileIcon(data)); $span.append(UI.getFileIcon(data));
$('<span>', {'class': 'cp-filepicker-content-element-name'}).text(name) $('<span>', {'class': 'cp-filepicker-content-element-name'}).text(name)
.appendTo($span); .appendTo($span);
if (data.static) { $span.attr('title', Util.fixHTML(data.href)); }
$span.click(function () { $span.click(function () {
if (typeof onFilePicked === "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});
} }
}); });

@ -24,7 +24,10 @@ define([
sframeChan.on('Q_DRIVE_USEROBJECT', function (data, cb) { sframeChan.on('Q_DRIVE_USEROBJECT', function (data, cb) {
if (!teamId) { return void cb({error: 'EINVAL'}); } 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); Cryptpad.userObjectCommand(data, cb);
}); });
sframeChan.on('Q_DRIVE_GETOBJECT', function (data, cb) { sframeChan.on('Q_DRIVE_GETOBJECT', function (data, cb) {

Loading…
Cancel
Save