Merge branch 'staging' into offline2

This commit is contained in:
ansuz 2021-02-18 15:19:49 +05:30
commit 0110342ef5
43 changed files with 796 additions and 300 deletions

View File

@ -1,3 +1,49 @@
# Pending
* remove round corners on limit bar
* update prompts related to password protection in various modals
* script to identify unused translations
* offline mode for file app
* display subscribe button on home page if subscriptions are supported
* display a home page notice via application_config.js via supplied HTML
* link to our sponsors websites via their logos
* OnlyOffice sheets
* use configured language from CryptPad
* fix a sorting issue caused by the overly eager suppression of a modal
* rich text
* use white background for rich text documents even in dark mode
* display button to adjust document width inline instead of in toolbar
* display comments inline on mobile instead of on the right
* translations
* russian
* style fixes
* text media-tags use markdown's block styles
* 404 page
* uses the dark theme's background color correctly
* remove an unnecessary scrollbar
* assert page uses dark mode
* separate archive deletion code from inactive pad removal
* darkfix branch
* removed unused colors
* admin support last message is no longer red
* sidebar hint uses new grey name
* more severe fade on drive icon hover
* drive info box inline links are now styled
* autocomplete dropdown styles weren't applied
* notifications bell now uses same text color as toolbar title
* filepicker background color
* chat colors
* in pads
* in the contacts app
* server updates
* `npm i` to get latest dependencies
* messages are not acknowledged or broadcast until they have been validated and written to the disk
* see [#553](https://github.com/xwiki-labs/cryptpad/issues/553)
* optimized GET_OLDER_HISTORY
* debugging app
* serverHash is included in the history of decrypted messages
* link to the docs from the support page's ticket creation interface
# 4.1.0 (B)
## Goals

View File

@ -181,11 +181,11 @@ define([
Pages.subscribeButton = function (onClick) {
var _link = h('a', {
href: "/accounts/",
href: AppConfig.upgradeURL || "/accounts/",
});
var subscribe = h('button', [
Msg.subscribe_button
Msg.features_f_subscribe,
]);
$(subscribe).click(function () {

View File

@ -72,6 +72,14 @@ define([
});
UI.addTooltips();
var subscribeButton;
/* Display a subscribe button if they are enabled and the button's translation key exists */
if (Config.allowSubscriptions) {
subscribeButton = Pages.subscribeButton(function () {
Feedback.send('HOME_SUBSCRIBE_CRYPTPAD');
});
}
var blocks = [
h('div.row.cp-page-section', [
h('div.col-sm-6',
@ -104,6 +112,7 @@ define([
h('div.col-sm-6', [
h('h2', Msg.home_support_title),
Pages.setHTML(h('span'), Msg.home_support),
subscribeButton,
Pages.crowdfundingButton(function () {
Feedback.send('HOME_SUPPORT_CRYPTPAD');
}),
@ -111,6 +120,17 @@ define([
])
];
var notice;
/* Admins can specify a notice to display in application_config.js via the `homeNotice` attribute.
If the text is the key for the translation system then then the most appropriate translated text
will be displayed. Otherwise, the direct text will be included as HTML.
*/
if (AppConfig.homeNotice) {
notice = h('div.alert.alert-info', Pages.setHTML(h('span'), [
Msg[AppConfig.homeNotice] || AppConfig.homeNotice
]));
}
return [
h('div#cp-main', [
Pages.infopageTopbar(),
@ -129,6 +149,7 @@ define([
icons,
])
]),
notice,
blocks
]),
Pages.infopageFooter(),

View File

@ -7,6 +7,15 @@ define([
], function (Config, h, Msg, Pages, Feedback) {
var urlArgs = Config.requireConf.urlArgs;
var logoLink = function (alt, src, url, cls) {
var img = h('img' + (cls || ''), {
src: src + '?' + urlArgs,
alt: alt,
});
if (!url) { return img; }
return h('a', { href: url, }, img);
};
return function () {
return h('div#cp-main', [
Pages.infopageTopbar(),
@ -66,26 +75,17 @@ define([
}),
]),
h('div.col-md-6.order-md-1.small-logos', [
h('img.cp-img-invert', {
src: '/customize/images/logo_ngi.png?' + urlArgs,
alt: 'NGI Award 2019'
}),
h('img.cp-img-invert', {
src: '/customize/images/logo_nlnet.svg?' + urlArgs,
alt: 'NLNet Foundation logo'
}),
h('img', {
src: '/customize/images/logo_bpifrance.svg?' + urlArgs,
alt: 'BPI France logo'
}),
h('img', {
src: '/customize/images/logo_moss.jpg?' + urlArgs,
alt: 'Mozilla Open Source Support logo'
}),
h('img', {
src: '/customize/images/logo_ngi_trust.png?' + urlArgs,
alt: 'NGI Trust logo'
}),
logoLink('NGI Award 2019', '/customize/images/logo_ngi.png',
'https://www.ngi.eu/', '.cp-img-invert'),
logoLink('NLnet Foundation logo', '/customize/images/logo_nlnet.svg',
'https://nlnet.nl', '.cp-img-invert'),
logoLink('BPI France logo', '/customize/images/logo_bpifrance.svg',
'https://bpifrance.com'),
logoLink('Mozilla Open Source Support logo', '/customize/images/logo_moss.jpg',
'https://www.mozilla.org/en-US/moss/'),
logoLink('NGI Trust logo', '/customize/images/logo_ngi_trust.png',
'https://www.ngi.eu/ngi-projects/ngi-trust/'),
]),
]),
h('div.row.cp-page-section', [

View File

@ -31,16 +31,6 @@
@cryptpad_color_brand_fader: fade(@cryptpad_color_brand, 50%);
@cryptpad_color_brand_fadest: fade(@cryptpad_color_brand, 25%);
@cryptpad_color_hint_grey: #777;
@cryptpad_color_dark_grey: #999999;
@cryptpad_color_neutral_grey: #aaaaaa;
@cryptpad_color_neutral2_grey: #cccccc;
@cryptpad_color_help_grey: #dddddd;
@cryptpad_color_grey: #e0e0e0;
@cryptpad_color_toolbar_grey: #EEEEEE;
@cryptpad_color_light_grey: #f1f1f1;
@cryptpad_color_lighter_grey: #f9f9f9;
@cryptpad_color_white: #FFF;
@cryptpad_color_grey_50: #FAFAFA;
@cryptpad_color_grey_100: #F5F5F5;
@ -278,8 +268,8 @@
// Support
@cp_support-bg: @cryptpad_color_grey_800;
@cp_support-msg-fg: @cryptpad_text_col;
@cp_support-msg-bg: @cryptpad_color_grey_900;
@cp_support-header-bg: @cryptpad_color_grey_800;
@cp_support-msg-bg: @cryptpad_color_grey_700;
@cp_support-header-bg: @cryptpad_color_grey_700;
// Toolbar
@cp_toolbar-bg: @cryptpad_color_grey_850;
@ -328,13 +318,11 @@
@cp_accounts-tab-hover: @cryptpad_color_grey_700;
@cp_accounts-tab-border: @cryptpad_color_grey_600;
// Admin
@cp_admin-isadmin-fg: @cryptpad_color_brand;
@cp_admin-isadmin-bg: @cryptpad_color_white;
@cp_admin-premium-fg: @cryptpad_color_light_red;
// Admin Support
@cp_admin-isadmin-bg: @cryptpad_color_brand_fadest;
@cp_admin-premium-bg: @cryptpad_color_red_fader;
@cp_admin-last-fg: @cryptpad_color_light_red;
@cp_admin-last-bg: @cryptpad_color_red_fader;
@cp_admin-last-fg: @cryptpad_text_col;
@cp_admin-last-bg: @cryptpad_color_grey_700;
// Code
@cp_preview-bg: @cryptpad_color_grey_900;
@ -387,6 +375,7 @@
// Rich text
@cp_pad-fg: @cryptpad_text_col;
@cp_pad-icon-filter: invert();
@cp_pad-resize: @cryptpad_color_grey_800;
// Comments
@cp_comments-fg: @cryptpad_text_col;
@cp_comments-bg: @cryptpad_color_grey_800;

View File

@ -31,16 +31,6 @@
@cryptpad_color_brand_fader: fade(@cryptpad_color_brand, 50%);
@cryptpad_color_brand_fadest: fade(@cryptpad_color_brand, 25%);
@cryptpad_color_hint_grey: #777;
@cryptpad_color_dark_grey: #999999;
@cryptpad_color_neutral_grey: #aaaaaa;
@cryptpad_color_neutral2_grey: #cccccc;
@cryptpad_color_help_grey: #dddddd;
@cryptpad_color_grey: #e0e0e0;
@cryptpad_color_toolbar_grey: #EEEEEE;
@cryptpad_color_light_grey: #f1f1f1;
@cryptpad_color_lighter_grey: #f9f9f9;
@cryptpad_color_white: #FFF;
@cryptpad_color_grey_50: #FAFAFA;
@cryptpad_color_grey_100: #F5F5F5;
@ -76,7 +66,7 @@
@cryptpad_color_yellow_fade: fade(#FFE69C, 50%); // different from dark
@cryptpad_color_lighter_blue: #d2e1f2;
@cryptpad_color_link: #0275D8;
@cryptpad_color_link: @cryptpad_color_brand;
// Premium plans colors
@cryptpad_color_basic: #DDEFFF;
@ -112,7 +102,7 @@
@cp_alertify-log-bg: fade(@cryptpad_color_brand, 90%);
@cp_alertify-log-fg: @cryptpad_color_white;
@cp_alertify-warn-bg: rgba(205, 37, 50);
@cp_alertify-disable-border: @cryptpad_color_dark_grey;
@cp_alertify-disable-border: @cryptpad_color_grey_500;
// Forms
@cp_forms-fg: @cryptpad_text_col;
@ -157,18 +147,18 @@
@cp_buttons-cancel-border: #949494;
// Sidebar layout
@cp_sidebar-left-bg: @cryptpad_color_toolbar_grey;
@cp_sidebar-left-bg: @cryptpad_color_grey_200;
@cp_sidebar-left-fg: @cryptpad_text_col;
@cp_sidebar-right-bg: @cryptpad_color_white;
@cp_sidebar-right-fg: @cryptpad_text_col;
@cp_sidebar-left-active: @cp_sidebar-right-bg;
@cp_sidebar-hint: @cryptpad_color_hint_grey;
@cp_sidebar-hint: @cryptpad_color_grey_600;
// Drive
@cp_drive-bg: @cp_sidebar-right-bg;
@cp_drive-fg: @cp_sidebar-right-fg;
@cp_drive-header-fg: fade(@cryptpad_text_col, 70%);
@cp_drive-icon-hover: fade(@cryptpad_text_col, 5%);
@cp_drive-icon-hover: fade(@cryptpad_text_col, 15%);
@cp_drive-icon-border: fade(@cryptpad_text_col, 20%);
@cp_drive-thumb-bg: transparent;
@cp_drive-selected-bg: fade(@cryptpad_text_col, 10%);
@ -199,18 +189,18 @@
// Dropdown
@cp_dropdown-fg: @cryptpad_text_col;
@cp_dropdown-bg: @cryptpad_color_lighter_grey;
@cp_dropdown-bg-hover: @cryptpad_color_light_grey;
@cp_dropdown-bg-active: @cryptpad_color_grey;
@cp_dropdown-bg: @cryptpad_color_grey_100;
@cp_dropdown-bg-hover: @cryptpad_color_grey_100;
@cp_dropdown-bg-active: @cryptpad_color_grey_300;
// Rendered Markdown
@cp_markdown-bg: @cryptpad_color_lighter_grey;
@cp_markdown-bg: @cryptpad_color_grey_100;
@cp_markdown-border: @cryptpad_color_grey_400;
@cp_markdown-block-fg: @cryptpad_text_col;
@cp_markdown-block-bg: @cryptpad_color_grey_200;
// Avatar
@cp_avatar-bg: @cryptpad_color_grey;
@cp_avatar-bg: @cryptpad_color_grey_300;
@cp_avatar-fg: @cryptpad_text_col;
// Corner
@ -224,7 +214,7 @@
@cp_creation-button-bg: @cryptpad_color_brand;
@cp_creation-button-fg: @cryptpad_color_white;
@cp_creation-error-bg: @cryptpad_color_blue;
@cp_creation-error-fg: @cryptpad_color_light_grey;
@cp_creation-error-fg: @cryptpad_color_grey_100;
// Export
@cp_export-bg: @cryptpad_color_grey_200;
@ -235,20 +225,20 @@
// File upload
@cp_upload-fg: @cryptpad_color_brand;
@cp_upload-header: @cryptpad_color_help_grey;
@cp_upload-progress: @cryptpad_color_help_grey;
@cp_upload-header: @cryptpad_color_grey_300;
@cp_upload-progress: @cryptpad_color_grey_300;
// Help
@cp_help-bg: @cryptpad_color_help_grey;
@cp_help-bg: @cryptpad_color_grey_300;
@cp_help-fg: @cryptpad_text_col;
@cp_help-link: @cryptpad_color_link;
// Static pages
@cp_static-bg: @cryptpad_color_toolbar_grey;
@cp_static-bg: @cryptpad_color_grey_200;
@cp_static-fg: @cryptpad_text_col;
@cp_static-link: @cryptpad_color_brand;
@cp_static-title: @cryptpad_color_brand;
@cp_static-footer: @cryptpad_color_help_grey;
@cp_static-footer: @cryptpad_color_grey_300;
@cp_static-footer-border: @cryptpad_color_white;
@cp_static-topbar-fg: @cryptpad_color_brand;
@cp_static-card-bg: @cryptpad_color_white;
@ -262,8 +252,8 @@
@cp-limit-bar-above: @cryptpad_color_red;
// Mentions
@cp_mentions-bg: @cryptpad_color_grey;
@cp_mentions-hover: @cryptpad_color_help_grey;
@cp_mentions-bg: @cryptpad_color_grey_300;
@cp_mentions-hover: @cryptpad_color_grey_300;
// Autocomplete
@cp_autocomplete-bg: @cryptpad_color_grey_100;
@ -273,13 +263,13 @@
// Modals
@cp_access-overlay: fade(@cryptpad_color_white, 50%);
@cp_snapshots-hover: @cryptpad_color_help_grey;
@cp_snapshots-hover: @cryptpad_color_grey_300;
// Support
@cp_support-bg: @cryptpad_color_lighter_grey;
@cp_support-bg: @cryptpad_color_grey_200;
@cp_support-msg-fg: @cryptpad_text_col;
@cp_support-msg-bg: @cryptpad_color_grey;
@cp_support-header-bg: @cryptpad_color_help_grey;
@cp_support-msg-bg: @cryptpad_color_grey_300;
@cp_support-header-bg: @cryptpad_color_grey_300;
// Toolbar
@cp_toolbar-bg: @cryptpad_color_grey_200;
@ -297,9 +287,9 @@
@cp_history-fg: @cp_toolbar-bottom-fg;
// Tokenfield
@cp_token-bg: @cryptpad_color_neutral2_grey;
@cp_token-bg: @cryptpad_color_grey_400;
@cp_token-fg: @cryptpad_text_col;
@cp_token-bg-hover: @cryptpad_color_neutral_grey;
@cp_token-bg-hover: @cryptpad_color_grey_500;
@cp_token-invalid: @cryptpad_color_warn_red;
// Usergrid
@ -311,7 +301,7 @@
@cp_shadow-color: fade(@cryptpad_color_black, 40%);
// Apps
@cp_app-bg: @cryptpad_color_light_grey;
@cp_app-bg: @cryptpad_color_grey_100;
@cp_app-fg: @cryptpad_text_col;
// Accounts
@ -321,20 +311,18 @@
@cp_accounts-active: @cryptpad_color_light_green;
@cp_accounts-inactive: @cryptpad_color_light_red;
@cp_accounts-mysubs-alert: @cryptpad_color_white;
@cp_accounts-mysubs-bg: @cryptpad_color_toolbar_grey;
@cp_accounts-mysubs-bg: @cryptpad_color_grey_200;
@cp_accounts-mysubs-fg: @cryptpad_text_col;
@cp_accounts-contact-hover: fade(@cryptpad_color_black, 20%);
@cp_accounts-tab-bg: @cryptpad_color_toolbar_grey;
@cp_accounts-tab-hover: @cryptpad_color_grey;
@cp_accounts-tab-border: @cryptpad_color_neutral_grey;
@cp_accounts-tab-bg: @cryptpad_color_grey_200;
@cp_accounts-tab-hover: @cryptpad_color_grey_300;
@cp_accounts-tab-border: @cryptpad_color_grey_500;
// Admin
@cp_admin-isadmin-fg: @cryptpad_color_brand;
@cp_admin-isadmin-bg: @cryptpad_color_white;
@cp_admin-premium-fg: @cryptpad_color_warn_red;
@cp_admin-premium-bg: lighten(@cryptpad_color_warn_red, 25%);
@cp_admin-last-fg: @cryptpad_color_warn_red;
@cp_admin-last-bg: lighten(@cryptpad_color_orange, 25%);
@cp_admin-isadmin-bg: @cryptpad_color_brand_fadest;
@cp_admin-premium-bg: @cryptpad_color_red_fader;
@cp_admin-last-fg: @cryptpad_text_col;
@cp_admin-last-bg: @cryptpad_color_grey_300;
// Code
@cp_preview-bg: @cryptpad_color_white;
@ -343,7 +331,7 @@
// Debug
@cp_debug-hover: fade(@cryptpad_color_black, 10%);
@cp_debug-icon-hover: @cryptpad_color_dark_grey;
@cp_debug-icon-hover: @cryptpad_color_grey_300;
// Mediatag
@cp_mediatag-text-bg: @cryptpad_color_white;
@ -387,6 +375,7 @@
// Rich text
@cp_pad-fg: @cryptpad_text_col;
@cp_pad-icon-filter: none;
@cp_pad-resize: @cryptpad_text_col;
// Comments
@cp_comments-fg: @cryptpad_text_col;
@cp_comments-bg: @cryptpad_color_white;
@ -396,14 +385,14 @@
// Poll
@cp_poll-th-bg: @cryptpad_color_lighter_blue;
@cp_poll-th-fg: @cryptpad_text_col;
@cp_poll-uncommitted-bg: @cryptpad_color_toolbar_grey;
@cp_poll-border-color: @cryptpad_color_hint_grey;
@cp_poll-uncommitted-bg: @cryptpad_color_grey_200;
@cp_poll-border-color: @cryptpad_color_grey_600;
@cp_poll-cell-fg: @cryptpad_text_col;
@cp_poll-unset: @cryptpad_color_help_grey;
@cp_poll-unset: @cryptpad_color_grey_300;
@cp_poll-yes: @cryptpad_color_light_green;
@cp_poll-no: @cryptpad_color_light_red;
@cp_poll-maybe: @cryptpad_color_light_yellow;
@cp_poll-hint: @cryptpad_color_dark_grey;
@cp_poll-hint: @cryptpad_color_grey_500;
// Profile
@cp_profile-border: @cryptpad_color_grey_400;
@ -422,6 +411,6 @@
// Whiteboard
@cp_whiteboard-board-bg: @cryptpad_color_white;
@cp_whiteboard-board-border: @cryptpad_color_hint_grey;
@cp_whiteboard-board-border: @cryptpad_color_grey_600;
@cp_whiteboard-bg: @cp_app-bg;
@cp_whiteboard-fg: @cryptpad_text_col;

View File

@ -456,6 +456,10 @@
background: @cp_drive-infobox-bg;
color: @cp_drive-infobox-fg;
cursor: default;
a {
color: @cryptpad_color_link;
text-decoration: underline;
}
span {
cursor: pointer;
float: right;

View File

@ -7,12 +7,12 @@
--LessLoader_require: LessLoader_currentFile();
}
& {
.ui-autocomplete {
ul.ui-autocomplete {
background-color: @cp_autocomplete-bg;
color: @cp_autocomplete-fg;
}
ul.ui-menu {
border: 1px solid @cp_autocomplete-border; // FIXME doesn't work
ul.ui-menu.ui-widget {
border: 1px solid @cp_autocomplete-border;
.ui-state-active {
background-color: @cp_autocomplete-hover;
color: @cp_autocomplete-fg;

View File

@ -17,7 +17,6 @@
max-width: 100%;
margin: 5px;
box-sizing: border-box;
border-radius: 3px;
background: @cp-limit-bar-bg;
position: relative;
text-align: center;

View File

@ -52,13 +52,6 @@
text-overflow: ellipsis;
}
}
div.plain-text-reader {
background: @cp_markdown-bg;
padding: 10px;
color: black;
text-align: left;
}
}
.mediatag_cryptpad() {
@ -154,6 +147,13 @@
color: @cryptpad_text_col;
background-color: @cp_markdown-block-bg;
}
div.plain-text-reader {
background: @cp_markdown-block-bg;
padding: 10px;
color: @cp_markdown-block-fg;
text-align: left;
}
}
.markdown_cryptpad() {

View File

@ -10,7 +10,7 @@
@invert: @bg-color;
@msg-bg-color-light: contrast(@invert, lighten(@bg-color, 5%), darken(@bg-color, 5%));
@msg-bg-color-lighter: contrast(@invert, lighten(@bg-color, 10%), darken(@bg-color, 10%));
@msg-bg-color-dark: contrast(@bg-color, lighten(@bg-color, 5%), darken(@bg-color, 5%));
@msg-bg-color-dark: fade(@cp_messenger-fg, 10%);
@msg-bg-color-darker: contrast(@bg-color, lighten(@bg-color, 10%), darken(@bg-color, 10%));
};
.messenger_main(
@ -275,8 +275,8 @@
}
.cp-app-contacts-tips {
margin: 1em;
background-color: @msg-bg-color-light;
background-color: var(--msg-bg-color-light);
background-color: @msg-bg-color-dark;
background-color: var(--msg-bg-color-dark);
font-size: 14px;
padding: 10px;
position: relative;

View File

@ -110,7 +110,7 @@
input {
flex: 1;
//border-radius: 0.25em 0 0 0.25em;
border: 1px solid @cryptpad_color_neutral_grey;
border: 1px solid @cryptpad_color_grey_500;
border-right: 0px;
}
button {

View File

@ -5,7 +5,7 @@
--LessLoader_require: LessLoader_currentFile();
}
& {
.ui-autocomplete {
ul.ui-autocomplete {
z-index: 100001; // alertify + 1
}
.tokenfield {

View File

@ -399,6 +399,9 @@
button {
.toolbar_button;
&.cp-notifications-bell {
color: @cryptpad_text_col;
}
}
.cp-toolbar-limit {

View File

@ -3,14 +3,13 @@
html, body {
.font_main();
height: 100%;
margin: 0px;
padding: 0px;
background-color: @cp_static-bg; // FIXME doesn't get applied
background-color: @cp_static-bg !important;
color: @cryptpad_text_col;
font-family: "IBM Plex Mono";
#cp-main {
height: 100vh;
margin: 0px;
width: 100%;
padding-top: 5%;

View File

@ -0,0 +1,13 @@
@import (reference) "../include/colortheme-all.less";
@import (reference) "../include/font.less";
html, body {
.font_main();
height: 100%;
margin: 0px;
padding: 0px;
background-color: @cp_static-bg !important;
color: @cryptpad_text_col;
font-family: "IBM Plex Mono";
}

View File

@ -30,8 +30,16 @@ Env = {
*/
module.exports = function (Env, cb) {
var complete = Util.once(Util.mkAsync(cb));
// the number of ms artificially introduced between CPU-intensive operations
var THROTTLE_FACTOR = 10;
var evictArchived = function (Env, cb) {
var Log;
var store;
var pinStore;
var blobs;
var retentionTime = +new Date() - (Env.archiveRetentionTime * 24 * 3600 * 1000);
var report = {
// archivedChannelsRemoved,
// archivedAccountsRemoved,
@ -53,67 +61,7 @@ module.exports = function (Env, cb) {
// runningTime,
};
// the administrator should have set an 'inactiveTime' in their config
// if they didn't, just exit.
if (!Env.inactiveTime || typeof(Env.inactiveTime) !== "number") {
return void complete("NO_INACTIVE_TIME");
}
// get a list of premium accounts on this instance
// pre-converted to the 'safeKey' format so we can easily compare
// them against ids we see on the filesystem
var premiumSafeKeys = Object.keys(Env.limits || {})
.map(function (id) {
return Keys.canonicalize(id);
})
.filter(Boolean)
.map(Util.escapeKeyCharacters);
// files which have not been changed since before this date can be considered inactive
var inactiveTime = +new Date() - (Env.inactiveTime * 24 * 3600 * 1000);
// files which were archived before this date can be considered safe to remove
var retentionTime = +new Date() - (Env.archiveRetentionTime * 24 * 3600 * 1000);
var store;
var pinStore;
var Log;
var blobs;
/* It's fairly easy to know if a channel or blob is active
but knowing whether it is pinned requires that we
keep the set of pinned documents in memory.
Some users will share the same set of documents in their pin lists,
so the representation of pinned documents should scale sub-linearly
with the number of users and pinned documents.
That said, sub-linear isn't great...
A Bloom filter is "a space-efficient probabilistic data structure"
which lets us check whether an item is _probably_ or _definitely not_
in a set. This is good enough for our purposes since we just want to
know whether something can safely be removed and false negatives
(not safe to remove when it actually is) are acceptable.
We set our capacity to some large number, and the error rate to whatever
we think is acceptable.
TODO make this configurable ?
*/
var BLOOM_CAPACITY = (1 << 20) - 1; // over a million items
var BLOOM_ERROR = 1 / 10000; // an error rate of one in a thousand
// the number of ms artificially introduced between CPU-intensive operations
var THROTTLE_FACTOR = 10;
// we'll use one filter for the set of active documents
var activeDocs = Bloom.optimalFilter(BLOOM_CAPACITY, BLOOM_ERROR);
// and another one for the set of pinned documents
var pinnedDocs = Bloom. optimalFilter(BLOOM_CAPACITY, BLOOM_ERROR);
var startTime = +new Date();
var msSinceStart = function () {
return (+new Date()) - startTime;
};
var loadStorage = function () {
store = Env.store;
@ -237,6 +185,105 @@ module.exports = function (Env, cb) {
}));
};
nThen(loadStorage)
.nThen(removeArchivedChannels)
.nThen(removeArchivedBlobProofs)
.nThen(removeArchivedBlobs)
.nThen(function () {
cb();
});
};
module.exports = function (Env, cb) {
var complete = Util.once(Util.mkAsync(cb));
var report = {
// archivedChannelsRemoved,
// archivedAccountsRemoved,
// archivedBlobProofsRemoved,
// archivedBlobsRemoved,
// totalChannels,
// activeChannels,
// totalBlobs,
// activeBlobs,
// totalAccounts,
// activeAccounts,
// channelsArchived,
launchTime: +new Date(),
// runningTime,
};
// the administrator should have set an 'inactiveTime' in their config
// if they didn't, just exit.
if (!Env.inactiveTime || typeof(Env.inactiveTime) !== "number") {
return void complete("NO_INACTIVE_TIME");
}
// get a list of premium accounts on this instance
// pre-converted to the 'safeKey' format so we can easily compare
// them against ids we see on the filesystem
var premiumSafeKeys = Object.keys(Env.limits || {})
.map(function (id) {
return Keys.canonicalize(id);
})
.filter(Boolean)
.map(Util.escapeKeyCharacters);
// files which have not been changed since before this date can be considered inactive
var inactiveTime = +new Date() - (Env.inactiveTime * 24 * 3600 * 1000);
// files which were archived before this date can be considered safe to remove
var retentionTime = +new Date() - (Env.archiveRetentionTime * 24 * 3600 * 1000);
var store;
var pinStore;
var Log;
var blobs;
/* It's fairly easy to know if a channel or blob is active
but knowing whether it is pinned requires that we
keep the set of pinned documents in memory.
Some users will share the same set of documents in their pin lists,
so the representation of pinned documents should scale sub-linearly
with the number of users and pinned documents.
That said, sub-linear isn't great...
A Bloom filter is "a space-efficient probabilistic data structure"
which lets us check whether an item is _probably_ or _definitely not_
in a set. This is good enough for our purposes since we just want to
know whether something can safely be removed and false negatives
(not safe to remove when it actually is) are acceptable.
We set our capacity to some large number, and the error rate to whatever
we think is acceptable.
TODO make this configurable ?
*/
var BLOOM_CAPACITY = (1 << 20) - 1; // over a million items
var BLOOM_ERROR = 1 / 10000; // an error rate of one in a thousand
// we'll use one filter for the set of active documents
var activeDocs = Bloom.optimalFilter(BLOOM_CAPACITY, BLOOM_ERROR);
// and another one for the set of pinned documents
var pinnedDocs = Bloom. optimalFilter(BLOOM_CAPACITY, BLOOM_ERROR);
var startTime = +new Date();
var msSinceStart = function () {
return (+new Date()) - startTime;
};
var loadStorage = function () {
store = Env.store;
pinStore = Env.pinStore;
Log = Env.Log;
blobs = Env.blobStore;
};
var categorizeChannelsByActivity = function (w) {
var channels = 0;
var active = 0;
@ -566,9 +613,6 @@ module.exports = function (Env, cb) {
};
nThen(loadStorage)
.nThen(removeArchivedChannels)
.nThen(removeArchivedBlobProofs)
.nThen(removeArchivedBlobs)
// iterate over all documents and add them to a bloom filter if they have been active
.nThen(categorizeChannelsByActivity)
@ -590,3 +634,5 @@ module.exports = function (Env, cb) {
complete(void 0, report);
});
};
module.exports.archived = evictArchived;

View File

@ -18,11 +18,11 @@ module.exports.create = function (Env, cb) {
id: Env.id,
channelMessage: function (Server, channel, msgStruct) {
channelMessage: function (Server, channel, msgStruct, cb) {
// netflux-server emits 'channelMessage' events whenever someone broadcasts to a channel
// historyKeeper stores these messages if the channel id indicates that they are
// a channel type with permanent history
HK.onChannelMessage(Env, Server, channel, msgStruct);
HK.onChannelMessage(Env, Server, channel, msgStruct, cb);
},
channelClose: function (channelName) {
// netflux-server emits 'channelClose' events whenever everyone leaves a channel

View File

@ -300,9 +300,10 @@ var trimMapByOffset = function (map, offset) {
* the fix is to use callbacks and implement queueing for writes
* to guarantee that offset computation is always atomic with writes
*/
const storeMessage = function (Env, channel, msg, isCp, optionalMessageHash) {
const storeMessage = function (Env, channel, msg, isCp, optionalMessageHash, cb) {
const id = channel.id;
const Log = Env.Log;
if (typeof(cb) !== "function") { cb = function () {}; }
Env.queueStorage(id, function (next) {
const msgBin = Buffer.from(msg + '\n', 'utf8');
@ -321,6 +322,7 @@ const storeMessage = function (Env, channel, msg, isCp, optionalMessageHash) {
// TODO make it possible to respond to clients with errors so they know
// their message wasn't stored
cb(err);
return void next();
}
}));
@ -332,6 +334,8 @@ const storeMessage = function (Env, channel, msg, isCp, optionalMessageHash) {
if (err) {
Log.warn("HK_STORE_MESSAGE_INDEX", err.stack);
// non-critical, we'll be able to get the channel index later
// cb with no error so that the message is broadcast
cb();
return void next();
}
if (typeof (index.line) === "number") { index.line++; }
@ -357,13 +361,17 @@ const storeMessage = function (Env, channel, msg, isCp, optionalMessageHash) {
if (offsetCount < 0) {
Log.warn('OFFSET_TRIM_OOO', {
channel: id,
map: index.OffsetByHash
map: index.offsetByHash
});
} else if (offsetCount > 0) {
trimOffsetByOrder(index.offsetByHash, index.offsets);
index.offsets = checkOffsetMap(index.offsetByHash);
}
}
// Message stored, call back
cb();
index.size += msgBin.length;
// handle the next element in the queue
@ -445,6 +453,14 @@ const getHistoryOffset = (Env, channelName, lastKnownHash, _cb) => {
return void cb(new Error('EUNKNOWN'));
}
// If we asked for a lastKnownHash but didn't find it AND if
// this channel has checkpoints, send EUNKNOWN so that the
// client can ask for normal history (without lastKnownHash)
if (lastKnownHash && !lkh && index.cpIndex.length) {
waitFor.abort();
return void cb(new Error('EUNKNOWN'));
}
// Otherwise use our lastKnownHash
cb(null, lkh);
}));
@ -496,7 +512,7 @@ const getHistoryAsync = (Env, channelName, lastKnownHash, beforeHash, handler, c
const start = (beforeHash) ? 0 : offset;
store.readMessagesBin(channelName, start, (msgObj, readMore, abort) => {
if (beforeHash && msgObj.offset >= offset) { return void abort(); }
var parsed = tryParse(Env, msgObj.buff.toString('utf8'));
const parsed = tryParse(Env, msgObj.buff.toString('utf8'));
if (!parsed) { return void readMore(); }
handler(parsed, readMore);
}, waitFor(function (err) {
@ -846,7 +862,9 @@ HK.onDirectMessage = function (Env, Server, seq, userId, json) {
* adds timestamps to incoming messages
* writes messages to the store
*/
HK.onChannelMessage = function (Env, Server, channel, msgStruct) {
HK.onChannelMessage = function (Env, Server, channel, msgStruct, cb) {
if (typeof(cb) !== "function") { cb = function () {}; }
//console.log(+new Date(), "onChannelMessage");
const Log = Env.Log;
@ -856,7 +874,7 @@ HK.onChannelMessage = function (Env, Server, channel, msgStruct) {
// we should probably just change this to expect a channel id directly
// don't store messages if the channel id indicates that it's an ephemeral message
if (!channel.id || channel.id.length === EPHEMERAL_CHANNEL_LENGTH) { return; }
if (!channel.id || channel.id.length === EPHEMERAL_CHANNEL_LENGTH) { return void cb(); }
const isCp = /^cp\|/.test(msgStruct[4]);
let id;
@ -868,7 +886,7 @@ HK.onChannelMessage = function (Env, Server, channel, msgStruct) {
// more straightforward and reliable.
if (Array.isArray(id) && id[2] && id[2] === channel.lastSavedCp) {
// Reject duplicate checkpoints
return;
return void cb('DUPLICATE');
}
}
@ -881,7 +899,10 @@ HK.onChannelMessage = function (Env, Server, channel, msgStruct) {
metadata = _metadata;
// don't write messages to expired channels
if (checkExpired(Env, Server, channel)) { return void w.abort(); }
if (checkExpired(Env, Server, channel)) {
cb('EEXPIRED');
return void w.abort();
}
}));
}).nThen(function (w) {
// if there's no validateKey present skip to the next block
@ -910,6 +931,7 @@ HK.onChannelMessage = function (Env, Server, channel, msgStruct) {
Log.info("HK_SIGNED_MESSAGE_REJECTED", 'Channel '+channel.id);
}
// always abort if there was an error...
cb('FAILED_VALIDATION');
return void w.abort();
});
});
@ -942,7 +964,7 @@ HK.onChannelMessage = function (Env, Server, channel, msgStruct) {
// storeMessage
//console.log(+new Date(), "Storing message");
storeMessage(Env, channel, JSON.stringify(msgStruct), isCp, getHash(msgStruct[4], Log));
storeMessage(Env, channel, JSON.stringify(msgStruct), isCp, getHash(msgStruct[4], Log), cb);
//console.log(+new Date(), "Message stored");
});
};

View File

@ -348,7 +348,10 @@ const getOlderHistory = function (data, cb) {
if (hash === oldestKnownHash) {
found = true;
}
messages.push(parsed);
messages.push({
msg: parsed,
hash: hash,
});
}, function (err) {
var toSend = [];
if (typeof (desiredMessages) === "number") {
@ -356,14 +359,14 @@ const getOlderHistory = function (data, cb) {
} else if (untilHash) {
for (var j = messages.length - 1; j >= 0; j--) {
toSend.unshift(messages[j]);
if (Array.isArray(messages[j]) && HK.getHash(messages[j][4]) === untilHash) {
if (messages[j] && messages[j].hash === untilHash) {
break;
}
}
} else {
let cpCount = 0;
for (var i = messages.length - 1; i >= 0; i--) {
if (/^cp\|/.test(messages[i][4]) && i !== (messages.length - 1)) {
if (/^cp\|/.test(messages[i].msg[4]) && i !== (messages.length - 1)) {
cpCount++;
}
toSend.unshift(messages[i]);

6
package-lock.json generated
View File

@ -403,9 +403,9 @@
}
},
"chainpad-server": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/chainpad-server/-/chainpad-server-4.0.9.tgz",
"integrity": "sha512-8h1W41ktE05TM6LuXrklpW2TUxWeNyIDiRaQygKsXaA/7pyJxF7+AmPVS+xW0c31VkHjQDPiaMzPoxhcxXnIyA==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/chainpad-server/-/chainpad-server-5.0.0.tgz",
"integrity": "sha512-rfJxcWdF5InMPnNkhMWD+VRwzKUcUQktMG7DpEs90zuB7w6e8rs4MAOTEmc2rgK5EB0fYTlOMdSft58hKnbgHw==",
"requires": {
"nthen": "0.1.8",
"pull-stream": "^3.6.9",

View File

@ -14,7 +14,7 @@
"dependencies": {
"@mcrowe/minibloom": "^0.2.0",
"chainpad-crypto": "^0.2.5",
"chainpad-server": "^4.0.9",
"chainpad-server": "^5.0.0",
"express": "~4.16.0",
"fs-extra": "^7.0.0",
"get-folder-size": "^2.0.1",

102
scripts/evict-archived.js Normal file
View File

@ -0,0 +1,102 @@
var Eviction = require("../lib/eviction");
var nThen = require("nthen");
var Store = require("../lib/storage/file");
var BlobStore = require("../lib/storage/blob");
var Quota = require("../lib/commands/quota");
var Environment = require("../lib/env");
var Decrees = require("../lib/decrees");
var config = require("../lib/load-config");
var Env = Environment.create(config);
var loadPremiumAccounts = function (Env, cb) {
nThen(function (w) {
// load premium accounts
Quota.updateCachedLimits(Env, w(function (err) {
if (err) {
Env.Log.error('EVICT_LOAD_PREMIUM_ACCOUNTS', {
error: err,
});
}
}));
}).nThen(function (w) {
// load and apply decrees
Decrees.load(Env, w(function (err) {
if (err) {
Env.Log.error('EVICT_LOAD_DECREES', {
error: err.code || err,
message: err.message,
});
}
}));
}).nThen(function () {
//console.log(Env.limits);
cb();
});
};
var prepareEnv = function (Env, cb) {
//Quota.applyCustomLimits(Env);
nThen(function (w) {
/* Database adaptors
*/
// load the store which will be used for iterating over channels
// and performing operations like archival and deletion
Store.create(config, w(function (err, _) {
if (err) {
w.abort();
throw err;
}
Env.store = _;
}));
Store.create({
filePath: config.pinPath,
}, w(function (err, _) {
if (err) {
w.abort();
throw err;
}
Env.pinStore = _;
}));
// load the logging module so that you have a record of which
// files were archived or deleted at what time
var Logger = require("../lib/log");
Logger.create(config, w(function (_) {
Env.Log = _;
}));
config.getSession = function () {};
BlobStore.create(config, w(function (err, _) {
if (err) {
w.abort();
return console.error(err);
}
Env.blobStore = _;
}));
}).nThen(function (w) {
loadPremiumAccounts(Env, w(function (/* err */) {
//if (err) { }
}));
}).nThen(function () {
cb();
});
};
//console.log("starting");
nThen(function (w) {
// load database adaptors and configuration values into the environment
prepareEnv(Env, w(function () {
//console.log("env prepared");
}));
}).nThen(function (w) {
Eviction.archived(Env, w(function () {
}));
});

View File

@ -0,0 +1,139 @@
var Messages = require("../www/common/translations/messages.json");
var Exec = require("child_process").exec;
var ignoreLines = function (source, pattern) {
if (!pattern.test(source)) { return source; }
return source.split('\n')
.map(function (line) {
if (pattern.test(line)) { return ''; }
return line;
})
.filter(Boolean)
.join("\n");
};
var GENERATED_PATTERNS = [
/(admin|settings)_.*(Hint|Title|Button)/,
/settings_colortheme/,
/loading_(state|drive|pad)_/,
/(admin|notifications|support|team|settings)_cat_/,
/features_f/,
];
var isPossiblyGenerated = function (key) {
return GENERATED_PATTERNS.some(function (patt) {
return patt.test(key);
});
};
var grep = function (pattern, cb) {
var exclude = [
'www/common/translations/*',
'www/common/onlyoffice/*',
'www/lib/*',
'www/common/pdfjs/*',
'*.css',
'www/common/highlight/*',
'*.min.js',
'.lesshintrc',
'CHANGELOG.md',
'LICENSE',
'package*.json',
'www/debug/chainpad.dist.js',
'www/pad/mathjax/*',
'www/common/hyperscript.js',
'www/common/jscolor.js',
'.//scripts/*',
'./lib/*',
'./docs/*',
'./github/*',
'*.svg',
'*.md',
'./config/*',
].map(function (patt) {
return "':(exclude)" + patt + "'";
}).join(' ');
// grep this repository, ignoring binary files and excluding anything matching the above patterns
//var ignoreBinaries= '--binary-files=without-match ';
var command = 'git grep ' + pattern + " -- ':/' " + exclude;
Exec(command, function (err, stdout /*, stderr */) {
if (err && err.code === 1 && err.killed === false) {
if (isPossiblyGenerated(pattern)) {
return cb(void 0, true, 'POSSIBLY_GENERATED');
}
return cb(void 0, true, "NOT_FOUND", stdout);
}
stdout = ignoreLines(stdout, /Binary file/);
if (err) {
if (err.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') {
return cb(void 0, true, 'TOO_MUCH', stdout);
}
return void cb(err);
}
if (/data\-localization/.test(stdout)) {
return cb(void 0, true, "DATA_LOCALIZATION", stdout);
}
if (/(Messages|Msg|messages)\./.test(stdout)) {
return cb(void 0, false);
}
//console.log(pattern, arguments);
cb(void 0, true, 'OTHER', stdout);
});
};
var keys = Object.keys(Messages).sort();
var total = keys.length;
var limit = total;
var lineCount = function (s) {
var i = 0;
s.replace(/\n/g, function () { i++; return ''; });
return i;
};
var conditionallyPrintContent = function (output) {
if (!output) { return; }
if (lineCount(output) < 12) {
output.split('\n').map(function (line) {
if (!line) { return; }
console.log('\t> ' + line);
});
//console.log(output);
console.log();
} else {
console.log("\t> too much content to print");
}
};
var next = function () {
var key = keys[0];
if (!key) { return; }
keys.shift();
if (!limit) { return void console.log("[DONE]"); }
limit--;
grep(key, function (err, flagged, reason, output) {
if (err) {
console.error("[%s]", key, err);
console.log();
return;
} else if (!flagged) {
} else if (reason === 'OTHER') {
console.log('[%s] flagged for [OTHER]', key);
conditionallyPrintContent(output);
} else {
console.log("[%s] flagged for [%s]", key, reason || '???');
conditionallyPrintContent(output);
}
next();
});
};
next();

View File

@ -104,7 +104,6 @@
color: fade(@cryptpad_text_col, 80%);
.cp-support-ispremium {
padding: 0 5px;
color: @cp_admin-premium-fg;
background-color: @cp_admin-premium-bg;
}
}
@ -118,19 +117,19 @@
}
}
.cp-support-list-message {
.cp-support-showdata {
background-color: lighten(@cp_admin-last-bg, 5%);
}
&:last-child:not(.cp-support-fromadmin) {
color: @cp_admin-last-fg;
background-color: @cp_admin-last-bg;
.cp-support-showdata {
background-color: lighten(@cp_admin-last-bg, 5%);
}
}
&:last-child {
&.cp-support-frompremium {
background-color: @cp_admin-premium-bg;
.cp-support-showdata {
background-color: lighten(@cp_admin-premium-bg, 30%);
background-color: fade(@cp_admin-premium-bg, 10%);
}
}
}
@ -160,10 +159,10 @@
}
.cp-support-fromadmin {
color: @cp_admin-isadmin-fg;
background-color: @cp_admin-isadmin-bg;
.cp-support-message-content {
color: @cp_admin-isadmin-fg;
background-color: @cp_admin-isadmin-bg !important;
.cp-support-message-from, .cp-support-showdata {
color: @cryptpad_text_col;
background-color: fade(@cp_admin-isadmin-bg, 10%) !important;
}
}

View File

@ -13,6 +13,7 @@ define([
'/common/outer/login-block.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'less!/customize/src/less2/pages/page-assert.less',
], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat, MediaTag, Block) {
window.Hyperjson = Hyperjson;
window.Sortify = Sortify;

View File

@ -64,9 +64,9 @@ define([
var CURRENT_VERSION = 'v2b';
//var READONLY_REFRESH_TO = 15000;
var debug = function (x) {
var debug = function (x, type) {
if (!window.CP_DEV_MODE) { return; }
console.debug(x);
console.debug(x, type);
};
var stringify = function (obj) {
@ -812,10 +812,16 @@ define([
};
// Get all existing locks
var getUserLock = function (id) {
var l = content.locks[id] || {};
return Object.keys(l).map(function (uid) { return l[uid]; });
};
var getLock = function () {
return Object.keys(content.locks).map(function (id) {
return content.locks[id];
var locks = [];
Object.keys(content.locks).forEach(function (id) {
Array.prototype.push.apply(locks, getUserLock(id));
});
return locks;
};
// Update the userlist in onlyoffice
@ -831,37 +837,38 @@ define([
};
// Update the locks status in onlyoffice
var handleNewLocks = function (o, n) {
Object.keys(n).forEach(function (id) {
// New lock
if (!o[id]) {
ooChannel.send({
type: "getLock",
locks: getLock()
});
return;
}
// Updated lock
if (stringify(n[id]) !== stringify(o[id])) {
ooChannel.send({
type: "releaseLock",
locks: [o[id]]
});
ooChannel.send({
type: "getLock",
locks: getLock()
});
}
var hasNew = false;
// Check if we have at least one new lock
Object.keys(n).some(function (id) {
if (typeof(n[id]) !== "object") { return; } // Ignore old format
// n[id] = { uid: lock, uid2: lock2 };
return Object.keys(n[id]).some(function (uid) {
// New lock
if (!o[id] || !o[id][uid]) {
hasNew = true;
return true;
}
});
});
// Remove old locks
Object.keys(o).forEach(function (id) {
// Removed lock
if (!n[id]) {
ooChannel.send({
type: "releaseLock",
locks: [o[id]]
});
return;
}
if (typeof(o[id]) !== "object") { return; } // Ignore old format
Object.keys(o[id]).forEach(function (uid) {
// Removed lock
if (!n[id] || !n[id][uid]) {
ooChannel.send({
type: "releaseLock",
locks: [o[id][uid]]
});
}
});
});
if (hasNew) {
ooChannel.send({
type: "getLock",
locks: getLock()
});
}
};
// Remove locks from offline users
@ -871,9 +878,11 @@ define([
Object.keys(locks).forEach(function (id) {
var nId = id.slice(0,32);
if (users.indexOf(nId) === -1) {
// Offline locks: support old format
var l = typeof(locks[id]) === "object" ? getUserLock(id) : [locks[id]];
ooChannel.send({
type: "releaseLock",
locks: [locks[id]]
locks: l
});
delete content.locks[id];
}
@ -927,6 +936,8 @@ define([
data: {"type":"open","status":"ok","data":{"Editor.bin":obj.openCmd.url}}
});
/*
// TODO: make sure we don't have new popups that can break our integration
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === "childList") {
@ -942,6 +953,7 @@ define([
observer.observe(window.frames[0].document.body, {
childList: true,
});
*/
};
var handleLock = function (obj, send) {
@ -964,7 +976,9 @@ define([
block: obj.block && obj.block[0],
};
var myId = getId();
content.locks[myId] = msg;
content.locks[myId] = content.locks[myId] || {};
var uid = Util.uid();
content.locks[myId][uid] = msg;
oldLocks = JSON.parse(JSON.stringify(content.locks));
// Remove old locks
deleteOfflineLocks();
@ -1047,7 +1061,7 @@ define([
type: "saveChanges",
changes: parseChanges(obj.changes),
changesIndex: ooChannel.cpIndex || 0,
locks: [content.locks[getId()]],
locks: getUserLock(getId()),
excelAdditionalInfo: null
}, null, function (err, hash) {
if (err) {
@ -1072,7 +1086,7 @@ define([
ooChannel.lastHash = hash;
// Check if a checkpoint is needed
makeCheckpoint();
// Remove my lock
// Remove my locks
delete content.locks[getId()];
oldLocks = JSON.parse(JSON.stringify(content.locks));
APP.onLocal();
@ -1094,12 +1108,12 @@ define([
APP.chan = chan;
var send = ooChannel.send = function (obj) {
debug(obj);
debug(obj, 'toOO');
chan.event('CMD', obj);
};
chan.on('CMD', function (obj) {
debug(obj);
debug(obj, 'fromOO');
switch (obj.type) {
case "auth":
handleAuth(obj, send);
@ -1144,7 +1158,7 @@ define([
if (obj.releaseLocks && content.locks && content.locks[getId()]) {
send({
type: "releaseLock",
locks: [content.locks[getId()]]
locks: getUserLock(getId())
});
delete content.locks[getId()];
APP.onLocal();
@ -1198,9 +1212,10 @@ define([
"user": {
"id": String(myOOId), //"c0c3bf82-20d7-4663-bf6d-7fa39c598b1d",
"firstname": metadataMgr.getUserData().name || Messages.anonymous,
"name": metadataMgr.getUserData().name || Messages.anonymous,
},
"mode": "edit",
"lang": (navigator.language || navigator.userLanguage || '').slice(0,2)
"lang": (window.cryptpadLanguage || navigator.language || navigator.userLanguage || '').slice(0,2)
},
"events": {
"onAppReady": function(/*evt*/) {
@ -2459,7 +2474,7 @@ define([
if (content.hashes) {
var latest = getLastCp(true);
var newLatest = getLastCp();
if (newLatest.index > latest.index) {
if (newLatest.index > latest.index || (newLatest.index && !latest.index)) {
ooChannel.queue = [];
ooChannel.ready = false;
// New checkpoint

File diff suppressed because one or more lines are too long

View File

@ -2074,6 +2074,7 @@ define([
//var decryptedMsg = crypto.decrypt(msg, true);
if (data.debug) {
msgs.push({
serverHash: msg.slice(0,64),
msg: msg,
author: parsed[1][1],
time: parsed[1][5]

View File

@ -1239,6 +1239,7 @@ define([
if (typeof(_msg) === "object") {
decryptedMsgs.push({
author: _msg.author,
serverHash: _msg.serverHash,
time: _msg.time,
msg: crypto.decrypt(_msg.msg, true, true)
});

View File

@ -55,7 +55,6 @@ define([
var ctx = {};
funcs.Messages = Messages;
Messages.addOptionalPassword = "Add a password (optional)"; // XXX
var evRealtimeSynced = Util.mkEvent(true);

View File

@ -1049,7 +1049,7 @@ MessengerUI, Messages) {
var $newPadBlock = UIElements.createDropdown(dropdownConfig);
var $button = $newPadBlock.find('button');
$button.attr('title', Messages.notificationsPage);
$button.addClass('fa fa-bell-o');
$button.addClass('fa fa-bell-o cp-notifications-bell');
var $n = $button.find('.cp-dropdown-button-title').hide();
var $empty = $(div).find('.cp-notifications-empty');

View File

@ -933,7 +933,7 @@
"support_disabledHint": "Diese CryptPad-Instanz wurde noch nicht für die Verwendung eines Support-Formulars konfiguriert.",
"support_cat_new": "Neues Ticket",
"support_formTitle": "Neues Ticket",
"support_formHint": "Mit diesem Formular kann ein neues Support-Ticket eröffnet werden. Es erlaubt die sichere Kontaktaufnahme mit den Administratoren zur Lösung von Problemen oder Beantwortung von Fragen. Bitte eröffne kein neues Ticket, wenn du bereits ein offenes Ticket bezüglich des gleichen Problems hast. Verwende stattdessen die Antworten-Schaltfläche, um weitere Informationen hinzuzufügen.",
"support_formHint": "Mit diesem Formular kannst du bei Fragen oder Problemen die Administratoren auf sicherem Weg kontaktieren.<br>Bitte beachte, dass einige Fragen/Probleme mit Hilfe des <a href=\"https://docs.cryptpad.fr/de/user_guide/index.html\" rel=\"noopener noreferrer\" target=\"_blank\">CryptPad-Benutzerhandbuchs</a> gelöst werden können. Bitte eröffne kein neues Ticket, wenn du bereits ein offenes Ticket bezüglich des gleichen Problems hast. Antworte stattdessen auf deine ursprüngliche Nachricht, um weitere Informationen hinzuzufügen.",
"support_formButton": "Absenden",
"support_formTitleError": "Fehler: Titel ist leer",
"support_formContentError": "Fehler: Inhalt ist leer",
@ -1403,5 +1403,6 @@
"pad_settings_width_large": "Volle Breite",
"pad_settings_info": "Standardeinstellungen für dieses Dokument. Sie werden angewendet, wenn neue Benutzer dieses Dokument aufrufen.",
"admin_getquotaHint": "Überprüfe anhand des öffentlichen Schlüssels die Gesamtgröße aller Elemente, die der Quota eines Benutzers oder Teams angerechnet werden.",
"admin_getquotaTitle": "Speicherbelegung von Accounts überprüfen"
"admin_getquotaTitle": "Speicherbelegung von Accounts überprüfen",
"addOptionalPassword": "Passwort hinzufügen (optional)"
}

View File

@ -956,7 +956,7 @@
"admin_supportInitPrivate": "Votre instance de CryptPad est configurée pour utiliser la messagerie de support mais votre compte utilisateur ne connaît pas la bonne clé privée nécessaire pour y avoir accès. Veuillez utiliser le formulaire suivant pour ajouter ou mettre à jour la clé dans votre compte.",
"admin_supportInitHint": "Vous pouvez configurer une messagerie de support afin de fournir aux utilisateurs de votre instance CryptPad un moyen de vous contacter de manière sécurisée en cas de problème avec leur compte.",
"admin_supportListHint": "Voici la liste des tickets envoyés par les utilisateurs au support. Tous les administrateurs peuvent voir les tickets et leurs réponses. Un ticket fermé ne peut pas être ré-ouvert. Vous ne pouvez supprimer (ou cacher) que les tickets fermés, et les tickets supprimés restent visible par les autres administrateurs.",
"support_formHint": "Ce formulaire peut être utilisé pour créer un nouveau ticket de support. Utilisez-le pour contacter les administrateurs de manière sécurisée afin de résoudre un problème ou d'obtenir des renseignements. Merci de ne pas créer de nouveau ticket si vous avez déjà un ticket ouvert concernant le même problème, vous pouvez utiliser le bouton \"Répondre\" dans ce cas.",
"support_formHint": "Utilisez ce formulaire pour contacter les administrateurs de manière sécurisée au sujet de problèmes ou de questions. <br>Veuillez noter que certains problèmes/questions peuvent déjà être traités dans le <a href=\"https://docs.cryptpad.fr/fr/user_guide/index.html\" rel=\"noopener noreferrer\" target=\"_blank\">Guide Utilisateur de CryptPad</a>. Veuillez ne pas créer de nouveau ticket si vous avez déjà un ticket ouvert concernant le même problème. Merci de répondre à votre message d'origine avec toute information supplémentaire.",
"support_listHint": "Voici la liste des tickets envoyés au support, ainsi que les réponses. Un ticket fermé ne peut pas être ré-ouvert, mais il est possible d'en créer un nouveau. Vous pouvez cacher les tickets qui ont été fermés.",
"support_notification": "Un administrateur a répondu à votre ticket de support",
"requestEdit_button": "Demander les droits d'édition",
@ -1403,5 +1403,6 @@
"admin_performanceProfilingHint": "Un aperçu du temps total passé à exécuter les différentes commandes côté serveur",
"admin_cat_performance": "Performance",
"redo": "Rétablir",
"undo": "Annuler"
"undo": "Annuler",
"addOptionalPassword": "Ajouter un mot de passe (optionnel)"
}

View File

@ -954,7 +954,7 @@
"support_disabledHint": "This CryptPad instance is not yet configured to use a support form.",
"support_cat_new": "New ticket",
"support_formTitle": "New Ticket",
"support_formHint": "This form can be used to create a new support ticket. Use it to contact the administrators to solve issues or ask any question in a secure way. Please don't create a new ticket if you already have an open ticket about the same issue, but use the reply button to provide more information.",
"support_formHint": "Use this form to securely contact the administrators about issues and questions.<br>Please note that some issues/questions may already be addressed in the <a href=\"https://docs.cryptpad.fr/en/user_guide/index.html\" rel=\"noopener noreferrer\" target=\"_blank\">CryptPad User Guide</a>. Please do not create a new ticket if you already have an open ticket about the same issue. Instead, reply to your original message with any additional information.",
"support_formButton": "Send",
"support_formTitleError": "Error: title is empty",
"support_formContentError": "Error: content is empty",
@ -1403,5 +1403,6 @@
"pad_settings_comments": "Choose whether the Comments should be visible or hidden by default.",
"pad_settings_hide": "Hide",
"pad_settings_show": "Show",
"settings_colortheme_custom": "Custom"
"settings_colortheme_custom": "Custom",
"addOptionalPassword": "Add a password (optional)"
}

View File

@ -824,5 +824,16 @@
"policy_howweuse_p1": "Данная информация помогает нам принимать решения о продвижении CryptPad и оценивать успешность наших действий. Информация о вашем местоположении помогает нам понять необходимость больших усилий для поддержки других языков.",
"policy_whatweknow_p2": "Мы используем <a href=\"https://www.elastic.co/products/kibana\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"open source analytics platform\">Kibana</a>, чтобы получше узнать наших пользователей. С помощью Kibana мы определяем как вы узнали о CryptPad: по прямой ссылке, через поисковый сервис или по ссылке на стороннем сайте как Reddit или Twitter.",
"policy_whatweknow_p1": "Так как приложение размещено в Интернете, CryptPad имеет доступ ко всем метаданным протокола HTTP. Это включает ваш IP адрес и различные HTTP заголовки, которые могут быть использованы для идентификации вашего браузера. Информацию, которую раскрывает ваш браузер, вы можете найти на сайте <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending\" title=\"what http headers is my browser sending\">WhatIsMyBrowser.com</a>.",
"terms": "Пользовательское соглашение"
"terms": "Пользовательское соглашение",
"about_contributors": "Основные участники",
"about_intro": "CryptPad создан командой исследователей в <a href=\"http://xwiki.com\">XWiki SAS</a> - небольшй компании из Парижа (Франция) и Яссы (Румыния). Над CryptPad работают 3 основных члена команды и несколько энтузиастов из XWiki SAS и из вне.",
"tos_availability": "Мы надеемся, что вам понравится наш сервис, но не можем гарантировать его доступность или производительность. Поэтому, пожалуйста, регулярно экспортируйте ваши данные.",
"features_f_file0": "Работа с документами",
"policy_choices_ads": "Если вы хотите заблокировать аналитика на нашем сайте, можете использовать блокировщики рекламы, например, <a href=\"https://www.eff.org/privacybadger\" title=\"download privacy badger\" target=\"_blank\" rel=\"noopener noreferrer\">Privacy Badger</a>.",
"policy_choices_vpn": "Если вы решили развернуть свой сервер, но хотите раскрывать ваш IP адрес, вы можете его защитить с помощью <a href=\"https://www.torproject.org/projects/torbrowser.html.en\" title=\"downloads from the Tor project\" target=\"_blank\" rel=\"noopener noreferrer\">Tor браузера</a>, or a <a href=\"https://riseup.net/en/vpn\" title=\" VPN сервиса от Riseup\" target=\"_blank\" rel=\"noopener noreferrer\">VPN</a>.",
"policy_choices_open": "Наш код открыт, поэтому вы всегда можете развернуть свой собственный сервер CryptPad.",
"policy_choices": "Ваш выбор",
"policy_ads_p1": "Мы не показываем онлайн рекламу, хотя можем давать ссылки на стороны, финансирующие наших исследования.",
"policy_links_p1": "Данный сайт содержит ссылки на другие сайты, в том числе созданные другими организациями. Мы не несем ответственности за обеспечение конфиденциальности или сдержимое сторонних сайтов. По умолчанию ссылки на сторонние сайты запускаются в новом окне браузера, чтобы предупредить об уходе с CryptPad.fr.",
"about": "О нас"
}

View File

@ -35,6 +35,9 @@
}
.cp-app-debug-progress, .cp-app-debug-init {
text-align: center;
input {
width: auto !important;
}
}
#cp-app-debug-loading {
text-align: center;

View File

@ -24,6 +24,21 @@ body.cp-app-pad {
overflow: hidden;
}
@media screen and (max-height: @browser_media-medium-screen),
screen and (max-width: @browser_media-medium-screen) {
#cp-app-pad-toc {
margin: 5px;
&.hidden {
margin: 1px;
}
}
#cp-app-pad-comments {
.cp-pad-show, .cp-pad-hide {
display: none;
}
}
}
#cp-app-pad-toc, #cp-app-pad-comments, #cp-app-pad-resize {
.cp-pad-show, .cp-pad-hide {
position: absolute;
@ -68,6 +83,10 @@ body.cp-app-pad {
margin-left: -40px;
margin-top: 10px;
margin-right: 0;
button {
color: @cp_pad-resize;
border-color: @cp_pad-resize;
}
&.hidden {
margin-left: -70px;
margin-right: 40px;
@ -106,6 +125,29 @@ body.cp-app-pad {
}
}
}
#cp-app-pad-comments {
order: 3;
width: 330px;
//background-color: white;
margin: 10px;
position: relative;
.cp-pad-show, .cp-pad-hide {
left: 0;
}
h2 {
font-size: 1.5rem;
text-align: right;
}
.comments_main();
}
.cp-app-pad-comments-modal {
display: flex;
flex-flow: column;
max-height: 100%;
h2 {
text-align: left;
}
}
.cke_toolbox_main {
background-color: @cp_toolbar-bg;
@ -193,21 +235,6 @@ body.cp-app-pad {
}
}
}
#cp-app-pad-comments {
order: 3;
width: 330px;
//background-color: white;
margin: 10px;
position: relative;
.cp-pad-show, .cp-pad-hide {
left: 0;
}
h2 {
font-size: 1.5rem;
text-align: right;
}
.comments_main();
}
&.cke_body_width {
div.cp-comment-bubble {
button {

View File

@ -636,6 +636,8 @@ define([
// If we've clicked on the show/hide buttons, always use our latest local value
if (typeof(Env.localHide) === "boolean") { hide = Env.localHide; }
if (Env.mobile) { hide = false; }
Env.$container.removeClass('hidden');
if (hide) { Env.$container.addClass('hidden'); }
@ -644,6 +646,10 @@ define([
$showBtn.addClass('notif');
}
if (Env.mobile && Env.current) {
Env.$container.find('.cp-comment-container[data-uid]').hide();
Env.$container.find('.cp-comment-container[data-uid="' + Env.current + '"]').show();
}
Env.$container.show();
};
@ -823,6 +829,9 @@ define([
v: canonicalize(Env.editor.getSelection().getSelectedText())
}]
};
Env.current = uid;
// There may be a race condition between updateMetadata and addMark that causes
// * updateMetadata first: comment not rendered (redrawComments called
// before addMark)
@ -839,6 +848,12 @@ define([
Env.$container.show();
Env.$container.find('> h2').after(form);
if (Env.modal) {
UI.openCustomModal(Env.modal);
Env.current = undefined;
Env.$container.find('.cp-comment-container[data-uid]').hide();
}
};
@ -934,7 +949,16 @@ define([
var $comment = $(e.target);
var uid = $comment.attr('data-uid');
if (!uid) { return; }
Env.$container.find('.cp-comment-container[data-uid="' + uid + '"]').click();
if (Env.modal) {
UI.openCustomModal(Env.modal);
Env.current = uid;
Env.$container.find('.cp-comment-container[data-uid]').hide();
setTimeout(function () {
Env.$container.find('.cp-comment-container[data-uid="' + uid + '"]').show().click();
});
} else {
Env.$container.find('.cp-comment-container[data-uid="' + uid + '"]').click();
}
});
var call = function(f) {

View File

@ -101,6 +101,7 @@ define([
logFights: true,
fights: [],
Cursor: Cursor,
mobile: $('body').width() <= 600
};
// MEDIATAG: Filter elements to serialize
@ -678,6 +679,7 @@ define([
var metadataMgr = framework._.sfCommon.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
var common = framework._.sfCommon;
var APP = window.APP;
var comments = Comments.create({
framework: framework,
@ -688,10 +690,13 @@ define([
$iframe: $iframe,
$inner: $inner,
$contentContainer: $contentContainer,
$container: $('#cp-app-pad-comments')
$container: $(APP.commentsEl),
modal: APP.mobile && APP.comments,
mobile: APP.mobile
});
var $resize = $('#cp-app-pad-resize');
if (module.mobile) { $resize.hide(); }
var $toc = $('#cp-app-pad-toc');
$toc.show();
@ -714,6 +719,7 @@ define([
mkHelpMenu(framework);
}
/*
framework._.sfCommon.getAttribute(['pad', 'width'], function(err, data) {
var active = data || typeof(data) === "undefined";
if (active) {
@ -722,6 +728,7 @@ define([
editor.execCommand('pagemode');
}
});
*/
framework.onEditableChange(function(unlocked) {
if (!framework.isReadOnly()) {
@ -813,9 +820,13 @@ define([
hide = md.defaultWidth === 0;
}
// If we've clicking on the show/hide buttons, always use our last value
// If we've clicked on the show/hide buttons, always use our last value
if (typeof(localHide) === "boolean") { hide = localHide; }
if (window.APP.mobile) {
hide = false;
}
$contentContainer.removeClass('cke_body_width');
$resize.removeClass('hidden');
if (hide) {
@ -870,7 +881,7 @@ define([
} else {
hide = md.defaultOutline === 0;
}
// If we've clicking on the show/hide buttons, always use our last value
// If we've clicked on the show/hide buttons, always use our last value
if (typeof(localHide) === "boolean") { hide = localHide; }
$toc.removeClass('hidden');
@ -1401,12 +1412,27 @@ define([
var $ckeToolbar = $('#cke_1_top').find('.cke_toolbox_main');
$mainContainer.prepend($ckeToolbar.addClass('cke_reset_all'));
$contentContainer.append(h('div#cp-app-pad-resize'));
$contentContainer.append(h('div#cp-app-pad-comments'));
var comments = h('div#cp-app-pad-comments');
var APP = window.APP;
APP.commentsEl = comments;
if (APP.mobile) {
APP.comments = UI.dialog.customModal(comments, {
buttons: [{
name: Messages.filePicker_close,
onClick: function () {}
}]
});
$(APP.comments).addClass('cp-app-pad-comments-modal');
} else {
$contentContainer.append(comments);
}
$contentContainer.prepend(h('div#cp-app-pad-toc'));
$ckeToolbar.find('.cke_button__image_icon').parent().hide();
var $iframe = $('iframe').contents();
if (window.CryptPad_theme === 'dark') {
/*if (window.CryptPad_theme === 'dark') {
$iframe.find('html').addClass('cp-dark').css({
'background-color': '#323232', // grey_850
'color': '#EEEEEE' // dark text_col
@ -1415,7 +1441,10 @@ define([
$iframe.find('html').css({
'background-color': '#FFF'
});
}
}*/
$iframe.find('html').css({
'background-color': '#FFF'
});
}).nThen(waitFor());
}).nThen(function(waitFor) {

View File

@ -56,7 +56,6 @@
line-height: 1em;
cursor: pointer;
background: @cp_drive-bg;
color: @cp_drive-fg;
border: 1px solid @cp_drive-icon-border;

View File

@ -30,5 +30,9 @@
padding: 0 5px;
}
}
a {
color: @cryptpad_color_link;
text-decoration: underline;
}
}

View File

@ -66,8 +66,13 @@ define([
var $div = $('<div>', {'class': 'cp-support-' + key + ' cp-sidebarlayout-element'});
$('<label>').text(Messages['support_'+safeKey+'Title'] || key).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages['support_'+safeKey+'Hint'] || 'Coming soon...').appendTo($div);
var $hintSpan = $('<span>', {'class': 'cp-sidebarlayout-description'}).appendTo($div);
var hintContent = Messages['support_'+safeKey+'Hint'] || 'Coming soon...';
if (safeKey === 'form') {
$hintSpan.html(hintContent);
} else {
$hintSpan.text(hintContent);
}
if (addButton) {
$('<button>', {
'class': 'btn btn-primary'