Merge branch 'staging' into ooPassword

pull/1/head
yflory 5 years ago
commit 3528516ab9

@ -1,3 +1,39 @@
# GoldenFrog release (3.6.0)
## Goals
We're following up our last few releases of major core developments with an effort to improve reliability in some unstable areas and make some superficial tweaks to improve usability of some critical interfaces.
## Update notes
Update to 3.6.0 from 3.5.0 using the normal update procedure:
1. stop your server
2. pull the latest code via git
3. run `bower update`
4. restart your server
## Features
* We've introduced a word-count feature in our rich text editor.
* The "share modal" which is accessible from both the "right-click menu" in the drive and the sharing button in the toolbar has been redesigned:
* different means of sharing access to documents have been split into different tabs to present users with less information to process
* each sharing method has an associated icon to make their actions easier to recognize at a glance
* various UI elements have been restyled to make their purpose and importance more obvious
* cancel buttons have a grey border to draw less attention
* OK buttons have a blue or grey background depending on whether they are active
* secondary buttons like "preview" have only a thin blue border so that they don't draw attention away from the primary button
* read-only text fields have a subtler appearance since they are shown primarily for the purpose of previewing your action
* text input fields (such as search) have a light background to suggest that you can use them
* We've made a minor adjustment to some of our styles for small screen to detect when a screen is very short in addition to when it is very narrow. As a result it should be somewhat easier to use on-screen keyboards.
## Bug fixes
* We found and fixed a subtle race condition which caused teams' quotas to be calculated incorrectly in certain circumstances.
* A minor bug in our login process caused users with premium accounts to incorrectly see an entry in their user menu as linking to our 'pricing' page instead of their 'subscription' management tools. This has since been fixed.
* We noticed that some of the rendered messages in the history mode of the notifications panel could fail to display text for some message types. These incorrect messages will be hidden from view wherever it is impossible to decide what should be displayed. We plan to address the issue in a deeper way in the near future.
* We've become aware of some odd behaviour in long-lived sessions where tabs seem to lose their connection to the sharedWorker which is common to all tabs open in a particular browser session. As far as we can tell the bug only affects Firefox browser. Unfortunately, debugging sharedWorkers in Firefox has been broken for a number of major versions, so we haven't been able to determine the cause of the issue. Until we're able to determine the underlying cause we've added extra checks to detect when particular features become isolated from the worker, where previously we assumed that if the worker was connected to the server then everything was behaving correctly. We recommend that you reload the tab if you notice that aspects of your shared folders or drives (for users or teams) display a read-only warning while your other tabs are behaving normally.
# FalklandWolf release (3.5.0)
## Goals

@ -18,7 +18,7 @@
"tests"
],
"dependencies": {
"jquery": "~2.1.3",
"jquery": "2.2.4",
"tweetnacl": "0.12.2",
"components-font-awesome": "^4.6.3",
"ckeditor": "4.7.3",
@ -51,6 +51,7 @@
"requirejs-plugins": "^1.0.3"
},
"resolutions": {
"bootstrap": "^v4.0.0"
"bootstrap": "^v4.0.0",
"jquery": "2.2.4"
}
}

@ -159,6 +159,9 @@
margin-bottom: @alertify_padding-base;
margin: 0;
overflow: auto;
:last-child {
margin-bottom: 0;
}
}
.alertify-tabs {
max-height: 100%;
@ -222,14 +225,22 @@
background-color: @alertify-input-fg;
color: @cryptpad_text_col;
border: 1px solid @alertify-input-bg;
margin-bottom: 15px;
margin-bottom: @alertify_padding-base;
width: 100%;
font-size: 100%;
padding: @alertify_padding-base;
&[readonly] {
background-color: @alertify-light-bg;
color: @cryptpad_text_col;
border-color: @alertify-input-fg;
border-color: @alertify-light-bg;
}
}
textarea {
overflow: hidden;
padding: 8px;
&[readonly] {
resize: none;
}
}
@ -509,5 +520,33 @@
overflow-x: auto;
}
}
// Bootstrap Alerts
.alert {
margin: 0px 0px @alertify_padding-base 0px;
font-size: 12px;
padding: 5px;
border-radius: 0px;
i {
margin-right: 10px;
}
&.alert-primary {
background-color: @alertify-base;
color: @alertify-fg;
border-color: @alertify-fg;
a {
color: @alertify-fg;
text-decoration: underline;
}
}
&.dismissable {
display: flex;
align-items: center;
span.fa-times {
font-size: @colortheme_app-font-size;
margin-left: 20px;
cursor: pointer;
}
}
}
}

@ -1,9 +1,12 @@
@import (reference) "./tools.less";
@import (reference) "./colortheme-all.less";
.avatar_vars(
@width: 30px
) {
@avatar-width: @width;
@avatar-font-size: @width / 1.2;
@avatar-default-bg: #D9D8D8;
@avatar-default-fg: darken(@avatar-default-bg, 40%);
}
.avatar_main(@width: 30px) {
--LessLoader_require: LessLoader_currentFile();
@ -30,16 +33,16 @@
justify-content: center;
align-items: center;
border-radius: 4px;
overflow: hidden;
box-sizing: content-box;
}
.cp-avatar-default {
.tools_unselectable();
background: white;
color: black;
background: @avatar-default-bg;
color: @avatar-default-fg;
font-size: @avatar-font-size;
font-size: var(--avatar-font-size);
text-transform: capitalize;
}
media-tag {
min-height: @avatar-width;

@ -1,12 +1,16 @@
@import (reference) "./colortheme-all.less";
@import (reference) "./variables.less";
.modals-ui-elements_main() {
--LessLoader_require: LessLoader_currentFile();
}
& {
.cp-spacer {
height: @variables_padding;
}
// Share modal
.msg.cp-inline-radio-group {
overflow: unset !important;
padding: 0px @variables_padding;
.radio-group {
display: flex;
flex-direction: row;

@ -1,3 +1,4 @@
@import (reference) "./colortheme-all.less";
.password_main() {
--LessLoader_require: LessLoader_currentFile();
}
@ -17,7 +18,7 @@
justify-content: center;
cursor: pointer;
&:hover {
background-color: rgba(0,0,0,0.1);
color: darken(@colortheme_alertify-primary, 10%);
}
}
}

@ -6,16 +6,18 @@
--LessLoader_require: LessLoader_currentFile();
};
& {
.cp-usergrid-container {
margin-bottom: 12px !important; // even when last child of .msg
.cp-usergrid-grid {
display: flex;
flex-wrap: wrap;
margin: -3px;
margin-bottom: 6px;
}
&:not(.large) {
.cp-usergrid-grid {
margin: -3px;
margin-bottom: 6px;
max-height: 130px;
overflow-y: auto;
@media screen and (max-height: 515px) {
max-height: unset; // remove double scrollbar
}
}
&.cp-usergrid-empty {
@ -28,17 +30,22 @@
input {
flex: 1;
min-width: 0;
margin: 0;
margin-bottom: 0 !important;
height: 38px;
&::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
color: @cryptpad_color_grey;
opacity: 1; /* Firefox */
}
}
margin-bottom: 15px;
margin-bottom: 10px;
&:empty {
margin: 0;
display: none;
}
button:last-child {
margin-right: 0px !important;
}
}
.cp-usergrid-user {
width: 70px;
@ -58,33 +65,48 @@
background-color: @colortheme_alertify-primary;
color: @colortheme_alertify-primary-text;
order: -1 !important;
.cp-usergrid-avatar {
media-tag, .cp-avatar-default {
opacity: 0.7;
}
}
}
.cp-usergrid-user-avatar {
min-height: 40px;
}
.cp-usergrid-user-name {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 100%;
text-align: center;
line-height: 18px;
line-height: 20px;
flex: 1;
}
border: 1px solid @colortheme_alertify-primary;
&:not(.large) {
.avatar_main(40px);
}
&.large {
.avatar_main(25px);
width: 140px;
width: 145px;
height: 35px;
flex-flow: row;
margin: 0;
margin-right: 15px;
margin-bottom: 1px;
&:nth-child(3n) {
margin-right: 0;
margin: 3px;
flex-basis: calc(33.3333333% - 6px);
flex-shrink: 1;
min-width: 0;
.cp-usergrid-user-name {
margin-left: 5px;
text-align: left;
line-height: 150%;
color: @cryptpad_text_col;
}
}
&.cp-selected {
.cp-usergrid-user-name {
color: @colortheme_alertify-primary-text;
}
}
}

@ -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.fi.js'], function (Messages) {
// Replace the existing keys in your copied file here:
// Messages.button_newpad = "New Rich Text Document";
return Messages;
});

@ -8,6 +8,8 @@ If the result of IO or computation is requested while an identical request
is already in progress, wait until the first one completes and provide its
result to every routine that requested it.
Asynchrony is guaranteed.
## Usage
Provide:
@ -51,11 +53,12 @@ module.exports = function (/* task */) {
var args = Array.prototype.slice.call(arguments);
//if (map[id] && map[id].length > 1) { console.log("BATCH-READ DID ITS JOB for [%s][%s]", task, id); }
map[id].forEach(function (h) {
h.apply(null, args);
setTimeout(function () {
map[id].forEach(function (h) {
h.apply(null, args);
});
delete map[id];
});
delete map[id];
});
};
};

@ -4,7 +4,7 @@ q(id, function (next) {
// whatever you need to do....
// when you're done
next();
next(); // guaranteed to be asynchronous :D
});
*/
@ -16,9 +16,11 @@ module.exports = function () {
var map = {};
var next = function (id) {
if (map[id] && map[id].length === 0) { return void delete map[id]; }
var task = map[id].shift();
task(fix1(next, id));
setTimeout(function () {
if (map[id] && map[id].length === 0) { return void delete map[id]; }
var task = map[id].shift();
task(fix1(next, id));
});
};
return function (id, task) {

@ -7,6 +7,10 @@
"type": "git",
"url": "git://github.com/xwiki-labs/cryptpad.git"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/cryptpad"
},
"dependencies": {
"chainpad-crypto": "^0.2.2",
"chainpad-server": "^3.0.5",

@ -228,13 +228,15 @@ var truthyKeys = function (O) {
});
};
var getChannelList = function (Env, publicKey, cb) {
var getChannelList = function (Env, publicKey, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
loadUserPins(Env, publicKey, function (pins) {
cb(truthyKeys(pins));
});
};
var getFileSize = function (Env, channel, cb) {
var getFileSize = function (Env, channel, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidId(channel)) { return void cb('INVALID_CHAN'); }
if (channel.length === 32) {
if (typeof(Env.msgStore.getChannelSize) !== 'function') {
@ -416,20 +418,46 @@ var getDeletedPads = function (Env, channels, cb) {
const batchTotalSize = BatchRead("GET_TOTAL_SIZE");
var getTotalSize = function (Env, publicKey, cb) {
batchTotalSize(publicKey, cb, function (done) {
var bytes = 0;
return void getChannelList(Env, publicKey, function (channels) {
if (!channels) { return done('INVALID_PIN_LIST'); } // unexpected
var unescapedKey = unescapeKeyCharacters(publicKey);
var limit = Env.limits[unescapedKey];
// Get a common key if multiple users share the same quota, otherwise take the public key
var batchKey = (limit && Array.isArray(limit.users)) ? limit.users.join('') : publicKey;
nThen(function (w) {
channels.forEach(function (channel) { // TODO semaphore?
getFileSize(Env, channel, w(function (e, size) {
if (!e) { bytes += size; }
batchTotalSize(batchKey, cb, function (done) {
var channels = [];
var bytes = 0;
nThen(function (waitFor) {
// Get the channels list for our user account
getChannelList(Env, publicKey, waitFor(function (_channels) {
if (!_channels) {
waitFor.abort();
return done('INVALID_PIN_LIST');
}
Array.prototype.push.apply(channels, _channels);
}));
// Get the channels list for users sharing our quota
if (limit && Array.isArray(limit.users) && limit.users.length > 1) {
limit.users.forEach(function (key) {
if (key === unescapedKey) { return; } // Don't count ourselves twice
getChannelList(Env, key, waitFor(function (_channels) {
if (!_channels) { return; } // Broken user, don't count their quota
Array.prototype.push.apply(channels, _channels);
}));
});
}).nThen(function () {
done(void 0, bytes);
}
}).nThen(function (waitFor) {
// Get size of the channels
var list = []; // Contains the channels already counted in the quota to avoid duplicates
channels.forEach(function (channel) { // TODO semaphore?
if (list.indexOf(channel) !== -1) { return; }
list.push(channel);
getFileSize(Env, channel, waitFor(function (e, size) {
if (!e) { bytes += size; }
}));
});
}).nThen(function () {
done(void 0, bytes);
});
});
};

@ -1,12 +1,17 @@
define([
'jquery',
'/common/cryptpad-common.js',
'/common/cryptget.js',
'/common/pinpad.js',
'/common/common-constants.js',
'/common/outer/local-store.js',
'/common/outer/login-block.js',
'/common/outer/network-config.js',
'/customize/login.js',
'/common/test.js',
'/bower_components/nthen/index.js',
'/bower_components/netflux-websocket/netflux-client.js',
'/bower_components/tweetnacl/nacl-fast.min.js'
], function ($, Cryptpad, Constants, LocalStore, Test, nThen) {
], function ($, Crypt, Pinpad, Constants, LocalStore, Block, NetConfig, Login, Test, nThen, Netflux) {
var Nacl = window.nacl;
var signMsg = function (msg, privKey) {
@ -27,9 +32,57 @@ define([
sessionStorage[Constants.userHashKey];
var proxy;
var rpc;
var network;
var rpcError;
var loadProxy = function (hash) {
nThen(function (waitFor) {
var wsUrl = NetConfig.getWebsocketURL();
var w = waitFor();
Netflux.connect(wsUrl).then(function (_network) {
network = _network;
w();
}, function (err) {
rpcError = err;
console.error(err);
});
}).nThen(function (waitFor) {
Crypt.get(hash, waitFor(function (err, val) {
if (err) {
waitFor.abort();
console.error(err);
return;
}
try {
var parsed = JSON.parse(val);
proxy = parsed;
} catch (e) {
console.log("Can't parse user drive", e);
}
}), {
network: network
});
}).nThen(function (waitFor) {
if (!network) { return void waitFor.abort(); }
Pinpad.create(network, proxy, waitFor(function (e, call) {
if (e) {
rpcError = e;
return void waitFor.abort();
}
rpc = call;
}));
}).nThen(function () {
Test(function () {
// This is only here to maybe trigger an error.
window.drive = proxy['drive'];
Test.passed();
});
});
};
var whenReady = function (cb) {
if (proxy) { return void cb(); }
if (proxy && (rpc || rpcError)) { return void cb(); }
console.log('CryptPad not ready...');
setTimeout(function () {
whenReady(cb);
@ -45,6 +98,17 @@ define([
console.log('CP receiving', data);
if (data.cmd === 'PING') {
ret.res = 'PONG';
} else if (data.cmd === 'LOGIN') {
Login.loginOrRegister(data.data.name, data.data.password, false, false, function (err) {
if (err) {
ret.error = 'LOGIN_ERROR';
srcWindow.postMessage(JSON.stringify(ret), domain);
return;
}
loadProxy(LocalStore.getUserHash());
srcWindow.postMessage(JSON.stringify(ret), domain);
});
return;
} else if (data.cmd === 'SIGN') {
if (!AUTHORIZED_DOMAINS.filter(function (x) { return x.test(domain); }).length) {
ret.error = "UNAUTH_DOMAIN";
@ -63,7 +127,16 @@ define([
}
} else if (data.cmd === 'UPDATE_LIMIT') {
return void whenReady(function () {
Cryptpad.updatePinLimit(function (e, limit, plan, note) {
if (rpcError) {
// Tell the user on accounts that there was an issue and they need to wait maximum 24h or contact an admin
ret.warning = true;
srcWindow.postMessage(JSON.stringify(ret), domain);
return;
}
rpc.updatePinLimits(function (e, limit, plan, note) {
if (e) {
ret.warning = true;
}
ret.res = [limit, plan, note];
srcWindow.postMessage(JSON.stringify(ret), domain);
});
@ -74,18 +147,8 @@ define([
srcWindow.postMessage(JSON.stringify(ret), domain);
});
nThen(function (waitFor) {
Cryptpad.ready(waitFor());
}).nThen(function (waitFor) {
Cryptpad.getUserObject(null, waitFor(function (obj) {
proxy = obj;
}));
}).nThen(function () {
console.log('IFRAME READY');
Test(function () {
// This is only here to maybe trigger an error.
window.drive = proxy['drive'];
Test.passed();
});
});
var userHash = LocalStore.getUserHash();
if (userHash) {
loadProxy(userHash);
}
});

@ -131,7 +131,7 @@ define([
if (['markdown', 'gfm'].indexOf(CodeMirror.highlightMode) === -1) { return; }
if (!$previewButton.is('.cp-toolbar-button-active')) { return; }
forceDrawPreview();
}, 150);
}, 400);
var previewTo;
$previewButton.click(function () {

@ -121,6 +121,10 @@ text.actor {
font-size: 11px;
text-height: 14px;
}
.sectionTitle, .titleText {
font-weight: bold;
}
/* Grid and axis */
.grid .tick {
stroke: lightgrey;

@ -6,22 +6,24 @@ define([
CodeMirror.defineSimpleMode("orgmode", {
start: [
{regex: /^(^\*{1,6}\s)(TODO|DOING|WAITING|NEXT){0,1}(CANCELLED|CANCEL|DEFERRED|DONE|REJECTED|STOP|STOPPED){0,1}(.*)$/, token: ["header org-level-star", "header org-todo", "header org-done", "header"]},
{regex: /(\*\s)(TODO|DOING|WAITING|NEXT|PENDING|)(CANCELLED|CANCELED|CANCEL|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/, sol: true, token: ["header level1 org-level-star","header level1 org-todo","header level1 org-done", "header level1 org-priority", "header level1", "header level1 void", "header level1 comment"]},
{regex: /(\*{1,}\s)(TODO|DOING|WAITING|NEXT|PENDING|)(CANCELLED|CANCELED|CANCEL|DEFERRED|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/, sol: true, token: ["header org-level-star","header org-todo","header org-done", "header org-priority", "header", "header void", "header comment"]},
{regex: /(\+[^\+]+\+)/, token: ["strikethrough"]},
{regex: /(\*[^\*]+\*)/, token: ["strong"]},
{regex: /(\/[^\/]+\/)/, token: ["em"]},
{regex: /(\_[^\_]+\_)/, token: ["link"]},
{regex: /(\~[^\~]+\~)/, token: ["comment"]},
{regex: /(\=[^\=]+\=)/, token: ["comment"]},
{regex: /\[\[[^\[\]]*\]\[[^\[\]]*\]\]/, token: "url"}, // links
{regex: /\[[xX\s]?\]/, token: 'qualifier'}, // checkbox
{regex: /\#\+BEGIN_[A-Z]*/, token: "comment", next: "env"}, // comments
{regex: /:?[A-Z_]+\:.*/, token: "comment"}, // property drawers
{regex: /(\#\+[A-Z_]*)(\:.*)/, token: ["keyword", 'qualifier']}, // environments
{regex: /(CLOCK\:|SHEDULED\:)(\s.+)/, token: ["comment", "keyword"]}
{regex: /\[\[[^\[\]]+\]\[[^\[\]]+\]\]/, token: "org-url"}, // links
{regex: /\[\[[^\[\]]+\]\]/, token: "org-image"}, // image
{regex: /\[[xX\s\-\_]\]/, token: 'qualifier org-toggle'}, // checkbox
{regex: /\#\+(?:(BEGIN|begin))_[a-zA-Z]*/, token: "comment", next: "env", sol: true}, // comments
{regex: /:?[A-Z_]+\:.*/, token: "comment", sol: true}, // property drawers
{regex: /(\#\+[a-zA-Z_]*)(\:.*)/, token: ["keyword", 'qualifier'], sol: true}, // environments
{regex: /(CLOCK\:|SHEDULED\:|DEADLINE\:)(\s.+)/, token: ["comment", "keyword"]}
],
env: [
{regex: /.*?\#\+END_[A-Z]*/, token: "comment", next: "start"},
{regex: /\#\+(?:(END|end))_[a-zA-Z]*/, token: "comment", next: "start", sol: true},
{regex: /.*/, token: "comment"}
]
});

@ -127,6 +127,18 @@ define([
return input;
};
dialog.selectableArea = function (value, opt) {
var attrs = merge({
readonly: 'readonly',
}, opt);
var input = h('textarea', attrs);
$(input).val(value).click(function () {
input.select();
});
return input;
};
dialog.okButton = function (content, classString) {
var sel = typeof(classString) === 'string'? 'button.ok.' + classString:'button.ok.primary';
return h(sel, { tabindex: '2', }, content || Messages.okButton);
@ -187,7 +199,8 @@ define([
dialog.tabs = function (tabs) {
var contents = [];
var titles = [];
tabs.forEach(function (tab) {
var active = 0;
tabs.forEach(function (tab, i) {
if (!tab.content || !tab.title) { return; }
var content = h('div.alertify-tabs-content', tab.content);
var title = h('span.alertify-tabs-title', tab.title);
@ -203,10 +216,11 @@ define([
});
titles.push(title);
contents.push(content);
if (tab.active) { active = i; }
});
if (contents.length) {
$(contents[0]).addClass('alertify-tabs-content-active');
$(titles[0]).addClass('alertify-tabs-active');
$(contents[active]).addClass('alertify-tabs-content-active');
$(titles[active]).addClass('alertify-tabs-active');
}
return h('div.alertify-tabs', [
h('div.alertify-tabs-titles', titles),

@ -147,6 +147,7 @@ define([
: Messages.owner_removeText;
var removeCol = UIElements.getUserGrid(msg, {
common: common,
large: true,
data: _owners,
noFilter: true
}, function () {
@ -238,6 +239,7 @@ define([
});
var addCol = UIElements.getUserGrid(Messages.owner_addText, {
common: common,
large: true,
data: _friends
}, function () {
//console.log(arguments);
@ -254,6 +256,7 @@ define([
});
var teamsList = UIElements.getUserGrid(Messages.owner_addTeamText, {
common: common,
large: true,
noFilter: true,
data: teamsData
}, function () {});
@ -739,12 +742,22 @@ define([
UIElements.getProperties = function (common, data, cb) {
var c1;
var c2;
var button = [{
className: 'primary',
name: Messages.okButton,
onClick: function () {},
keys: [13]
}];
NThen(function (waitFor) {
getPadProperties(common, data, waitFor(function (e, c) {
c1 = c[0];
c1 = UI.dialog.customModal(c[0], {
buttons: button
});
}));
getRightsProperties(common, data, waitFor(function (e, c) {
c2 = c[0];
c2 = UI.dialog.customModal(c[0], {
buttons: button
});
}));
}).nThen(function () {
var tabs = UI.dialog.tabs([{
@ -784,8 +797,6 @@ define([
var noOthers = icons.length === 0 ? '.cp-usergrid-empty' : '';
var buttonSelect = h('button', Messages.share_selectAll);
var buttonDeselect = h('button', Messages.share_deselectAll);
var inputFilter = h('input', {
placeholder: Messages.share_filterFriend
});
@ -793,9 +804,7 @@ define([
var div = h('div.cp-usergrid-container' + noOthers + (config.large?'.large':''), [
label ? h('label', label) : undefined,
h('div.cp-usergrid-filter', (config.noFilter || config.noSelect) ? undefined : [
inputFilter,
buttonSelect,
buttonDeselect
inputFilter
]),
]);
var $div = $(div);
@ -808,23 +817,8 @@ define([
$div.find('.cp-usergrid-user:not(.cp-selected):not([data-name*="'+name+'"])').hide();
}
};
$(inputFilter).on('keydown keyup change', redraw);
$(buttonSelect).click(function () {
$div.find('.cp-usergrid-user:not(.cp-selected):visible').addClass('cp-selected');
onSelect();
});
$(buttonDeselect).click(function () {
$div.find('.cp-usergrid-user.cp-selected').removeClass('cp-selected').each(function (i, el) {
var order = $(el).attr('data-order');
if (!order) { return; }
$(el).attr('style', 'order:'+order);
});
redraw();
onSelect();
});
$(div).append(h('div.cp-usergrid-grid', icons));
if (!config.noSelect) {
$div.on('click', '.cp-usergrid-user', function () {
@ -885,7 +879,8 @@ define([
var friendsList = UIElements.getUserGrid(null, {
common: common,
data: friends,
noFilter: false
noFilter: false,
large: true
}, refreshButtons);
var friendDiv = friendsList.div;
$div.append(friendDiv);
@ -911,6 +906,7 @@ define([
var teamsList = UIElements.getUserGrid(Messages.share_linkTeam, {
common: common,
noFilter: true,
large: true,
data: teams
}, refreshButtons);
$div.append(teamsList.div);
@ -1016,12 +1012,29 @@ define([
if (!hashes || (!hashes.editHash && !hashes.viewHash)) { return; }
// check if the pad is password protected
var hash = hashes.editHash || hashes.viewHash;
var href = origin + pathname + '#' + hash;
var parsedHref = Hash.parsePadUrl(href);
var hasPassword = parsedHref.hashData.password;
var makeFaqLink = function () {
var link = h('span', [
h('i.fa.fa-question-circle'),
h('a', {href: '#'}, Messages.passwordFaqLink)
]);
$(link).click(function () {
common.openURL(config.origin + "/faq.html#security-pad_password");
});
return link;
};
var parsed = Hash.parsePadUrl(pathname);
var canPresent = ['code', 'slide'].indexOf(parsed.type) !== -1;
var rights = h('div.msg.cp-inline-radio-group', [
h('label', Messages.share_linkAccess),
h('br'),
h('div.radio-group',[
UI.createRadio('accessRights', 'cp-share-editable-false',
Messages.share_linkView, true, { mark: {tabindex:1} }),
@ -1068,9 +1081,42 @@ define([
h('br'),
] : [
UI.createCheckbox('cp-share-embed', Messages.share_linkEmbed, false, { mark: {tabindex:1} }),
h('br'),
];
linkContent.push(UI.dialog.selectable('', { id: 'cp-share-link-preview', tabindex: 1 }));
linkContent.push(h('div.cp-spacer'));
linkContent.push(UI.dialog.selectableArea('', { id: 'cp-share-link-preview', tabindex: 1, rows:3}));
// Show alert if the pad is password protected
if (hasPassword) {
linkContent.push(h('div.alert.alert-primary', [
h('i.fa.fa-lock'),
Messages.share_linkPasswordAlert, h('br'),
makeFaqLink()
]));
}
// warning about sharing links
var localStore = window.cryptpadStore;
var dismissButton = h('span.fa.fa-times');
var shareLinkWarning = h('div.alert.alert-warning.dismissable',
{ style: 'display: none;' },
[
h('span.cp-inline-alert-text', Messages.share_linkWarning),
dismissButton
]);
linkContent.push(shareLinkWarning);
localStore.get('hide-alert-shareLinkWarning', function (val) {
if (val === '1') { return; }
$(shareLinkWarning).show();
$(dismissButton).on('click', function () {
localStore.put('hide-alert-shareLinkWarning', '1');
$(shareLinkWarning).remove();
});
});
var link = h('div.cp-share-modal', linkContent);
var $link = $(link);
@ -1137,7 +1183,19 @@ define([
// XXX Don't display access rights if no contacts
var contactsContent = h('div.cp-share-modal');
$(contactsContent).append(friendsList);
var $contactsContent = $(contactsContent);
$contactsContent.append(friendsList);
// Show alert if the pad is password protected
if (hasPassword) {
$contactsContent.append(h('div.alert.alert-primary', [
h('i.fa.fa-unlock'),
Messages.share_contactPasswordAlert, h('br'),
makeFaqLink()
]));
}
var contactButtons = [makeCancelButton(),
friendsObject.button];
@ -1156,9 +1214,18 @@ define([
};
var embedContent = [
h('p', Messages.viewEmbedTag),
h('br'),
UI.dialog.selectable(getEmbedValue(), { id: 'cp-embed-link-preview', tabindex: 1 })
UI.dialog.selectableArea(getEmbedValue(), { id: 'cp-embed-link-preview', tabindex: 1, rows: 3})
];
// Show alert if the pad is password protected
if (hasPassword) {
embedContent.push(h('div.alert.alert-primary', [
h('i.fa.fa-lock'), ' ',
Messages.share_embedPasswordAlert, h('br'),
makeFaqLink()
]));
}
var embedButtons = [
makeCancelButton(), {
className: 'primary',
@ -1187,13 +1254,15 @@ define([
// Create modal
var tabs = [{
title: Messages.share_linkCategory,
icon: "fa fa-link",
content: frameLink
}, {
title: Messages.share_contactCategory,
icon: "fa fa-address-book",
content: frameContacts
content: frameContacts,
active: hasFriends
}, {
title: Messages.share_linkCategory,
icon: "fa fa-link",
content: frameLink,
active: !hasFriends
}, {
title: Messages.share_embedCategory,
icon: "fa fa-code",
@ -1261,6 +1330,21 @@ define([
if (!hashes.fileHash) { throw new Error("You must provide a file hash"); }
var url = origin + pathname + '#' + hashes.fileHash;
// check if the file is password protected
var parsedHref = Hash.parsePadUrl(url);
var hasPassword = parsedHref.hashData.password;
var makeFaqLink = function () {
var link = h('span', [
h('i.fa.fa-question-circle'),
h('a', {href: '#'}, Messages.passwordFaqLink)
]);
$(link).click(function () {
common.openURL(config.origin + "/faq.html#security-pad_password");
});
return link;
};
var getLinkValue = function () { return url; };
var makeCancelButton = function() {
@ -1272,9 +1356,40 @@ define([
// Share link tab
var linkContent = [
UI.dialog.selectable(getLinkValue(), { id: 'cp-share-link-preview', tabindex: 1 })
UI.dialog.selectableArea(getLinkValue(), { id: 'cp-share-link-preview', tabindex: 1, rows:2 })
];
// Show alert if the pad is password protected
if (hasPassword) {
linkContent.push(h('div.alert.alert-primary', [
h('i.fa.fa-lock'),
Messages.share_linkPasswordAlert, h('br'),
makeFaqLink()
]));
}
// warning about sharing links
var localStore = window.cryptpadStore;
var dismissButton = h('span.fa.fa-times');
var shareLinkWarning = h('div.alert.alert-warning.dismissable',
{ style: 'display: none;' },
[
h('span.cp-inline-alert-text', Messages.share_linkWarning),
dismissButton
]);
linkContent.push(shareLinkWarning);
localStore.get('hide-alert-shareLinkWarning', function (val) {
if (val === '1') { return; }
$(shareLinkWarning).show();
$(dismissButton).on('click', function () {
localStore.put('hide-alert-shareLinkWarning', '1');
$(shareLinkWarning).remove();
});
});
var link = h('div.cp-share-modal', linkContent);
var linkButtons = [
@ -1307,7 +1422,17 @@ define([
var friendsList = friendsObject.content;
var contactsContent = h('div.cp-share-modal');
$(contactsContent).append(friendsList);
var $contactsContent = $(contactsContent);
$contactsContent.append(friendsList);
// Show alert if the pad is password protected
if (hasPassword) {
$contactsContent.append(h('div.alert.alert-primary', [
h('i.fa.fa-unlock'),
Messages.share_contactPasswordAlert, h('br'),
makeFaqLink()
]));
}
var contactButtons = [makeCancelButton(),
friendsObject.button];
@ -1321,12 +1446,20 @@ define([
// Embed tab
var embed = h('div.cp-share-modal', [
h('p', Messages.fileEmbedScript),
h('br'),
UI.dialog.selectable(common.getMediatagScript()),
h('p', Messages.fileEmbedTag),
h('br'),
UI.dialog.selectable(common.getMediatagFromHref(fileData)),
]);
// Show alert if the pad is password protected
if (hasPassword) {
embed.append(h('div.alert.alert-primary', [
h('i.fa.fa-lock'), ' ',
Messages.share_embedPasswordAlert, h('br'),
makeFaqLink()
]));
}
var embedButtons = [{
className: 'cancel',
name: Messages.cancel,
@ -1349,13 +1482,15 @@ define([
// Create modal
var tabs = [{
title: Messages.share_linkCategory,
icon: "fa fa-link",
content: frameLink
}, {
title: Messages.share_contactCategory,
icon: "fa fa-address-book",
content: frameContacts
content: frameContacts,
active: hasFriends,
}, {
title: Messages.share_linkCategory,
icon: "fa fa-link",
content: frameLink,
active: !hasFriends
}, {
title: Messages.share_embedCategory,
icon: "fa fa-code",
@ -1759,7 +1894,7 @@ define([
if (e) { return void console.error(e); }
UIElements.getProperties(common, data, function (e, $prop) {
if (e) { return void console.error(e); }
UI.alert($prop[0], undefined, true);
UI.openCustomModal($prop[0]);
});
});
});

@ -84,7 +84,7 @@ define([
var defaultCode = renderer.code;
renderer.code = function (code, language) {
if (language === 'mermaid' && (code.match(/^sequenceDiagram/) || code.match(/^graph/))) {
if (language === 'mermaid' && code.match(/^(graph|pie|gantt|sequenceDiagram|classDiagram|gitGraph)/)) {
return '<pre class="mermaid">'+code+'</pre>';
} else {
return defaultCode.apply(renderer, arguments);
@ -197,7 +197,6 @@ define([
'APPLET',
'VIDEO', // privacy implications of videos are the same as images
'AUDIO', // same with audio
'SVG'
];
var unsafeTag = function (info) {
/*if (info.node && $(info.node).parents('media-tag').length) {
@ -307,8 +306,39 @@ define([
var Dom = domFromHTML($('<div>').append($div).html());
$content[0].normalize();
$content.find('pre.mermaid[data-processed="true"]').remove();
var mermaid_source = [];
var mermaid_cache = {};
// iterate over the unrendered mermaid inputs, caching their source as you go
$(newDomFixed).find('pre.mermaid').each(function (index, el) {
if (el.childNodes.length === 1 && el.childNodes[0].nodeType === 3) {
var src = el.childNodes[0].wholeText;
el.setAttribute('mermaid-source', src);
mermaid_source[index] = src;
}
});
// iterate over rendered mermaid charts
$content.find('pre.mermaid:not([processed="true"])').each(function (index, el) {
// retrieve the attached source code which it was drawn
var src = el.getAttribute('mermaid-source');
// check if that source exists in the set of charts which are about to be rendered
if (mermaid_source.indexOf(src) === -1) {
// if it's not, then you can remove it
if (el.parentNode && el.parentNode.children.length) {
el.parentNode.removeChild(el);
}
} else if (el.childNodes.length === 1 && el.childNodes[0].nodeType !== 3) {
// otherwise, confirm that the content of the rendered chart is not a text node
// and keep a copy of it
mermaid_cache[src] = el.childNodes[0];
}
});
var oldDom = domFromHTML($content[0].outerHTML);
var patch = makeDiff(oldDom, Dom, id);
if (typeof(patch) === 'string') {
throw new Error(patch);
@ -348,8 +378,32 @@ define([
var target = document.getElementById($a.attr('data-href'));
if (target) { target.scrollIntoView(); }
});
// loop over mermaid elements in the rendered content
$content.find('pre.mermaid').each(function (index, el) {
// since you've simply drawn the content that was supplied via markdown
// you can assume that the index of your rendered charts matches that
// of those in the markdown source.
var src = mermaid_source[index];
el.setAttribute('mermaid-source', src);
var cached = mermaid_cache[src];
// check if you had cached a pre-rendered instance of the supplied source
if (typeof(cached) !== 'object') { return; }
// if there's a cached rendering, empty out the contained source code
// which would otherwise be drawn again.
// apparently this is the fastest way to empty out an element
while (el.firstChild) { el.removeChild(el.firstChild); } //el.innerHTML = '';
// insert the cached graph
el.appendChild(cached);
// and set a flag indicating that this graph need not be reprocessed
el.setAttribute('data-processed', true);
});
try {
Mermaid.init();
// finally, draw any graphs which have changed and were thus not cached
Mermaid.init(undefined, $content.find('pre.mermaid:not([data-processed="true"])'));
} catch (e) { console.error(e); }
}
};

@ -79,7 +79,7 @@ define([
var faColor = 'cptools-palette';
var faTrash = 'fa-trash';
var faDelete = 'fa-eraser';
var faProperties = 'fa-database';
var faProperties = 'fa-info-circle';
var faTags = 'fa-hashtag';
var faUploadFiles = 'cptools-file-upload';
var faUploadFolder = 'cptools-folder-upload';
@ -4187,7 +4187,7 @@ define([
}
getProperties(el, function (e, $prop) {
if (e) { return void logError(e); }
UI.alert($prop[0], undefined, true);
UI.openCustomModal($prop[0]);
});
}
else if ($this.hasClass("cp-app-drive-context-hashtag")) {

@ -43,7 +43,7 @@ define([
MessengerUI.create = function ($container, common, toolbar) {
var metadataMgr = common.getMetadataMgr();
var origin = metadataMgr.getPrivateData().origin;
var readOnly = metadataMgr.getPrivateData().readOnly || toolbar.readOnly;
var readOnly = metadataMgr.getPrivateData().readOnly || (toolbar && toolbar.readOnly);
var isApp = typeof(toolbar) !== "undefined";

@ -769,18 +769,18 @@ define([
continue;
}
var href;
var decryptedHref;
try {
href = el.href && ((el.href.indexOf('#') !== -1) ? el.href : exp.cryptor.decrypt(el.href));
decryptedHref = el.href && ((el.href.indexOf('#') !== -1) ? el.href : exp.cryptor.decrypt(el.href));
} catch (e) {}
if (href && href.indexOf('#') === -1) {
if (decryptedHref && decryptedHref.indexOf('#') === -1) {
// If we can't decrypt the href, it means we don't have the correct secondaryKey and we're in readOnly mode:
// abort now, we won't be able to fix anything anyway
continue;
}
var parsed = Hash.parsePadUrl(href || el.roHref);
var parsed = Hash.parsePadUrl(decryptedHref || el.roHref);
var secret;
// Clean invalid hash
@ -797,9 +797,9 @@ define([
}
// If we have an edit link, check the view link
if (href && parsed.hashData.type === "pad" && parsed.hashData.version) {
if (decryptedHref && parsed.hashData.type === "pad" && parsed.hashData.version) {
if (parsed.hashData.mode === "view") {
el.roHref = href;
el.roHref = decryptedHref;
delete el.href;
} else if (!el.roHref) {
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
@ -818,7 +818,9 @@ define([
}
// Fix href
if (href && href.slice(0,1) !== '/') { el.href = exp.cryptor.encrypt(Hash.getRelativeHref(el.href)); }
if (decryptedHref && decryptedHref.slice(0,1) !== '/') {
el.href = exp.cryptor.encrypt(Hash.getRelativeHref(decryptedHref));
}
// Fix creation time
if (!el.ctime) { el.ctime = el.atime; }
// Fix title

@ -488,6 +488,20 @@ define([
Cryptpad.storeInTeam(data, cb);
});
sframeChan.on('EV_GOTO_URL', function (url) {
if (url) {
window.location.href = url;
} else {
window.location.reload();
}
});
sframeChan.on('EV_OPEN_URL', function (url) {
if (url) {
window.open(url);
}
});
};
addCommonRpc(sframeChan);
@ -956,20 +970,6 @@ define([
});
});
sframeChan.on('EV_GOTO_URL', function (url) {
if (url) {
window.location.href = url;
} else {
window.location.reload();
}
});
sframeChan.on('EV_OPEN_URL', function (url) {
if (url) {
window.open(url);
}
});
sframeChan.on('Q_PIN_GET_USAGE', function (teamId, cb) {
Cryptpad.isOverPinLimit(teamId, function (err, overLimit, data) {
cb({

@ -533,7 +533,7 @@ MessengerUI, Messages) {
Common.getSframeChannel().event('EV_SHARE_OPEN', {
hidden: true
});
$shareBlock.click(function () {
$shareBlock.click(function () {
var title = (config.title && config.title.getTitle && config.title.getTitle())
|| (config.title && config.title.defaultName)
|| "";

@ -54,6 +54,7 @@
.kanban-item-text {
cursor: text;
overflow-wrap: anywhere;
flex: 1;
}
}
@ -67,7 +68,7 @@
margin-right: 10px;
min-width: 0;
overflow: hidden;
white-space: nowrap;
//white-space: nowrap;
text-overflow: ellipsis;
}
#kanban-edit {

@ -286,7 +286,7 @@ define([
kanban.inEditMode = true;
// create a form to enter element
var boardId = $(el.parentNode.parentNode).attr("data-id");
var $item = $('<div>', {'class': 'kanban-item'});
var $item = $('<div>', {'class': 'kanban-item new-item'});
var $input = getInput().val(name).appendTo($item);
kanban.addForm(boardId, $item[0]);
$input.focus();

@ -147,9 +147,13 @@
self.drake = self.dragula(self.boardContainer, {
moves: function (el, source, handle, sibling) {
if (self.options.readOnly) { return false; }
if (el.classList.contains('new-item')) { return false; }
return handle.classList.contains('kanban-item');
},
accepts: function (el, target, source, sibling) {
if (sibling === null) {
return false;
}
if (self.options.readOnly) { return false; }
return true;
},
@ -349,7 +353,7 @@
titleBoard = document.createElement('div');
titleBoard.classList.add('kanban-title-board');
titleBoard.innerHTML = board.title;
titleBoard.setAttribute('title', board.title);
//titleBoard.setAttribute('title', board.title);
titleBoard.clickfn = board.boardTitleClick;
__onboardTitleClickHandler(titleBoard);
headerBoard.appendChild(titleBoard);

@ -51,6 +51,7 @@ define([
'cp-settings-info-block',
'cp-settings-displayname',
'cp-settings-language-selector',
'cp-settings-resettips',
'cp-settings-logout-everywhere',
'cp-settings-autostore',
'cp-settings-userfeedback',
@ -67,7 +68,6 @@ define([
],
'drive': [
'cp-settings-drive-duplicate',
'cp-settings-resettips',
'cp-settings-thumbnails',
'cp-settings-drive-backup',
'cp-settings-drive-import-local',
@ -835,7 +835,7 @@ define([
var localStore = window.cryptpadStore;
$button.click(function () {
Object.keys(localStore.store).forEach(function (k) {
if(k.slice(0, 9) === "hide-info") {
if(/^(hide-(info|alert))/.test(k)) {
localStore.put(k, null);
}
});

Loading…
Cancel
Save