Merge branch 'staging' into soon

pull/1/head
ansuz 4 years ago
commit 106dd007ac

@ -1,3 +1,46 @@
# 4.9.0
## Goals and announcements
We allocated most of this release cycle towards a schedule of one-on-one user interviews and some broad usage studies leveraging our new Form app. The remainder of our time was spent on some minor improvements. We'll continue at a slightly slower pace of implementation for the coming weeks while we complete our scheduled interviews and take some much-needed vacations.
## Update notes
It appears our promotion of the checkup page through our recent release notes and the inclusion of a link to it from the instance admin have been moderately successful. We've observed that more instance admins are noticing and fixing some common configuration issues.
This release features some minor changes to one instance configuration test which incorrectly provided an exemption for the use of `http://localhost:3000` as an `httpUnsafeOrigin` value. This exemption was provided because it's valid for local development, however, it suppressed errors when this configuration was used for production instances where it could cause a variety of problems. As usual, we recommend checking your instance's admin page after updating to confirm that you are passing the latest tests. Information about the checkup page is included in [our documentation](https://docs.cryptpad.fr/en/admin_guide/admin_panel.html#network).
To update from 4.8.0 to 4.9.0:
1. Stop your server
2. Get the latest code with git
3. Install the latest dependencies with `bower update` and `npm i`
4. Restart your server
5. Confirm that your instance is passing all the tests included on the `/checkup/` page
## Features
* We've added the ability to store URLs in user and team drives as requested in a private support ticket and [this issue](https://github.com/xwiki-labs/cryptpad/issues/732). Links can be shared directly with contacts. Unlike pads, links are not collaborative objects, so updating a link's name will not update the entry in another user's drive if you've already shared it with them. Links are integrated into our apps' _insert_ menu to facilitate quick insertion of links you've stored into your documents. We're interested in measuring how this functionality is used in practice so we can decide whether it's worth spending more time on it, so we've added some telemetry to measure (in aggregate) how often its components are used. We anonymize IP addresses in the logs for CryptPad.fr, but as always, you can disable telemetry via your settings panel.
* Our rich text editor now supports indentation with the tab key, as per [this ticket](https://github.com/xwiki-labs/cryptpad/issues/634).
* Forms received another round of improvements to styles, workflows, and some basic survey functionality to yield more accurate results.
* Ordered lists are now shuffled for each survey participant so that their initial order has less effect on the final results.
* CSV export now uses a layout that makes poll options easier to read.
* Unregistered users can now add a name to their response.
* Form results are displayed automatically (when available) to those who have answered.
* Authors and auditors can now click on some types of answers to jump directly to other answers from the same user.
* Users with very large drives might notice that their account loads slightly faster now, due to some minor optimizations in an integrity check that the client performs when loading accounts.
## Bugs
* We've added a guard against a type error that could be triggered when loading teams under certain rare conditions.
* Unregistered users' drives now show the "bread-crumb" UI for navigating between folders when viewing a shared folder in read-only mode. We've also suppressed the "Files" button for displaying the tree view which was non-functional for such users.
* A change in the format of support tickets caused tickets recently created by premium users to not be recognized as such. We've fixed the categorization in the admin panel's support ticket view.
* We've fixed a number of minor issues with forms:
* The maximum number of selectable choices for checkbox questions can no longer exceed the number of available choices.
* We guard against a type error that could occur when parsing dates.
* Forms imported from templates now have their initial title corrected.
* We've disabled the use of our indexedDB caching system for form results, since it was quietly dropping older responses when more than 100 responses had been submitted. We plan to re-enable caching for results once we've updated the eviction metric to better handle the response format.
# 4.8.0 # 4.8.0
## Goals ## Goals

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 83 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.3 KiB

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360.4 94.7"><defs><style>.cls-1{fill:#4591c4}.cls-2{fill:#999}</style></defs><title>CryptPad_logo_color</title><g id="Layer_2" data-name="Layer 2"><g id="svg2"><g id="g4845"><path id="path4811" class="cls-1" d="M99.5 63.6a24.8 24.8 0 0 1-5.9-.6 8.5 8.5 0 0 1-3.8-1.9 7.1 7.1 0 0 1-2-3.4 19.4 19.4 0 0 1-.6-5.2v-10a19.4 19.4 0 0 1 .6-5.1 7.1 7.1 0 0 1 2-3.4 8.5 8.5 0 0 1 3.8-2 24.8 24.8 0 0 1 5.9-.6h22.3v6.2h-22a11.8 11.8 0 0 0-2.7.3 3.5 3.5 0 0 0-1.7.9 3.3 3.3 0 0 0-.9 1.6 11.1 11.1 0 0 0-.2 2.5v9.4a11.1 11.1 0 0 0 .2 2.5 3.3 3.3 0 0 0 .9 1.6 3.3 3.3 0 0 0 1.7.8l2.7.2h22v6.1z"/><path id="path4813" class="cls-1" d="M126.5 63.6V48.8a16 16 0 0 1 .7-4.9 7.5 7.5 0 0 1 2.1-3.3 8.9 8.9 0 0 1 3.7-1.9 21.4 21.4 0 0 1 5.5-.6h4.6v5.7h-4.4l-2.5.2a3.3 3.3 0 0 0-1.6.8 3.2 3.2 0 0 0-.8 1.6 10.1 10.1 0 0 0-.3 2.5v14.7z"/><path id="path4815" class="cls-1" d="M154.1 51.4a3.8 3.8 0 0 0 .9 3 4.6 4.6 0 0 0 3.1.8h15.7V38h6.8v25.2q0 4.3-2.1 6.2t-6.9 1.9h-10.2v-5.8h9.6a3.3 3.3 0 0 0 2.1-.6 2.7 2.7 0 0 0 .7-2.2v-2h-16.1a19 19 0 0 1-4.9-.5 7.8 7.8 0 0 1-3.3-1.7 6.5 6.5 0 0 1-1.8-2.8 12.7 12.7 0 0 1-.6-4V38h6.9z"/><path id="path4817" class="cls-1" d="M207.5 38a25.1 25.1 0 0 1 5.9.6 8.5 8.5 0 0 1 3.8 1.9 7.1 7.1 0 0 1 2 3.4 19.7 19.7 0 0 1 .6 5.2v3.4a19.4 19.4 0 0 1-.6 5.2 7.1 7.1 0 0 1-2 3.4 8.5 8.5 0 0 1-3.8 1.9 25.1 25.1 0 0 1-5.9.6h-14.3v7.6h-7V49.1a19.7 19.7 0 0 1 .6-5.2 7.1 7.1 0 0 1 2-3.4 8.6 8.6 0 0 1 3.8-1.9 25.1 25.1 0 0 1 5.9-.6zm5.3 11.3a11.7 11.7 0 0 0-.3-2.7 2.9 2.9 0 0 0-1-1.6 4 4 0 0 0-1.9-.8l-3-.2h-7.8l-2.7.2a3.5 3.5 0 0 0-1.7.8 3.2 3.2 0 0 0-.8 1.6 11.7 11.7 0 0 0-.2 2.6v8.4h13.4l3-.2a3.8 3.8 0 0 0 1.9-.7 2.9 2.9 0 0 0 1-1.6 11.9 11.9 0 0 0 .3-2.8z"/><path id="path4819" class="cls-1" d="M226.5 63.6V43.8H223V38h3.6v-7.2h7.2V38h8.1v5.7h-8.1v19.9z"/><path id="path4821" class="cls-2" d="M252.4 54.4v9.2h-7.2V31.4H271a25.7 25.7 0 0 1 5.8.5 8.4 8.4 0 0 1 3.7 1.8 6.8 6.8 0 0 1 2 3.2 17.1 17.1 0 0 1 .6 4.8v2.8a16.9 16.9 0 0 1-.6 4.8 6 6 0 0 1-2 3 8.3 8.3 0 0 1-3.7 1.6 31.6 31.6 0 0 1-5.9.4zm23.5-12.3q0-2.6-1.1-3.5t-4-.9h-18.4v11h18.5a6.5 6.5 0 0 0 3.9-.9q1.1-.9 1.1-3.4z"/><path id="path4823" class="cls-2" d="M296.2 63.6a18.4 18.4 0 0 1-4.6-.5 7.4 7.4 0 0 1-2.9-1.3 4.6 4.6 0 0 1-1.5-2.1 8.7 8.7 0 0 1-.4-2.8v-2.4a9.3 9.3 0 0 1 .4-2.9 4.7 4.7 0 0 1 1.4-2.1 6.5 6.5 0 0 1 2.7-1.3 17.7 17.7 0 0 1 4.4-.4h18.9v-.6q0-2.5-1-3.3a5.3 5.3 0 0 0-3.4-.8h-7V38h7a20.7 20.7 0 0 1 5.2.6 8.9 8.9 0 0 1 3.5 1.7 6.7 6.7 0 0 1 2 2.9 12.5 12.5 0 0 1 .6 4.2v6.2a17.2 17.2 0 0 1-.5 4.7 6.3 6.3 0 0 1-1.9 3.1 7.9 7.9 0 0 1-3.6 1.7 26.8 26.8 0 0 1-5.6.5zM314.7 52h-18.2a3.1 3.1 0 0 0-1.9.5 2.5 2.5 0 0 0-.7 2.1v1.7a2.1 2.1 0 0 0 .8 1.9 3.7 3.7 0 0 0 2.1.5H310l2.1-.2a3.2 3.2 0 0 0 1.5-.7 3 3 0 0 0 .9-1.4 7.8 7.8 0 0 0 .3-2.3z"/><path id="path4825" class="cls-2" d="M339.1 63.6a25.4 25.4 0 0 1-6-.6 8.6 8.6 0 0 1-3.8-1.9 7.1 7.1 0 0 1-2-3.4 19.4 19.4 0 0 1-.6-5.2v-3.4a19.7 19.7 0 0 1 .6-5.2 7.1 7.1 0 0 1 2-3.4 8.6 8.6 0 0 1 3.8-1.9 25.4 25.4 0 0 1 6-.6h14.2v-8.2h7v22.8a19.4 19.4 0 0 1-.6 5.2 7.1 7.1 0 0 1-2 3.4A8.5 8.5 0 0 1 354 63a25.1 25.1 0 0 1-5.9.6zm-5.3-11.2a11.7 11.7 0 0 0 .3 2.7 2.9 2.9 0 0 0 1 1.6 4 4 0 0 0 1.9.8l3.1.2h7.8l2.7-.2a3.3 3.3 0 0 0 1.7-.8 3.2 3.2 0 0 0 .9-1.6 11.9 11.9 0 0 0 .2-2.6v-8.4H340l-3 .2a4 4 0 0 0-1.9.8 2.9 2.9 0 0 0-1 1.6 11.7 11.7 0 0 0-.3 2.7z"/><path id="path4827" class="cls-1" d="M39.2 0L9.9 5.4A6.3 6.3 0 1 0 3.2 16v42.2c0 4 1.8 8.3 5.3 12.7A65.5 65.5 0 0 0 21.6 83a128.2 128.2 0 0 0 17.6 10.5A128.1 128.1 0 0 0 56.7 83a65.4 65.4 0 0 0 13.1-12.1c3.5-4.5 5.3-8.8 5.3-12.7V16A6.3 6.3 0 0 0 72 4.2a6.2 6.2 0 0 0-3.6 1.2zm-.1 6.2l26.7 4.9a5.9 5.9 0 0 0 .2 1.1L50.3 22.5a15.3 15.3 0 0 0-22.6.1l-15.5-10a6.3 6.3 0 0 0 .3-1.4zm28.8 9a6.5 6.5 0 0 0 1.8 1.1v41a10.4 10.4 0 0 1-.1 1.7 21.5 21.5 0 0 1-4.1 7.8 56.1 56.1 0 0 1-11.3 10.4 110.6 110.6 0 0 1-15 9 110.8 110.8 0 0 1-15-9A55.8 55.8 0 0 1 13 66.8a19.8 19.8 0 0 1-4.4-9.3V16.4a6.3 6.3 0 0 0 1.7-1l19.8 12.7a10.1 10.1 0 0 1 9-5.4 10 10 0 0 1 9 5.4z"/><g id="g4829"><path id="path4831" class="cls-2" d="M23 54.8a4.6 4.6 0 1 0 0 9.3 4.6 4.6 0 0 0 0-9.3z"/><path id="path4833" class="cls-2" d="M24.3 28.5a14.9 14.9 0 0 0 4.2 15.4l-7.3 14.8a2.8 2.8 0 0 0 2.3 3.8h11v-5.2h-6.8l6.4-12.8a2.7 2.7 0 0 0-.8-3.3 9.7 9.7 0 0 1-4.3-9.6z"/></g><g id="g4835"><path id="path4837" class="cls-2" d="M55.3 54.8a4.6 4.6 0 1 0 0 9.3 4.6 4.6 0 0 0 0-9.3z"/><path id="path4839" class="cls-2" d="M53.8 28.6l-4.7 3.1a10.2 10.2 0 0 1 .1 1.2 10 10 0 0 1-4.3 8.3 2.7 2.7 0 0 0-.8 3.3l6.3 12.8h-6.8v5.2h11a2.8 2.8 0 0 0 2.3-3.8l-7.3-14.8a15.2 15.2 0 0 0 4.8-11 15.3 15.3 0 0 0-.6-4.3z"/></g><path id="path4841" class="cls-1" d="M43.2 33.3a4.2 4.2 0 1 1-4.2-4.2 4.2 4.2 0 0 1 4.2 4.2z"/><path id="path4843" class="cls-1" d="M45.3 88.4a6.3 6.3 0 1 1-6.3-6.3 6.3 6.3 0 0 1 6.3 6.3z"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 4.7 KiB

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 393.94 476.2"><defs><style>.cls-1{opacity:0.04;}.cls-2{fill:#999;}.cls-3{fill:#4591c4;}</style></defs><title>CryptPadlogo</title><g id="Layer_2" data-name="Layer 2"><g id="svg2"><g id="g4764" class="cls-1"><path class="cls-2" d="M139.36,288.16l32.07-64.43a13.59,13.59,0,0,0-4.23-16.62,48.65,48.65,0,0,1-21.28-48.25l-23.47-15.31a75.74,75.74,0,0,0-3.36,21.87c0,22,9.52,41.45,24.35,55.54l-27.24,54.8c-.11,0-.2,0-.31,0a23.27,23.27,0,1,0,0,46.53,23,23,0,0,0,17.31-7.86h40.27V288.16Z"/><path class="cls-2" d="M278.28,275.73c-.57,0-1.11.13-1.68.17l-27.33-55.09c14.75-14.07,24.2-33.47,24.2-55.39a77.13,77.13,0,0,0-3.06-21.72l-23.62,15.45a51.69,51.69,0,0,1,.44,6.27A50.21,50.21,0,0,1,225.65,207a13.58,13.58,0,0,0-4.22,16.62l31.77,64.58h-34V314.4h41.63a23.23,23.23,0,1,0,17.41-38.67Z"/><polygon class="cls-2" points="270.41 143.7 270.41 143.7 270.41 143.7 270.41 143.7"/><circle class="cls-3" cx="196.06" cy="167.4" r="21.21"/><path class="cls-3" d="M362.25,21.36a31.14,31.14,0,0,0-18.1,5.8L197,0,50,27.16a31.62,31.62,0,1,0-33.68,53.4v212c0,19.95,8.93,41.51,26.62,64.08,15.66,20,37.82,40.46,65.89,60.83a603,603,0,0,0,57,36.21,31.54,31.54,0,0,0,60,1.25,606,606,0,0,0,59.26-37.46c28.09-20.37,50.23-40.86,65.9-60.83,17.7-22.6,26.61-44.13,26.61-64.08V80.38a31.46,31.46,0,0,0-15.39-59ZM62.82,55.94,196.61,31.32l134.33,24.8a29.58,29.58,0,0,0,.9,5.61L253.09,113a76.78,76.78,0,0,0-113.69.36L61.55,62.82A31.8,31.8,0,0,0,62.82,55.94ZM350.49,288.21a51.41,51.41,0,0,1-.73,8.51c-3.16,12.6-10.11,25.8-20.82,39.47-13.41,17.09-32.43,34.52-56.48,51.95a532.75,532.75,0,0,1-54.19,34.09,31.5,31.5,0,0,0-43.73-.62,534,534,0,0,1-53.06-33.47c-24-17.43-42.9-34.86-56.31-51.95-12.77-16.31-20.25-31.94-22.26-46.71,0-.41,0-.86,0-1.27V82.37a31.51,31.51,0,0,0,8.69-5.25l88.88,57.75.37-.36,10.5,6.88a51,51,0,0,1,45.07-27,50.37,50.37,0,0,1,45.08,27.15l22.09-14.3L341.44,76.4A32.55,32.55,0,0,0,350.49,82Z"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

@ -105,7 +105,7 @@ define([
var imprintUrl = AppConfig.imprint && (typeof(AppConfig.imprint) === "boolean" ? var imprintUrl = AppConfig.imprint && (typeof(AppConfig.imprint) === "boolean" ?
'/imprint.html' : AppConfig.imprint); '/imprint.html' : AppConfig.imprint);
Pages.versionString = "v4.8.0"; Pages.versionString = "v4.9.0";
// used for the about menu // used for the about menu

@ -123,7 +123,7 @@ module.exports.create = function (config) {
maxWorkers: config.maxWorkers, maxWorkers: config.maxWorkers,
disableIntegratedTasks: config.disableIntegratedTasks || false, disableIntegratedTasks: config.disableIntegratedTasks || false,
disableIntegratedEviction: typeof(config.disableIntegratedEviction) === 'undefined'? true: config.disableIntegratedEviction, // XXX false, disableIntegratedEviction: typeof(config.disableIntegratedEviction) === 'undefined'? true: config.disableIntegratedEviction, // XXX 4.10.0 false,
lastEviction: +new Date(), lastEviction: +new Date(),
evictionReport: {}, evictionReport: {},
commandTimers: {}, commandTimers: {},

2
package-lock.json generated

@ -1,6 +1,6 @@
{ {
"name": "cryptpad", "name": "cryptpad",
"version": "4.8.0", "version": "4.9.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

@ -1,7 +1,7 @@
{ {
"name": "cryptpad", "name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server", "description": "realtime collaborative visual editor with zero knowlege server",
"version": "4.8.0", "version": "4.9.0",
"license": "AGPL-3.0+", "license": "AGPL-3.0+",
"repository": { "repository": {
"type": "git", "type": "git",

@ -833,7 +833,8 @@ define([
var premium = t.some(function (msg) { var premium = t.some(function (msg) {
var _ed = Util.find(msg, ['content', 'msg', 'content', 'sender', 'edPublic']); var _ed = Util.find(msg, ['content', 'msg', 'content', 'sender', 'edPublic']);
if (ed !== _ed) { return; } if (ed !== _ed) { return; }
return Util.find(msg, ['content', 'msg', 'content', 'sender', 'plan']); return Util.find(msg, ['content', 'msg', 'content', 'sender', 'plan']) ||
Util.find(msg, ['content', 'msg', 'content', 'sender', 'quota', 'plan']);
}); });
var lastMsg = t[t.length - 1]; var lastMsg = t[t.length - 1];
var lastMsgEd = Util.find(lastMsg, ['content', 'msg', 'content', 'sender', 'edPublic']); var lastMsgEd = Util.find(lastMsg, ['content', 'msg', 'content', 'sender', 'edPublic']);

@ -705,17 +705,19 @@ define([
var isOnion = function (host) { var isOnion = function (host) {
return /\.onion$/.test(host); return /\.onion$/.test(host);
}; };
var isLocalhost = function (host) {
return /^http:\/\/localhost/.test(host);
};
assert(function (cb, msg) { assert(function (cb, msg) {
// provide an exception for development instances // provide an exception for development instances
if (/http:\/\/localhost/.test(trimmedUnsafe)) { return void cb(true); } if (isLocalhost(trimmedUnsafe) && isLocalhost(window.location.href)) { return void cb(true); }
// if both the main and sandbox domains are onion addresses // if both the main and sandbox domains are onion addresses
// then the HTTPS requirement is unnecessary // then the HTTPS requirement is unnecessary
if (isOnion(trimmedUnsafe) && isOnion(trimmedSafe)) { return void cb(true); } if (isOnion(trimmedUnsafe) && isOnion(trimmedSafe)) { return void cb(true); }
// otherwise expect that both inner and outer domains use HTTPS // otherwise expect that both inner and outer domains use HTTPS
setWarningClass(msg);
msg.appendChild(h('span', [ msg.appendChild(h('span', [
"Both ", "Both ",
code('httpUnsafeOrigin'), code('httpUnsafeOrigin'),

@ -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,75 @@ 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.link_open = "Open URL";
// openLinkInNewTab ("Open Link in New Tab")
// fc_open ("Open")
// share_linkOpen ("Preview")
// resources_openInNewTab ("Open it in a new tab")
Messages.link_open = Messages.fc_open; // XXX 4.10.0
//Messages.link_store = "Store link in drive";
// toolbar_storeInDrive ? ("Store in CryptDrive")
// autostore_store ? ("Store")
Messages.link_store = Messages.toolbar_storeInDrive; // XXX 4.10.0
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;
Feedback.send('LINK_RECEIVED_LATER');
},
keys: [27]
}, {
className: 'primary',
name: Messages.link_open,
onClick: function () {
if (clicked) { return true; }
clicked = true;
common.openUnsafeURL(url);
Feedback.send("LINK_RECEIVED_OPEN");
},
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();
Feedback.send("LINK_RECEIVED_STORE");
});
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();

@ -9,6 +9,15 @@
return Array.prototype.slice.call(A, start, end); return Array.prototype.slice.call(A, start, end);
}; };
Util.shuffleArray = function (a) {
for (var i = a.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
};
Util.bake = function (f, args) { Util.bake = function (f, args) {
if (typeof(args) === 'undefined') { args = []; } if (typeof(args) === 'undefined') { args = []; }
if (!Array.isArray(args)) { args = [args]; } if (!Array.isArray(args)) { args = [args]; }
@ -152,6 +161,10 @@
}; };
}; };
Util.inc = function (map, key, val) {
map[key] = (map[key] || 0) + (typeof(val) === 'number'? val: 1);
};
Util.find = function (map, path) { Util.find = function (map, path) {
var l = path.length; var l = path.length;
for (var i = 0; i < l; i++) { for (var i = 0; i < l; i++) {

@ -443,6 +443,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_new)),
]), ]),
]), ]),
$separator.clone()[0], $separator.clone()[0],
@ -1106,11 +1111,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 +1165,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 +1277,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 +1916,31 @@ 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') {
//console.error(name, Util.fixHTML(name));
// this is only safe because our build of tippy sets titles as
// 'textContent' instead of innerHTML, otherwise
// we would need to use Util.fixHTML
$element.attr('title', name);
}
var type = Messages.fm_link_type;
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 +1956,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 +2169,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 +2506,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 +2732,74 @@ define([
}); });
$input.click(); $input.click();
}; };
var showLinkModal = function () {
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', [
h('label', {for: 'cp-app-drive-link-name'}, Messages.fm_link_name),
name = h('input#cp-app-drive-link-name', { autocomplete: 'off', placeholder: Messages.fm_link_name_placeholder }),
h('label', {for: 'cp-app-drive-link-url'}, Messages.fm_link_url),
url = h('input#cp-app-drive-link-url', { type: 'url', autocomplete: 'off', placeholder: Messages.form_input_ph_url }),
warning,
]);
var protocolPattern = /https*:\/\//;
var fragmentPattern = /#.*$/;
var setNamePlaceholder = function (val) {
var temp = val.replace(protocolPattern, '').replace(fragmentPattern, '').trim().slice(0, 48);
if (!protocolPattern.test(val) || !temp) {
temp = Messages.fm_link_name_placeholder;
}
name.setAttribute('placeholder', temp);
};
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;
}
setNamePlaceholder(v);
$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 $name = $(name);
var n = $name.val().trim() || $name.attr('placeholder');
var u = $url.val().trim();
if (!n || !u) { return true; }
if (!Util.isValidURL(u)) {
// XXX 4.10.0 add style for invalid input? input:invalid
UI.warn(Messages.fm_link_invalid);
return true;
}
manager.addLink(currentPath, {
name: n,
url: u
}, refresh);
Feedback.send("LINK_CREATED");
},
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 +2828,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 +2872,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_new
});
options.push({tag: 'hr'});
} }
getNewPadTypes().forEach(function (type) { getNewPadTypes().forEach(function (type) {
var attributes = { var attributes = {
@ -3073,6 +3194,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_type));
} }
// Pads // Pads
getNewPadTypes().forEach(function (type) { getNewPadTypes().forEach(function (type) {
@ -3479,6 +3607,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 +4611,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 +4631,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 +4683,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,
@ -119,6 +124,9 @@ define([
channel: mailbox.notifications, channel: mailbox.notifications,
curvePublic: mailbox.curvePublic curvePublic: mailbox.curvePublic
}); });
if (config.static) {
Feedback.send("LINK_SHARED_WITH_CONTACT");
}
return; return;
} }
} }
@ -137,6 +145,21 @@ 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);
});
Feedback.send("LINK_ADDED_TO_DRIVE");
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 +369,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 +387,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 +431,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 +592,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 +717,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 +736,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 +767,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,10 @@ 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
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 +113,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)) {
@ -2053,8 +2058,17 @@ define([
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
// Tell all the owners that the pad was deleted from the server // 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) { m.forEach(function (obj) {
var mb = JSON.parse(obj); var mb = JSON.parse(obj);
if (mb.curvePublic === curvePublic) { return; } if (mb.curvePublic === curvePublic) { return; }

@ -97,7 +97,7 @@ define([
var checkCheckpoints = function (array) { var checkCheckpoints = function (array) {
if (!Array.isArray(array)) { return; } if (!Array.isArray(array)) { return; }
// Keep the last 100 messages // Keep the last 100 messages
if (array.length > 100) { // XXX if (array.length > 100) { // XXX 4.10.0
array.splice(0, array.length - 100); array.splice(0, array.length - 100);
} }
// Remove every message before the first checkpoint // Remove every message before the first checkpoint

@ -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';

@ -195,7 +195,7 @@ define([
Pinpad.create(ctx.store.network, data, function (e, call) { Pinpad.create(ctx.store.network, data, function (e, call) {
if (e) { return void cb(e); } if (e) { return void cb(e); }
team.rpc = call; team.rpc = call;
team.onRpcReadyEvt.fire(); if (team && team.onRpcReadyEvt) { team.onRpcReadyEvt.fire(); }
cb(); cb();
}, Cache); }, Cache);
}); });

@ -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,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 onCommand = function (Env, cmdData, cb) {
var cmd = cmdData.cmd; var cmd = cmdData.cmd;
var data = cmdData.data || {}; var data = cmdData.data || {};
switch (cmd) { var method = COMMANDS[cmd];
case 'move':
_move(Env, data, cb); break; if (typeof(method) === 'function') {
case 'restore': return void method(Env, data, cb);
_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();
} }
// if the command was not handled then call back
cb();
}; };
// Set the value everywhere the given pad is stored (main and shared folders) // Set the value everywhere the given pad is stored (main and shared folders)
@ -1129,8 +1152,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;
} }
}); });
@ -1233,7 +1256,10 @@ define([
Array.prototype.push.apply(result, sfChannels); 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) { var addPad = function (Env, path, pad, cb) {
@ -1383,6 +1409,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 +1469,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 +1494,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 +1540,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 +1657,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;

@ -1925,7 +1925,6 @@ define([
var cryptputCfg = $.extend(true, {}, rtConfig, {password: password}); var cryptputCfg = $.extend(true, {}, rtConfig, {password: password});
if (data.templateContent) { if (data.templateContent) {
Cryptget.put(currentPad.hash, JSON.stringify(data.templateContent), function () { Cryptget.put(currentPad.hash, JSON.stringify(data.templateContent), function () {
console.error(arguments);
startRealtime(); startRealtime();
cb(); cb();
}, cryptputCfg); }, cryptputCfg);
@ -2004,6 +2003,8 @@ define([
sframeChan.on('EV_BURN_AFTER_READING', function () { sframeChan.on('EV_BURN_AFTER_READING', function () {
startRealtime(); startRealtime();
// feedback fails for users in noDrive mode
Utils.Feedback.send("BURN_AFTER_READING", Boolean(cfg.noDrive));
}); });
sframeChan.ready(); sframeChan.ready();

@ -23,6 +23,12 @@ define([
var $title; var $title;
exp.setToolbar = function (toolbar) { exp.setToolbar = function (toolbar) {
$title = toolbar && (toolbar.title || toolbar.pageTitle); $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; }; exp.getTitle = function () { return exp.title; };

@ -867,10 +867,6 @@ MessengerUI, Messages, Pages) {
'class': "cp-toolbar-link-logo" 'class': "cp-toolbar-link-logo"
}).append(UIElements.getSvgLogo()); }).append(UIElements.getSvgLogo());
/*.append($('<img>', {
//src: '/customize/images/logo_white.png?' + ApiConfig.requireConf.urlArgs
src: '/customize/favicon/main-favicon.png?' + ApiConfig.requireConf.urlArgs
}));*/
var onClick = function (e) { var onClick = function (e) {
e.preventDefault(); e.preventDefault();
if (e.ctrlKey) { if (e.ctrlKey) {

@ -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(); }

@ -192,8 +192,8 @@ define([
}, },
}; };
Messages.convertPage = "Convert"; // 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 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 createToolbar = function () {
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle', 'notifications']; var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle', 'notifications'];

@ -247,6 +247,15 @@
.cp-form-anon-answer { .cp-form-anon-answer {
text-align: center; text-align: center;
margin: 20px 0 30px 0; margin: 20px 0 30px 0;
.cp-form-anon-answer-input {
margin-top: 20px;
display: flex;
white-space: nowrap;
align-items: center;
input {
margin-left: 10px;
}
}
} }
} }
@ -464,6 +473,16 @@
display: flex; display: flex;
flex-flow: column; flex-flow: column;
position: relative; position: relative;
#cp-form-response-msg {
background: @cp_form-bg1;
margin-bottom: 20px;
padding: 10px;
p:last-child {
margin-bottom: 0;
}
}
.cp-form-creator-results-controls { .cp-form-creator-results-controls {
margin-bottom: 20px; margin-bottom: 20px;
//background: @cp_form-bg1; //background: @cp_form-bg1;
@ -529,6 +548,7 @@
.cp-form-individual { .cp-form-individual {
background: @cp_form-bg1; background: @cp_form-bg1;
padding: 10px; padding: 10px;
margin-bottom: 20px;
& > *:not(:last-child) { & > *:not(:last-child) {
margin-right: 10px; margin-right: 10px;
} }
@ -659,6 +679,16 @@
.avatar_main(30px); .avatar_main(30px);
margin-right: 10px; margin-right: 10px;
} }
&.cp-clickable {
cursor: pointer;
&:hover {
color: @cryptpad_color_link;
&::after {
font-family: FontAwesome;
content: "\00a0\f06e";
}
}
}
} }
.cp-poll-time-day { .cp-poll-time-day {
flex-basis: 100px; flex-basis: 100px;
@ -677,6 +707,16 @@
-ms-overflow-style: none; /* IE and Edge */ -ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */ scrollbar-width: none; /* Firefox */
} }
.cp-form-poll-option, .cp-poll-time-day {
flex-flow: column;
text-align: center;
.cp-form-weekday-separator {
display: none;
}
}
.cp-poll-time-day {
height: 50px;
}
} }
&.cp-form-poll-switch { &.cp-form-poll-switch {
display: flex; display: flex;
@ -706,12 +746,21 @@
} }
.cp-form-poll-option, .cp-poll-switch { .cp-form-poll-option, .cp-poll-switch {
width: 200px; width: 200px;
.cp-form-weekday-separator {
margin-right: 5px;
margin-left: 5px;
}
} }
.cp-poll-time-day { .cp-poll-time-day {
flex-basis: 40px; flex-basis: 40px;
border-right: none; border-right: none;
border-right: 1px solid @cryptpad_text_col; border-right: 1px solid @cryptpad_text_col;
border-bottom: 0px; border-bottom: 0px;
flex-flow: column;
text-align: center;
.cp-form-weekday-separator {
display: none;
}
} }
} }
.cp-form-poll-choice, .cp-form-poll-answer { .cp-form-poll-choice, .cp-form-poll-answer {

@ -298,6 +298,7 @@ define([
del del
]); ]);
$(del).click(function () { $(del).click(function () {
var $block = $(el).closest('.cp-form-edit-block');
$(el).remove(); $(el).remove();
// We've just deleted an item/option so we should be under the MAX limit and // We've just deleted an item/option so we should be under the MAX limit and
// we can show the "add" button again // we can show the "add" button again
@ -306,6 +307,13 @@ define([
$add.show(); $add.show();
if (v.type === "time") { $(addMultiple).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; return el;
}; };
@ -575,7 +583,22 @@ define([
]; ];
}; };
var makePollTable = function (answers, opts) { var getWeekDays = function (large) {
var baseDate = new Date(2017, 0, 1); // just a Sunday
var weekDays = [];
for(var i = 0; i < 7; i++) {
weekDays.push(baseDate.toLocaleDateString(undefined, { weekday: 'long' }));
baseDate.setDate(baseDate.getDate() + 1);
}
if (!large) {
weekDays = weekDays.map(function (day) { return day.slice(0,3); });
}
return weekDays.map(function (day) { return day.replace(/^./, function (str) { return str.toUpperCase(); }); });
};
// "resultsPageObj" is an object with "content" and "answers"
// only available when viewing the Responses page
var makePollTable = function (answers, opts, resultsPageObj) {
// Sort date values // Sort date values
if (opts.type !== "text") { if (opts.type !== "text") {
opts.values.sort(function (a, b) { opts.values.sort(function (a, b) {
@ -583,18 +606,25 @@ define([
}); });
} }
// Create first line with options // Create first line with options
var allDays = getWeekDays(true);
var els = opts.values.map(function (data) { var els = opts.values.map(function (data) {
var _date;
if (opts.type === "day") { if (opts.type === "day") {
var _date = new Date(data); _date = new Date(data);
data = _date.toLocaleDateString(); data = _date.toLocaleDateString();
} }
if (opts.type === "time") { if (opts.type === "time") {
var _dateT = new Date(data); _date = new Date(data);
data = Flatpickr.formatDate(_dateT, timeFormat); data = Flatpickr.formatDate(_date, timeFormat);
} }
var day = _date && allDays[_date.getDay()];
return h('div.cp-poll-cell.cp-form-poll-option', { return h('div.cp-poll-cell.cp-form-poll-option', {
title: Util.fixHTML(data) title: Util.fixHTML(data)
}, data); }, [
opts.type === 'day' ? h('span.cp-form-weekday', day) : undefined,
opts.type === 'day' ? h('span.cp-form-weekday-separator', ' - ') : undefined,
h('span', data)
]);
}); });
// Insert axis switch button // Insert axis switch button
var switchAxis = h('button.btn.btn-default', [ var switchAxis = h('button.btn.btn-default', [
@ -611,13 +641,20 @@ define([
opts.values.forEach(function (d) { opts.values.forEach(function (d) {
var date = new Date(d); var date = new Date(d);
var day = date.toLocaleDateString(); var day = date.toLocaleDateString();
_days[day] = _days[day] || 0; _days[day] = {
_days[day]++; n: (_days[day] && _days[day].n) || 0,
name: allDays[date.getDay()]
};
_days[day].n++;
}); });
Object.keys(_days).forEach(function (day) { Object.keys(_days).forEach(function (day) {
days.push(h('div.cp-poll-cell.cp-poll-time-day', { days.push(h('div.cp-poll-cell.cp-poll-time-day', {
style: 'flex-grow:'+(_days[day]-1)+';' style: 'flex-grow:'+(_days[day].n - 1)+';'
}, day)); }, [
h('span.cp-form-weekday', _days[day].name),
h('span.cp-form-weekday-separator', ' - '),
h('span', day)
]));
}); });
lines.unshift(h('div', days)); lines.unshift(h('div', days));
} }
@ -640,13 +677,19 @@ define([
}, v); }, v);
return cell; 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) title: Util.fixHTML(name)
}, [ }, [
avatar, avatar,
h('span', name) h('span', name)
])); ]));
bodyEls.push(h('div', els)); 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); var body = h('div.cp-form-poll-body', bodyEls);
@ -788,6 +831,7 @@ define([
if (filterCurve && user === filterCurve) { return; } if (filterCurve && user === filterCurve) { return; }
try { try {
return { return {
curve: user,
user: answers[user].msg._userdata, user: answers[user].msg._userdata,
results: answers[user].msg[uid] results: answers[user].msg[uid]
}; };
@ -950,7 +994,7 @@ define([
printResults: function (answers, uid) { printResults: function (answers, uid) {
var results = []; var results = [];
var empty = 0; var empty = 0;
Object.keys(answers).forEach(function (author) { Object.keys(answers).forEach(function (author) { // TODO deduplicate these?
var obj = answers[author]; var obj = answers[author];
var answer = obj.msg[uid]; var answer = obj.msg[uid];
if (!answer || !answer.trim()) { return empty++; } if (!answer || !answer.trim()) { return empty++; }
@ -1016,7 +1060,7 @@ define([
printResults: function (answers, uid) { printResults: function (answers, uid) {
var results = []; var results = [];
var empty = 0; var empty = 0;
Object.keys(answers).forEach(function (author) { Object.keys(answers).forEach(function (author) { // TODO deduplicate these
var obj = answers[author]; var obj = answers[author];
var answer = obj.msg[uid]; var answer = obj.msg[uid];
if (!answer || !answer.trim()) { return empty++; } if (!answer || !answer.trim()) { return empty++; }
@ -1090,8 +1134,7 @@ define([
var obj = answers[author]; var obj = answers[author];
var answer = obj.msg[uid]; var answer = obj.msg[uid];
if (!answer || !answer.trim()) { return empty++; } if (!answer || !answer.trim()) { return empty++; }
count[answer] = count[answer] || 0; Util.inc(count, answer);
count[answer]++;
}); });
Object.keys(count).forEach(function (value) { Object.keys(count).forEach(function (value) {
results.push(h('div.cp-form-results-type-radio-data', [ results.push(h('div.cp-form-results-type-radio-data', [
@ -1193,8 +1236,7 @@ define([
var c = count[q_uid] = count[q_uid] || {}; var c = count[q_uid] = count[q_uid] || {};
var res = answer[q_uid]; var res = answer[q_uid];
if (!res || !res.trim()) { return; } if (!res || !res.trim()) { return; }
c[res] = c[res] || 0; Util.inc(c, res);
c[res]++;
}); });
}); });
Object.keys(count).forEach(function (q_uid) { Object.keys(count).forEach(function (q_uid) {
@ -1304,8 +1346,7 @@ define([
var answer = obj.msg[uid]; var answer = obj.msg[uid];
if (!Array.isArray(answer) || !answer.length) { return empty++; } if (!Array.isArray(answer) || !answer.length) { return empty++; }
answer.forEach(function (val) { answer.forEach(function (val) {
count[val] = count[val] || 0; Util.inc(count, val);
count[val]++;
}); });
}); });
Object.keys(count).forEach(function (value) { Object.keys(count).forEach(function (value) {
@ -1420,8 +1461,7 @@ define([
var res = answer[q_uid]; var res = answer[q_uid];
if (!Array.isArray(res) || !res.length) { return; } if (!Array.isArray(res) || !res.length) { return; }
res.forEach(function (v) { res.forEach(function (v) {
c[v] = c[v] || 0; Util.inc(c, v);
c[v]++;
}); });
}); });
}); });
@ -1470,7 +1510,9 @@ define([
if (!Array.isArray(opts.values)) { return; } if (!Array.isArray(opts.values)) { return; }
var map = {}; var map = {};
var invMap = {}; var invMap = {};
var els = opts.values.map(function (data, i) { var sorted = false;
Util.shuffleArray(opts.values);
var els = opts.values.map(function (data) {
var uid = Util.uid(); var uid = Util.uid();
map[uid] = data; map[uid] = data;
invMap[data] = uid; invMap[data] = uid;
@ -1479,7 +1521,7 @@ define([
h('i.fa.fa-ellipsis-v'), h('i.fa.fa-ellipsis-v'),
h('i.fa.fa-ellipsis-v'), h('i.fa.fa-ellipsis-v'),
]), ]),
h('span.cp-form-sort-order', (i+1)), h('span.cp-form-sort-order', '?'),
h('span', data) h('span', data)
]); ]);
$(div).data('val', data); $(div).data('val', data);
@ -1490,10 +1532,11 @@ define([
els els
]); ]);
var $tag = $(tag); var $tag = $(tag);
var reorder = function () { var reorder = function (reset) {
$tag.find('.cp-form-type-sort').each(function (i, el) { $tag.find('.cp-form-type-sort').each(function (i, el) {
$(el).find('.cp-form-sort-order').text(i+1); $(el).find('.cp-form-sort-order').text(reset ? '?' : i+1);
}); });
sorted = !reset;
}; };
var cursorGetter; var cursorGetter;
var setCursorGetter = function (f) { cursorGetter = f; }; var setCursorGetter = function (f) { cursorGetter = f; };
@ -1516,16 +1559,18 @@ define([
return { return {
tag: tag, tag: tag,
getValue: function () { getValue: function () {
if (!sorted) { return; }
return sortable.toArray().map(function (id) { return sortable.toArray().map(function (id) {
return map[id]; return map[id];
}); });
}, },
reset: function () { reset: function () {
Util.shuffleArray(opts.values);
var toSort = (opts.values).map(function (val) { var toSort = (opts.values).map(function (val) {
return invMap[val]; return invMap[val];
}); });
sortable.sort(toSort); sortable.sort(toSort);
reorder(); reorder(true);
}, },
edit: function (cb, tmp) { edit: function (cb, tmp) {
var v = Util.clone(opts); var v = Util.clone(opts);
@ -1554,7 +1599,7 @@ define([
if (!Array.isArray(answer) || !answer.length) { return empty++; } if (!Array.isArray(answer) || !answer.length) { return empty++; }
answer.forEach(function (el, i) { answer.forEach(function (el, i) {
var score = l - 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) { var sorted = Object.keys(count).sort(function (a, b) {
@ -1583,7 +1628,7 @@ define([
if (!opts) { opts = TYPES.poll.defaultOpts; } if (!opts) { opts = TYPES.poll.defaultOpts; }
if (!Array.isArray(opts.values)) { return; } if (!Array.isArray(opts.values)) { return; }
var lines = makePollTable(answers, opts); var lines = makePollTable(answers, opts, false);
// Add form // Add form
var addLine = opts.values.map(function (data) { var addLine = opts.values.map(function (data) {
@ -1667,31 +1712,53 @@ define([
}; };
}, },
printResults: function (answers, uid, form) { printResults: function (answers, uid, form, content) {
var opts = form[uid].opts || TYPES.poll.defaultOpts; var opts = form[uid].opts || TYPES.poll.defaultOpts;
var _answers = getBlockAnswers(answers, uid); 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); var total = makePollTotal(_answers, opts);
if (total) { lines.push(h('div', total)); } if (total) { lines.push(h('div', total)); }
return h('div.cp-form-type-poll', lines); return h('div.cp-form-type-poll', lines);
}, },
exportCSV: function (answer) { exportCSV: function (answer, form) {
if (answer === false) { return; } var opts = form.opts || TYPES.poll.defaultOpts;
if (!answer || !answer.values) { return ['']; } 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 = ''; var str = '';
Object.keys(answer.values).sort().forEach(function (k, i) { Object.keys(answer.values).sort().forEach(function (k, i) {
if (i !== 0) { str += ';'; } if (i !== 0) { str += ';'; }
str += k.replace(';', '').replace(':', '') + ':' + answer.values[k]; 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') 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(); var $container = $('div.cp-form-creator-results').empty();
if (!Object.keys(answers || {}).length) { if (!Object.keys(answers || {}).length) {
@ -1699,9 +1766,15 @@ define([
return; return;
} }
if (content.answers.msg) {
var description = h('div.cp-form-creator-results-description#cp-form-response-msg');
var $desc = $(description).appendTo($container);
DiffMd.apply(DiffMd.render(content.answers.msg), $desc, APP.common);
}
var controls = h('div.cp-form-creator-results-controls'); var controls = h('div.cp-form-creator-results-controls');
var $controls = $(controls).appendTo($container); 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); var exportCSV = h('div.cp-form-creator-results-export', exportButton);
$(exportCSV).appendTo($container); $(exportCSV).appendTo($container);
var results = h('div.cp-form-creator-results-content'); var results = h('div.cp-form-creator-results-content');
@ -1729,7 +1802,9 @@ define([
var type = block.type; var type = block.type;
var model = TYPES[type]; var model = TYPES[type];
if (!model || !model.printResults) { return; } 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); var q = h('div.cp-form-block-question', block.q || Messages.form_default);
@ -1823,14 +1898,23 @@ define([
e.preventDefault(); e.preventDefault();
APP.common.openURL(Hash.hashToHref(ud.profile, 'profile')); APP.common.openURL(Hash.hashToHref(ud.profile, 'profile'));
}); });
if (showUser === curve) {
setTimeout(function () {
showUser = undefined;
$(viewButton).click();
});
}
return div; return div;
}); });
$results.append(els); $results.append(els);
}); });
if (showUser) {
$s.click();
}
}; };
var addResultsButton = function (framework, content) { var addResultsButton = function (framework, content) {
var $res = $(h('button.cp-toolbar-appmenu', [ var $res = $(h('button.cp-toolbar-appmenu.cp-toolbar-form-button', [
h('i.fa.fa-bar-chart'), h('i.fa.fa-bar-chart'),
h('span.cp-button-name', Messages.form_results) h('span.cp-button-name', Messages.form_results)
])); ]));
@ -1872,25 +1956,42 @@ define([
var makeFormControls = function (framework, content, update, evOnChange) { var makeFormControls = function (framework, content, update, evOnChange) {
var loggedIn = framework._.sfCommon.isLoggedIn(); var loggedIn = framework._.sfCommon.isLoggedIn();
var metadataMgr = framework._.cpNfInner.metadataMgr; var metadataMgr = framework._.cpNfInner.metadataMgr;
var user = metadataMgr.getUserData();
if (!loggedIn && !content.answers.anonymous) { return; } if (!loggedIn && !content.answers.anonymous) { return; }
var cbox; var cbox;
var anonName, $anonName;
cbox = UI.createCheckbox('cp-form-anonymous', cbox = UI.createCheckbox('cp-form-anonymous',
Messages.form_anonymousBox, true, { mark: { tabindex:1 } }); Messages.form_anonymousBox, true, { mark: { tabindex:1 } });
var $anonBox = $(cbox).find('input');
if (loggedIn) { if (loggedIn) {
if (!content.answers.anonymous || APP.cantAnon) { if (!content.answers.anonymous || APP.cantAnon) {
$(cbox).hide().find('input').attr('disabled', 'disabled').prop('checked', false); $(cbox).hide().find('input').attr('disabled', 'disabled').prop('checked', false);
} }
} else {
anonName = h('div.cp-form-anon-answer-input', [
Messages.form_answerAs,
h('input', {
value: user.name || '',
placeholder: Messages.form_anonName
})
]);
$anonName = $(anonName).hide();
$anonBox.on('change', function () {
if (Util.isChecked($anonBox)) { $anonName.hide(); }
else { $anonName.show(); }
});
} }
var send = h('button.cp-open.btn.btn-primary', update ? Messages.form_update : Messages.form_submit); var send = h('button.cp-open.btn.btn-primary', update ? Messages.form_update : Messages.form_submit);
var reset = h('button.cp-open.btn.btn-danger-alt', Messages.form_reset); var reset = h('button.cp-open.cp-reset-button.btn.btn-danger-alt', Messages.form_reset);
$(reset).click(function () { $(reset).click(function () {
if (!Array.isArray(APP.formBlocks)) { return; } if (!Array.isArray(APP.formBlocks)) { return; }
APP.formBlocks.forEach(function (data) { APP.formBlocks.forEach(function (data) {
if (typeof(data.reset) === "function") { data.reset(); } if (typeof(data.reset) === "function") { data.reset(); }
}); });
$(reset).attr('disabled', 'disabled');
}); });
var $send = $(send).click(function () { var $send = $(send).click(function () {
$send.attr('disabled', 'disabled'); $send.attr('disabled', 'disabled');
@ -1898,14 +1999,16 @@ define([
if (!results) { return; } if (!results) { return; }
var user = metadataMgr.getUserData(); var user = metadataMgr.getUserData();
if (!Util.isChecked($(cbox).find('input'))) { if (!Util.isChecked($anonBox)) {
results._userdata = loggedIn ? { results._userdata = loggedIn ? {
avatar: user.avatar, avatar: user.avatar,
name: user.name, name: user.name,
notifications: user.notifications, notifications: user.notifications,
curvePublic: user.curvePublic, curvePublic: user.curvePublic,
profile: user.profile profile: user.profile
} : { name: user.name }; } : {
name: $anonName ? $anonName.find('input').val() : user.name
};
} }
var sframeChan = framework._.sfCommon.getSframeChannel(); var sframeChan = framework._.sfCommon.getSframeChannel();
@ -1985,7 +2088,10 @@ define([
return h('div.cp-form-send-container', [ return h('div.cp-form-send-container', [
invalid, invalid,
cbox ? h('div.cp-form-anon-answer', cbox) : undefined, cbox ? h('div.cp-form-anon-answer', [
cbox,
anonName
]) : undefined,
reset, send reset, send
]); ]);
}; };
@ -1997,6 +2103,18 @@ define([
APP.formBlocks = []; APP.formBlocks = [];
if (APP.isClosed && content.answers.privateKey && !APP.isEditor) {
var sframeChan = framework._.sfCommon.getSframeChannel();
sframeChan.query("Q_FORM_FETCH_ANSWERS", content.answers, function (err, obj) {
var answers = obj && obj.results;
if (answers) { APP.answers = answers; }
$('body').addClass('cp-app-form-results');
$('.cp-toolbar-form-button').remove();
renderResults(content, answers);
});
return;
}
var evOnChange = Util.mkEvent(); var evOnChange = Util.mkEvent();
if (!APP.isEditor) { if (!APP.isEditor) {
var _answers = Util.clone(answers || {}); var _answers = Util.clone(answers || {});
@ -2004,6 +2122,7 @@ define([
delete _answers._userdata; delete _answers._userdata;
evOnChange.reg(function (noBeforeUnload, isSave) { evOnChange.reg(function (noBeforeUnload, isSave) {
if (noBeforeUnload) { return; } if (noBeforeUnload) { return; }
$container.find('.cp-reset-button').removeAttr('disabled');
var results = getFormResults(); var results = getFormResults();
if (isSave) { if (isSave) {
answers = Util.clone(results || {}); answers = Util.clone(results || {});
@ -2356,6 +2475,9 @@ define([
// In view mode, add "Submit" and "reset" buttons // In view mode, add "Submit" and "reset" buttons
$container.append(makeFormControls(framework, content, Boolean(answers), evOnChange)); $container.append(makeFormControls(framework, content, Boolean(answers), evOnChange));
if (!answers) {
$container.find('.cp-reset-button').attr('disabled', 'disabled');
}
}; };
var getTempFields = function () { var getTempFields = function () {
@ -2443,6 +2565,72 @@ define([
}; };
refreshPublic(); refreshPublic();
var responseMsg = h('div.cp-form-response-msg-container');
var $responseMsg = $(responseMsg);
var refreshResponse = function () {
if (true) { return; } // XXX 4.10.0
$responseMsg.empty();
Messages.form_updateMsg = "Update response message"; // XXX 4.10.0
Messages.form_addMsg = "Add response message"; // XXX 4.10.0
Messages.form_responseMsg = "Add a message that will be displayed in the response page."; // XXX 4.10.0
var text = content.answers.msg ? Messages.form_updateMsg : Messages.form_addMsg;
var btn = h('button.btn.btn-secondary', text);
$(btn).click(function () {
var editor;
if (!APP.responseModal) {
var t = h('textarea');
var div = h('div', [
h('p', Messages.form_responseMsg),
t
]);
var cm = SFCodeMirror.create("gfm", CMeditor, t);
editor = APP.responseEditor = cm.editor;
editor.setOption('lineNumbers', true);
editor.setOption('lineWrapping', true);
editor.setOption('styleActiveLine', true);
editor.setOption('readOnly', false);
setTimeout(function () {
editor.setValue(content.answers.msg || '');
editor.refresh();
editor.save();
editor.focus();
});
var buttons = [{
className: 'primary',
name: Messages.settings_save,
onClick: function () {
var v = editor.getValue();
content.answers.msg = v.trim(0, 2000); // XXX 4.10.0 max length?
framework.localChange();
framework._.cpNfInner.chainpad.onSettle(function () {
UI.log(Messages.saved);
refreshResponse();
});
},
//keys: []
}, {
className: 'cancel',
name: Messages.cancel,
onClick: function () {},
keys: [27]
}];
APP.responseModal = UI.dialog.customModal(div, { buttons: buttons });
} else {
editor = APP.responseEditor;
setTimeout(function () {
editor.setValue(content.answers.msg || '');
editor.refresh();
editor.save();
editor.focus();
});
}
UI.openCustomModal(APP.responseModal);
});
// $responseMsg.append(btn); // XXX 4.10.0
};
//refreshResponse();
// Allow anonymous answers // Allow anonymous answers
var privacyContainer = h('div.cp-form-privacy-container'); var privacyContainer = h('div.cp-form-privacy-container');
var $privacy = $(privacyContainer); var $privacy = $(privacyContainer);
@ -2538,11 +2726,13 @@ define([
evOnChange.reg(refreshPublic); evOnChange.reg(refreshPublic);
evOnChange.reg(refreshPrivacy); evOnChange.reg(refreshPrivacy);
evOnChange.reg(refreshEndDate); evOnChange.reg(refreshEndDate);
//evOnChange.reg(refreshResponse);
return [ return [
endDateContainer, endDateContainer,
privacyContainer, privacyContainer,
resultsType, resultsType,
responseMsg
]; ];
}; };

@ -176,7 +176,7 @@ define([
validateKey: keys.secondaryValidateKey, validateKey: keys.secondaryValidateKey,
owners: [myKeys.edPublic], owners: [myKeys.edPublic],
crypto: crypto, crypto: crypto,
//Cache: Utils.Cache // XXX //Cache: Utils.Cache // XXX 4.10.0
}; };
var results = {}; var results = {};
config.onError = function (info) { config.onError = function (info) {
@ -265,6 +265,7 @@ define([
}, function (obj) { }, function (obj) {
if (obj && obj.error) { return void cb(obj); } if (obj && obj.error) { return void cb(obj); }
var messages = obj.messages; var messages = obj.messages;
if (!messages.length) { return void cb(); }
var res = Utils.Crypto.Mailbox.openOwnSecretLetter(messages[0].msg, { var res = Utils.Crypto.Mailbox.openOwnSecretLetter(messages[0].msg, {
validateKey: data.validateKey, validateKey: data.validateKey,
ephemeral_private: Nacl.util.decodeBase64(answer.curvePrivate), ephemeral_private: Nacl.util.decodeBase64(answer.curvePrivate),

@ -1326,6 +1326,11 @@ define([
})); }));
$(waitFor()); $(waitFor());
}).nThen(function(waitFor) { }).nThen(function(waitFor) {
// TODO this breaks users' ability to tab out of the editor
// but that's a problem in other editors and nobody has complained so far
// so we'll include this as-is for now while we search for a good pattern
// addresses this issue more generally
Ckeditor.config.tabSpaces = 4;
Ckeditor.config.toolbarCanCollapse = true; Ckeditor.config.toolbarCanCollapse = true;
Ckeditor.config.language = Messages._getLanguage(); Ckeditor.config.language = Messages._getLanguage();
if (screen.height < 800) { if (screen.height < 800) {

@ -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});
} }
}); });

@ -345,7 +345,7 @@ define([
var senderKey = content.sender && content.sender.edPublic; var senderKey = content.sender && content.sender.edPublic;
var fromMe = senderKey === privateData.edPublic; var fromMe = senderKey === privateData.edPublic;
var fromAdmin = ctx.adminKeys.indexOf(senderKey) !== -1; 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', [ var userData = h('div.cp-support-showdata', [
Messages.support_showData, Messages.support_showData,

@ -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