Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging

This commit is contained in:
ClemDee
2019-06-07 15:47:29 +02:00
70 changed files with 2677 additions and 692 deletions

View File

@@ -1,3 +1,27 @@
# Xenops release (v2.23.0)
## Goals
For this release we wanted to focus on releasing a small set of features built on top of some foundations established in our last release. Since we were able to complete this feature set in less than a week, we decided to bundle them together so users could take benefit from them sooner.
This work is being funded by the grant we received from NLnet foundation as a part of their PET (Privacy Enhancing Technology) fund. You can read all about this grant on our latest blog post (https://blog.cryptpad.fr/2019/05/27/Our-future-is-collaborative/).
## Update notes
* This update only uses clientside dependencies. Fetch the latest code for the core repository, and depending on when you last updated you may need to `bower update` as well.
* User data is "pinned" on CryptPad instances to keep track of what encrypted data can be safely removed. At one point this system was optional and could be disabled by setting `enablePinning = false` in `customize/application_config.js`. At some point we stopped testing whether CryptPad could actually work without pinning enabled, and at this point it is definitely broken. As such, we've decided to drop support for this configuration.
## Features
* Some of our multilingual contributors have contributed translations in the German, Russian, and Italian. The history of their contributions is available on our weblate instance (https://weblate.cryptpad.fr/projects/cryptpad/app/).
* This release introduces a practical use-case of the encrypted mailbox infrastructure which we developed in our last release. Registered users are now able to use this system to accept friend requests and review the status of friend requests that have been accepted or declined. Unlike our previous friend request system, our usage of encrypted mailboxes allows for users to send friend requests from other user's profiles whether or not they are online.
* We've also put some time towards improving user profiles as well. When you change your display name from anywhere within CryptPad the name used in your profile will be updated as well. We've also made updates to other users' profiles render in real-time, since the rest of CryptPad generally updates instantly.
## Bug fixes
* Some small components of CryptPad time out if they don't work within a set amount of time, and apparently this timeout was causing problems in the newest Tor browser version. We've drastically increased the timeout to make it less likely to cause problems when loading very large documents.
* We realized that Weblate was committing "empty strings" to our translation files. Our internationalization system was configured to fall back to the English translation if no translation was available in the user's preferred language, but these empty strings fooled the system into displaying nothing instead. We addressed the issue by checking whether a string was really present, and not just whether a value existed.
# Wolf release (v2.22.0)
## Goals

View File

@@ -1,9 +1,26 @@
# 6-stretch is the ONLY node 6 release supported by arm32v7, arm64v8 and x86-64 docker hub labels
FROM node:6-stretch
# We use multi stage builds
FROM node:6-stretch-slim AS build
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq git jq python
RUN npm install -g bower
# install tini in this stage to avoid the need of jq and python
# in the final image
ADD docker-install-tini.sh /usr/local/bin/docker-install-tini.sh
RUN /usr/local/bin/docker-install-tini.sh
COPY . /cryptpad
WORKDIR /cryptpad
RUN npm install --production \
&& npm install -g bower \
&& bower install --allow-root
FROM node:6-stretch-slim
# You want USE_SSL=true if not putting cryptpad behind a proxy
ENV USE_SSL=false
ENV STORAGE=\'./storage/file\'
ENV STORAGE="'./storage/file'"
ENV LOG_TO_STDOUT=true
# Persistent storage needs
@@ -17,36 +34,14 @@ VOLUME /cryptpad/block
VOLUME /cryptpad/blob
VOLUME /cryptpad/blobstage
# Required packages
# jq is a build only dependency, removed in cleanup stage
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
git jq python
# Copy cryptpad and tini from the build container
COPY --from=build /sbin/tini /sbin/tini
COPY --from=build /cryptpad /cryptpad
# Install tini for faux init
# sleep 1 is to ensure overlay2 can catch up with the copy prior to running chmod
COPY ./docker-install-tini.sh /
RUN chmod a+x /docker-install-tini.sh \
&& sleep 1 \
&& /docker-install-tini.sh \
&& rm /docker-install-tini.sh
# Cleanup apt
RUN apt-get remove -y --purge jq python \
&& apt-get auto-remove -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install cryptpad
COPY . /cryptpad
WORKDIR /cryptpad
RUN npm install --production \
&& npm install -g bower \
&& bower install --allow-root
# Unsafe / Safe ports
EXPOSE 3000 3001
# Run cryptpad on startup
CMD ["/sbin/tini", "--", "/cryptpad/container-start.sh"]

View File

@@ -45,7 +45,7 @@
"localforage": "^1.5.2",
"html2canvas": "^0.4.1",
"croppie": "^2.5.0",
"sortablejs": "#^1.6.0",
"sortablejs": "^1.6.0",
"saferphore": "^0.0.1",
"jszip": "Stuk/jszip#^3.1.5",
"requirejs-plugins": "^1.0.3"

View File

@@ -1,6 +1,7 @@
(function () {
// add your module to this map so it gets used
var map = {
'ca': 'Català',
'de': 'Deutsch',
'es': 'Español',
'el': 'Ελληνικά',
@@ -12,6 +13,7 @@ var map = {
'ro': 'Română',
'ru': 'Русский',
'zh': '繁體中文',
'te': 'తెలుగు',
};
var messages = {};
@@ -69,10 +71,25 @@ define(req, function(Util, AppConfig, Default, Language) {
});
}
Util.extend(messages, Default);
var extend = function (a, b) {
for (var k in b) {
if (Util.isObject(b[k])) {
a[k] = Util.isObject(a[k]) ? a[k] : {};
extend(a[k], b[k]);
continue;
}
if (Array.isArray(b[k])) {
a[k] = b[k].slice();
continue;
}
a[k] = b[k] || a[k];
}
};
extend(messages, Default);
if (Language && language !== defaultLanguage) {
// Add the translated keys to the returned object
Util.extend(messages, Language);
extend(messages, Language);
}
messages._languages = map;

View File

@@ -144,6 +144,10 @@
padding: @alertify_padding-base;
background: #fff;
box-shadow: @alertify_box-shadow;
&.wide {
width: 1000px;
max-width: 70%;
}
}
.msg {

View File

@@ -22,7 +22,6 @@
}
* {
.tools_unselectable();
cursor: default;
}
}

View File

@@ -32,8 +32,9 @@
display: none;
align-items: center;
justify-content: center;
span {
cursor: pointer;
cursor: pointer;
&:hover {
background-color: rgba(0,0,0,0.1);
}
}
}

View File

@@ -0,0 +1,83 @@
@import (reference) "./colortheme-all.less";
@import (reference) './modal.less';
@import (reference) './alertify.less';
@import (reference) './avatar.less';
@import (reference) './checkmark.less';
@import (reference) './password-input.less';
.share_main () {
.alertify_main();
.checkmark_main(20px);
.password_main();
.modal_main();
.cp-share-columns {
display: flex;
flex-flow: row;
.cp-share-column {
width: 50%;
padding: 0 10px;
}
}
.cp-share-grid, .cp-share-list {
.avatar_main(50px);
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.cp-share-list {
margin-bottom: 15px;
}
.cp-share-grid {
max-height: 228px;
overflow-x: auto;
}
.cp-recent-only {
.cp-share-grid, .cp-share-grid-filter {
display: none;
}
}
.cp-share-grid-filter {
display: flex;
input {
flex: 1;
margin-bottom: 0 !important;
&::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
color: @colortheme_alertify-primary-text;
opacity: 1; /* Firefox */
}
}
margin-bottom: 15px;
}
.cp-share-friend {
width: 70px;
height: 70px;
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
padding: 5px;
margin-bottom: 6px;
cursor: default;
transition: order 0.5s, background-color 0.5s;
&.cp-selected {
background-color: @colortheme_alertify-primary;
color: @colortheme_alertify-primary-text;
order: -1 !important;
}
.cp-share-friend-name {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 100%;
text-align: center;
}
border: 1px solid @colortheme_alertify-primary;
&.cp-fake-friend {
visibility: hidden;
}
}
}

View File

@@ -909,9 +909,6 @@
}
button {
position: relative;
&.fa-bell-o {
cursor: default;
}
.cp-dropdown-button-title {
position: absolute;
bottom: 0;
@@ -998,7 +995,6 @@
span {
text-align: center;
width: 100%;
cursor: default;
font-size: 32px;
display: inline-flex;
justify-content: center;

View File

@@ -0,0 +1,15 @@
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe, #sbox-share-iframe, #sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
margin:0;
padding:0;
overflow:hidden;
}

View File

@@ -17,7 +17,7 @@ Translations can now be made using [Weblate](https://weblate.cryptpad.fr). We ma
## Translate an existing language
* All translations can be done using the Weblate UI. For better help about how to use the tool, please check the [Weblate documentation](https://docs.weblate.org/en/latest/user/index.html).
* All translations can be done using the Weblate UI. For better help about how to use the tool, please check the [Weblate documentation](https://docs.weblate.org/en/latest/).
* Our Weblate instance is configured to always require approval for changes.
### Update an existing translation

View File

@@ -0,0 +1,14 @@
/*
* You can override the translation text using this file.
* The recommended method is to make a copy of this file (/customize.dist/translations/messages.{LANG}.js)
in a 'customize' directory (/customize/translations/messages.{LANG}.js).
* If you want to check all the existing translation keys, you can open the internal language file
but you should not change it directly (/common/translations/messages.{LANG}.js)
*/
define(['/common/translations/messages.ca.js'], function (Messages) {
// Replace the existing keys in your copied file here:
// Messages.button_newpad = "New Rich Text Document";
return Messages;
});

View File

@@ -0,0 +1,14 @@
/*
* You can override the translation text using this file.
* The recommended method is to make a copy of this file (/customize.dist/translations/messages.{LANG}.js)
in a 'customize' directory (/customize/translations/messages.{LANG}.js).
* If you want to check all the existing translation keys, you can open the internal language file
but you should not change it directly (/common/translations/messages.{LANG}.js)
*/
define(['/common/translations/messages.te.js'], function (Messages) {
// Replace the existing keys in your copied file here:
// Messages.button_newpad = "New Rich Text Document";
return Messages;
});

View File

@@ -21,7 +21,7 @@ Run from the cryptpad source directory, keeping instance state in `/var/cryptpad
docker build -t xwiki/cryptpad .
docker run --restart=always -d --name cryptpad -p 3000:3000 -p 3001:3001 \
-v /var/cryptpad/files:/cryptpad/datastore \
-v /var/cryptpad/customize:/cryptpad/customize
-v /var/cryptpad/customize:/cryptpad/customize \
-v /var/cryptpad/blob:/cryptpad/blob \
-v /var/cryptpad/blobstage:/cryptpad/blobstage \
-v /var/cryptpad/pins:/cryptpad/pins \

View File

@@ -1,11 +1,14 @@
/* jslint node: true */
"use strict";
var config;
var configPath = process.env.CRYPTPAD_CONFIG || "../config/config";
try {
config = require("../config/config");
config = require(configPath);
if (config.adminEmail === 'i.did.not.read.my.config@cryptpad.fr') {
console.log("You can configure the administrator email (adminEmail) in your config/config.js file");
}
} catch (e) {
console.log("You can customize the configuration by copying config/config.example.js to config/config.js");
console.log("Config not found, loading the example config. You can customize the configuration by copying config/config.example.js to " + configPath);
config = require("../config/config.example");
}
module.exports = config;

1327
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -18,12 +18,11 @@
"sortify": "^1.0.4",
"stream-to-pull-stream": "^1.7.2",
"tweetnacl": "~0.12.2",
"ws": "^1.0.1",
"ws": "^3.3.1",
"get-folder-size": "^2.0.1"
},
"devDependencies": {
"flow-bin": "^0.59.0",
"heapdump": "^0.3.9",
"jshint": "~2.9.1",
"less": "2.7.1",
"lesshint": "^4.5.0",

View File

@@ -62,13 +62,13 @@ via our [GitHub issue tracker](https://github.com/xwiki-labs/cryptpad/issues/),
[Matrix channel](https://riot.im/app/#/room/#cryptpad:matrix.org), or by
[e-mail](mailto:research@xwiki.com).
# Team
CryptPad is actively developed by a team at [XWiki SAS](https://www.xwiki.com), a company that has been building Open-Source software since 2004 with contributors from around the world. Between 2015 and 2019 it was funded by a research grant from the French state through [BPI France](https://www.bpifrance.fr/). It is currently financed by [NLnet PET](https://nlnet.nl/PET/), subscribers of CryptPad.fr and donations to our [Open-Collective campaign](https://opencollective.com/cryptpad).
# Contributing
We love Open Source and we love contribution. It is our intent to keep this project available
under the AGPL license forever but in order to finance more development on this and other FOSS
projects, we also wish to sell other licenses to this software. Before making a pull request,
please read and
[sign the Commons Management Agreement](https://www.clahub.com/agreements/cjdelisle/cryptpad).
We love Open Source and we love contribution. Learn more about [contributing](https://github.com/xwiki-labs/cryptpad/wiki/Contributor-overview).
If you have any questions or comments, or if you're interested in contributing to Cryptpad, come say hi on IRC, `#cryptpad` on Freenode.

View File

@@ -6,33 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>
<iframe id="sbox-iframe">

View File

@@ -203,7 +203,7 @@ define([
};
var createToolbar = function () {
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle'];
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle', 'notifications'];
var configTb = {
displayed: displayed,
sfCommon: common,

View File

@@ -6,33 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="/common/sframe-app-outer.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>
<iframe id="sbox-iframe">

View File

@@ -156,12 +156,14 @@ define([
]);
};
dialog.frame = function (content) {
dialog.frame = function (content, opt) {
opt = opt || {};
var cls = opt.wide ? '.wide' : '';
return $(h('div.alertify', {
tabindex: 1,
}, [
h('div.dialog', [
h('div', content),
h('div'+cls, content),
])
])).click(function (e) {
e.stopPropagation();
@@ -351,6 +353,9 @@ define([
var close = function (el) {
var $el = $(el).fadeOut(150, function () {
$el.detach();
if (opt.onClose) {
opt.onClose();
}
});
};
@@ -373,10 +378,10 @@ define([
if (opt.forefront) { $(frame).addClass('forefront'); }
return frame;
};
UI.openCustomModal = function (content) {
UI.openCustomModal = function (content, opt) {
var frame = dialog.frame([
content
]);
], opt);
$(frame).find('button[data-keys]').each(function (i, el) {
var keys = JSON.parse($(el).attr('data-keys'));
customListenForKeys(keys, function () {

View File

@@ -78,5 +78,27 @@ define([
});
};
Msg.updateMyData = function (store, curve) {
if (store.messenger) {
store.messenger.updateMyData();
}
var myData = createData(store.proxy);
var todo = function (friend) {
if (!friend || !friend.notifications) { return; }
myData.channel = friend.channel;
store.mailbox.sendTo('UPDATE_DATA', myData, {
channel: friend.notifications,
curvePublic: friend.curvePublic
}, function (obj) {
if (obj && obj.error) { console.error(obj); }
});
};
if (curve) {
var friend = getFriend(store.proxy, curve);
return void todo(friend);
}
eachFriend(store.proxy.friends || {}, todo);
};
return Msg;
});

View File

@@ -44,6 +44,7 @@ define([
profile: proxy.profile && proxy.profile.view,
edPublic: proxy.edPublic,
curvePublic: proxy.curvePublic,
notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']),
avatar: proxy.profile && proxy.profile.avatar
};
};
@@ -611,6 +612,15 @@ define([
cb();
});
});
} else {
removeFromFriendList(curvePublic, function () {
delete channels[channel.id];
emit('UNFRIEND', {
curvePublic: curvePublic,
fromMe: true
});
cb();
});
}
channel.wc.bcast(cryptMsg).then(function () {}, function (err) {
console.error(err);

View File

@@ -205,7 +205,7 @@ define([
if (content === oldThumbnailState) { return; }
oldThumbnailState = content;
Thumb.fromDOM(opts, function (err, b64) {
Thumb.setPadThumbnail(common, opts.href, null, b64);
Thumb.setPadThumbnail(common, opts.type, null, b64);
});
};
var nafa = Util.notAgainForAnother(mkThumbnail, Thumb.UPDATE_INTERVAL);
@@ -243,11 +243,10 @@ define([
var getKey = function (type, channel) {
return 'thumbnail-' + type + '-' + channel;
};
Thumb.setPadThumbnail = function (common, href, channel, b64, cb) {
Thumb.setPadThumbnail = function (common, type, channel, b64, cb) {
cb = cb || function () {};
var parsed = Hash.parsePadUrl(href);
channel = channel || common.getMetadataMgr().getPrivateData().channel;
var k = getKey(parsed.type, channel);
var k = getKey(type, channel);
common.setThumbnail(k, b64, cb);
};
Thumb.displayThumbnail = function (common, href, channel, password, $container, cb) {
@@ -270,7 +269,7 @@ define([
if (!v) {
v = 'EMPTY';
}
Thumb.setPadThumbnail(common, href, hexFileName, v, function (err) {
Thumb.setPadThumbnail(common, parsed.type, hexFileName, v, function (err) {
if (!metadata.thumbnail) { return; }
addThumbnail(err, metadata.thumbnail, $container, cb);
});

View File

@@ -316,39 +316,209 @@ define([
});
};
var getFriendsList = function (config) {
var common = config.common;
var title = config.title;
var friends = config.friends;
var myName = common.getMetadataMgr().getUserData().name;
var order = [];
if (!friends) { return; }
var others = Object.keys(friends).map(function (curve, i) {
if (curve.length <= 40) { return; }
var data = friends[curve];
if (!data.notifications) { return; }
var avatar = h('span.cp-share-friend-avatar.cp-avatar');
UIElements.displayAvatar(common, $(avatar), data.avatar, data.displayName);
return h('div.cp-share-friend', {
'data-curve': data.curvePublic,
'data-name': data.displayName,
'data-order': i,
style: 'order:'+i+';'
},[
avatar,
h('span.cp-share-friend-name', data.displayName)
]);
}).filter(function (x) { return x; });
var smallCurves = Object.keys(friends).map(function (c) {
return friends[c].curvePublic.slice(0,8);
});
var noOthers = others.length === 0 ? '.cp-recent-only' : '';
var buttonSelect = h('button.cp-share-with-friends', Messages.share_selectAll);
var buttonDeselect = h('button.cp-share-with-friends', Messages.share_deselectAll);
var inputFilter = h('input', {
placeholder: Messages.share_filterFriend
});
var div = h('div.cp-share-friends.cp-share-column' + noOthers, [
h('label', Messages.share_linkFriends),
h('div.cp-share-grid-filter', [
inputFilter,
buttonSelect,
buttonDeselect
]),
]);
var $div = $(div);
// Fill with fake friends to have a uniform spacing (from the flexbox)
var addFake = function (els) {
$div.find('.cp-fake-friend').remove();
var n = (6 - els.length%6)%6;
for (var j = 0; j < n; j++) {
els.push(h('div.cp-share-friend.cp-fake-friend', {
style: 'order:9999999;'
}));
}
};
addFake(others);
// Hide friends when they are filtered using the text input
var redraw = function () {
var name = $(inputFilter).val().trim().replace(/"/g, '');
$div.find('.cp-share-friend').show();
if (!name) { return; }
$div.find('.cp-share-friend:not(.cp-selected):not([data-name*="'+name+'"])').hide();
};
$(inputFilter).on('keydown keyup change', redraw);
$(buttonSelect).click(function () {
$div.find('.cp-share-friend:not(.cp-selected):visible').addClass('cp-selected');
});
$(buttonDeselect).click(function () {
$div.find('.cp-share-friend.cp-selected').removeClass('cp-selected').each(function (i, el) {
var order = $(el).attr('data-order');
if (!order) { return; }
$(el).attr('style', 'order:'+order);
});
redraw();
});
// Replace "copy link" by "share with friends" if at least one friedn is selected
// Also create the "share with friends" button if it doesn't exist
var refreshButtons = function () {
var $nav = $div.parents('.alertify').find('nav');
if (!$nav.find('.cp-share-with-friends').length) {
var button = h('button.primary.cp-share-with-friends', {
'data-keys': '[13]'
}, Messages.share_withFriends);
$(button).click(function () {
var href = Hash.getRelativeHref($('#cp-share-link-preview').val());
var $friends = $div.find('.cp-share-friend.cp-selected');
$friends.each(function (i, el) {
var curve = $(el).attr('data-curve');
if (!curve || !friends[curve]) { return; }
var friend = friends[curve];
if (!friend.notifications || !friend.curvePublic) { return; }
common.mailbox.sendTo("SHARE_PAD", {
href: href,
name: myName,
title: title
}, {
channel: friend.notifications,
curvePublic: friend.curvePublic
});
});
UI.findCancelButton().click();
// Update the "recently shared with" array:
// Get the selected curves
var curves = $friends.toArray().map(function (el) {
return ($(el).attr('data-curve') || '').slice(0,8);
}).filter(function (x) { return x; });
// Prepend them to the "order" array
Array.prototype.unshift.apply(order, curves);
order = Util.deduplicateString(order);
// Make sure we don't have "old" friends and save
order = order.filter(function (curve) {
return smallCurves.indexOf(curve) !== -1;
});
common.setAttribute(['general', 'share-friends'], order);
});
$nav.append(button);
}
var friendMode = $div.find('.cp-share-friend.cp-selected').length;
if (friendMode) {
$nav.find('button.primary[data-keys]').hide();
$nav.find('button.cp-share-with-friends').show();
} else {
$nav.find('button.primary[data-keys]').show();
$nav.find('button.cp-share-with-friends').hide();
}
};
common.getAttribute(['general', 'share-friends'], function (err, val) {
order = val || [];
// Sort friends by "recently shared with"
others.sort(function (a, b) {
var ca = ($(a).attr('data-curve') || '').slice(0,8);
var cb = ($(b).attr('data-curve') || '').slice(0,8);
if (!ca && !cb) { return 0; }
if (!ca) { return 1; }
if (!cb) { return -1; }
var ia = order.indexOf(ca);
var ib = order.indexOf(cb);
if (ia === -1 && ib === -1) { return 0; }
if (ia === -1) { return 1; }
if (ib === -1) { return -1; }
return ia - ib;
});
// Reorder the friend icons
others.forEach(function (el, i) {
if ($(el).is('.cp-fake-friend')) { return; }
$(el).attr('data-order', i).css('order', i);
});
// Display them
$div.append(h('div.cp-share-grid', others));
$div.find('.cp-share-friend').click(function () {
var sel = $(this).hasClass('cp-selected');
if (!sel) {
$(this).addClass('cp-selected');
} else {
var order = $(this).attr('data-order');
order = order ? 'order:'+order : '';
$(this).removeClass('cp-selected').attr('style', order);
}
refreshButtons();
});
});
return div;
};
UIElements.createShareModal = function (config) {
var origin = config.origin;
var pathname = config.pathname;
var hashes = config.hashes;
var common = config.common;
if (!hashes) { return; }
// Share link tab
var link = h('div.cp-share-modal', [
h('label', Messages.share_linkAccess),
h('br'),
UI.createRadio('cp-share-editable', 'cp-share-editable-true',
Messages.share_linkEdit, true, { mark: {tabindex:1} }),
UI.createRadio('cp-share-editable', 'cp-share-editable-false',
Messages.share_linkView, false, { mark: {tabindex:1} }),
/*h('input#cp-share-editable-true.cp-share-editable-value', {
type: 'radio',
name: 'cp-share-editable',
value: 1,
}),
h('label', { 'for': 'cp-share-editable-true' }, Messages.share_linkEdit),
h('input#cp-share-editable-false.cp-share-editable-value', {
type: 'radio',
name: 'cp-share-editable',
value: 0
}),
h('label', { 'for': 'cp-share-editable-false' }, Messages.share_linkView),*/
h('br'),
h('label', Messages.share_linkOptions),
h('br'),
UI.createCheckbox('cp-share-embed', Messages.share_linkEmbed, false, { mark: {tabindex:1} }),
UI.createCheckbox('cp-share-present', Messages.share_linkPresent, false, { mark: {tabindex:1} }),
h('br'),
UI.dialog.selectable('', { id: 'cp-share-link-preview', tabindex: 1 })
var hasFriends = Object.keys(config.friends || {}).length !== 0;
var friendsList = hasFriends ? getFriendsList(config) : undefined;
var friendsUIClass = hasFriends ? '.cp-share-columns' : '';
var link = h('div.cp-share-modal' + friendsUIClass, [
h('div.cp-share-column', [
hasFriends ? h('p', Messages.share_description) : undefined,
h('label', Messages.share_linkAccess),
h('br'),
UI.createRadio('cp-share-editable', 'cp-share-editable-true',
Messages.share_linkEdit, true, { mark: {tabindex:1} }),
UI.createRadio('cp-share-editable', 'cp-share-editable-false',
Messages.share_linkView, false, { mark: {tabindex:1} }),
h('br'),
h('label', Messages.share_linkOptions),
h('br'),
UI.createCheckbox('cp-share-embed', Messages.share_linkEmbed, false, { mark: {tabindex:1} }),
UI.createCheckbox('cp-share-present', Messages.share_linkPresent, false, { mark: {tabindex:1} }),
h('br'),
UI.dialog.selectable('', { id: 'cp-share-link-preview', tabindex: 1 }),
]),
friendsList
]);
if (!hashes.editHash) {
$(link).find('#cp-share-editable-false').attr('checked', true);
@@ -380,6 +550,7 @@ define([
$(link).find('#cp-share-link-preview').val(getLinkValue());
});
var linkButtons = [{
className: 'cancel',
name: Messages.cancel,
onClick: function () {},
keys: [27]
@@ -403,7 +574,10 @@ define([
},
keys: [[13, 'ctrl']]
}];
var frameLink = UI.dialog.customModal(link, {buttons: linkButtons});
var frameLink = UI.dialog.customModal(link, {
buttons: linkButtons,
onClose: config.onClose,
});
// Embed tab
var getEmbedValue = function () {
@@ -420,6 +594,7 @@ define([
UI.dialog.selectable(getEmbedValue())
]);
var embedButtons = [{
className: 'cancel',
name: Messages.cancel,
onClick: function () {},
keys: [27]
@@ -433,7 +608,10 @@ define([
},
keys: [13]
}];
var frameEmbed = UI.dialog.customModal(embed, { buttons: embedButtons});
var frameEmbed = UI.dialog.customModal(embed, {
buttons: embedButtons,
onClose: config.onClose,
});
// Create modal
var tabs = [{
@@ -464,7 +642,9 @@ define([
$(link).find('#cp-share-link-preview').val(getLinkValue(val));
});
common.getMetadataMgr().onChange(function () {
hashes = common.getMetadataMgr().getPrivateData().availableHashes;
// "hashes" is only available is the secure "share" app
hashes = common.getMetadataMgr().getPrivateData().hashes;
if (!hashes) { return; }
$(link).find('#cp-share-link-preview').val(getLinkValue());
});
return tabs;
@@ -481,12 +661,20 @@ define([
// Share link tab
var link = h('div.cp-share-modal', [
UI.dialog.selectable('', { id: 'cp-share-link-preview' })
var hasFriends = Object.keys(config.friends || {}).length !== 0;
var friendsList = hasFriends ? getFriendsList(config) : undefined;
var friendsUIClass = hasFriends ? '.cp-share-columns' : '';
var link = h('div.cp-share-modal' + friendsUIClass, [
h('div.cp-share-column', [
hasFriends ? h('p', Messages.share_description) : undefined,
UI.dialog.selectable('', { id: 'cp-share-link-preview' }),
]),
friendsList
]);
var getLinkValue = function () { return url; };
$(link).find('#cp-share-link-preview').val(getLinkValue());
var linkButtons = [{
className: 'cancel',
name: Messages.cancel,
onClick: function () {},
keys: [27]
@@ -513,6 +701,7 @@ define([
UI.dialog.selectable(common.getMediatagFromHref(fileData)),
]);
var embedButtons = [{
className: 'cancel',
name: Messages.cancel,
onClick: function () {},
keys: [27]
@@ -554,12 +743,20 @@ define([
var url = origin + pathname + '#' + hashes.editHash;
// Share link tab
var link = h('div.cp-share-modal', [
h('label', Messages.sharedFolders_share),
h('br'),
UI.dialog.selectable(url, { id: 'cp-share-link-preview', tabindex: 1 })
var hasFriends = Object.keys(config.friends || {}).length !== 0;
var friendsList = hasFriends ? getFriendsList(config) : undefined;
var friendsUIClass = hasFriends ? '.cp-share-columns' : '';
var link = h('div.cp-share-modal' + friendsUIClass, [
h('div.cp-share-column', [
h('label', Messages.sharedFolders_share),
h('br'),
hasFriends ? h('p', Messages.share_description) : undefined,
UI.dialog.selectable(url, { id: 'cp-share-link-preview', tabindex: 1 })
]),
friendsList
]);
var linkButtons = [{
className: 'cancel',
name: Messages.cancel,
onClick: function () {},
keys: [27]
@@ -791,16 +988,23 @@ define([
button = $('<span>');
break;
}
var active = $(".cp-toolbar-history:visible").length !== 0;
button = $('<button>', {
title: Messages.historyButton,
title: active ? Messages.history_closeTitle : Messages.historyButton,
'class': "fa fa-history cp-toolbar-icon-history",
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.historyText));
button.toggleClass("active", active);
if (data.histConfig) {
button
.click(common.prepareFeedback(type))
.on('click', function () {
common.getHistory(data.histConfig);
});
if (active) {
button.click(function () { $(".cp-toolbar-history-close").trigger("click"); });
}
else {
button
.click(common.prepareFeedback(type))
.on('click', function () {
common.getHistory(data.histConfig);
});
}
}
break;
case 'more':

View File

@@ -1,15 +1,19 @@
define([
'/customize/application_config.js',
'/common/common-feedback.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-messenger.js',
'/common/outer/mailbox.js',
'/bower_components/nthen/index.js',
], function (Feedback, Hash, Util, nThen) {
'/bower_components/chainpad-crypto/crypto.js',
], function (AppConfig, Feedback, Hash, Util, Messenger, Mailbox, nThen, Crypto) {
// Start migration check
// Versions:
// 1: migrate pad attributes
// 2: migrate indent settings (codemirror)
return function (userObject, cb, progress) {
return function (userObject, cb, progress, store) {
var version = userObject.version || 0;
nThen(function () {
@@ -186,6 +190,135 @@ define([
Feedback.send('Migrate-8', true);
userObject.version = version = 8;
}
}).nThen(function () {
if (!AppConfig.migrateFriends) { return; } // XXX
// Migration 9: send our mailbox channel to existing friends
var migrateFriends = function () {
var network = store.network;
var channels = {};
var ctx = {
store: store
};
var myData = Messenger.createData(userObject);
var close = function (chan) {
var channel = channels[chan];
if (!channel) { return; }
try {
channel.wc.leave();
} catch (e) {}
delete channels[chan];
};
var onDirectMessage = function (msg, sender) {
if (sender !== network.historyKeeper) { return; }
var parsed = JSON.parse(msg);
// Metadata msg? we don't care
if ((parsed.validateKey || parsed.owners) && parsed.channel) { return; }
// End of history message, "onReady"
if (parsed.channel && channels[parsed.channel]) {
// History cleared while we were offline
// ==> we asked for an invalid last known hash
if (parsed.error && parsed.error === "EINVAL") {
var histMsg = ['GET_HISTORY', parsed.channel, {}];
network.sendto(network.historyKeeper, JSON.stringify(histMsg))
.then(function () {}, function () {});
return;
}
// End of history
if (parsed.state && parsed.state === 1) {
// Channel is ready and we didn't receive their mailbox channel: send our channel
myData.channel = parsed.channel;
var updateMsg = ['UPDATE', myData.curvePublic, +new Date(), myData];
var cryptMsg = channels[parsed.channel].encrypt(JSON.stringify(updateMsg));
channels[parsed.channel].wc.bcast(cryptMsg).then(function () {}, function (err) {
console.error("Can't migrate this friend", channels[parsed.channel].friend, err);
});
close(parsed.channel);
return;
}
} else if (parsed.channel) {
return;
}
// History message: we only care about "UPDATE" messages
var chan = parsed[3];
if (!chan || !channels[chan]) { return; }
var channel = channels[chan];
var msgIn = channel.decrypt(parsed[4]);
var parsedMsg = JSON.parse(msgIn);
if (parsedMsg[0] === 'UPDATE') {
if (parsedMsg[1] === myData.curvePublic) { return; }
var data = parsedMsg[3];
// If it doesn't contain the mailbox channel, ignore the message
if (!data.notifications) { return; }
// Otherwise we know their channel, we can send them our own
channel.friend.notifications = data.notifications;
myData.channel = chan;
Mailbox.sendTo(ctx, 'UPDATE_DATA', myData, {
channel: data.notifications,
curvePublic: data.curvePublic
}, function (obj) {
if (obj && obj.error) { return void console.error(obj); }
console.log('friend migrated', channel.friend);
});
close(chan);
}
};
network.on('message', function(msg, sender) {
try {
onDirectMessage(msg, sender);
} catch (e) {
console.error(e);
}
});
var friends = userObject.friends || {};
Object.keys(friends).forEach(function (curve) {
if (curve.length !== 44) { return; }
var friend = friends[curve];
// Check if it is already a "new" friend
if (friend.notifications) { return; }
/** Old friend:
* 1. Open the messenger channel
* 2. Check if they sent us their mailbox channel
* 3.a. Yes ==> sent them a mail containing our mailbox channel
* 3.b. No ==> post our mailbox data to the messenger channel
*/
network.join(friend.channel).then(function (wc) {
var keys = Crypto.Curve.deriveKeys(friend.curvePublic, userObject.curvePrivate);
var encryptor = Crypto.Curve.createEncryptor(keys);
channels[friend.channel] = {
wc: wc,
friend: friend,
decrypt: encryptor.decrypt,
encrypt: encryptor.encrypt
};
var cfg = {
lastKnownHash: friend.lastKnownHash
};
var msg = ['GET_HISTORY', friend.channel, cfg];
network.sendto(network.historyKeeper, JSON.stringify(msg))
.then(function () {}, function (err) {
console.error("Can't migrate this friend", friend, err);
});
}, function (err) {
console.error("Can't migrate this friend", friend, err);
});
});
};
if (version < 9) {
migrateFriends();
Feedback.send('Migrate-9', true);
userObject.version = version = 9;
}
/*}).nThen(function (waitFor) {
// Test progress bar in the loading screen
var i = 0;
@@ -197,7 +330,7 @@ define([
}, 500);
progress(0, 0);*/
}).nThen(function () {
cb();
setTimeout(cb);
});
};
});

View File

@@ -1,12 +1,15 @@
define([
'jquery',
'/common/hyperscript.js',
'/common/common-hash.js',
'/common/common-ui-elements.js',
'/customize/messages.js',
], function ($, h, UIElements, Messages) {
], function ($, h, Hash, UIElements, Messages) {
var handlers = {};
// Friend request
handlers['FRIEND_REQUEST'] = function (common, data, el) {
var content = data.content;
var msg = content.msg;
@@ -17,9 +20,9 @@ define([
common.addFriendRequest(data);
// Display the notification
$(el).find('.cp-notification-content').addClass("cp-clickable");
$(el).find('.cp-notification-content p')
.html(Messages._getKey('friendRequest_notification', [msg.content.displayName || Messages.anonymous]))
.html(Messages._getKey('friendRequest_notification', [msg.content.displayName || Messages.anonymous]));
$(el).find('.cp-notification-content').addClass("cp-clickable")
.click(function () {
UIElements.displayFriendRequestModal(common, data);
});
@@ -41,6 +44,24 @@ define([
$(el).find('.cp-notification-dismiss').css('display', 'flex');
};
// Share pad
handlers['SHARE_PAD'] = function (common, data, el) {
var content = data.content;
var msg = content.msg;
var type = Hash.parsePadUrl(msg.content.href).type;
var key = type === 'drive' ? 'notification_folderShared' :
(type === 'file' ? 'notification_fileShared' :
'notification_padShared');
$(el).find('.cp-notification-content p')
.html(Messages._getKey(key, [msg.content.name || Messages.anonymous, msg.content.title]));
$(el).find('.cp-notification-content').addClass("cp-clickable")
.click(function () {
common.openURL(msg.content.href);
});
$(el).find('.cp-notification-dismiss').css('display', 'flex');
};
return {
add: function (common, data, el) {
var type = data.content.msg.type;

View File

@@ -66,7 +66,7 @@ define([
}
broadcast([clientId], "UPDATE_METADATA");
if (Array.isArray(path) && path[0] === 'profile' && store.messenger) {
store.messenger.updateMyData();
Messaging.updateMyData(store);
}
onSync(cb);
};
@@ -644,7 +644,7 @@ define([
}
store.proxy[Constants.displayNameKey] = value;
broadcast([clientId], "UPDATE_METADATA");
if (store.messenger) { store.messenger.updateMyData(); }
Messaging.updateMyData(store);
onSync(cb);
};
@@ -1644,14 +1644,16 @@ define([
});
userObject.migrate(waitFor());
}).nThen(function (waitFor) {
Store.initAnonRpc(null, null, waitFor());
Store.initRpc(null, null, waitFor());
}).nThen(function (waitFor) {
loadMailbox(waitFor);
Migrate(proxy, waitFor(), function (version, progress) {
postMessage(clientId, 'LOADING_DRIVE', {
state: (2 + (version / 10)),
progress: progress
});
});
Store.initAnonRpc(null, null, waitFor());
Store.initRpc(null, null, waitFor());
}).nThen(function (waitFor) {
postMessage(clientId, 'LOADING_DRIVE', {
state: 3
@@ -1661,7 +1663,6 @@ define([
loadMessenger();
loadCursor();
loadOnlyOffice();
loadMailbox(waitFor);
loadUniversal(Profile, 'profile', waitFor);
cleanFriendRequests();
}).nThen(function () {
@@ -1816,8 +1817,8 @@ define([
// Ping clients regularly to make sure one tab was not closed without sending a removeClient()
// command. This allow us to avoid phantom viewers in pads.
var PING_INTERVAL = 30000;
var MAX_PING = 5000;
var PING_INTERVAL = 120000;
var MAX_PING = 30000;
var MAX_FAILED_PING = 2;
setInterval(function () {

View File

@@ -144,8 +144,30 @@ define([
cb(true);
};
handlers['UPDATE_DATA'] = function (ctx, box, data, cb) {
var msg = data.msg;
var curve = msg.author;
var friend = ctx.store.proxy.friends && ctx.store.proxy.friends[curve];
if (!friend || typeof msg.content !== "object") { return void cb(true); }
Object.keys(msg.content).forEach(function (key) {
friend[key] = msg.content[key];
});
ctx.updateMetadata();
cb(true);
};
return {
add: function (ctx, box, data, cb) {
/**
* data = {
msg: {
type: 'STRING',
author: 'curvePublicString',
content: {} (depend on the "type")
},
hash: 'string'
}
*/
if (!data.msg) { return void cb(true); }
var type = data.msg.type;

View File

@@ -65,7 +65,7 @@ proxy.mailboxes = {
};
// Send a message to someone else
var sendTo = function (ctx, type, msg, user, cb) {
var sendTo = Mailbox.sendTo = function (ctx, type, msg, user, cb) {
if (!Crypto.Mailbox) {
return void cb({error: "chainpad-crypto is outdated and doesn't support mailboxes."});
}

View File

@@ -357,16 +357,14 @@ define([
UI.removeLoadingScreen(emitResize);
var privateDat = cpNfInner.metadataMgr.getPrivateData();
var hash = privateDat.availableHashes.editHash ||
privateDat.availableHashes.viewHash;
var href = privateDat.pathname + '#' + hash;
var type = privateDat.app;
if (AppConfig.textAnalyzer && textContentGetter) {
AppConfig.textAnalyzer(textContentGetter, privateDat.channel);
}
if (options.thumbnail && privateDat.thumbnails) {
if (hash) {
options.thumbnail.href = href;
if (type) {
options.thumbnail.type = type;
options.thumbnail.getContent = function () {
if (!cpNfInner.chainpad) { return; }
return cpNfInner.chainpad.getUserDoc();

View File

@@ -231,7 +231,20 @@ define([
};
var $block = exp.$language = UIElements.createDropdown(dropdownConfig);
$block.find('button').attr('title', Messages.languageButtonTitle);
$block.find('a').click(function () {
var isHovering = false;
var $aLanguages = $block.find('a');
$aLanguages.mouseenter(function () {
isHovering = true;
setMode($(this).attr('data-value'));
});
$aLanguages.mouseleave(function () {
if (isHovering) {
setMode($block.find(".cp-dropdown-element-active").attr('data-value'));
}
});
$aLanguages.click(function () {
isHovering = false;
setMode($(this).attr('data-value'), onModeChanged);
onLocal();
});
@@ -272,7 +285,21 @@ define([
setTheme(lastTheme, $block);
$block.find('a').click(function () {
var isHovering = false;
var $aThemes = $block.find('a');
$aThemes.mouseenter(function () {
isHovering = true;
var theme = $(this).attr('data-value');
setTheme(theme, $block);
});
$aThemes.mouseleave(function () {
if (isHovering) {
setTheme(lastTheme, $block);
Common.setAttribute(themeKey, lastTheme);
}
});
$aThemes.click(function () {
isHovering = false;
var theme = $(this).attr('data-value');
setTheme(theme, $block);
Common.setAttribute(themeKey, theme);

View File

@@ -5,7 +5,8 @@ define([
'/common/common-ui-elements.js',
'/common/notifications.js',
'/common/hyperscript.js',
], function ($, Util, UI, UIElements, Notifications, h) {
'/customize/messages.js',
], function ($, Util, UI, UIElements, Notifications, h, Messages) {
var Mailbox = {};
Mailbox.create = function (Common) {
@@ -48,7 +49,10 @@ define([
};
var createElement = function (data) {
var notif;
var dismiss = h('span.fa.fa-times');
var dismissIcon = h('span.fa.fa-times');
var dismiss = h('div.cp-notification-dismiss', {
title: Messages.notifications_dismiss
}, dismissIcon)
dismiss.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
@@ -65,7 +69,7 @@ define([
'data-hash': data.content.hash
}, [
h('div.cp-notification-content', h('p', formatData(data))),
h('div.cp-notification-dismiss', dismiss)
dismiss
]);
return notif;
};
@@ -132,9 +136,14 @@ define([
});
};
var subscribed = false;
// Get all existing notifications + the new ones when they come
mailbox.subscribe = function (cfg) {
if (!subscribed) {
execCommand('SUBSCRIBE', null, function () {});
subscribed = true;
}
if (typeof(cfg.onViewed) === "function") {
onViewedHandlers.push(cfg.onViewed);
}
@@ -166,10 +175,6 @@ define([
}
});
execCommand('SUBSCRIBE', null, function () {
//console.log('subscribed');
});
return mailbox;
};

View File

@@ -19,6 +19,7 @@ define([
var SFrameChannel;
var sframeChan;
var FilePicker;
var Share;
var Messaging;
var Notifier;
var Utils = {
@@ -38,6 +39,7 @@ define([
'/common/cryptget.js',
'/common/outer/worker-channel.js',
'/filepicker/main.js',
'/share/main.js',
'/common/common-messaging.js',
'/common/common-notifier.js',
'/common/common-hash.js',
@@ -49,7 +51,7 @@ define([
'/customize/application_config.js',
'/common/test.js',
], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel,
_FilePicker, _Messaging, _Notifier, _Hash, _Util, _Realtime,
_FilePicker, _Share, _Messaging, _Notifier, _Hash, _Util, _Realtime,
_Constants, _Feedback, _LocalStore, _AppConfig, _Test) {
CpNfOuter = _CpNfOuter;
Cryptpad = _Cryptpad;
@@ -57,6 +59,7 @@ define([
Cryptget = _Cryptget;
SFrameChannel = _SFrameChannel;
FilePicker = _FilePicker;
Share = _Share;
Messaging = _Messaging;
Notifier = _Notifier;
Utils.Hash = _Hash;
@@ -269,9 +272,6 @@ define([
sessionStorage[Utils.Constants.displayPadCreationScreen];
delete sessionStorage[Utils.Constants.displayPadCreationScreen];
var updateMeta = function () {
// TODO availableHashes in privateData may need updates once we have
// a better privileges workflow
//console.log('EV_METADATA_UPDATE');
var metaObj, isTemplate;
nThen(function (waitFor) {
@@ -290,12 +290,12 @@ define([
type: cfg.type || parsed.type
};
var additionalPriv = {
app: parsed.type,
accountName: Utils.LocalStore.getAccountName(),
origin: window.location.origin,
pathname: window.location.pathname,
fileHost: ApiConfig.fileHost,
readOnly: readOnly,
availableHashes: hashes,
isTemplate: isTemplate,
feedbackAllowed: Utils.Feedback.state,
isPresent: parsed.hashData && parsed.hashData.present,
@@ -326,6 +326,10 @@ define([
additionalPriv.registeredOnly = true;
}
if (['debug', 'profile'].indexOf(parsed.type) !== -1) {
additionalPriv.hashes = hashes;
}
for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; }
if (cfg.addData) {
@@ -380,6 +384,27 @@ define([
});
});
sframeChan.on('Q_GET_ATTRIBUTE', function (data, cb) {
Cryptpad.getAttribute(data.key, function (e, data) {
cb({
error: e,
data: data
});
});
});
sframeChan.on('Q_SET_ATTRIBUTE', function (data, cb) {
Cryptpad.setAttribute(data.key, data.value, function (e) {
cb({error:e});
});
});
Cryptpad.mailbox.onEvent.reg(function (data) {
sframeChan.event('EV_MAILBOX_EVENT', data);
});
sframeChan.on('Q_MAILBOX_COMMAND', function (data, cb) {
Cryptpad.mailbox.execCommand(data, cb);
});
};
addCommonRpc(sframeChan);
@@ -589,20 +614,6 @@ define([
}, href);
});
sframeChan.on('Q_GET_ATTRIBUTE', function (data, cb) {
Cryptpad.getAttribute(data.key, function (e, data) {
cb({
error: e,
data: data
});
});
});
sframeChan.on('Q_SET_ATTRIBUTE', function (data, cb) {
Cryptpad.setAttribute(data.key, data.value, function (e) {
cb({error:e});
});
});
sframeChan.on('Q_DRIVE_GETDELETED', function (data, cb) {
Cryptpad.getDeletedPads(data, function (err, obj) {
if (err) { return void console.error(err); }
@@ -711,6 +722,45 @@ define([
initFilePicker(data);
});
// Share modal
var ShareModal = {};
var initShareModal = function (cfg) {
cfg.hashes = hashes;
cfg.password = password;
// cfg.hidden means pre-loading the filepicker while keeping it hidden.
// if cfg.hidden is true and the iframe already exists, do nothing
if (!ShareModal.$iframe) {
var config = {};
config.onShareAction = function (data) {
sframeChan.event('EV_SHARE_ACTION', data);
};
config.onClose = function () {
ShareModal.$iframe.hide();
};
config.data = cfg;
config.addCommonRpc = addCommonRpc;
config.modules = {
Cryptpad: Cryptpad,
SFrameChannel: SFrameChannel,
Utils: Utils
};
ShareModal.$iframe = $('<iframe>', {id: 'sbox-share-iframe'}).appendTo($('body'));
ShareModal.modal = Share.create(config);
} else if (!cfg.hidden) {
ShareModal.modal.refresh(cfg, function () {
ShareModal.$iframe.show();
});
}
if (cfg.hidden) {
ShareModal.$iframe.hide();
return;
}
ShareModal.$iframe.focus();
};
sframeChan.on('EV_SHARE_OPEN', function (data) {
initShareModal(data || {});
});
sframeChan.on('Q_TEMPLATE_USE', function (data, cb) {
Cryptpad.useTemplate(data, Cryptget, cb);
});
@@ -879,13 +929,6 @@ define([
Cryptpad.universal.execCommand(data, cb);
});
Cryptpad.mailbox.onEvent.reg(function (data) {
sframeChan.event('EV_MAILBOX_EVENT', data);
});
sframeChan.on('Q_MAILBOX_COMMAND', function (data, cb) {
Cryptpad.mailbox.execCommand(data, cb);
});
Cryptpad.onTimeoutEvent.reg(function () {
sframeChan.event('EV_WORKER_TIMEOUT');
});

View File

@@ -121,13 +121,9 @@ define([
return '<script src="' + origin + '/common/media-tag-nacl.min.js"></script>';
};
funcs.getMediatagFromHref = function (obj) {
if (!obj || !obj.hash) { return; }
var data = ctx.metadataMgr.getPrivateData();
var secret;
if (obj) {
secret = Hash.getSecrets('file', obj.hash, obj.password);
} else {
secret = Hash.getSecrets('file', data.availableHashes.fileHash, data.password);
}
var secret = Hash.getSecrets('file', obj.hash, obj.password);
if (secret.keys && secret.channel) {
var key = Hash.encodeBase64(secret.keys && secret.keys.cryptKey);
var hexFileName = secret.channel;
@@ -391,12 +387,6 @@ define([
}
};
funcs.isStrongestStored = function () {
var data = ctx.metadataMgr.getPrivateData();
if (data.availableHashes.fileHash) { return true; }
return !data.readOnly || !data.availableHashes.editHash;
};
funcs.setDisplayName = function (name, cb) {
cb = cb || $.noop;
ctx.sframeChan.query('Q_SETTINGS_SET_DISPLAY_NAME', name, cb);
@@ -432,6 +422,19 @@ define([
return JSON.parse(JSON.stringify(friendRequests));
};
funcs.getFriends = function () {
var priv = ctx.metadataMgr.getPrivateData();
var friends = priv.friends;
var goodFriends = {};
Object.keys(friends).forEach(function (curve) {
if (curve.length !== 44) { return; }
var data = friends[curve];
if (!data.notifications) { return; }
goodFriends[curve] = friends[curve];
});
return goodFriends;
};
// Feedback
funcs.prepareFeedback = function (key) {
if (typeof(key) !== 'string') { return $.noop; }

View File

@@ -304,8 +304,10 @@ MessengerUI, Messages) {
} else if (Common.isLoggedIn() && data.curvePublic && !friends[data.curvePublic]
&& !priv.readOnly) {
if (pendingFriends[data.curvePublic] && pendingFriends[data.curvePublic] > friendTo) {
$('<span>', {'class': 'cp-toolbar-userlist-friend'}).text(Messages.userlist_pending)
.appendTo($rightCol);
$('<button>', {
'class': 'fa fa-hourglass-half cp-toolbar-userlist-button',
'title': Messages.profile_friendRequestSent
}).appendTo($nameSpan);
} else if (friendRequests[data.curvePublic]) {
$('<button>', {
'class': 'fa fa-bell cp-toolbar-userlist-button',
@@ -523,23 +525,18 @@ MessengerUI, Messages) {
if (!config.metadataMgr) {
throw new Error("You must provide a `metadataMgr` to display the userlist");
}
var metadataMgr = config.metadataMgr;
var origin = config.metadataMgr.getPrivateData().origin;
var pathname = config.metadataMgr.getPrivateData().pathname;
var hashes = metadataMgr.getPrivateData().availableHashes;
var $shareBlock = $('<button>', {
'class': 'fa fa-shhare-alt cp-toolbar-share-button',
title: Messages.shareButton
});
var modal = UIElements.createShareModal({
origin: origin,
pathname: pathname,
hashes: hashes,
common: Common
Common.getSframeChannel().event('EV_SHARE_OPEN', {
hidden: true
});
$shareBlock.click(function () {
UI.openCustomModal(UI.dialog.tabs(modal));
Common.getSframeChannel().event('EV_SHARE_OPEN', {
title: Common.getMetadataMgr().getMetadata().title
});
});
toolbar.$leftside.append($shareBlock);
@@ -552,23 +549,19 @@ MessengerUI, Messages) {
if (!config.metadataMgr) {
throw new Error("You must provide a `metadataMgr` to display the userlist");
}
var metadataMgr = config.metadataMgr;
var origin = config.metadataMgr.getPrivateData().origin;
var pathname = config.metadataMgr.getPrivateData().pathname;
var hashes = metadataMgr.getPrivateData().availableHashes;
var $shareBlock = $('<button>', {
'class': 'fa fa-shhare-alt cp-toolbar-share-button',
title: Messages.shareButton
});
var modal = UIElements.createFileShareModal({
origin: origin,
pathname: pathname,
hashes: hashes,
common: Common
Common.getSframeChannel().event('EV_SHARE_OPEN', {
hidden: true,
file: true
});
$shareBlock.click(function () {
UI.openCustomModal(UI.dialog.tabs(modal));
Common.getSframeChannel().event('EV_SHARE_OPEN', {
file: true
});
});
toolbar.$leftside.append($shareBlock);

View File

@@ -0,0 +1,2 @@
{
}

View File

@@ -389,7 +389,7 @@
"profile_inviteButtonTitle": "Crear un enlace de invitación para este usuario.",
"profile_inviteExplanation": "Hacer clic en <strong>OK</strong> creará un enlace de mensaje seguro que <em>sólo {0} podrá ver.</em><br><br>El enlace será copiado a tu portapapeles y puede ser compartido públicamente.",
"profile_viewMyProfile": "Ver mi perfil",
"userlist_addAsFriendTitle": "Agregar \"{0}\" como contacto",
"userlist_addAsFriendTitle": "Enviar \"{0}\" una solicitud de amistad",
"userlist_thisIsYou": "Tú mismo (\"{0}\")",
"contacts_title": "Contactos",
"contacts_addError": "Error al agregar este contacto a la lista",
@@ -561,5 +561,34 @@
"fc_remove_sharedfolder": "Eliminar",
"fc_hashtag": "Etiquetas",
"register_passwordTooShort": "La contraseña debe tener por los menos {0} caracteres de largo.",
"useTemplateCancel": "Recomenzar (Esc)"
"useTemplateCancel": "Recomenzar (Esc)",
"register_whyRegister": "Por qué conectarse?",
"settings_cat_cursor": "Cursor",
"settings_cat_pad": "Texto enriquecido",
"settings_cat_creation": "Nueva hoja",
"settings_cat_subscription": "Suscripción",
"settings_backupHint": "Respalde o recupere todo sus contenido ne CryptDrive. No tendrán el contenido de sus archivos, solo las llaves para acceder a ellos.",
"settings_backupHint2": "Descargue el contenido actual de sus notas. Las notas serán descargadas en un formato de leíble si el formato esta disponible.",
"settings_backup2": "Descargar mi CryptDrive",
"settings_backup2Confirm": "Esto descargara todas las notas y archivos desde su CryptDrive. Si desea continuar, elija un nombre y presione OK",
"settings_exportTitle": "Exportar su CryptDrive",
"settings_exportDescription": "Por favor espere mientras descargamos y desencriptamos sus documentos. Esto podría tomar unos minutos. Al cerrar la etiqueta interrumpirá el proceso.",
"settings_exportFailed": "Si la nota requiere más de un minuto en ser descargada no será incluida en el archivo exportado. Un link para cada nota que no haya exportada será mostrada.",
"settings_exportWarning": "Nota: esta herramienta aún está en versión beta y podría tener problema al ser escalada. Para una mejor performance se recomiendo dejar esta etiqueta con foco.",
"settings_exportCancel": "Está seguro que quiera cancelar la exportación? Tendrá que comenzar desde el comienzo la próxima vez.",
"settings_export_reading": "Leyendo su CryptDrive...",
"settings_export_download": "Descargando y desencriptando sus documentos...",
"settings_export_compressing": "Comprimiendo...",
"settings_export_done": "Sus descarga está lista!",
"settings_exportError": "Ver errores",
"settings_exportErrorDescription": "No logramos agregar los siguientes documentos en lo exportado:",
"settings_exportErrorEmpty": "Este documento no puede ser exportado (contenido vacío o invalido).",
"settings_exportErrorMissing": "Este documento no puede ser encontrado en nuestros servidores (expirado o borrado por su dueño)",
"settings_exportErrorOther": "Ha ocurrido un error al tratar de exportar este documento: {0}",
"settings_thumbnails": "Imágenes en miniatura",
"settings_disableThumbnailsAction": "Deshabilitar la creación de imágenes pequeñas en su CryptDrive",
"settings_disableThumbnailsDescription": "Las imágenes en miniatura serán guardadas en su navegador de manera automática cuando visite una nueva hoja. Puede deshabilitar esta función acá.",
"settings_resetThumbnailsAction": "Limpiar",
"settings_resetThumbnailsDescription": "Limpiar todas las imágenes en miniatura de sus notas guardadas en su navegador.",
"settings_resetThumbnailsDone": "Todas las imágenes en miniatura han sido borradas."
}

View File

@@ -278,13 +278,8 @@
"profile_create": "Create a profile",
"profile_description": "Description",
"profile_fieldSaved": "New value saved: {0}",
"profile_inviteButton": "Connect",
"profile_inviteButtonTitle": "Create a link that will invite this user to connect with you.",
"profile_inviteExplanation": "Clicking <strong>OK</strong> will create a link to a secure messaging session that <em>only {0} will be able to redeem.</em><br><br>The link will be copied to your clipboard and can be shared publicly.",
"profile_viewMyProfile": "View my profile",
"userlist_addAsFriendTitle": "Send \"{0}\" a friend request",
"userlist_thisIsYou": "This is you (\"{0}\")",
"userlist_pending": "Pending...",
"contacts_title": "Contacts",
"contacts_addError": "Error while adding that contact to the list",
"contacts_added": "Contact invite accepted.",

View File

@@ -102,7 +102,7 @@
"forgetPrompt": "Нажав ОК, вы удалите документ в корзину. Уверены?",
"movedToTrash": "Документ был удалён в корзину.<br><a href=\"/drive/\">Доступ к диску</a>",
"shareButton": "Поделиться",
"shareSuccess": "Ссылка скопирована в буфер обмена",
"shareSuccess": "Ссылка скопирована в буфер обмена.",
"userListButton": "Список пользователей",
"chatButton": "Чат",
"userAccountButton": "Ваш профиль",
@@ -370,5 +370,28 @@
"fm_padIsOwned": "Вы владелец этого пэда",
"fm_padIsOwnedOther": "Этот пэд принадлежит другому пользователю",
"fm_deletedPads": "Эти пэды больше не существуют на сервере, они были удалены с вашего CryptDrive: {0}",
"fm_tags_name": "Имя тэга"
"fm_tags_name": "Имя тэга",
"printCSS": "Пользовательские настройки вида (CSS)",
"viewEmbedTag": "Чтобы встроить данный документ вставьте iframe в нужную страницу. Вы можете настроить внешний вид используя CSS и HTML атрибуты. ",
"debug_getGraphText": "Это код DOT для генерации графика истории этого документа:",
"fm_ownedPadsName": "Собственный",
"fm_info_anonymous": "Вы не вошли в учетную запись, поэтому срок действия ваших пэдов истечет через 3 месяца (<a href=\"https://blog.cryptpad.fr/2017/05/17/You-gotta-log-in/\" target=\"_blank\">find out more</a>). Они хранятся в вашем браузере, поэтому очистка истории может привести к их исчезновению..<br><a href=\"/register/\">Sign up</a> or <a href=\"/login/\">Log in</a> to keep them alive.<br>",
"fm_backup_title": "Резервная ссылка",
"fm_burnThisDriveButton": "Удалить всю информацию, хранящуюся на CryptPad в браузере.",
"fm_tags_used": "Количество использований",
"fm_restoreDrive": "Восстановление прежнего состояния диска. Для достижения наилучших результатов не вносите изменения в диск, пока этот процесс не будет завершен.",
"fm_passwordProtected": "Этот документ защищен паролем",
"fc_newfolder": "Новая папка",
"fc_newsharedfolder": "Новая общая папка",
"fc_rename": "Переименовать",
"fc_open": "Открыть",
"fc_open_ro": "Отркыть (режим чтения)",
"fc_delete": "Переместить в корзину",
"fc_delete_owned": "Удалить с сервера",
"fc_restore": "Восстановить",
"fc_remove_sharedfolder": "Удалить",
"fc_empty": "Удалить корзину",
"fc_prop": "Свойства",
"fc_hashtag": "Теги",
"fc_sizeInKilobytes": "Размер в килобайтах"
}

View File

@@ -0,0 +1,3 @@
{
}

View File

@@ -6,33 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>
<iframe id="sbox-iframe">

View File

@@ -6,33 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>
<iframe id="sbox-iframe">

View File

@@ -229,7 +229,7 @@ define([
};
var getGraph = function (chainpad, cb) {
var hashes = metadataMgr.getPrivateData().availableHashes;
var hashes = metadataMgr.getPrivateData().hashes;
var hash = hashes.editHash || hashes.viewHash;
var chan = Hash.hrefToHexChannelId('/drive/#'+hash);

View File

@@ -4,6 +4,7 @@
@import (reference) "../../customize/src/less2/include/limit-bar.less";
@import (reference) "../../customize/src/less2/include/tokenfield.less";
@import (reference) '../../customize/src/less2/include/framework.less';
@import (reference) '../../customize/src/less2/include/share.less';
&.cp-app-drive {
.framework_min_main(
@@ -14,6 +15,7 @@
.limit-bar_main();
.tokenfield_main();
.share_main();
@drive_hover: #eee;
@drive_hover-light: lighten(@drive_hover, 20%);
@@ -118,7 +120,7 @@
}
#cp-app-drive-tree {
resize: none;
width: 100%;
width: 100% !important;
max-width: unset;
max-height: unset;
border-bottom: 1px solid @drive_mobile-tree-border-col;
@@ -770,6 +772,9 @@
.cp-toolbar-icon-history {
float: right;
&.active {
background-color: rgba(0, 0, 255, 0.2);
}
.cp-toolbar-drawer-element {
display: none;
}
@@ -820,6 +825,7 @@
padding-left: 10px;
}
.cp-app-drive-toolbar-leftside {
flex-shrink: 0;
& > span {
height: 100%;
margin: 0;
@@ -861,30 +867,58 @@
width: auto;
overflow: hidden;
white-space: nowrap;
display: flex;
flex-flow: row-reverse;
flex-shrink: 1;
min-width: 50px;
max-width: 100%;
text-align: left;
.cp-app-drive-path-element {
display: inline-block;
height: @variables_bar-height;
line-height: @variables_bar-height;
font-size: @colortheme_app-font-size;
padding: 0 5px;
border: 0;
background: darken(@colortheme_drive-bg, 10%);
color: @colortheme_drive-color;
box-sizing: border-box;
transition: all 0.15s;
&.cp-app-drive-path-separator {
color: #ccc;
}
&.cp-app-drive-path-lickable {
cursor: pointer;
display: flex;
flex-direction: row;
.cp-app-drive-path-inner {
display: flex;
flex-flow: row-reverse;
flex-grow: 1;
.cp-app-drive-path-element {
display: inline-block;
flex-shrink: 0;
max-width: 100%;
height: @variables_bar-height;
line-height: @variables_bar-height;
font-size: @colortheme_app-font-size;
padding: 0 5px;
border: 0;
background: darken(@colortheme_drive-bg, 7%);
color: @colortheme_drive-color;
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
transition: all 0.15s;
&:first-child {
flex-shrink: 1;
}
&.cp-app-drive-path-separator {
color: #ccc;
}
&.cp-app-drive-path-collapse {
position: relative;
}
&:hover {
background: darken(@colortheme_drive-bg, 15%);
&:not(.cp-app-drive-path-separator) {
background-color: darken(@colortheme_drive-bg, 15%);
text-decoration: underline;
cursor: pointer;
}
& ~ .cp-app-drive-path-element {
background-color: darken(@colortheme_drive-bg, 15%);
}
& ~ .cp-app-drive-path-element:not(.cp-app-drive-path-separator) {
text-decoration: underline;
}
}
}
}

View File

@@ -6,33 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>
<iframe id="sbox-iframe">

View File

@@ -1611,6 +1611,7 @@ define([
return title;
}; */
var getPrettyName = function (name) {
var pName;
switch (name) {
@@ -1628,6 +1629,64 @@ define([
return pName;
};
var drivePathOverflowing = function () {
var $container = $(".cp-app-drive-path");
if ($container.length) {
$container.css("overflow", "hidden");
var overflown = $container[0].scrollWidth > $container[0].clientWidth;
$container.css("overflow", "");
return overflown;
}
};
var collapseDrivePath = function () {
var $container = $(".cp-app-drive-path-inner");
var $spanCollapse = $(".cp-app-drive-path-collapse");
$spanCollapse.css("display", "none");
var $pathElements = $container.find(".cp-app-drive-path-element");
$pathElements.not($spanCollapse).css("display", "");
var oneFolder = currentPath.length > 1 + (currentPath[0] === SHARED_FOLDER);
if (oneFolder && drivePathOverflowing()) {
var collapseLevel = 0;
var removeOverflowElement = function () {
if (drivePathOverflowing()) {
if ($pathElements.length <= 3) {
return false;
}
collapseLevel++;
if ($($pathElements.get(-2)).is(".cp-app-drive-path-separator")) {
$($pathElements.get(-2)).css("display", "none");
$pathElements = $pathElements.not($pathElements.get(-2));
}
$($pathElements.get(-2)).css("display", "none");
$pathElements = $pathElements.not($pathElements.get(-2));
return true;
}
};
currentPath.every(removeOverflowElement);
$spanCollapse.css("display", "");
removeOverflowElement();
var tipPath = currentPath.slice(0, collapseLevel);
tipPath[0] = getPrettyName(tipPath[0]);
$spanCollapse.attr("title", tipPath.join(" / "));
$spanCollapse[0].onclick = function () {
APP.displayDirectory(getLastOpenedFolder().slice(0, collapseLevel));
};
}
};
window.addEventListener("resize", collapseDrivePath);
var treeResizeObserver = new MutationObserver(collapseDrivePath);
treeResizeObserver.observe($("#cp-app-drive-tree")[0], {"attributes": true});
var toolbarButtonAdditionObserver = new MutationObserver(collapseDrivePath);
$(function () { toolbarButtonAdditionObserver.observe($("#cp-app-drive-toolbar")[0], {"childList": true, "subtree": true}); });
// Create the title block with the "parent folder" button
var createTitle = function ($container, path, noStyle) {
if (!path || path.length === 0) { return; }
@@ -1639,6 +1698,9 @@ define([
var el = isVirtual ? undefined : manager.find(path);
path = path[0] === SEARCH ? path.slice(0,1) : path;
var $inner = $('<div>', {'class': 'cp-app-drive-path-inner'});
$container.prepend($inner);
var skipNext = false; // When encountering a shared folder, skip a key in the path
path.forEach(function (p, idx) {
if (skipNext) { skipNext = false; return; }
@@ -1671,13 +1733,21 @@ define([
var $span2 = $('<span>', {
'class': 'cp-app-drive-path-element cp-app-drive-path-separator'
}).text(' / ');
$container.prepend($span2);
$inner.prepend($span2);
}
$span.text(name).prependTo($container);
$span.text(name).prependTo($inner);
});
var $spanCollapse = $('<span>', {
'class': 'cp-app-drive-path-element cp-app-drive-path-collapse'
}).text(' ... ');
$inner.append($spanCollapse);
collapseDrivePath();
};
var createInfoBox = function (path) {
var $box = $('<div>', {'class': 'cp-app-drive-content-info-box'});
var msg;
@@ -2628,25 +2698,29 @@ define([
// NOTE: Elements in the trash are not using the same storage structure as the others
var _displayDirectory = function (path, force) {
APP.hideMenu();
if (!APP.editable) { debug("Read-only mode"); }
if (!appStatus.isReady && !force) { return; }
// Only Trash and Root are available in not-owned files manager
if (!path || displayedCategories.indexOf(path[0]) === -1) {
log(Messages.fm_categoryError);
currentPath = [ROOT];
_displayDirectory(currentPath);
return;
if (!path || path.length === 0) {
// Only Trash and Root are available in not-owned files manager
if (!path || displayedCategories.indexOf(path[0]) === -1) {
log(Messages.fm_categoryError);
}
if (!APP.loggedIn && APP.newSharedFolder) {
// ANON_SHARED_FOLDER
path = [SHARED_FOLDER, ROOT];
} else {
path = [ROOT];
}
}
appStatus.ready(false);
currentPath = path;
var s = $content.scrollTop() || 0;
$content.html("");
sel.$selectBox = $('<div>', {'class': 'cp-app-drive-content-select-box'})
.appendTo($content);
if (!path || path.length === 0) {
path = [ROOT];
}
var isInRoot = manager.isPathIn(path, [ROOT]);
var inTrash = manager.isPathIn(path, [TRASH]);
var isTrashRoot = manager.comparePath(path, [TRASH]);
@@ -2657,6 +2731,10 @@ define([
var isTags = path[0] === TAGS;
// ANON_SHARED_FOLDER
var isSharedFolder = path[0] === SHARED_FOLDER && APP.newSharedFolder;
if (isSharedFolder && path.length < 2) {
path = [SHARED_FOLDER, 'root'];
currentPath = path;
}
var root = isVirtual ? undefined : manager.find(path);
if (manager.isSharedFolder(root)) {
@@ -3237,12 +3315,17 @@ define([
if (paths.length !== 1) { return; }
el = manager.find(paths[0].path);
var parsed, modal;
var friends = common.getFriends();
if (manager.isSharedFolder(el)) {
data = manager.getSharedFolderData(el);
parsed = Hash.parsePadUrl(data.href);
modal = UIElements.createSFShareModal({
origin: APP.origin,
pathname: "/drive/",
friends: friends,
title: data.title,
common: common,
hashes: {
editHash: parsed.hash
}
@@ -3255,6 +3338,7 @@ define([
var padData = {
origin: APP.origin,
pathname: "/" + padType + "/",
friends: friends,
hashes: {
editHash: parsed.hash,
viewHash: roParsed.hash,
@@ -3264,13 +3348,16 @@ define([
hash: parsed.hash,
password: data.password
},
title: data.title,
common: common
};
modal = padType === 'file' ? UIElements.createFileShareModal(padData)
: UIElements.createShareModal(padData);
modal = UI.dialog.tabs(modal);
}
UI.openCustomModal(modal);
UI.openCustomModal(modal, {
wide: Object.keys(friends).length !== 0
});
}
else if ($(this).hasClass('cp-app-drive-context-newfolder')) {
if (paths.length !== 1) { return; }

View File

@@ -6,25 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>
<iframe id="sbox-iframe">

View File

@@ -50,7 +50,8 @@ define([
var postMsg = function (data) {
iframe.postMessage(data, '*');
};
var whenReady = waitFor(function (msg) {
var w = waitFor();
var whenReady = function (msg) {
if (msg.source !== iframe) { return; }
var data = JSON.parse(msg.data);
if (!data.txid) { return; }
@@ -67,7 +68,8 @@ define([
config.modules.SFrameChannel.create(msgEv, postMsg, waitFor(function (sfc) {
sframeChan = sfc;
}));
});
w();
};
window.addEventListener('message', whenReady);
}).nThen(function () {
var updateMeta = function () {

View File

@@ -7,27 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="/common/sframe-app-outer.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html,
body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position: fixed;
top: 0px;
left: 0px;
bottom: 0px;
right: 0px;
width: 100%;
height: 100%;
border: none;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>

View File

@@ -6,33 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="/common/onlyoffice/main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>
<iframe id="sbox-iframe">

View File

@@ -6,33 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="/common/onlyoffice/main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>
<iframe id="sbox-iframe">

View File

@@ -6,33 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="/common/sframe-app-outer.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>
<iframe id="sbox-iframe">

View File

@@ -6,33 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>
<iframe id="sbox-iframe">

View File

@@ -739,10 +739,6 @@ define([
var initThumbnails = function () {
var privateDat = metadataMgr.getPrivateData();
if (!privateDat.thumbnails) { return; } // Thumbnails are disabled
var hash = privateDat.availableHashes.editHash ||
privateDat.availableHashes.viewHash;
if (!hash) { return; }
var href = privateDat.pathname + '#' + hash;
var $el = $('.cp-app-poll-realtime');
//var $el = $('#cp-app-poll-table');
var scrollTop;
@@ -771,7 +767,7 @@ define([
.css('position', '');
$('#cp-app-poll-form').scrollTop(scrollTop);
},
href: href,
type: 'poll',
getContent: function () { return JSON.stringify(APP.proxy.content); }
};
Thumb.initPadThumbnails(common, options);

View File

@@ -6,33 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>
<iframe id="sbox-iframe">

View File

@@ -93,7 +93,7 @@ define([
return;
}
var hash = common.getMetadataMgr().getPrivateData().availableHashes.viewHash;
var hash = common.getMetadataMgr().getPrivateData().hashes.viewHash;
var url = APP.origin + '/profile/#' + hash;
var $button = $('<button>', {

View File

@@ -6,33 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>
<iframe id="sbox-iframe">

8
www/share/app-share.less Normal file
View File

@@ -0,0 +1,8 @@
@import (reference) '../../customize/src/less2/include/colortheme-all.less';
@import (reference) '../../customize/src/less2/include/tippy.less';
@import (reference) '../../customize/src/less2/include/share.less';
&.cp-app-share {
.tippy_main();
.share_main();
}

29
www/share/inner.html Normal file
View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html style="height: 100%; background: transparent;">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/share/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
body #cp-loading {
display: none;
position: absolute;
top: 15vh;
bottom: 15vh;
left: 10vw;
right: 10vw;
z-index: 200000;
overflow: hidden;
}
body #cp-loading .cp-loading-container {
margin-top: 35vh;
}
body #cp-loading .cp-loading-cryptofist {
display: none;
}
</style>
</head>
<body class="cp-app-share" style="background: transparent;">
</body>
</html>

83
www/share/inner.js Normal file
View File

@@ -0,0 +1,83 @@
define([
'jquery',
'/bower_components/nthen/index.js',
'/common/sframe-common.js',
'/common/common-ui-elements.js',
'/common/common-interface.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/share/app-share.less',
], function (
$,
nThen,
SFCommon,
UIElements,
UI)
{
var APP = window.APP = {};
var andThen = function (common) {
var metadataMgr = common.getMetadataMgr();
var sframeChan = common.getSframeChannel();
var hideShareDialog = function () {
sframeChan.event('EV_SHARE_CLOSE');
};
var createShareDialog = function (data) {
var priv = metadataMgr.getPrivateData();
var hashes = priv.hashes;
var origin = priv.origin;
var pathname = priv.pathname;
var f = (data && data.file) ? UIElements.createFileShareModal
: UIElements.createShareModal;
var friends = common.getFriends();
var modal = f({
origin: origin,
pathname: pathname,
hashes: hashes,
common: common,
title: data.title,
friends: friends,
onClose: function () {
hideShareDialog();
},
fileData: {
hash: hashes.fileHash,
password: priv.password
}
});
UI.findCancelButton().click();
UI.openCustomModal(UI.dialog.tabs(modal), {
wide: Object.keys(friends).length !== 0
});
};
sframeChan.on('EV_SHARE_REFRESH', function (data) {
createShareDialog(data);
});
};
var main = function () {
var common;
nThen(function (waitFor) {
$(waitFor(function () {
UI.removeLoadingScreen();
}));
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
}).nThen(function (/*waitFor*/) {
var metadataMgr = common.getMetadataMgr();
if (metadataMgr.getMetadataLazy() !== 'uninitialized') {
andThen(common);
return;
}
metadataMgr.onChange(function () {
andThen(common);
});
});
};
main();
});

148
www/share/main.js Normal file
View File

@@ -0,0 +1,148 @@
// Load #1, load as little as possible because we are in a race to get the loading screen up.
define([
'/bower_components/nthen/index.js',
'/api/config',
'jquery',
'/common/requireconfig.js',
], function (nThen, ApiConfig, $, RequireConfig) {
var requireConfig = RequireConfig();
var ready = false;
var create = function (config) {
// Loaded in load #2
var sframeChan;
nThen(function (waitFor) {
$(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
$('#sbox-share-iframe').attr('src',
ApiConfig.httpSafeOrigin + '/share/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req)));
// This is a cheap trick to avoid loading sframe-channel in parallel with the
// loading screen setup.
var done = waitFor();
var onMsg = function (msg) {
var data = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) {
var Cryptpad = config.modules.Cryptpad;
var Utils = config.modules.Utils;
nThen(function (waitFor) {
// The inner iframe tries to get some data from us every ms (cache, store...).
// It will send a "READY" message and wait for our answer with the correct txid.
// First, we have to answer to this message, otherwise we're going to block
// sframe-boot.js. Then we can start the channel.
var msgEv = Utils.Util.mkEvent();
var iframe = $('#sbox-share-iframe')[0].contentWindow;
var postMsg = function (data) {
iframe.postMessage(data, '*');
};
var w = waitFor();
var whenReady = function (msg) {
if (msg.source !== iframe) { return; }
var data = JSON.parse(msg.data);
if (!data.txid) { return; }
// Remove the listener once we've received the READY message
window.removeEventListener('message', whenReady);
// Answer with the requested data
postMsg(JSON.stringify({ txid: data.txid, language: Cryptpad.getLanguage() }));
// Then start the channel
window.addEventListener('message', function (msg) {
if (msg.source !== iframe) { return; }
msgEv.fire(msg);
});
config.modules.SFrameChannel.create(msgEv, postMsg, waitFor(function (sfc) {
sframeChan = sfc;
}));
w();
};
window.addEventListener('message', whenReady);
}).nThen(function () {
var updateMeta = function () {
//console.log('EV_METADATA_UPDATE');
var metaObj;
nThen(function (waitFor) {
Cryptpad.getMetadata(waitFor(function (err, n) {
if (err) { console.log(err); }
metaObj = n;
}));
}).nThen(function (/*waitFor*/) {
metaObj.doc = {};
var additionalPriv = {
accountName: Utils.LocalStore.getAccountName(),
origin: window.location.origin,
pathname: window.location.pathname,
feedbackAllowed: Utils.Feedback.state,
hashes: config.data.hashes,
password: config.data.password,
file: config.data.file,
};
for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; }
sframeChan.event('EV_METADATA_UPDATE', metaObj);
});
};
Cryptpad.onMetadataChanged(updateMeta);
sframeChan.onReg('EV_METADATA_UPDATE', updateMeta);
config.addCommonRpc(sframeChan);
sframeChan.on('Q_GET_FILES_LIST', function (types, cb) {
Cryptpad.getSecureFilesList(types, function (err, data) {
cb({
error: err,
data: data
});
});
});
sframeChan.on('EV_SHARE_CLOSE', function () {
config.onClose();
});
sframeChan.on('EV_SHARE_ACTION', function (data) {
config.onShareAction(data);
});
sframeChan.onReady(function () {
if (ready === true) { return; }
if (typeof ready === "function") {
ready();
}
ready = true;
});
});
});
var refresh = function (data, cb) {
if (!ready) {
ready = function () {
refresh(data, cb);
};
return;
}
sframeChan.event('EV_SHARE_REFRESH', data);
cb();
};
return {
refresh: refresh
};
};
return {
create: create
};
});

View File

@@ -6,33 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="/common/onlyoffice/main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>
<iframe id="sbox-iframe">

View File

@@ -6,33 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="/common/sframe-app-outer.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>
<iframe id="sbox-iframe">

View File

@@ -458,6 +458,7 @@ define([
mkHelpMenu(framework);
CodeMirror.mkIndentSettings(framework._.cpNfInner.metadataMgr);
CodeMirror.init(framework.localChange, framework._.title, framework._.toolbar);
CodeMirror.configureTheme(common);
framework.onContentUpdate(function (newContent) {

View File

@@ -6,33 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>
<iframe id="sbox-iframe">

View File

@@ -6,33 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="/common/sframe-app-outer.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
<link href="/customize/src/outer.css" rel="stylesheet" type="text/css">
</head>
<body>
<iframe id="sbox-iframe">

View File

@@ -445,11 +445,7 @@ define([
var oldThumbnailState;
var privateDat = metadataMgr.getPrivateData();
if (!privateDat.thumbnails) { return; }
var hash = privateDat.availableHashes.editHash ||
privateDat.availableHashes.viewHash;
var href = privateDat.pathname + '#' + hash;
var mkThumbnail = function () {
if (!hash) { return; }
if (framework.getState() !== 'READY') { return; }
if (!framework._.cpNfInner.chainpad) { return; }
var content = framework._.cpNfInner.chainpad.getUserDoc();
@@ -457,7 +453,7 @@ define([
var D = Thumb.getResizedDimensions($canvas[0], 'pad');
Thumb.fromCanvas($canvas[0], D, function (err, b64) {
oldThumbnailState = content;
Thumb.setPadThumbnail(framework._.sfCommon, href, privateDat.channel, b64);
Thumb.setPadThumbnail(framework._.sfCommon, 'whiteboard', privateDat.channel, b64);
});
};
window.setInterval(mkThumbnail, Thumb.UPDATE_INTERVAL);