diff --git a/CHANGELOG.md b/CHANGELOG.md index 04b57386b..c8d4d8a9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/customize.dist/pages.js b/customize.dist/pages.js index 436c3be93..85141f975 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -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 () { diff --git a/customize.dist/pages/index.js b/customize.dist/pages/index.js index d099640e4..f15d1819b 100644 --- a/customize.dist/pages/index.js +++ b/customize.dist/pages/index.js @@ -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(), diff --git a/customize.dist/pages/what-is-cryptpad.js b/customize.dist/pages/what-is-cryptpad.js index c100782ce..b5d2f8ac4 100644 --- a/customize.dist/pages/what-is-cryptpad.js +++ b/customize.dist/pages/what-is-cryptpad.js @@ -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', [ diff --git a/customize.dist/src/less2/include/colortheme-dark.less b/customize.dist/src/less2/include/colortheme-dark.less index eb578ad68..fc17d7d24 100644 --- a/customize.dist/src/less2/include/colortheme-dark.less +++ b/customize.dist/src/less2/include/colortheme-dark.less @@ -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; diff --git a/customize.dist/src/less2/include/colortheme.less b/customize.dist/src/less2/include/colortheme.less index d96ce4e0f..f353758c8 100644 --- a/customize.dist/src/less2/include/colortheme.less +++ b/customize.dist/src/less2/include/colortheme.less @@ -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; diff --git a/customize.dist/src/less2/include/drive.less b/customize.dist/src/less2/include/drive.less index 30d369ff5..c0f0d3a7c 100644 --- a/customize.dist/src/less2/include/drive.less +++ b/customize.dist/src/less2/include/drive.less @@ -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; diff --git a/customize.dist/src/less2/include/dropdown.less b/customize.dist/src/less2/include/dropdown.less index f67a51dc6..ddba87cd7 100644 --- a/customize.dist/src/less2/include/dropdown.less +++ b/customize.dist/src/less2/include/dropdown.less @@ -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; diff --git a/customize.dist/src/less2/include/limit-bar.less b/customize.dist/src/less2/include/limit-bar.less index 33c733efd..324daf722 100644 --- a/customize.dist/src/less2/include/limit-bar.less +++ b/customize.dist/src/less2/include/limit-bar.less @@ -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; diff --git a/customize.dist/src/less2/include/markdown.less b/customize.dist/src/less2/include/markdown.less index 7c7d3f41b..717b49990 100644 --- a/customize.dist/src/less2/include/markdown.less +++ b/customize.dist/src/less2/include/markdown.less @@ -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() { diff --git a/customize.dist/src/less2/include/messenger.less b/customize.dist/src/less2/include/messenger.less index a47f738e8..e3599f74e 100644 --- a/customize.dist/src/less2/include/messenger.less +++ b/customize.dist/src/less2/include/messenger.less @@ -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; diff --git a/customize.dist/src/less2/include/sidebar-layout.less b/customize.dist/src/less2/include/sidebar-layout.less index 275bf769b..ef93eae4a 100644 --- a/customize.dist/src/less2/include/sidebar-layout.less +++ b/customize.dist/src/less2/include/sidebar-layout.less @@ -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 { diff --git a/customize.dist/src/less2/include/tokenfield.less b/customize.dist/src/less2/include/tokenfield.less index a8a9805ce..e71d43c96 100644 --- a/customize.dist/src/less2/include/tokenfield.less +++ b/customize.dist/src/less2/include/tokenfield.less @@ -5,7 +5,7 @@ --LessLoader_require: LessLoader_currentFile(); } & { - .ui-autocomplete { + ul.ui-autocomplete { z-index: 100001; // alertify + 1 } .tokenfield { diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index 7b4e43d0d..3f19aa433 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -399,6 +399,9 @@ button { .toolbar_button; + &.cp-notifications-bell { + color: @cryptpad_text_col; + } } .cp-toolbar-limit { diff --git a/customize.dist/src/less2/pages/page-404.less b/customize.dist/src/less2/pages/page-404.less index 575efa19c..bcacdf1ee 100644 --- a/customize.dist/src/less2/pages/page-404.less +++ b/customize.dist/src/less2/pages/page-404.less @@ -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%; diff --git a/customize.dist/src/less2/pages/page-assert.less b/customize.dist/src/less2/pages/page-assert.less new file mode 100644 index 000000000..1d1442ff5 --- /dev/null +++ b/customize.dist/src/less2/pages/page-assert.less @@ -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"; +} + diff --git a/lib/eviction.js b/lib/eviction.js index 84db87d13..673c7313f 100644 --- a/lib/eviction.js +++ b/lib/eviction.js @@ -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; diff --git a/lib/historyKeeper.js b/lib/historyKeeper.js index cfdb14717..30b311eb7 100644 --- a/lib/historyKeeper.js +++ b/lib/historyKeeper.js @@ -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 diff --git a/lib/hk-util.js b/lib/hk-util.js index cea5e5636..f141bf35a 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -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"); }); }; diff --git a/lib/workers/db-worker.js b/lib/workers/db-worker.js index 5750ff7ac..a8aa8f154 100644 --- a/lib/workers/db-worker.js +++ b/lib/workers/db-worker.js @@ -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]); diff --git a/package-lock.json b/package-lock.json index eb0d82927..c2e5071b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 4f150fc38..f87caf8b8 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/evict-archived.js b/scripts/evict-archived.js new file mode 100644 index 000000000..7f90f9ff5 --- /dev/null +++ b/scripts/evict-archived.js @@ -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 () { + + })); +}); diff --git a/scripts/unused-translations.js b/scripts/unused-translations.js new file mode 100644 index 000000000..9c09ad54e --- /dev/null +++ b/scripts/unused-translations.js @@ -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(); diff --git a/www/admin/app-admin.less b/www/admin/app-admin.less index da4703203..6d8b5c2b0 100644 --- a/www/admin/app-admin.less +++ b/www/admin/app-admin.less @@ -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; } } diff --git a/www/assert/main.js b/www/assert/main.js index db417c761..f40a8e006 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -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; diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index f68463f9e..577c1d286 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -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 diff --git a/www/common/onlyoffice/v2b/web-apps/apps/spreadsheeteditor/main/app.js b/www/common/onlyoffice/v2b/web-apps/apps/spreadsheeteditor/main/app.js index 7fb071c12..82c9e70be 100644 --- a/www/common/onlyoffice/v2b/web-apps/apps/spreadsheeteditor/main/app.js +++ b/www/common/onlyoffice/v2b/web-apps/apps/spreadsheeteditor/main/app.js @@ -20,21 +20,21 @@ t.asc_setTooltip(this.inputTip.getValue()),t},onBtnClick:function(t){this._handl this.store.length<1&&this.emptyText.length>0&&$(this.el).find(".inner").addBack().filter(".inner").append('
'+this.emptyText+" |
'+this.emptyText+" |
\n \n | \n\n \n | \n
\n \n | \n
\n \n \n | \n
\n \n \n | \n
\n \n | \n
\r\n \r\n \r\n | \r\n|
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n | \r\n |
\r\n \r\n | \r\n|
\r\n \r\n \r\n | \r\n\r\n \r\n | \r\n
','",''," | ","|
','",''," | ","|
",'"," | ","|
',''," | ",'','"," | ","
','",''," | ","
','",''," | ","
','",''," | ","
'," | ","
','",''," | ","
',''," | ","
','",''," | ","
','",''," | ","
','",''," | ","
','",''," | ","
','",''," | ",'','",''," | ","
','",''," | ",'','",''," | ","
',''," | ",'',''," | ","
",''," | ","",''," | ","
\r\n \r\n \r\n | \r\n|
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n | \r\n |
\r\n \r\n | \r\n|
\r\n \r\n \r\n | \r\n\r\n \r\n | \r\n
','",''," | ","|
','",''," | ","|
",'"," | ","|
',''," | ",'','"," | ","
','",''," | ","
','",''," | ","
','",''," | ","
'," | ","
','",''," | ","
',''," | ","
','",''," | ","
','",''," | ","
','",''," | ","
','",''," | ","
','",''," | ",'','",''," | ","
','",''," | ",'','",''," | ","
',''," | ",'',''," | ","
",''," | ","",''," | ","
\r\n \r\n | \r\n|
\r\n \r\n | \r\n\r\n \r\n | \r\n
\r\n \r\n | \r\n|
\r\n \r\n \r\n | \r\n\r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n|
\r\n \r\n | \r\n|
\r\n \r\n | \r\n||
\r\n \r\n \r\n | \r\n\r\n \r\n | \r\n\r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n|
\r\n \r\n | \r\n|
\r\n \r\n | \r\n|
\r\n \r\n | \r\n|
\r\n \r\n \r\n \r\n \r\n \r\n | \r\n \r\n \r\n \r\n \r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n|
\r\n \r\n | \r\n|
\r\n \r\n | \r\n\r\n \r\n | \r\n
\r\n \r\n | \r\n|
\r\n \r\n | \r\n|
\r\n \r\n | \r\n|
\r\n \r\n | \r\n||
\r\n \r\n \r\n | \r\n\r\n \r\n | \r\n\r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n
\r\n \r\n \r\n | \r\n
\r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n
\r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n
\r\n \r\n | \r\n
\r\n \r\n | \r\n
\r\n \r\n | \r\n
\r\n \r\n \r\n | \r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n
\r\n \r\n | \r\n
\r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n|
\r\n \r\n | \r\n\r\n \r\n | \r\n
\r\n \r\n \r\n | \r\n
\r\n \r\n \r\n | \r\n\r\n \r\n \r\n | \r\n
\r\n \r\n \r\n | \r\n\r\n \r\n \r\n \r\n | \r\n
\r\n \r\n \r\n | \r\n|
\r\n \r\n \r\n | \r\n\r\n \r\n | \r\n
\r\n \r\n | \r\n|
\r\n \r\n \r\n | \r\n\r\n \r\n \r\n | \r\n
\r\n \r\n \r\n | \r\n\r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n|
\r\n \r\n \r\n | \r\n\r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n||
\r\n \r\n | \r\n\r\n \r\n | \r\n\r\n \r\n | \r\n
\r\n \r\n | \r\n\r\n \r\n | \r\n\r\n \r\n | \r\n
\r\n \r\n | \r\n\r\n \r\n | \r\n\r\n \r\n | \r\n
\r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n
\r\n \r\n | \r\n|
\r\n \r\n \r\n | \r\n\r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n
\r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n||
\r\n \r\n | \r\n\r\n \r\n | \r\n\r\n \r\n | \r\n
\r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n
\r\n \r\n | \r\n|
\r\n \r\n \r\n | \r\n\r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n|
\r\n \r\n | \r\n\r\n | \r\n
\r\n \r\n | \r\n|
\r\n \r\n \r\n | \r\n\r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n|
\r\n \r\n | \r\n\r\n \r\n | \r\n
\r\n \r\n \r\n | \r\n\r\n \r\n \r\n | \r\n
--\x3e\r\n \x3c!----\x3e\r\n \x3c!-- | --\x3e\r\n \x3c!--|
--\x3e\r\n \x3c!----\x3e\r\n \x3c!-- | --\x3e\r\n \x3c!----\x3e\r\n \x3c!----\x3e\r\n \x3c!-- | --\x3e\r\n \x3c!--
--\x3e\r\n \x3c!----\x3e\r\n \x3c!-- | --\x3e\r\n \x3c!--|
--\x3e\r\n \x3c!----\x3e\r\n \x3c!-- | --\x3e\r\n \x3c!----\x3e\r\n \x3c!----\x3e\r\n \x3c!-- | --\x3e\r\n \x3c!--
--\x3e\r\n \x3c!-- | |
\r\n \r\n | \r\n|
\r\n \r\n \r\n | \r\n\r\n |
\r\n \r\n | \r\n
\r\n \r\n | \r\n||
\r\n \r\n | \r\n\r\n \r\n | \r\n\r\n \r\n | \r\n
\r\n \r\n | \r\n\r\n \r\n | \r\n\r\n \r\n | \r\n
\r\n \r\n \r\n | \r\n
\r\n \r\n \r\n | \r\n
\r\n \r\n | \r\n
","<% }) %>"," |
","<% }) %>"," |
',' | '," | ||||||||
',' | '," | ||||||||
',' | '," | ||||||||
',' | '," | ||||||||
',' | ','
| ","||||||||
',' | ','',''," | ","||||||||
',' | '," |
",' | '," |
",' | '," |
",' | '," |
",' | '," |
",' | '," |
",' | '," |
",' | '," |
",' | '," |
",' | '," |
",' | '," |
",' | "," |
",' | '," |
"," |
<%= Common.Utils.String.htmlEncode(item.user) %> | ',"<%= Common.Utils.String.htmlEncode(item.permissions) %> | ","
'," | |
',' | '," |
'," | |
",' | "," |
\r\n | \r\n |
\r\n | \r\n |
\r\n | \r\n |
\r\n | \r\n |
"," | |
","<% }) %>"," |
","<% }) %>"," |
',' | '," | ||||||||
',' | '," | ||||||||
',' | '," | ||||||||
',' | '," | ||||||||
',' | ','
| ","||||||||
',' | ','',''," | ","||||||||
',' | '," |
",' | '," |
",' | '," |
",' | '," |
",' | '," |
",' | '," |
",' | '," |
",' | '," |
",' | '," |
",' | '," |
",' | '," |
",' | "," |
",' | '," |
"," |
<%= Common.Utils.String.htmlEncode(item.user) %> | ',"<%= Common.Utils.String.htmlEncode(item.permissions) %> | ","
'," | |
',' | '," |
'," | |
",' | "," |
\r\n | \r\n |
\r\n | \r\n |
\r\n | \r\n |
\r\n | \r\n |
"," | |