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

pull/1/head
ClemDee 6 years ago
commit d924cee747

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

@ -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
# 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
# Copy cryptpad and tini from the build container
COPY --from=build /sbin/tini /sbin/tini
COPY --from=build /cryptpad /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"]
CMD ["/sbin/tini", "--", "/cryptpad/container-start.sh"]

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

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

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

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

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

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

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

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

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

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

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

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

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

File diff suppressed because it is too large Load Diff

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

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

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

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

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

@ -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 () {

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

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

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

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

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

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

@ -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 () {

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

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

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

@ -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,12 +285,26 @@ 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);
});
if ($drawer) { $drawer.append($block); }
if (cb) { cb(); }
};

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

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

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

@ -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',
@ -491,7 +493,7 @@ MessengerUI, Messages) {
if ($messagebox.length) {
$messagebox.scrollTop($messagebox[0].scrollHeight);
}
$button.addClass('cp-toolbar-button-active');
config.$contentContainer.addClass('cp-chat-visible');
$button.removeClass('cp-toolbar-notification');
@ -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);

@ -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."
}

@ -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.",

@ -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": "Размер в килобайтах"
}

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

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

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

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

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

@ -1611,6 +1611,7 @@ define([
return title;
}; */
var getPrettyName = function (name) {
var pName;
switch (name) {
@ -1627,6 +1628,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) {
@ -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; }

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

@ -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 () {

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

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

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

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

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

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

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

@ -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>', {

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save