Merge branch 'staging' into moderation

This commit is contained in:
yflory 2025-06-27 16:03:16 +02:00
commit 93d0fbb7d8
172 changed files with 5547 additions and 13779 deletions

3
.gitignore vendored
View File

@ -35,6 +35,9 @@ www/common/onlyoffice/v*
www/common/worker.bundle.js
_build
FUNDING.json
funding.json
# ---> Node
# Logs
logs

View File

@ -4,11 +4,58 @@ SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and cont
SPDX-License-Identifier: AGPL-3.0-or-later
-->
# Release (2025.3.1)
# 2025.3.1
## Goals
This release introduces version 8 of OnlyOffice applications (Document, Sheet, Presentation) alongside smaller fixes and improvements.
## Features
- Upgrade OnlyOffice to version 8.3 [#1807](https://github.com/cryptpad/cryptpad/pull/1807)
## Improvements
- Show error message when x2t conversion fails in OO editor [#1890](https://github.com/cryptpad/cryptpad/pull/1890)
- Add x2t warning [#1887](https://github.com/cryptpad/cryptpad/pull/1887)
## Fixes
- Fix OnlyOffice document copy [#1864](https://github.com/cryptpad/cryptpad/pull/1864)
- Fix u8 increment issue in file-crypto [#1860](https://github.com/cryptpad/cryptpad/pull/1860) thanks @ansuz
- Fix eviction issues [#1856](https://github.com/cryptpad/cryptpad/pull/1856)
## Dependencies
- Upgrade TweetNaCl [#1854](https://github.com/cryptpad/cryptpad/pull/1854)
## Upgrade notes
> [!NOTE]
> The new version of OnlyOffice is much bigger than the previous versions. When installing version 8.3 with `install-onlyoffice.sh` ~830MB additional disk space is needed.
If you are upgrading from a version older than `2025.3.0` please read the upgrade notes of all versions between yours and `2025.3.1` to avoid configuration issues.
To upgrade:
1. Stop your server
2. Get the latest code with git
```bash
git fetch --depth 1 origin tag 2025.3.1
git checkout 2025.3.1
npm ci
npm run install:components
```
1. Restart your server
2. Review your instance's checkup page to ensure that you are passing all tests
## Contributors
Alumni: @ansuz
CryptPad team: @AAAMON @wginolas @yflory
# 💐 Spring 2025 release (2025.3.0)
## Goals

7
FUNDING.json Normal file
View File

@ -0,0 +1,7 @@
{
"drips": {
"ethereum": {
"ownedBy": "0xa117114b3a22e038c1545043d610dbd4029355b1"
}
}
}

View File

@ -18,7 +18,7 @@ Note that every GitHub release page has an RSS compatible feed that you can subs
We do also communicate about this topic on:
- [Our blog](https://blog.cryptpad.org)
- [Our Matrix public space](https://matrix.to/#/#cryptpad:matrix.xwiki.com)
- [Our Mastodon account](https://fosstodon.org/@cryptpad)
- [Our Mastodon account](https://social.xwiki.com/@CryptPad)
## Reporting a Vulnerability

View File

@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<head>
<title data-localization="main_title">CryptPad: Collaboration suite, encrypted and open-source</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="/customize/favicon/main-favicon.png" id="favicon"/>
<script src="/customize/pre-loading.js?ver=1.1"></script>
<link href="/customize/src/pre-loading.css?ver=1.0" rel="stylesheet" type="text/css">

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 -256 1850 1850"
id="svg3013"
version="1.1"
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
width="100%"
height="100%"
sodipodi:docname="forum.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata3023">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3021" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1403"
id="namedview3019"
showgrid="false"
inkscape:zoom="0.26081081"
inkscape:cx="-486.943"
inkscape:cy="498.4456"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg3013"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" />
<g
transform="matrix(1,0,0,-1,37.966102,1305.4576)"
id="g3015"
style="fill:#0087ff;fill-opacity:1">
<path
d="m 1664,896 q 53,0 90.5,-37.5 Q 1792,821 1792,768 1792,715 1754.5,677.5 1717,640 1664,640 V 256 q 0,-52 -38,-90 -38,-38 -90,-38 -417,347 -812,380 -58,-19 -91,-66 -33,-47 -31,-100.5 2,-53.5 40,-92.5 -20,-33 -23,-65.5 -3,-32.5 6,-58 9,-25.5 33.5,-55 Q 683,41 706.5,20.5 730,0 768,-30 739,-88 656.5,-113 574,-138 488,-124.5 402,-111 356,-69 349,-46 326.5,18.5 304,83 294.5,113 q -9.5,30 -23,89 -13.5,59 -15,101 -1.5,42 3.5,98.5 5,56.5 22,110.5 H 160 Q 94,512 47,559 0,606 0,672 v 192 q 0,66 47,113 47,47 113,47 h 480 q 435,0 896,384 52,0 90,-38 38,-38 38,-90 V 896 z M 1536,292 v 954 Q 1142,944 768,903 V 633 q 377,-42 768,-341 z"
id="path3017"
inkscape:connector-curvature="0"
style="fill:#0087ff;fill-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: Font Awesome by Dave Gandy
SPDX-License-Identifier: CC-BY-SA-3.0

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:url(#SVGID_2_);}
</style>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="4.7893" y1="12.0669" x2="6.7712" y2="12.0669">
<stop offset="0" style="stop-color:#4592C4"/>
<stop offset="1" style="stop-color:#545ACD"/>
</linearGradient>
<polygon class="st0" points="4.8,13 6.4,13 6.8,11.2 5.1,11.2 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="0.4529" y1="12" x2="23.5471" y2="12">
<stop offset="0" style="stop-color:#4592C4"/>
<stop offset="1" style="stop-color:#545ACD"/>
</linearGradient>
<path class="st1" d="M21.6,17.8c0-0.1,0.1-0.1,0.1-0.2c1.2-1.7,1.8-3.8,1.8-6c0-6.2-5.2-11.2-11.5-11.2S0.5,5.4,0.5,11.6
c0,6.2,5.2,11.1,11.6,11.1c1.6,0,3.1-0.3,4.5-0.9c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0.2-0.1,0.4-0.1,0.6-0.1c0.2,0,0.5,0,0.7,0.1
l4.7,1.7l-1.2-4.9C21.4,18.3,21.4,18.1,21.6,17.8z M9.1,11.2H7.5L7.2,13h1.5v0.7H7L6.6,16H5.8l0.5-2.3H4.7L4.2,16H3.5l0.4-2.3H2.5
V13h1.5l0.4-1.8H2.9v-0.7h1.6L5,8.1h0.7l-0.4,2.3h1.6l0.5-2.3h0.7l-0.5,2.3h1.4V11.2z M11.2,16h-0.9v-5.9h0.9V16z M11.1,8.9
C11,9,10.9,9.1,10.7,9.1c-0.2,0-0.3,0-0.4-0.1c-0.1-0.1-0.2-0.2-0.2-0.4c0-0.2,0.1-0.4,0.2-0.4c0.1-0.1,0.2-0.1,0.4-0.1
c0.1,0,0.3,0,0.4,0.1s0.2,0.2,0.2,0.4S11.2,8.9,11.1,8.9z M16.3,10.9c-0.2-0.1-0.5-0.1-0.6-0.1c-0.5,0-0.9,0.2-1.2,0.6
c-0.3,0.4-0.5,0.9-0.5,1.4V16h-0.9v-5.9h0.7l0.1,1.1h0c0.2-0.4,0.5-0.7,0.8-0.9c0.3-0.2,0.6-0.3,1-0.3c0.3,0,0.5,0,0.7,0.1
L16.3,10.9z M17.9,15.3c-0.5-0.5-0.7-1.3-0.7-2.2c0-1,0.2-1.7,0.7-2.3s1.2-0.8,2-0.8c0.3,0,0.6,0,0.8,0.1c0.3,0.1,0.5,0.1,0.7,0.2
l-0.3,0.8c-0.2-0.1-0.4-0.1-0.6-0.2c-0.2-0.1-0.4-0.1-0.6-0.1c-1.2,0-1.8,0.8-1.8,2.3c0,0.7,0.1,1.3,0.4,1.7s0.7,0.6,1.3,0.6
c0.5,0,1-0.1,1.5-0.3v0.8c-0.4,0.2-0.9,0.3-1.5,0.3C19.1,16.1,18.4,15.8,17.9,15.3z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
SPDX-License-Identifier: AGPL-3.0-or-later

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#Shape_2_);}
</style>
<g id="Octicons_1_">
<g id="issue-opened">
<linearGradient id="Shape_2_" gradientUnits="userSpaceOnUse" x1="1.1619" y1="12" x2="22.8381" y2="12">
<stop offset="0" style="stop-color:#4592C4"/>
<stop offset="1" style="stop-color:#545ACD"/>
</linearGradient>
<path id="Shape_1_" class="st0" d="M12,3.2c4.9,0,8.8,4,8.8,8.8s-4,8.8-8.8,8.8s-8.8-4-8.8-8.8S7.1,3.2,12,3.2L12,3.2z M12,1.2
C6,1.2,1.2,6,1.2,12S6,22.8,12,22.8S22.8,18,22.8,12S18,1.2,12,1.2L12,1.2z M13.5,5.8h-3.1v7.7h3.1V5.8L13.5,5.8z M13.5,15.1h-3.1
v3.1h3.1V15.1L13.5,15.1z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 979 B

View File

@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
SPDX-License-Identifier: AGPL-3.0-or-later

View File

@ -1,69 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0.1 (1.0.1+r73)"
sodipodi:docname="twiitter.svg"
xml:space="preserve"
style="enable-background:new 0 0 24 24;"
viewBox="0 0 24 24"
y="0px"
x="0px"
id="Layer_1"
version="1.1"><metadata
id="metadata1416"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs1414" /><sodipodi:namedview
inkscape:current-layer="Layer_1"
inkscape:window-maximized="0"
inkscape:window-y="10"
inkscape:window-x="10"
inkscape:cy="14.107333"
inkscape:cx="-5.7258526"
inkscape:zoom="17.645834"
showgrid="false"
id="namedview1412"
inkscape:window-height="1036"
inkscape:window-width="1900"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff"
inkscape:document-rotation="0" />
<style
id="style1402"
type="text/css">
.st0{fill:url(#SVGID_1_);}
</style>
<linearGradient
y2="12"
x2="23.6009"
y1="12"
x1="0.3991"
gradientUnits="userSpaceOnUse"
id="SVGID_1_">
<stop
id="stop1404"
style="stop-color:#4592C4"
offset="0" />
<stop
id="stop1406"
style="stop-color:#545ACD"
offset="1" />
</linearGradient>
<path
style="fill:#0087ff;fill-opacity:1"
id="path1409"
d="M23.6,4.8c-0.9,0.4-1.8,0.6-2.7,0.8C21.9,5,22.6,4,23,2.9c-0.9,0.5-1.9,0.9-3,1.2c-0.9-0.9-2.1-1.5-3.5-1.5 c-2.6,0-4.8,2.1-4.8,4.8c0,0.4,0,0.7,0.1,1.1c-4-0.2-7.5-2.1-9.8-5C1.6,4.1,1.4,5,1.4,5.8c0,1.7,0.8,3.1,2.1,4 C2.7,9.8,2,9.6,1.3,9.2c0,0,0,0,0,0.1c0,2.3,1.6,4.2,3.8,4.7c-0.4,0.1-0.8,0.2-1.3,0.2c-0.3,0-0.6,0-0.9-0.1 c0.6,1.9,2.4,3.3,4.4,3.3c-1.6,1.3-3.7,2-5.9,2c-0.4,0-0.8,0-1.1-0.1c2.1,1.4,4.6,2.2,7.3,2.2c8.8,0,13.6-7.3,13.6-13.6 c0-0.2,0-0.4,0-0.6C22.2,6.6,23,5.8,23.6,4.8z"
class="st0" />
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
SPDX-License-Identifier: AGPL-3.0-or-later

View File

@ -15,13 +15,13 @@ define([
elem.innerHTML = [
'<div class="cp-loading-logo">',
'<img class="cp-loading-cryptofist" src="/api/logo?' + urlArgs + '" alt="' + Messages.label_logo + '">',
'<img class="cp-loading-cryptofist" src="/api/logo?' + urlArgs + '" alt="" aria-hidden="true">',
'</div>',
'<div class="cp-loading-container">',
'<div class="cp-loading-spinner-container">',
'<span class="cp-spinner"></span>',
'</div>',
'<div class="cp-loading-progress">',
'<div class="cp-loading-progress" aria-hidden="true" role="presentation">',
'<div class="cp-loading-progress-list"></div>',
'<div class="cp-loading-progress-container"></div>',
'</div>',

View File

@ -7,7 +7,7 @@ define([
'chainpad-listmap',
'/components/chainpad-crypto/crypto.js',
'/common/common-util.js',
'/common/outer/network-config.js',
'/common/network-config.js',
'/common/common-login.js',
'/common/common-credential.js',
'/components/chainpad/chainpad.dist.js',
@ -104,9 +104,9 @@ define([
var proceed = function (result) {
hashing = false;
if (cb && typeof cb === "function" && cb(result)) { return; }
LocalStore.clearLoginToken();
Realtime.whenRealtimeSyncs(result.realtime, function () {
if (cb && typeof cb === "function" && cb(result)) { return; }
LocalStore.clearLoginToken();
Exports.redirect();
});
};

View File

@ -136,14 +136,27 @@ define(req, function(AppConfig, Default, Language) {
Messages.admin_documentsDeletionHint = "Archive a list of documents using their id (one document id per line)";
Messages.admin_documentsDeletionTitle = "Archive multiple documents";
// XXX
Messages.help_close_button = 'Close help notification'; // XXX
Messages.badges_admin = "Instance administrator";
Messages.badges_moderator = "Instance moderator";
Messages.badges_premium = "Premium user";
Messages.badges_error = "Error while validating this user's data";
Messages.profile_badges = "Badges";
Messages.user_profile = 'Go to user profile';
Messages.add_password = "Add your document password";
Messages.show_password = "Show password";
Messages.hide_password = "Hide password";
Messages.page_next = "Next page";
Messages.page_previous = "Previous page";
Messages.contact_mastodon = 'Mastodon'; // XXX
Messages.contact_forum = 'Forum'; // XXX
Messages.toolbar_text_tools = "Text tools";
Messages.toolbar_show_text_tools = "Show text tools";
Messages.toolbar_hide_text_tools = "Hide text tools";
Messages.settings_profileAvatarLabel = "Avatar";
Messages.settings_profileHeaderTitle = "Edit your profile";
Messages.settings_profileHeaderHint = "Edit your public profile...";
Messages.fc_open_formaud = 'Open (as auditor)';
return Messages;
});

View File

@ -10,8 +10,10 @@ define([
'/customize/messages.js',
'jquery',
'/api/config',
'/common/extensions.js',
'optional!/api/instance',
], function (h, Language, Util, AppConfig, Msg, $, ApiConfig, Instance) {
], function (h, Language, Util, AppConfig, Msg, $, ApiConfig,
Extensions, Instance) {
var Pages = {};
Pages.setHTML = function (e, html) {
@ -49,15 +51,8 @@ define([
return Pages.externalLink(el, Pages.localizeDocsLink(href));
};
var accounts = Pages.accounts = {
Pages.accounts = {
donateURL: AppConfig.donateURL || "https://opencollective.com/cryptpad/",
upgradeURL: AppConfig.upgradeURL
};
Pages.areSubscriptionsAllowed = function () {
try {
return ApiConfig.allowSubscriptions && accounts.upgradeURL && !ApiConfig.restrictRegistration;
} catch (err) { return void console.error(err); }
};
var languageSelector = function () {
@ -82,6 +77,7 @@ define([
if (!ref) { return; }
var attrs = {
href: ref,
role: 'button',
};
var iconName = '';
if (!/^\//.test(ref)) {
@ -160,7 +156,7 @@ define([
return h('footer.cp-footer', [
h('div.cp-footer-left', [
h('a', {href:"https://cryptpad.org"}, [
h('a', {href:"https://cryptpad.org", role: 'button'}, [
h('div.cp-logo-foot', [
h('img', {
src: '/customize/CryptPad_logo.svg',
@ -193,7 +189,7 @@ define([
var registerLink;
if (!ApiConfig.restrictRegistration) {
registerLink = h('a.nav-item.nav-link.cp-register-btn', { href: '/register/'}, [
registerLink = h('a.nav-item.nav-link.cp-register-btn', { href: '/register/', role: 'button'}, [
h('i.fa.fa-user', {'aria-hidden':'true'}),
Msg.login_register
]);
@ -201,14 +197,14 @@ define([
if (username === null) {
rightLinks = [
h('a.nav-item.nav-link.cp-login-btn', { href: '/login/'}, [
h('a.nav-item.nav-link.cp-login-btn', { href: '/login/', role: 'button'}, [
h('i.fa.fa-sign-in', {'aria-hidden':'true'}),
Msg.login_login
]),
registerLink,
];
} else {
rightLinks = h('a.nav-item.nav-link.cp-user-btn', { href: '/drive/' }, [
rightLinks = h('a.nav-item.nav-link.cp-user-btn', { href: '/drive/', role: 'button'}, [
h('i.fa.fa-user-circle', {'aria-hidden':'true'}),
" ",
username
@ -216,7 +212,7 @@ define([
}
var isHome = ['/', '/index.html'].includes(window.location.pathname);
var homeLink = h('a.nav-item.nav-link.cp-back-home' /* .navbar-brand */, { href: '/index.html' }, [
var homeLink = h('a.nav-item.nav-link.cp-back-home' /* .navbar-brand */, { href: '/index.html', role: 'button'}, [
h('i.fa.fa-arrow-left'),
h('img', {
src: '/customize/CryptPad_logo.svg',
@ -226,12 +222,19 @@ define([
Msg.homePage
]);
let pricingName = Msg.features;
Extensions.getExtensionsSync('PRICING_NAME').some(ext => {
if (!ext.name) { return; }
pricingName = ext.name;
return true;
});
return h('nav.navbar.navbar-expand-lg',
[
!isHome? homeLink: undefined,
h('a.nav-item.nav-link', { href: '/features.html'}, [
h('a.nav-item.nav-link', { href: '/features.html', role: 'button'}, [
h('i.fa.fa-info-circle'),
Pages.areSubscriptionsAllowed()? Msg.pricing: Msg.features
pricingName
]),
h('a.nav-item.nav-link', { href: 'https://docs.cryptpad.org'},
[h('i.fa.fa-book', {'aria-hidden':'true'}),Msg.docs_link]),

View File

@ -19,10 +19,38 @@ define([
].indexOf(Config.adminEmail) === -1;
var adminMailbox = Config.supportMailbox && LocalStore.isLoggedIn();
let contacts = [
{
name: Msg.contact_chat || "Chat",
image: "/customize/images/sayhi.svg",
href: "https://matrix.to/#/#cryptpad:matrix.xwiki.com",
},
{
name: Msg.contact_mastodon || "Mastodon",
image: "/customize/images/mastodon.svg",
href: "https://social.xwiki.com/@CryptPad",
},
{
name: Msg.contact_bug || "Bug report",
image: "/customize/images/github.svg",
href: "https://github.com/cryptpad/cryptpad/issues/",
},
{
name: Msg.contact_forum || "Forum",
image: "/customize/images/forum.svg",
href: "https://forum.cryptpad.org/",
},
{
name: Msg.contact_email || "Email",
image: "/customize/images/email.svg",
href: 'mailto:' + developerEmail,
},
];
return h('div#cp-main', [
Pages.infopageTopbar(),
h('div.container.cp-container', [
h('div.row.cp-page-title',[
h('div.row.cp-page-title', [
h('div.col-12.text-center', h('h1', Msg.contact)),
]),
(adminEmail || adminMailbox) ? h('div.row.cp-iconCont.align-items-center', [
@ -64,76 +92,22 @@ define([
Pages.setHTML(h('h2.text-center'), Msg.contact_dev),
h('p.center', Msg.contact_devHint)
),
].concat(contacts.map(item =>
h('div',
h('a.card-small', {href : "https://matrix.to/#/#cryptpad:matrix.xwiki.com"},
h('a.card-small', { href: item.href },
h('div.card-body',
h('p', [
h('img', {
src: '/customize/images/sayhi.svg',
src: item.image,
alt: '',
'aria-hidden': 'true'
}),
Msg.contact_chat || 'Chat'
item.name
])
)
)
),
h('div',
h('a.card-small', {href : "https://fosstodon.org/@cryptpad"},
h('div.card-body',
h('p', [
h('img', {
src: '/customize/images/mastodon.svg',
alt: '',
'aria-hidden': 'true'
}),
'Mastodon'
])
)
)
),
h('div',
h('a.card-small', {href : "https://twitter.com/cryptpad"},
h('div.card-body',
h('p', [
// this is not a typo. adblock plus blocks images with src *twitter* apparently
h('img', {
src: '/customize/images/twiitter.svg',
alt: '',
'aria-hidden': 'true'}),
'Twitter'
])
)
)
),
h('div',
h('a.card-small', {href : "https://github.com/cryptpad/cryptpad/issues/"},
h('div.card-body',
h('p', [
h('img', {
src: '/customize/images/github.svg',
alt: '',
'aria-hidden': 'true'}),
Msg.contact_bug || 'Bug report'
])
)
)
),
h('div',
h('a.card-small', {href : "mailto:" + developerEmail},
h('div.card-body',
h('p', [
h('img', {
src: '/customize/images/email.svg',
alt: '',
'aria-hidden': 'true'
}),
Msg.contact_email || 'Email'
])
)
)
),
]),
)
))),
]),
Pages.infopageFooter(),
]);

View File

@ -13,9 +13,8 @@ define([
'/common/common-ui-elements.js',
'/common/common-constants.js',
'/common/pad-types.js',
], function ($, h, Msg, AppConfig, LocalStore, Pages, Config, UIElements, Constants, PadTypes) {
var accounts = Pages.accounts;
'/common/extensions.js'
], function ($, h, Msg, AppConfig, LocalStore, Pages, Config, UIElements, Constants, PadTypes, Extensions) {
return function () {
document.title = Msg.features;
Msg.features_f_apps_note = PadTypes.availableTypes.map(function (app) {
@ -25,12 +24,6 @@ define([
AppConfig.enableEarlyAccess) { return; }
return Msg.type[app];
}).filter(function (x) { return x; }).join(', ');
var premiumButton = h('a', {
href: accounts.upgradeURL,
target: '_blank',
rel: 'noopener noreferrer',
class: 'cp-features-register-button',
}, Msg.features_f_subscribe);
var groupItemTemplate = function (title, content) {
return h('li.list-group-item', [
@ -127,39 +120,24 @@ define([
]),
]),
]);
var premiumFeatures =
h('div.col-12.col-sm-4.cp-premium-user',[
h('div.card',[
h('div.title-card',[
h('h3.text-center',Msg.features_premium)
]),
h('div.card-body.cp-pricing',[
h('div.text-center', h('a', {
href: accounts.upgradeURL,
target: '_blank'
}, Msg._getKey('features_pricing', ['5', '10', '15']))),
h('div.text-center', Msg.features_emailRequired),
]),
h('ul.list-group.list-group-flush', [
'reg', // Msg.features_f_reg, .features_f_reg_note
'storage2',
'support', // Msg.features_f_support, .features_f_support_note
'supporter' // Msg.features_f_supporter, .features_f_supporter_note
].map(groupItem)),
h('div.card-body',[
h('div.cp-features-register#cp-features-subscribe', [
premiumButton
]),
LocalStore.isLoggedIn() ? undefined : h('div.cp-note', Msg.features_f_subscribe_note)
]),
]),
]);
var availableFeatures = [
anonymousFeatures,
registeredFeatures,
Pages.areSubscriptionsAllowed() ? premiumFeatures: undefined,
];
// Msg.features_premium
// Msg.features_pricing
// Msg.features_emailRequired
// Msg.features_f_subscribe, .features_f_subscribe_note
// Msg.features_f_reg, .features_f_reg_note
// Msg.features_f_support, .features_f_support_note
// Msg.features_f_supporter, .features_f_supporter_note
Extensions.getExtensionsSync('EXTRA_PRICING').forEach(ext => {
if (!ext.getContent) { return; }
availableFeatures.push(ext.getContent(groupItem));
});
return h('div#cp-main', [
Pages.infopageTopbar(),
h('div.container.cp-container',[

View File

@ -149,20 +149,15 @@ define([
locationBlock = h('div', h('br'));
}
var subButton = function () {
if (Pages.areSubscriptionsAllowed() && !LocalStore.getPremium()) {
var sub = h('div.cp-sub-prompt', [
h('span', Msg.home_morestorage),
h('a', {href:"/accounts/", class:'subscribe-btn'}, [
h('i.fa.fa-ticket'),
Msg.features_f_subscribe
])
]);
return sub;
} else {
return h('div');
}
};
let extraButtons = [];
// Messages.home_morestorage
// Messages.features_f_subscribe
Extensions.getExtensionsSync('HOMEPAGE_BUTTON').forEach(ext => {
if (!ext.getButton) { return; }
const b = ext.getButton();
if (!b) { return; }
extraButtons.push(b);
});
let popup = h('div.cp-extensions-popups');
@ -220,7 +215,7 @@ define([
h('i.fa.fa-hdd-o', {'aria-hidden': 'true'}),
Msg.team_cat_drive
]),
subButton
extraButtons
])
])
]),

View File

@ -12,7 +12,7 @@ var elem = document.createElement('div');
elem.setAttribute('id', 'placeholder');
elem.innerHTML = [
'<div class="placeholder-logo-container">',
'<img class="placeholder-logo" alt="CryptPad Logo" src="' + logoPath + '">',
'<img class="placeholder-logo" alt="" aria-hidden="true" src="' + logoPath + '">',
'</div>',
'<div class="placeholder-message-container">',
'<p>Loading...</p>',

View File

@ -7,6 +7,7 @@
@import (reference) "./tools.less";
@import (reference) "./colortheme-all.less";
@import (reference) "./variables.less";
@import (reference) "./badges.less";
.avatar_vars(
@width: 30px
) {
@ -22,16 +23,21 @@
.avatar_vars(@width);
--avatar-width: @avatar-width;
--avatar-font-size: @avatar-font-size;
@badge-width: min(0.4*@width, 100px);
--badges-width: @badge-width;
--badges-offset: ceil(-1*@badge-width/5);
--badges-font: ceil(@badge-width*2/3);
}
& {
.avatar_vars();
&.cp-avatar {
overflow: hidden;
overflow: visible;
text-overflow: ellipsis;
font-size: 16px;
display: flex;
align-items: center;
border-radius: @variables_radius;
position: relative;
.cp-avatar-default, media-tag {
display: inline-flex;
@ -77,6 +83,14 @@
max-height: none; // To override 'media-tag img' in slide.less
}
}
.badges_include();
& > i.cp-badge {
position: absolute;
z-index: 10;
bottom: var(--badges-offset);
right: var(--badges-offset);
border: 1px solid @cp_badges-color;
}
}
}

View File

@ -17,18 +17,33 @@
--badges-width: @badges-width;
--badges-font: ceil(@badges-width*2/3);
}
.badges_include() {
--LessLoader_require: LessLoader_currentFile();
}
& {
.badges_vars();
i.cp-badge {
width: var(--badges-width);
height: var(--badges-width);
box-sizing: border-box;
border: 1px solid @cryptpad_text_col;
box-sizing: content-box;
border-radius: 50%;
display: inline-flex;
justify-content: center;
align-items: center;
font-size: var(--badges-font);
color: @cp_badges-color;
background-color: @cryptpad_color_link;
z-index: 10;
&.cp-selected {
outline-offset: 3px;
outline: 2px solid @cryptpad_text_col;
}
each(@cp_badges-bg, {
&[data-badge="@{key}"] {
background-color: @value;
}
});
}
}

View File

@ -98,6 +98,14 @@
darken(desaturate(extract(@cp_palette, 7),60%), 60%),
darken(desaturate(extract(@cp_palette, 8),70%), 60%);
@cp_badges-color: @cp_app-bg;
@cp_badges-bg: {
admin: #99cfff;
moderator: #ffb8a6;
premium: #deb2ff;
error: @cryptpad_color_red,;
}
@cryptpad_color_link:@cryptpad_color_brand_300;
// Premium plans colors
@ -321,6 +329,8 @@
@cp_toolbar-bottom-bg: @cryptpad_text_col;
@cp_toolbar-bottom-fg: @cryptpad_color_grey_900;
@cp_toolbar-logo-bg: @cryptpad_color_grey_200;
@cp_toolbar-bg2: @cryptpad_color_grey_850;
@cp_toolbar-bg3: @cryptpad_color_grey_800;
@cp_toolbar-fade1: fade(@cryptpad_text_col, 10%);
@cp_toolbar-fade3: fade(@cryptpad_text_col, 30%);

View File

@ -97,6 +97,14 @@
darken(extract(@cp_palette, 7), 50%),
darken(extract(@cp_palette, 8), 50%);
@cp_badges-color: @cp_app-bg;
@cp_badges-bg: {
admin: #0087ff;
moderator: #ff6038;
premium: #b864f7;
error: @cryptpad_color_red;
}
@cryptpad_color_link: @cryptpad_color_brand;
// Premium plans colors
@ -320,6 +328,10 @@
@cp_toolbar-bottom-bg: @cryptpad_color_grey_200;
@cp_toolbar-bottom-fg: @cryptpad_text_col;
@cp_toolbar-logo-bg: @cryptpad_color_white;
//@cp_toolbar-bg2: #e5e5e5;
//@cp_toolbar-bg3: #b2b2b2;
@cp_toolbar-bg2: @cryptpad_color_grey_200;
@cp_toolbar-bg3: @cryptpad_color_grey_300;
@cp_toolbar-fade1: fade(@cryptpad_color_black, 10%);
@cp_toolbar-fade3: fade(@cryptpad_color_black, 30%);

View File

@ -33,6 +33,11 @@
.creation_vars();
#cp-creation-container {
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
position: absolute;
z-index: 100000000; // #loading * 10
top: 0px;
@ -122,6 +127,10 @@
font-size: 16px;
color: inherit;
}
a:focus-visible {
border-radius: 50%;
outline: @variables_focus_style;
}
}
}
.cp-creation-early {
@ -142,11 +151,14 @@
border: none;
border-radius: @variables_radius;
cursor: pointer;
outline: none;
text-transform: uppercase;
&:hover {
&:hover, &:focus-visible {
background: contrast(@cp_creation-button-fg, lighten(@cp_creation-button-bg, 5%), darken(@cp_creation-button-bg, 5%));
}
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
}
}
@ -186,12 +198,13 @@
}
}
.cp-creation-slider {
display: block;
display: none;
overflow: hidden;
max-height: 0px;
max-width: 0px;
//margin-top: 10px;
&.active {
display: block;
transition: max-height 0.5s ease-in-out;
max-width: none;
max-height: 100px;
@ -259,13 +272,19 @@
input {
width: 45px;
margin: 0 5px;
padding: 0;
padding-left: 3px;
margin: 0 5px;
padding: 0 0 0 3px;
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
}
select {
margin-right: 5px;
border-radius: @variables_radius;
margin: 0.2rem;
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
}
}
&.active {
@ -286,10 +305,22 @@
input {
width: 150px;
padding: 0 5px;
margin: 0.2rem;
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
}
label {
flex: none;
}
.cp-password-reveal {
margin:0.2rem;
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
}
}
}
}
@ -313,9 +344,12 @@
justify-content: space-between;
.cp-creation-template-more {
font-size: 30px;
cursor: pointer;
margin: 0 5px;
text-align: center;
background-color: transparent;
border-color: transparent;
color: @cryptpad_text_col;
border-radius: @variables_radius;
&:first-child {
left: 5px;
}
@ -328,6 +362,10 @@
&.hidden {
visibility: hidden;
}
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
}
}
.cp-creation-template-container {
@ -366,6 +404,10 @@
color: var(--creation-color);
}
}
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
transition: all 0.1s;

View File

@ -138,6 +138,12 @@
display: none;
}
}
#cp-app-drive-content-container {
.cp-splitter {
display: none;
}
}
}
}
@ -493,7 +499,7 @@
}
}
}
/* CONTENT */
#cp-app-drive-content-container {
display: flex;
@ -512,6 +518,13 @@
display: flex;
align-items: center;
justify-content: flex-end;
touch-action: none;
-ms-touch-action: none;
-webkit-touch-action: none;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
&:hover {
border-left: 1px solid @cryptpad_text_col;
}

View File

@ -251,6 +251,21 @@
}
}
&.btn-case {
text-transform: capitalize;
font-size: 16px;
}
&.btn-primary-alt {
background-color: @cryptpad_color_brand_300;
border-color: @cryptpad_color_brand_300;
color: @cryptpad_color_grey_800;
&:hover, &:not(:disabled):active, &:focus {
color: @cryptpad_color_grey_800;
border-color: @cryptpad_color_brand_300;
background-color: lighten(@cryptpad_color_brand_300, 5%);
}
}
&.btn-light {
border-color: @cryptpad_text_col;
color: @cryptpad_text_col;

View File

@ -5,6 +5,7 @@
*/
@import (reference) "./colortheme-all.less";
@import (reference) "./variables.less";
.help_main () {
--LessLoader_require: LessLoader_currentFile();
@ -15,17 +16,21 @@
position: relative;
background-color: @cp_help-bg;
max-height: 50%;
overflow-y: auto;
&.cp-help-hidden {
display: none;
}
.cp-help-close {
background: none;
border: none;
position: absolute;
color: @cryptpad_text_col;
top: 5px;
right: 5px;
cursor: pointer;
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
}
.cp-help-text {
color: @cp_help-fg;
@ -34,6 +39,10 @@
a {
color: @cp_help-link;
text-decoration: underline;
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
}
h1 {
font-size: 20px;

View File

@ -110,6 +110,7 @@
overflow-y: auto;
a {
color: @cp_loading-link;
border-radius: @variables_radius;
}
p:last-child {
margin-bottom: 0;
@ -126,17 +127,25 @@
.cp-password-form {
display: flex;
flex-wrap: wrap;
gap:0.5rem;
width: 100%;
.cp-password-container {
flex-shrink: 1;
min-width: 0;
}
.cp-password-reveal {
padding: 0px 24px;
margin-left: 0.5rem;
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
}
}
.cp-password-input {
font-size: 16px;
&:focus-visible {
outline: @variables_focus_style;
}
}
.tools_placeholder-color();
}

View File

@ -289,3 +289,32 @@
}
}
//reusable styling for the markdown toggle button across all apps
.markdown-toolbar-button-style() {
padding: 3px 5px;
height: fit-content;
margin: 0;
line-height: 1;
border-radius: @variables_radius;
display: inline-flex;
background-color: @cp_toolbar-bottom-bg;
color: @cp_toolbar-bottom-fg;
&:hover {
background-color: fade(@cryptpad_text_col, 20%);
border-color: @cryptpad_text_col;
color: @cryptpad_text_col;
}
&.cp-toolbar-button-active {
background-color: @cp_toolbar-bg;
border-color: @cp_toolbar-fg;
color: @cp_toolbar-fg;
&:hover {
background-color: fade(@cryptpad_text_col, 20%);
}
}
}

View File

@ -5,6 +5,7 @@
*/
@import (reference) './avatar.less';
@import (reference) './badges.less';
@import (reference) "./colortheme-all.less";
.messenger_vars (
@ -12,12 +13,12 @@
) {
@msg-color: @cryptpad_text_col;
@msg-color-hover: contrast(@msg-color, lighten(@msg-color, 10%), darken(@msg-color, 10%));
@msg-bg-color: @bg-color;
@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: fade(@cp_messenger-fg, 10%);
@msg-bg-color-darker: contrast(@bg-color, lighten(@bg-color, 10%), darken(@bg-color, 10%));
@msg-bg-color: @cp_app-bg;
@msg-bg-color-dark: @cp_toolbar-bg2;
@msg-bg-color-darker: @cp_toolbar-bg3;
@msg-bg-color-light: @cp_toolbar-bg2;
@msg-bg-color-lighter: @cp_toolbar-bg3;
};
.messenger_main(
@bg-color: @cp_messenger-bg,
@ -116,9 +117,17 @@
}
}
}
i.cp-badge {
color: var(--msg-bg-color-dark);
border-color: var(--msg-bg-color-dark);
}
&:hover {
background-color: @msg-bg-color-darker;
background-color: var(--msg-bg-color-darker);
i.cp-badge {
color: var(--msg-bg-color-darker);
border-color: var(--msg-bg-color-darker);
}
}
&.cp-app-contacts-notify {
animation: notif 2s ease-in-out infinite;
@ -163,7 +172,6 @@
}
.cp-app-contacts-muted-button {
margin: 10px;
border: 0;
display: none;
order: 3;
.fa-bell-slash {
@ -192,7 +200,7 @@
}
#cp-app-contacts-friendlist .cp-app-contacts-friend, #cp-app-contacts-messaging .cp-avatar {
.avatar_main(30px);
.avatar_main(35px);
&.cp-avatar {
display: flex;
}
@ -207,7 +215,6 @@
//margin-right: 5px;
flex-shrink: 0;
z-index: 1;
margin: 4px;
}
.cp-app-contacts-status {
display: inline-block;
@ -238,8 +245,12 @@
padding: 20px;
}
.cp-app-contacts-header {
background-color: @msg-bg-color-dark;
background-color: var(--msg-bg-color-dark);
background-color: @msg-bg-color-lighter;
background-color: var(--msg-bg-color-lighter);
i.cp-badge {
color: var(--msg-bg-color-lighter);
border-color: var(--msg-bg-color-lighter);
}
padding: 0;
display: flex;
justify-content: space-between;
@ -256,15 +267,20 @@
}
}
.cp-avatar,
.cp-avatar-container,
.cp-app-contacts-right-col {
flex: 1 1 auto;
text-align: center;
}
.cp-app-contacts-remove-history {
.hover;
}
.cp-avatar-container {
display: flex;
align-items: center;
}
.cp-avatar {
margin: 10px;
margin: 0 10px;
}
.cp-app-contacts-more-history {
//display: none;

View File

@ -355,4 +355,19 @@
color: @cp_form-invalid;
}
}
.cp-userteam-picker {
.cp-userteam-filter {
margin: 5px 0;
input {
margin-bottom: 0 !important;
}
}
.cp-usergrid-container {
label {
margin-bottom: 0;
}
}
}
}

View File

@ -64,7 +64,10 @@
}
.cp-markdown-toolbar {
height: @toolbar_line-height;
min-height: @toolbar_line-height;
padding: 0.1rem 0;
height: auto;
flex-wrap: wrap;
background-color: @cp_toolbar-bg;
color: @cp_toolbar-fg;
display: none;
@ -74,7 +77,6 @@
border-radius: @variables_radius;
color: @toolbar-color;
.toolbar_button;
font: normal normal normal 14px/1 FontAwesome;
&:hover {
background-color: contrast(@cp_toolbar-bg, darken(@cp_toolbar-bg, 5%), lighten(@cp_toolbar-bg, 5%));
}
@ -85,6 +87,11 @@
button:nth-of-type(1) {
margin-left: 0.3rem;
}
@media screen and (max-width: @browser_media-medium-screen) {
button:nth-of-type(1) {
margin-left: 0;
}
}
}
.cp-toolbar-container {
@ -108,7 +115,7 @@
border: 1px solid transparent;
color: inherit;
font: @toolbar_button-font;
* {
*:not(i) {
color: inherit;
font: @toolbar_button-font;
}
@ -152,8 +159,8 @@
}
.cp-toolbar-userlist-drawer {
font: @colortheme_app-font-size @colortheme_font;
min-width: 175px;
width: 175px;
min-width: 200px;
width: 200px;
display: block;
overflow-y: auto;
overflow-x: hidden;
@ -203,20 +210,30 @@
flex-flow: column;
margin: 10px 0;
margin-bottom: 20px;
.cp-userlist-entry {
display: flex;
align-items: center;
}
&>span {
height: 48px;
padding: 5px;
margin: 2px 0;
background: @cp_toolbar-fade1;
background: @cp_toolbar-bg2;
border-radius: @variables_radius;
border-right: 3px solid transparent;
.avatar_main(30px);
.cp-avatar-default, media-tag {
margin-right: 5px;
.avatar_main(35px);
i.cp-badge {
color: @cp_toolbar-bg2;
border-color: @cp_toolbar-bg2;
}
&.cp-userlist-clickable {
cursor: pointer;
&:hover {
background: @cp_toolbar-fade3;
background: @cp_toolbar-bg3;
i.cp-badge {
color: @cp_toolbar-bg3;
border-color: @cp_toolbar-bg3;
}
}
}
.cp-toolbar-userlist-rightcol {
@ -224,6 +241,7 @@
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
margin-left: 5px;
white-space: nowrap;
display: flex;
flex-flow: column;
@ -398,7 +416,6 @@
.fa {
font: normal normal normal 14px/1 FontAwesome;
font-family: FontAwesome;
}
.tools_unselectable();

View File

@ -51,6 +51,14 @@
margin-right: 0px !important;
}
}
&:not(.radio) {
.cp-usergrid-user {
transition: order 0.5s, background-color 0.5s;
&.cp-selected {
order: -1 !important;
}
}
}
.cp-usergrid-user {
width: 70px;
height: 70px;
@ -62,14 +70,12 @@
border-radius: @variables_radius;
margin: 3px !important;
cursor: default;
transition: order 0.5s, background-color 0.5s;
margin-top: 1px;
.tools_unselectable();
&.cp-selected {
background-color: @cp_usergrid-selected-bg;
color: @cp_usergrid-selected-fg;
order: -1 !important;
.cp-usergrid-avatar {
media-tag, .cp-avatar-default {
opacity: 0.7;

View File

@ -5,7 +5,7 @@
---
services:
cryptpad:
image: "cryptpad/cryptpad:version-2025.3.0"
image: "cryptpad/cryptpad:version-2025.6.0"
hostname: cryptpad
environment:

View File

@ -290,5 +290,5 @@ server {
}
# Finally, serve anything the above exceptions don't govern.
try_files /customize/www/$uri /customize/www/$uri/index.html /www/$uri /www/$uri/index.html /customize/$uri;
try_files /customize/www/$uri /customize/www/$uri/index.html /src/$uri /www/$uri /www/$uri/index.html /src/$uri /customize/$uri;
}

View File

@ -1,10 +1,5 @@
{
"version": "v1.0.0",
"drips": {
"ethereum": {
"ownedBy": "0xa117114b3a22e038c1545043d610dbd4029355b1"
}
},
"entity": {
"type": "organisation",
"role": "owner",

View File

@ -27,19 +27,79 @@ main() {
ask_for_license
# Remember the 1st version that is installed. This will help us install only
# needed OnlyOffice versions in a later version of this script.
set_prop oldest_needed_version v1
# Check if 'oldest_needed_version' is already set, if not, set it to v8
if [ -z "${PROPS['oldest_needed_version']+set}" ]; then
echo "'oldest_needed_version' is not set. Setting it to v8."
set_prop "oldest_needed_version" "v8"
else
echo "'oldest_needed_version' is already set to ${PROPS['oldest_needed_version']}. No changes made."
fi
mkdir -p "$OO_DIR"
install_old_version v1 4f370beb
install_old_version v2b d9da72fd
install_old_version v4 6ebc6938
install_old_version v5 88a356f0
install_old_version v6 abd8a309
install_version v7 v7.3.3.60+11 1e65be6dc87d97e82b4972f303956e5397b34d637ca80a4239c48e49ab829ee5afc8f5b1680b2fb14230d63ff872ec5f9b562bb6c3f1811316b68f8b436f7ee6
install_version v8 v8.3.3.23+4 01abfb3e13dae2066c9fcdc9fd3a3a21cd08212feb7ee2f927d8acaa5c3e560f8ce7c78c533c6aad7048aaecc14f7445891f06cb38a1720e1637a971c0a02295
install_x2t v7.3+1 ab0c05b0e4c81071acea83f0c6a8e75f5870c360ec4abc4af09105dd9b52264af9711ec0b7020e87095193ac9b6e20305e446f2321a541f743626a598e5318c1
available_versions=(v1 v2b v4 v5 v6 v7 v8 x2t)
start_installing=false
for version in "${available_versions[@]}"; do
if [ "$start_installing" = true ]; then
selected_versions+=("$version")
elif [ "$version" = "${PROPS['oldest_needed_version']}" ]; then
start_installing=true
selected_versions+=("$version")
fi
done
for version in "${selected_versions[@]}"; do
case $version in
v1)
set_prop oldest_needed_version v1; install_old_version v1 4f370beb
# We delete 'help' from previous versions as they are useless and take up storage
rm -rf "$OO_DIR/v1/web-apps/apps/documenteditor/main/resources/help"
rm -rf "$OO_DIR/v1/web-apps/apps/presentationeditor/main/resources/help"
rm -rf "$OO_DIR/v1/web-apps/apps/spreadsheeteditor/main/resources/help"
;;
v2b)
install_old_version v2b d9da72fd
rm -rf "$OO_DIR/v2b/web-apps/apps/documenteditor/main/resources/help"
rm -rf "$OO_DIR/v2b/web-apps/apps/presentationeditor/main/resources/help"
rm -rf "$OO_DIR/v2b/web-apps/apps/spreadsheeteditor/main/resources/help"
;;
v4)
install_old_version v4 6ebc6938
rm -rf "$OO_DIR/v4/web-apps/apps/documenteditor/main/resources/help"
rm -rf "$OO_DIR/v4/web-apps/apps/presentationeditor/main/resources/help"
rm -rf "$OO_DIR/v4/web-apps/apps/spreadsheeteditor/main/resources/help"
;;
v5)
install_old_version v5 88a356f0
rm -rf "$OO_DIR/v5/web-apps/apps/documenteditor/main/resources/help"
rm -rf "$OO_DIR/v5/web-apps/apps/presentationeditor/main/resources/help"
rm -rf "$OO_DIR/v5/web-apps/apps/spreadsheeteditor/main/resources/help"
;;
v6)
install_old_version v6 abd8a309
rm -rf "$OO_DIR/v6/web-apps/apps/documenteditor/main/resources/help"
rm -rf "$OO_DIR/v6/web-apps/apps/presentationeditor/main/resources/help"
rm -rf "$OO_DIR/v6/web-apps/apps/spreadsheeteditor/main/resources/help"
;;
v7)
install_version v7 v7.3.3.60+11 1e65be6dc87d97e82b4972f303956e5397b34d637ca80a4239c48e49ab829ee5afc8f5b1680b2fb14230d63ff872ec5f9b562bb6c3f1811316b68f8b436f7ee6
rm -rf "$OO_DIR/v7/web-apps/apps/documenteditor/main/resources/help"
rm -rf "$OO_DIR/v7/web-apps/apps/presentationeditor/main/resources/help"
rm -rf "$OO_DIR/v7/web-apps/apps/spreadsheeteditor/main/resources/help"
rm -rf "$OO_DIR/v7/web-apps/apps/common/main/resources/help/"
# From all the older versions only v7 has 'dictionaries', we remove it for the same reasons
rm -rf "$OO_DIR/v7/dictionaries/"
;;
v8) install_version v8 v8.3.3.23+4 01abfb3e13dae2066c9fcdc9fd3a3a21cd08212feb7ee2f927d8acaa5c3e560f8ce7c78c533c6aad7048aaecc14f7445891f06cb38a1720e1637a971c0a02295 ;;
x2t) install_x2t v7.3+1 ab0c05b0e4c81071acea83f0c6a8e75f5870c360ec4abc4af09105dd9b52264af9711ec0b7020e87095193ac9b6e20305e446f2321a541f743626a598e5318c1 ;;
*)
echo "Unknown version: $version"
exit 1
;;
esac
done
rm -rf "$BUILDS_DIR"

View File

@ -86,4 +86,28 @@ removeBlock.complete = function (Env, body, cb) {
Block.removeLoginBlock(Env, publicKey, reason, edPublic, cb);
};
// Get an upload cookie
// Get a cookie allowing you to upload to the blobstage of your user
const uploadCookie = Commands.UPLOAD_COOKIE = function (Env, body, cb) {
const { publicKey } = body;
// they must provide a valid public key
if (publicKey && typeof(publicKey) === "string"
&& publicKey.length === 44) {
return cb();
}
cb("INVALID_KEY");
};
uploadCookie.complete = function (Env, body, cb) {
const { publicKey } = body;
const safeKey = Util.escapeKeyCharacters(publicKey);
Env.blobStore.uploadCookie(safeKey, (err, cookie) => {
if (err) { return void cb(err); }
cb(void 0, {cookie});
});
};

View File

@ -196,7 +196,6 @@ Quota.shouldContactServer = function (Env) {
(
typeof(Env.blockDailyCheck) === 'undefined' &&
Env.adminEmail === false
&& Env.allowSubscriptions === false
)
);
};
@ -225,22 +224,23 @@ var queryQuotaServer = function (Env, cb) {
}
};
const accountsOrigin = Env.accounts_api;
var H = Https;
if (typeof(Env.accounts_api) === 'string') {
if (typeof(accountsOrigin) === 'string') {
try {
let url = new URL(Env.accounts_api);
let url = new URL(accountsOrigin);
if (!['https:', 'http:'].includes(url.protocol)) { throw new Error("INVALID_PROTOCOL"); }
if (url.protocol === 'http:') { H = Http; }
let port = Number(url.port);
if (port && typeof(port) === 'number') { options.port = port; }
options.host = url.hostname;
Env.Log.info("USING_CUSTOM_ACCOUNTS_API", {
value: Env.accounts_api,
value: accountsOrigin,
});
} catch (err) {
Env.Log.error("INVALID_CUSTOM_QUOTA_API", {
error: err.message,
value: Env.accounts_api,
value: accountsOrigin,
});
}
}
@ -282,9 +282,11 @@ Quota.queryQuotaServer = function (Env, cb) {
Quota.updateCachedLimits = function (Env, _cb) {
var cb = Util.mkAsync(_cb);
Quota.applyCustomLimits(Env);
if (!Env.allowSubscriptions || !Env.accounts_api) { return void cb(); }
if (!Env.accounts_api) {
Quota.applyCustomLimits(Env);
return void cb();
}
Quota.queryQuotaServer(Env, function (err, json) {
if (err) { return void cb(err); }
if (!json) { return void cb(); }

View File

@ -75,7 +75,7 @@ Upload.status = function (Env, safeKey, filesize, _cb) { // FIXME FILES
};
Upload.upload = function (Env, safeKey, chunk, cb) {
Env.blobStore.upload(safeKey, chunk, cb);
Env.blobStore.uploadWs(safeKey, chunk, cb);
};
Upload.cancel = function (Env, safeKey, arg, cb) {

View File

@ -2,5 +2,5 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
module.exports = require("../www/common/common-hash");
module.exports = require("../src/common/common-hash");

View File

@ -2,4 +2,4 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
module.exports = require("../www/common/common-util");
module.exports = require("../src/common/common-util");

View File

@ -103,7 +103,7 @@ module.exports.create = function (config) {
httpSafePort: httpSafePort,
websocketPort: config.websocketPort,
accounts_api: config.accounts_api || undefined, // this simplifies integration with an accounts page
accounts_api: config.accounts_api,
shouldUpdateNode: !isRecentVersion(),
@ -212,7 +212,6 @@ module.exports.create = function (config) {
See also the cached 'restrictRegistration' value in server.js#serveConfig
*/
restrictRegistration: false,
allowSubscriptions: config.allowSubscriptions === true,
blockDailyCheck: config.blockDailyCheck === true,
consentToContact: false,
@ -225,8 +224,6 @@ module.exports.create = function (config) {
instanceJurisdiction: {},
instanceNotice: {},
myDomain: config.myDomain,
mySubdomain: config.mySubdomain, // only exists for the accounts integration
customLimits: {},
// FIXME this attribute isn't in the default conf
// but it is referenced in Quota
@ -259,6 +256,14 @@ module.exports.create = function (config) {
selfDestructTo: {},
};
Object.keys(plugins || {}).forEach(name => {
let plugin = plugins[name];
if (!plugin.customizeEnv) { return; }
try { plugin.customizeEnv(Env); }
catch (e) {}
});
(function () {
var max = config.maxWorkers;
// if the supplied value is not a positive number, leave maxWorkers undefined

View File

@ -69,6 +69,7 @@ const NOAUTH = require("./challenge-commands/base.js");
COMMANDS.MFA_CHECK = NOAUTH.MFA_CHECK;
COMMANDS.WRITE_BLOCK = NOAUTH.WRITE_BLOCK; // Account creation + password change
COMMANDS.REMOVE_BLOCK = NOAUTH.REMOVE_BLOCK;
COMMANDS.UPLOAD_COOKIE = NOAUTH.UPLOAD_COOKIE;
const TOTP = require("./challenge-commands/totp.js");
COMMANDS.TOTP_SETUP = TOTP.TOTP_SETUP;

View File

@ -19,6 +19,7 @@ const BlobStore = require("./storage/blob");
const BlockStore = require("./storage/block");
const plugins = require("./plugin-manager");
const gzipStatic = require('connect-gzip-static');
const CPCrypto = require('./crypto');
const DEFAULT_QUERY_TIMEOUT = 5000;
const PID = process.pid;
@ -26,6 +27,8 @@ const PID = process.pid;
let SSOUtils = plugins.SSO && plugins.SSO.utils;
var Env = JSON.parse(process.env.Env);
let blobStore;
let cpcrypto;
Env.plugins = plugins;
const response = Util.response(function (errLabel, info) {
if (!Env.Log) { return; }
@ -70,6 +73,7 @@ const EVENTS = {};
EVENTS.ENV_UPDATE = function (data /*, cb */) {
try {
Env = JSON.parse(data);
Env.blobStore = blobStore;
Env.Log = Log;
Env.plugins = plugins;
Env.sendMessage = sendMessage;
@ -263,7 +267,6 @@ app.use('/ssoauth', (req, res, next) => {
next();
});
app.use('/blob', function (req, res, next) {
/* Head requests are used to check the size of a blob.
Clients can configure a maximum size to download automatically,
@ -340,6 +343,8 @@ Object.keys(plugins || {}).forEach(name => {
app.use(Express.static(Path.resolve('./customize/www')));
app.use(gzipStatic(Path.resolve('./www')));
app.use("/common", Express.static('./src/common'));
var mainPages = Env.mainPages || Default.mainPages();
var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$');
app.get(mainPagePattern, Express.static('./customize'));
@ -609,7 +614,7 @@ var serveConfig = makeRouteCache(function () {
urlArgs: 'ver=' + Env.version + cacheString(),
},
removeDonateButton: (Env.removeDonateButton === true),
allowSubscriptions: (Env.allowSubscriptions === true),
accounts_api: Env.accounts_api,
websocketPath: Env.websocketPath,
httpUnsafeOrigin: Env.httpUnsafeOrigin,
adminEmail: Env.adminEmail,
@ -629,7 +634,6 @@ var serveConfig = makeRouteCache(function () {
fileHost: Env.fileHost,
shouldUpdateNode: Env.shouldUpdateNode || undefined,
listMyInstance: Env.listMyInstance,
accounts_api: Env.accounts_api,
sso: ssoCfg,
enforceMFA: Env.enforceMFA,
onlyOffice: Env.onlyOffice
@ -772,6 +776,51 @@ app.get('/api/logo', function (req, res) {
});
});
app.post('/upload-blob', Express.json({limit:"500kb"}), (req, res) => {
const { chunk, sig, edPublic } = req.body;
if (!cpcrypto) {
return void res.status(500).send({error: 'NOCRYPTO'});
}
const forbidden = reason => {
return void res.status(403).send({error: reason});
};
try {
// Check signature
const sigu8 = Util.decodeBase64(sig);
const vkey = Util.decodeBase64(edPublic);
const ok = cpcrypto.open(sigu8, vkey);
if (!ok) { return forbidden('INVALID_KEY'); }
const cookie = Util.encodeUTF8(sigu8.subarray(64));
// Check cookie
const safeKey = Util.escapeKeyCharacters(edPublic);
Env.blobStore.checkUploadCookie(safeKey, value => {
if (value !== cookie) {
return forbidden('INVALID_COOKIE');
}
// Upload chunk
Env.blobStore.upload(safeKey, chunk, (err) => {
if (err) {
return res.status(500).send({error: err});
}
// Get new cookie
Env.blobStore.uploadCookie(safeKey, (err, _c) => {
if (err) {
return res.status(500).send({error: err});
}
res.status(200).send({
cookie: _c
});
});
});
});
} catch (e) {
return void res.status(500).send({error: e.message});
}
});
// This endpoint handles authenticated RPCs over HTTP
// via an interactive challenge-response protocol
app.use(Express.json());
@ -779,6 +828,7 @@ app.post('/api/auth', function (req, res, next) {
AuthCommands.handle(Env, req, res, next);
});
app.use(function (req, res /*, next */) {
if (/^(\/favicon\.ico\/|.*\.js\.map|.*\/translations\/.*\.json)/.test(req.url)) {
// ignore common 404s
@ -822,7 +872,10 @@ nThen(function (w) {
getSession: function () {},
}, w(function (err, blob) {
if (err) { return; }
Env.blobStore = blob;
Env.blobStore = blobStore = blob;
}));
CPCrypto.init(w(function (err, crypto) {
cpcrypto = crypto;
}));
}).nThen(function () {
// TODO inform the parent process that this worker is ready

View File

@ -2,4 +2,4 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
module.exports = require("../www/common/common-signing-keys");
module.exports = require("../src/common/common-signing-keys");

View File

@ -14,8 +14,8 @@ Stats.instanceData = function (Env) {
version: Env.version,
installMethod: Env.installMethod,
domain: Env.myDomain,
subdomain: Env.mySubdomain,
domain: Env.accounts_domain,
subdomain: Env.accounts_subdomain,
httpUnsafeOrigin: Env.httpUnsafeOrigin,
httpSafeOrigin: Env.httpSafeOrigin,

View File

@ -10,6 +10,8 @@ var BlobStore = module.exports;
var nThen = require("nthen");
var Semaphore = require("saferphore");
var Util = require("../common-util");
const Crypto = require('crypto');
const PERMISSIVE = 511;
const readFileBin = require("../stream-file").readFileBin;
@ -270,15 +272,17 @@ var readBlobMetadata = function (env, blobId, handler, _cb) {
/********** METHODS **************/
var upload = function (Env, safeKey, content, cb) {
var uploadWs = function (Env, safeKey, content, cb) {
var dec;
try { dec = Buffer.from(content, 'base64'); }
catch (e) { return void cb('DECODE_BUFFER'); }
var len = dec.length;
var session = Env.getSession(safeKey);
/*
if (typeof(session.currentUploadSize) !== 'number' ||
typeof(session.pendingUploadSize) !== 'number') {
// improperly initialized... maybe they didn't check before uploading?
@ -289,6 +293,7 @@ var upload = function (Env, safeKey, content, cb) {
if (session.currentUploadSize > session.pendingUploadSize) {
return cb('E_OVER_LIMIT');
}
*/
var stagePath = makeStagePath(Env, safeKey);
@ -300,15 +305,50 @@ var upload = function (Env, safeKey, content, cb) {
blobstage.write(dec);
session.currentUploadSize += len;
cb(void 0, dec.length);
//Env.incrementBytesWritten(len);
});
} else {
session.blobstage.write(dec);
session.currentUploadSize += len;
cb(void 0, dec.length);
//Env.incrementBytesWritten(len);
}
};
var upload = function (Env, safeKey, content, cb) {
var dec;
try { dec = Buffer.from(content, 'base64'); }
catch (e) { return void cb('DECODE_BUFFER'); }
var path = makeStagePath(Env, safeKey);
Fs.appendFile(path, dec, cb);
};
const getRandomCookie = function () {
return Crypto.randomBytes(16).toString('hex');
};
var uploadCookie = function (Env, safeKey, cb) {
var stagePath = makeStagePath(Env, safeKey);
var cookiePath = stagePath + '.cookie';
const cookie = getRandomCookie();
Fse.mkdirp(Path.dirname(cookiePath), PERMISSIVE, function (err) {
if (err && err.code !== 'EEXIST') { return void cb(err); }
Fs.writeFile(cookiePath, cookie, err => {
cb(err, cookie);
});
});
};
var checkUploadCookie = function (Env, safeKey, cb) {
var stagePath = makeStagePath(Env, safeKey);
var cookiePath = stagePath + '.cookie';
Fs.readFile(cookiePath, function (err, content) {
if (err) { return void cb(); }
let expireTime = +new Date() - (5*60*1000);
Fs.stat(cookiePath, function (err, stats) {
if (stats.mtime < expireTime) { return void cb(); }
cb(content.toString('utf8'));
});
});
};
var closeBlobstage = function (Env, safeKey) {
var session = Env.getSession(safeKey);
@ -370,6 +410,10 @@ var upload_complete = function (Env, safeKey, id, cb) {
// FIXME we could just move and handle the EEXISTS instead of the above block
Fse.move(oldPath, newPath, function (e) {
if (e) { return void cb('RENAME_ERR'); }
// clear upload cookie
Fs.unlink(oldPath+'.cookie', function () {});
cb(void 0, id);
});
});
@ -448,6 +492,9 @@ var owned_upload_complete = function (Env, safeKey, id, cb) {
// otherwise it worked...
}));
}).nThen(function () {
// clear upload cookie
Fs.unlink(oldPath+'.cookie', function () {});
// clean up their session when you're done
// call back with the blob id...
cb(void 0, id);
@ -734,11 +781,26 @@ BlobStore.create = function (config, _cb) {
if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); }
isFile(makeStagePath(Env, safeKey), cb);
},
uploadWs: function (safeKey, content, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); }
uploadWs(Env, safeKey, content, Util.once(Util.mkAsync(cb)));
},
upload: function (safeKey, content, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); }
upload(Env, safeKey, content, Util.once(Util.mkAsync(cb)));
},
uploadCookie: function (safeKey, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); }
uploadCookie(Env, safeKey, Util.once(Util.mkAsync(cb)));
},
checkUploadCookie: function (safeKey, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); }
checkUploadCookie(Env, safeKey, Util.once(Util.mkAsync(cb)));
},
cancel: function (safeKey, fileSize, _cb) {
var cb = Util.once(Util.mkAsync(_cb));

View File

@ -1,53 +0,0 @@
// SPDX-FileCopyrightText: 2025 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// Move file from "src" to "www"
// This script can be used to copy src files into their old location in order to
// merge code more easily.
// Set REVERSE to true to copy from "www" to "src" once the changes have been merged.
const REVERSE = false;
const Fs = require('node:fs');
const map = {
'./src/worker/components/roster.js': './www/common/outer/roster.js',
'./src/worker/components/sharedfolder.js': './www/common/outer/sharedfolder.js',
'./src/common/cache-store.js': './www/common/outer/cache-store.js',
'./src/common/common-constants.js': './www/common/common-constants.js',
'./src/common/common-credential.js': './www/common/common-credential.js',
'./src/common/common-feedback.js': './www/common/common-feedback.js',
'./src/common/common-hash.js': './www/common/common-hash.js',
'./src/common/common-realtime.js': './www/common/common-realtime.js',
'./src/common/common-signing-keys.js': './www/common/common-signing-keys.js',
'./src/common/common-util.js': './www/common/common-util.js',
'./src/common/cryptget.js': './www/common/cryptget.js',
'./src/common/http-command.js': './www/common/outer/http-command.js',
'./src/common/login-block.js': './www/common/outer/login-block.js',
'./src/common/network-config.js': './www/common/outer/network-config.js',
'./src/common/notify.js': './www/common/notify.js',
'./src/common/onlyoffice/current-version.js': './www/common/onlyoffice/current-version.js',
'./src/common/pad-types.js': './www/common/pad-types.js',
'./src/common/pinpad.js': './www/common/pinpad.js',
'./src/common/proxy-manager.js': './www/common/proxy-manager.js',
'./src/common/recurrence.js': './www/calendar/recurrence.js',
'./src/common/rpc.js': './www/common/rpc.js',
'./src/common/user-object.js': './www/common/user-object.js',
'./src/common/user-object-setter.js': './www/common/user-object-setter.js',
'./src/common/worker-channel.js': './www/common/outer/worker-channel.js'
};
Object.keys(map).forEach(newPath => {
let oldPath = map[newPath];
if (!Fs.existsSync(newPath)) {
throw new Error("File path mismatch: " + newPath);
}
if (!Fs.existsSync(oldPath)) {
throw new Error("File path mismatch: " + oldPath);
}
const from = REVERSE ? oldPath : newPath;
const to = REVERSE ? newPath : oldPath;
Fs.cpSync(from, to);
});

3228
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -80,7 +80,7 @@ More information about this can be found in [our translation guide](/customize.d
The best places to reach the development team and the community are the [CryptPad Forum](https://forum.cryptpad.org) and the [Matrix chat](https://matrix.to/#/#cryptpad:matrix.xwiki.com)
The team is also on the fediverse: [@cryptpad@fosstodon.org](https://fosstodon.org/@cryptpad)
The team is also on the fediverse: [@cryptpad@xwiki.com](https://social.xwiki.com/@CryptPad)
# Team

View File

@ -36,7 +36,10 @@ getApi('config', ApiConfig => {
Messages,
Broadcast
}).then((store) => {
console.log(store);
console.log('API added to global "CP"');
const api = globalThis.CP = store.api;
const cfg = { channel: '0123456789abcdef0123456789abcedf' };
api.pad.join(cfg, console.log);
});
});
});

View File

@ -1,44 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
var Https = require('https');
var Config = require("../lib/load-config");
var Package = require("../package.json");
var body = JSON.stringify({
domain: Config.myDomain,
subdomain: Config.mySubdomain || null,
adminEmail: Config.adminEmail,
version: Package.version,
});
var options = {
host: 'accounts.cryptpad.fr',
path: '/api/getauthorized',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body)
}
};
Https.request(options, function (response) {
if (!('' + response.statusCode).match(/^2\d\d$/)) {
throw new Error('SERVER ERROR ' + response.statusCode);
}
var str = '';
response.on('data', function (chunk) {
str += chunk;
});
response.on('end', function () {
try {
var json = JSON.parse(str);
console.log(json);
} catch (e) {
throw new Error(e);
}
});
}).on('error', function (e) {
console.error(e);
}).end(body);

View File

@ -225,8 +225,8 @@ if (typeof(module) !== 'undefined' && module.exports) {
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/outer/network-config.js',
'/common/outer/cache-store.js',
'/common/network-config.js',
'/common/cache-store.js',
'/common/pinpad.js',
'/components/nthen/index.js',
'/components/chainpad/chainpad.dist.js',

View File

@ -1,130 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (nThen, Util, ApiConfig = {}, Nacl) => {
const getApiOrigin = function () {
if (!Object.keys(ApiConfig).length) { return; }
var url;
var unsafeOriginURL = new URL(ApiConfig.httpUnsafeOrigin);
try {
url = new URL(ApiConfig.websocketPath, ApiConfig.httpUnsafeOrigin);
url.protocol = unsafeOriginURL.protocol;
return url.origin;
} catch (err) {
console.error(err);
return ApiConfig.httpUnsafeOrigin;
}
};
var API_ORIGIN = getApiOrigin();
const setCustomize = data => {
ApiConfig = data.ApiConfig;
API_ORIGIN = getApiOrigin();
};
var clone = o => JSON.parse(JSON.stringify(o));
var randomToken = () => Util.encodeBase64(Nacl.randomBytes(24));
var postData = function (url, data, cb) {
var CB = Util.once(Util.mkAsync(cb));
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
}).then(response => {
if (response.ok) {
return void response.text().then(result => { CB(void 0, Util.tryParse(result)); }); // checkup error when using .json()
//return void response.json().then(result => { CB(void 0, result); });
}
response.json().then().then(result => {
CB(response.status, result);
});
//CB(response.status, response);
}).catch(error => {
CB(error);
});
};
var serverCommand = function (keypair, my_data, cb) {
var obj = clone(my_data);
obj.publicKey = Util.encodeBase64(keypair.publicKey);
obj.nonce = randomToken();
var href = new URL('/api/auth/', API_ORIGIN);
var txid, date;
nThen(function (w) {
// Tell the server we want to do some action
postData(href, obj, w((err, data) => {
if (err) {
w.abort();
console.error(err);
// there might be more info here
if (data) { console.error(data); }
return void cb(err);
}
// if the requested action is valid, it responds with a txid and a nonce
// bundle all that up into an object, stringify it, and sign it.
// respond with an object: {sig, txid}
if (!data.date || !data.txid) {
w.abort();
return void cb('REQUEST_REJECTED');
}
txid = data.txid;
date = data.date;
}));
}).nThen(function (w) {
var copy = clone(obj);
copy.txid = txid;
copy.date = date;
var toSign = Util.decodeUTF8(JSON.stringify(copy));
var sig = Nacl.sign.detached(toSign, keypair.secretKey);
var encoded = Util.encodeBase64(sig);
var obj2 = {
sig: encoded,
txid: txid,
};
postData(href, obj2, w((err, data) => {
if (err) {
w.abort();
console.error(err);
// there might be more info here
if (data) { console.error(data); }
return void cb("RESPONSE_REJECTED", data);
}
cb(void 0, data);
}));
});
};
serverCommand.setCustomize = setCustomize;
return serverCommand;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('nthen'),
require('./common-util'),
undefined,
require('tweetnacl/nacl-fast')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/components/nthen/index.js',
'/common/common-util.js',
'/api/config',
'/components/tweetnacl/nacl-fast.min.js',
], (nThen, Util, ApiConfig) => {
return factory(nThen, Util, ApiConfig, window.nacl);
});
} else {
// unsupported initialization
}
})();

View File

@ -14,7 +14,7 @@ const factory = (ApiConfig = {}) => {
var path = ApiConfig.websocketPath || '/cryptpad_websocket';
if (/^ws{1,2}:\/\//.test(path)) { return path; }
var l = new URL(origin || self?.location?.href);
var l = new URL(origin || globalThis?.location?.href || ApiConfig.httpUnsafeOrigin);
if (origin) {
l.href = origin;
}

View File

@ -110,7 +110,7 @@ const factory = (nThen, Util, ApiConfig = {}, Nacl) => {
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('nthen'),
require('./common-util'),
require('../common-util'),
undefined,
require('tweetnacl/nacl-fast')
);

View File

@ -224,7 +224,7 @@ const factory = (Util, ApiConfig = {}, ServerCommand, Nacl) => {
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('./common-util'),
require('../common-util'),
undefined,
require('./http-command'),
require('tweetnacl/nacl-fast')

View File

@ -4,7 +4,7 @@
(() => {
const factory = (UserObject, Util, Hash,
SF, Messages = {}, Feedback, nThen) => {
Messages = {}, Feedback, nThen, SF = {}) => {
let setCustomize = data => {
Messages = data.Messages;
@ -617,7 +617,7 @@ const factory = (UserObject, Util, Hash,
if (data.password) { folderData.password = data.password; }
if (data.owned) { folderData.owners = [Env.edPublic]; }
}).nThen(function (waitFor) {
Env.Store.getPadMetadata(null, {
Env.Store.pad.getMetadata(null, {
channel: folderData.channel
}, waitFor(function (obj) {
if (obj && (obj.error || obj.rejected)) {
@ -669,7 +669,7 @@ const factory = (UserObject, Util, Hash,
}
if (data.folderData) {
// If we're importing a folder, check its serverside metadata
Env.Store.getPadMetadata(null, { channel: folderData.channel }, function (md) {
Env.Store.pad.getMetadata(null, { channel: folderData.channel }, function (md) {
var fData = Env.user.proxy[UserObject.SHARED_FOLDERS][id];
if (md.owners) { fData.owners = md.owners; }
if (md.expire) { fData.expire = +md.expire; }
@ -1843,20 +1843,20 @@ if (typeof(module) !== 'undefined' && module.exports) {
require('./user-object'),
require('./common-util'),
require('./common-hash'),
require('../worker/components/sharedfolder'),
undefined,
require('./common-feedback'),
require('nthen')
require('nthen'),
require('../worker/components/sharedfolder'),
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/user-object.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/outer/sharedfolder.js',
'/customize/messages.js',
'/common/common-feedback.js',
'/components/nthen/index.js',
// sharedfolder.js not needed outside of worker
], factory);
} else {
// unsupported initialization

View File

@ -687,7 +687,6 @@ const factory = (Util) => {
return;
}
var stop = false;
var newrule = false;
_toAdd.some(function (_newS) {
@ -700,6 +699,11 @@ const factory = (Util) => {
_ev.start = +_evS;
_ev.end = +_evE;
_ev._count = c;
Rec.applyUpdates([_ev]);
_evS = new Date(_ev.start);
_evE = new Date(_ev.end);
if (_ev.isAllDay && _ev.startDay) { _ev.startDay = getDateStr(_evS); }
if (_ev.isAllDay && _ev.endDay) { _ev.endDay = getDateStr(_evE); }

View File

@ -48,6 +48,12 @@ const factory = (Util, Hash, Realtime) => {
var oldHref = exp.getHref(data);
if (oldHref === href) { return; }
data.href = exp.cryptor.encrypt(href);
// Recompute roHref for form (auditor link)
const parsed = Hash.parsePadUrl(href);
if (parsed.type !== "form") { return; }
const secret = Hash.getSecrets(parsed.type,
parsed.hash, data.password);
data.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret);
});
};

View File

@ -2,29 +2,29 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Sortify, UserObject, ProxyManager,
Migrate, Hash, Util, Constants, Feedback,
Realtime, Messaging, Pinpad, Rpc, Merge, Cache,
SF, AccountTS, DriveTS, Cursor,
Realtime, Messaging, Pinpad, Rpc, Cache,
SF, AccountTS, DriveTS, PadTS, Cursor,
Support, Integration, OnlyOffice,
Mailbox, Profile, Team, Messenger, History,
Calendar, BadgeTS, Block, NetConfig,
Crypto, ChainPad, CpNetflux, Listmap,
Netflux, nThen) => {
let ApiConfig = {};
let AppConfig = {};
const Account = AccountTS.Account;
const Drive = DriveTS.Drive;
const Pad = PadTS.Pad;
const Badge = BadgeTS.Badge;
const window = globalThis;
globalThis.nacl = globalThis.nacl || Crypto.Nacl;
let ApiConfig = {};
let AppConfig = {};
const Saferphore = Util.Saferphore;
var onReadyEvt = Util.mkEvent(true);
var onCacheReadyEvt = Util.mkEvent(true);
var onJoinedEvt = Util.mkEvent(true);
var onPadRejectedEvt = Util.mkEvent(true);
const setCustomize = data => {
@ -63,16 +63,23 @@ const factory = (Sortify, UserObject, ProxyManager,
Store.onReadyEvt = onReadyEvt;
Store.pad = Pad.init({
Store, store, postMessage, broadcast
});
Store.drive = Drive.initAPI({
Store, store, postMessage, broadcast
});
// Drive clients
var driveEventClients = [];
var sendDriveEvent = function (q, data, sender) {
var sendDriveEvent = store.sendDriveEvent = function (q, data, sender) {
driveEventClients.forEach(function (cId) {
if (cId === sender) { return; }
postMessage(cId, q, data);
});
};
var getStore = function (teamId) {
var getStore = Store.getStore = function (teamId) {
if (!teamId) { return store; }
try {
var teams = store.modules['team'];
@ -136,37 +143,6 @@ const factory = (Sortify, UserObject, ProxyManager,
onSync(data.teamId, cb);
};
const copyObject = (src, target) => {
Object.keys(src).forEach(k => {
delete src[k];
});
Object.keys(target).forEach(k => {
src[k] = Util.clone(target[k]);
});
};
const getProxy = teamId => {
let proxy;
if (!teamId) {
proxy = store.drive?.proxy;
} else {
const s = getStore(teamId);
proxy = s?.drive?.proxy;
}
return proxy;
};
Store.drive = {
get: (clientId, data, cb) => {
let proxy = getProxy(data.teamId);
if (!proxy) { return void cb({error: 'ENOTFOUND'}); }
cb(proxy);
},
set: (clientId, data, cb) => {
let proxy = getProxy(data.teamId);
if (!proxy) { return void cb({error: 'ENOTFOUND'}); }
copyObject(proxy, data.value);
onSync(data.teamId, cb);
}
};
Store.getSharedFolder = function (clientId, data, cb) {
var s = getStore(data.teamId);
@ -351,17 +327,6 @@ const factory = (Sortify, UserObject, ProxyManager,
});
};
// Update for all users from accounts and return current user limits
Store.updatePinLimit = function (clientId, data, cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
store.rpc.updatePinLimits(function (e, limit, plan, note) {
if (e) { return void cb({error: e}); }
account.limit = limit;
account.plan = plan;
account.note = note;
cb(account);
});
};
// Get current user limits
Store.getPinLimit = function (clientId, data, cb) {
var s = getStore(data && data.teamId);
@ -383,47 +348,6 @@ const factory = (Sortify, UserObject, ProxyManager,
cb(account);
};
// clearOwnedChannel is only used for private chat and forms
Store.clearOwnedChannel = function (clientId, data, cb) {
var s = getStore(data && data.teamId);
if (!s.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
s.rpc.clearOwnedChannel(data.channel, function (err) {
cb({error:err});
});
};
var myDeletions = {};
Store.removeOwnedChannel = function (clientId, data, cb) {
// "data" used to be a string (channelID), now it can also be an object
// data.force tells us we can safely remove the drive ID
var channel = data;
var force = false;
var teamId;
var reason;
if (data && typeof(data) === "object") {
channel = data.channel;
force = data.force;
teamId = data.teamId;
reason = data.reason;
}
if (channel === store.driveChannel && !force) {
return void cb({error: 'User drive removal blocked!'});
}
var s = getStore(teamId);
if (!s) { return void cb({ error: 'ENOTFOUND' }); }
if (!s.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
// If this channel is loaded, remember that we deleted it ourselves
if (Store.channels[channel]) { myDeletions[channel] = true; }
s.rpc.removeOwnedChannel(channel, function (err) {
if (err) { delete myDeletions[channel]; }
cb({error:err});
}, reason);
};
var arePinsSynced = function (cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
@ -499,7 +423,7 @@ const factory = (Sortify, UserObject, ProxyManager,
var initTempRpc = (clientId, cb) => {
if (store.rpc) { return void cb(store.rpc); }
var kp = Crypto.Nacl.sign.keyPair();
var keys = {
var keys = store.tempKeys = {
edPublic: Util.encodeBase64(kp.publicKey),
edPrivate: Util.encodeBase64(kp.secretKey)
};
@ -859,7 +783,7 @@ const factory = (Sortify, UserObject, ProxyManager,
}));
}).nThen(function (_w) {
if (otherOwners) {
Store.setPadMetadata(null, {
Store.pad.setMetadata(null, {
channel: c,
command: 'RM_OWNERS',
value: [edPublic],
@ -922,7 +846,7 @@ const factory = (Sortify, UserObject, ProxyManager,
}).nThen(function (waitFor) {
// Delete Drive
store.ownDeletion = true;
Store.removeOwnedChannel(clientId, {
Store.pad.destroy(clientId, {
channel: store.driveChannel,
force: true
}, waitFor());
@ -973,15 +897,6 @@ const factory = (Sortify, UserObject, ProxyManager,
});
};
/**
* Merge the anonymous drive into the user drive at registration
* data
* - anonHash
*/
Store.migrateAnonDrive = function (clientId, data, cb) {
var hash = data.anonHash;
Merge.anonDriveIntoUser(store, hash, cb);
};
// Set the display name (username) in the proxy
Store.setDisplayName = function (clientId, value, cb) {
@ -1204,7 +1119,7 @@ const factory = (Sortify, UserObject, ProxyManager,
return void cb({ notStored: true });
}
var channelData = Store.channels && Store.channels[channel];
var channelData = Store.pad.getChannels()[channel];
var owners;
if (channelData && channelData.wc && channel === channelData.wc.id) {
@ -1679,8 +1594,6 @@ const factory = (Sortify, UserObject, ProxyManager,
/////////////////////// PAD //////////////////////////////////////
//////////////////////////////////////////////////////////////////
var channels = Store.channels = store.channels = {};
Store.getSnapshot = function (clientId, data, cb) {
Store.getHistoryRange(clientId, {
cpCount: 1,
@ -1689,55 +1602,6 @@ const factory = (Sortify, UserObject, ProxyManager,
}, cb);
};
var getVersionHash = function (clientId, data) {
var validateKey;
var fakeNetflux = Hash.createChannelId();
nThen(function (waitFor) {
Store.getPadMetadata(null, {
channel: data.channel
}, waitFor(function (md) {
if (md && md.rejected) {
postMessage(clientId, "PAD_ERROR", {type: "ERESTRICTED"});
waitFor.abort();
return;
}
validateKey = md.validateKey;
}));
}).nThen(function () {
Store.getHistoryRange(clientId, {
cpCount: 1,
channel: data.channel,
lastKnownHash: data.versionHash
}, function (obj) {
if (obj && obj.error) {
postMessage(clientId, "PAD_ERROR", obj.error);
return;
}
var msgs = obj.messages || [];
if (msgs.length && msgs[msgs.length - 1].serverHash !== data.versionHash) {
postMessage(clientId, "PAD_ERROR", {type: "HASH_NOT_FOUND"});
return;
}
postMessage(clientId, "PAD_CONNECT", {
myID: fakeNetflux,
id: data.channel,
members: [fakeNetflux]
});
(obj.messages || []).forEach(function (data) {
postMessage(clientId, "PAD_MESSAGE", {
msg: data.msg,
time: data.time,
user: fakeNetflux.slice(0,16), // fake history keeper to avoid validate
});
});
if (validateKey && store.messenger) {
store.messenger.storeValidateKey(data.channel, validateKey);
}
postMessage(clientId, "PAD_READY");
});
});
};
Store.onRejected = function (allowed, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!Array.isArray(allowed)) { return void cb('ERESTRICTED'); }
@ -1788,226 +1652,6 @@ const factory = (Sortify, UserObject, ProxyManager,
});
};
Store.joinPad = function (clientId, data) {
if (data.versionHash) {
return void getVersionHash(clientId, data);
}
if (!Hash.isValidChannel(data.channel)) {
return void postMessage(clientId, "PAD_ERROR", 'INVALID_CHAN');
}
var isNew = typeof channels[data.channel] === "undefined";
var channel = channels[data.channel] = channels[data.channel] || {
queue: [],
data: {},
clients: [],
bcast: function (cmd, data, notMe) {
channel.clients.forEach(function (cId) {
if (cId === notMe) { return; }
postMessage(cId, cmd, data);
});
},
history: [],
pushHistory: function (msg, isCp) {
if (isCp) {
// the current message is a checkpoint.
// push it to your worker's history, prepending it with cp|
// cp| and anything else related to checkpoints has already
// been stripped by chainpad-netflux-worker or within async store
// when the message was outgoing.
channel.history.push('cp|' + msg);
// since the latest message is a checkpoint, we are able to drop
// some of the older history, but we can't rely on checkpoints being
// correct, as they might be checkpoints from different forks
var i;
for (i = channel.history.length - 101; i > 0; i--) {
if (/^cp\|/.test(channel.history[i])) { break; }
}
channel.history = channel.history.slice(Math.max(i, 0));
return;
}
channel.history.push(msg);
}
};
if (channel.clients.indexOf(clientId) === -1) {
channel.clients.push(clientId);
}
if (!isNew && channel.wc) {
postMessage(clientId, "PAD_CONNECT", {
myID: channel.wc.myID,
id: channel.wc.id,
members: channel.wc.members
});
channel.wc.members.forEach(function (m) {
postMessage(clientId, "PAD_JOIN", m);
});
channel.history.forEach(function (msg) {
postMessage(clientId, "PAD_MESSAGE", {
msg: CpNetflux.removeCp(msg),
user: channel.wc.myID,
validateKey: channel.data.validateKey
});
});
postMessage(clientId, "PAD_READY");
return;
}
var onError = function (err) {
// If it's a deletion started from this worker, different UI message
if (err && err.type === "EDELETED" && myDeletions[data.channel]) {
delete myDeletions[channel];
err.ownDeletion = true;
}
channel.bcast("PAD_ERROR", err);
if (err && err.type === "EDELETED" && Cache && Cache.clearChannel) {
Cache.clearChannel(data.channel);
}
// If this is a DELETED, EXPIRED or RESTRICTED pad, leave the channel
if (["EDELETED", "EEXPIRED", "ERESTRICTED"].indexOf(err.type) === -1) { return; }
Store.leavePad(null, data, function () {});
};
var conf = {
Cache: store.neverCache ? undefined : Cache,
priority: 1,
onCacheStart: function () {
postMessage(clientId, "PAD_CACHE");
},
onCacheReady: function () {
postMessage(clientId, "PAD_CACHE_READY");
},
onReady: function (pad) {
var padData = pad.metadata || {};
channel.data = padData;
if (padData && padData.validateKey && store.messenger) {
store.messenger.storeValidateKey(data.channel, padData.validateKey);
}
postMessage(clientId, "PAD_READY", pad.noCache);
/*
if (!store.proxy) {
postMessage(clientId, "PAD_READY", pad.noCache);
return;
}
onReadyEvt.reg(function () {
postMessage(clientId, "PAD_READY", pad.noCache);
});
*/
},
onMessage: function (m, user, validateKey, isCp, hash) {
channel.lastHash = hash;
channel.pushHistory(m, isCp);
channel.bcast("PAD_MESSAGE", {
user: user,
msg: m,
validateKey: validateKey
});
},
onJoin: function (m) {
channel.bcast("PAD_JOIN", m);
},
onLeave: function (m) {
channel.bcast("PAD_LEAVE", m);
},
onError: onError,
onChannelError: onError,
onRejected: Store.onRejected,
onConnectionChange: function (info) {
if (!info.state) {
channel.bcast("PAD_DISCONNECT");
}
},
onMetadataUpdate: function (metadata) {
channel.data = metadata || {};
getAllStores().forEach(function (s) {
var allData = s.manager.findChannel(data.channel, true);
allData.forEach(function (obj) {
obj.data.owners = metadata.owners;
obj.data.atime = +new Date();
if (metadata.expire) {
obj.data.expire = +metadata.expire;
}
});
var send = s.sendEvent || sendDriveEvent;
send('DRIVE_CHANGE', {
path: ['drive', UserObject.FILES_DATA]
});
});
channel.bcast("PAD_METADATA", metadata);
},
crypto: {
// The encryption and decryption is done in the outer window.
// This async-store only deals with already encrypted messages.
encrypt: function (m) { return m; },
decrypt: function (m) { return m; }
},
noChainPad: true,
channel: data.channel,
metadata: data.metadata,
network: store.network || store.networkPromise,
websocketURL: NetConfig.getWebsocketURL(),
onInit: function () {
onJoinedEvt.fire();
},
//readOnly: data.readOnly,
onConnect: function (wc, sendMessage) {
channel.sendMessage = function (msg, cId, cb) {
// Send to server
sendMessage(msg, function (err) {
if (err) {
return void cb({ error: err });
}
// Broadcast to other tabs
channel.lastHash = msg.slice(0,64);
channel.pushHistory(CpNetflux.removeCp(msg), /^cp\|/.test(msg));
channel.bcast("PAD_MESSAGE", {
user: wc.myID,
msg: CpNetflux.removeCp(msg),
validateKey: channel.data.validateKey
}, cId);
cb();
});
};
channel.wc = wc;
channel.queue.forEach(function (data) {
channel.sendMessage(data.message, clientId);
});
channel.queue = [];
channel.bcast("PAD_CONNECT", {
myID: wc.myID,
id: wc.id,
members: wc.members
});
}
};
channel.cpNf = CpNetflux.start(conf);
};
Store.leavePad = function (clientId, data, cb) {
var channel = channels[data.channel];
if (!channel || !channel.cpNf) { return void cb ({error: 'EINVAL'}); }
Store.dropChannel(data.channel);
cb();
};
Store.sendPadMsg = function (clientId, data, cb) {
var msg = data.msg;
var channel = channels[data.channel];
if (!channel) {
return; }
if (!channel.wc) {
channel.queue.push(msg);
return void cb();
}
channel.sendMessage(msg, clientId, cb);
};
Store.corruptedCache = function (clientId, channel) {
var chan = channels[channel];
if (!chan || !chan.cpNf) { return; }
Cache.clearChannel(channel);
if (!chan.cpNf.resetCache) { return; }
chan.cpNf.resetCache();
};
// Unpin and pin the new channel in all team when changing a pad password
Store.changePadPasswordPin = function (clientId, data, cb) {
var oldChannel = data.oldChannel;
@ -2084,15 +1728,6 @@ const factory = (Sortify, UserObject, ProxyManager,
cb();
};
Store.getLastHash = function (clientId, data, cb) {
var chan = channels[data.channel];
if (!chan) { return void cb({error: 'ENOCHAN'}); }
if (!chan.lastHash) { return void cb({error: 'EINVAL'}); }
cb({
hash: chan.lastHash
});
};
// Delete a pad received with a burn after reading URL
var notifyOwnerPadRemoved = function (data, obj) {
@ -2152,7 +1787,7 @@ const factory = (Sortify, UserObject, ProxyManager,
edPrivate: Hash.encodeBase64(pair.secretKey)
}, function (e, rpc) {
if (e) { return void console.error(e); }
Store.getPadMetadata(null, {
Store.pad.getMetadata(null, {
channel: channel
}, function (md) {
rpc.removeOwnedChannel(channel, function (err) {
@ -2167,75 +1802,6 @@ const factory = (Sortify, UserObject, ProxyManager,
}
};
// Fetch the latest version of the metadata on the server and return it.
// If the pad is stored in our drive, update the local values of "owners" and "expire"
Store.getPadMetadata = function (clientId, data, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (store.offline || !store.anon_rpc) { return void cb({ error: 'OFFLINE' }); }
if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); }
if (data.channel.length !== 32) { return void cb({ error: 'EINVAL'}); }
if (!Hash.isValidChannel(data.channel)) {
Feedback.send('METADATA_INVALID_CHAN');
return void cb({ error: 'EINVAL' });
}
store.anon_rpc.send('GET_METADATA', data.channel, function (err, obj) {
if (err) { return void cb({error: err}); }
var metadata = (obj && obj[0]) || {};
cb(metadata);
// If you don't have access to the metadata, stop here
// (we can't update the local data)
if (metadata.rejected) { return; }
// Update owners and expire time in the drive
getAllStores().forEach(function (s) {
var allData = s.manager.findChannel(data.channel, true);
var changed = false;
allData.forEach(function (obj) {
if (Sortify(obj.data.owners) !== Sortify(metadata.owners)) {
changed = true;
}
obj.data.owners = metadata.owners;
obj.data.atime = +new Date();
if (metadata.expire) {
obj.data.expire = +metadata.expire;
}
});
// If we had to change the "owners" field, redraw the drive UI
if (!changed) { return; }
var send = s.sendEvent || sendDriveEvent;
send('DRIVE_CHANGE', {
path: ['drive', UserObject.FILES_DATA]
});
});
});
};
Store.setPadMetadata = function (clientId, data, cb) {
if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); }
if (!data.command) { return void cb({ error: 'EINVAL' }); }
var s = getStore(data.teamId);
if (!s) { return void cb({ error: 'ENOTFOUND' }); }
var otherChannels = data.channels;
delete data.channels;
s.rpc.setMetadata(data, function (err, res) {
if (err) { return void cb({ error: err }); }
if (!Array.isArray(res) || !res.length) { return void cb({}); }
cb(res[0]);
});
// If we have other related channels, send the command for them too
if (Array.isArray(otherChannels)) {
otherChannels.forEach(function (chan) {
var _d = Util.clone(data);
_d.channel = chan;
Store.setPadMetadata(clientId, _d, function () {
});
});
}
};
Store.deleteMailboxMessage = function (clientId, data, cb) {
if (!store.anon_rpc) { return void cb({error: 'RPC_NOT_READY'}); }
store.anon_rpc.send('DELETE_MAILBOX_MESSAGE', data, function (e) {
@ -2598,114 +2164,21 @@ const factory = (Sortify, UserObject, ProxyManager,
s.manager.command(cmdData, cb2);
};
// Check if this is a channel that we shouldn't leave when closing the debug app
var alwaysOnline = function (chanId) {
if (!store) { return; }
// Drive
if (store.driveChannel === chanId) { return true; }
// Shared folders
if (SF.isSharedFolderChannel(chanId)) { return true; }
// Teams
if (Util.find(store, ['proxy', 'teams'])) {
var t = Util.find(store, ['proxy', 'teams']) || {};
return Object.keys(t).some(function (id) {
return t[id].channel === chanId;
});
}
// Profile
if (Util.find(store, ['proxy', 'profile', 'href'])) {
return Hash.hrefToHexChannelId(Util.find(store, ['proxy', 'profile', 'href']))
=== chanId;
}
};
var dropChannel = Store.dropChannel = function (chanId) {
console.error('Drop channel', chanId);
try {
store.messenger.leavePad(chanId);
} catch (e) { console.error(e); }
try {
store.modules['cursor'].leavePad(chanId);
} catch (e) { console.error(e); }
try {
store.modules['integration'].leavePad(chanId);
} catch (e) { console.error(e); }
try {
store.onlyoffice.leavePad(chanId);
} catch (e) { console.error(e); }
try {
if (alwaysOnline(chanId)) {
delete Store.channels[chanId];
return;
}
} catch (e) { console.error(e); }
try {
Cache.leaveChannel(chanId);
} catch (e) { console.error(e); }
if (!Store.channels[chanId]) { return; }
if (Store.channels[chanId].cpNf) {
Store.channels[chanId].cpNf.stop();
}
delete Store.channels[chanId];
};
Store._removeClient = function (clientId) {
var driveIdx = driveEventClients.indexOf(clientId);
if (driveIdx !== -1) {
driveEventClients.splice(driveIdx, 1);
}
try {
store.onlyoffice?.removeClient(clientId);
} catch (e) { console.error(e); }
try {
store.mailbox?.removeClient(clientId);
} catch (e) { console.error(e); }
store.onlyoffice?.removeClient?.(clientId);
store.mailbox?.removeClient?.(clientId);
Object.keys(store.modules).forEach(function (key) {
if (!store.modules[key]) { return; }
if (!store.modules[key].removeClient) { return; }
try {
store.modules[key].removeClient(clientId);
} catch (e) { console.error(e); }
store.modules[key]?.removeClient?.(clientId);
});
Object.keys(Store.channels).forEach(function (chanId) {
var chanIdx = Store.channels[chanId].clients.indexOf(clientId);
if (chanIdx !== -1) {
Store.channels[chanId].clients.splice(chanIdx, 1);
}
if (Store.channels[chanId].clients.length === 0) {
dropChannel(chanId);
}
});
Store.pad?.removeClient?.(clientId);
};
/*
var loadProfile = function (waitFor) {
store.profile = Profile.init({
store: store,
updateMetadata: function () {
broadcast([], "UPDATE_METADATA");
},
pinPads: function (data, cb) { Store.pinPads(null, data, cb); },
}, waitFor, function (ev, data, clients) {
clients.forEach(function (cId) {
postMessage(cId, 'PROFILE_EVENT', {
ev: ev,
data: data
});
});
});
};
*/
var loadOnlyOffice = function () {
if (store.onlyoffice) { return; }
store.onlyoffice = OnlyOffice.init(store, function (ev, data, clients) {
@ -2776,7 +2249,7 @@ const factory = (Sortify, UserObject, ProxyManager,
loadSharedFolder: loadSharedFolder,
settings: proxy.settings,
removeOwnedChannel: function (channel, cb) {
Store.removeOwnedChannel('', channel, cb);
Store.pad.destroy('', channel, cb);
},
store: store,
Store: Store
@ -3028,7 +2501,7 @@ const factory = (Sortify, UserObject, ProxyManager,
// don't always call onCacheReady and onReady?
// it depends on what was required by the first tab
onAccountCacheReady(returned => {
store.returned ||= returned;
store.cacheReturned ||= returned;
cacheCb(returned);
});
onAccountReady(returned => {
@ -3051,8 +2524,9 @@ const factory = (Sortify, UserObject, ProxyManager,
setInterval(function () {
var clients = [];
Object.keys(Store.channels).forEach(function (chanId) {
var c = Store.channels[chanId].clients;
const channels = Store.pad.getChannels();
Object.keys(channels).forEach(function (chanId) {
var c = channels[chanId].clients;
Array.prototype.push.apply(clients, c);
});
clients = Util.deduplicateString(clients);
@ -3092,7 +2566,7 @@ const factory = (Sortify, UserObject, ProxyManager,
} = drive;
onDriveCacheReady(() => {
cacheCb(store.returned);
cacheCb(store.cacheReturned || store.returned);
});
onDriveReady(() => {
cb(store.returned);
@ -3134,13 +2608,6 @@ const factory = (Sortify, UserObject, ProxyManager,
*/
var initialized = false;
// Are we still in noDrive mode?
Store.hasDrive = function (clientId, data, cb) {
cb({
state: Boolean(store.proxy)
});
};
const loadHK = (cb) => {
if (store?.network?.historyKeeper) {
return setTimeout(cb);
@ -3170,7 +2637,8 @@ const factory = (Sortify, UserObject, ProxyManager,
};
// If we load CryptPad for the first time from an existing pad, don't create a
// drive automatically.
var onNoDrive = function (clientId, cb, initRpc) {
var onNoDrive = function (clientId, _cb, initRpc) {
const cb = Util.once(_cb);
var andThen = function () {
// Initialize modules that can be used by pads
// and don't require account data
@ -3182,30 +2650,37 @@ const factory = (Sortify, UserObject, ProxyManager,
cb({});
});
};
if (initRpc) {
// In integration mode, we may need an RPC
return initTempRpc(clientId, getAnon);
}
getAnon();
// We need to know the HistoryKeeper ID
// to initialize the anon RPC
loadHK(err => {
if (err) { return; }
if (initRpc) {
// In integration mode, we may need
// an authenticated RPC
return initTempRpc(clientId, getAnon);
}
getAnon();
});
// If initRpc is true, we need to wait for a network
// before calling back
if (!store.network && !initRpc) { cb({}); }
};
// We need an anonymous RPC to be able to check if the pad exists and to get
// its metadata, so we have to create a network first.
if (!store.network) {
var wsUrl = NetConfig.getWebsocketURL();
return void Netflux.connect(wsUrl).then(function (network) {
// If we already haave a network (race condition), use the
// existing one and forget this one
store.networkPromise = Netflux.connect(wsUrl);
andThen();
return store.networkPromise.then((network) => {
if (store.network === network) { return; }
// If we already haave a network (race condition)
// use the existing one and forget this one
if (!store.network) { store.network = network; }
else {
network.disconnect();
network = store.network;
}
// We need to know the HistoryKeeper ID to initialize the anon RPC
loadHK(err => {
if (err) { return void cb(err); }
andThen();
});
}, function (err) {
console.error(err);
cb({error: 'OFFLINE'});
@ -3240,6 +2715,10 @@ const factory = (Sortify, UserObject, ProxyManager,
if (obj?.error) {
Feedback.send("NO_DRIVE_ERROR", true);
}
if (!!data.neverDrive) {
// Send temp RPC keys to the browser to allow upload
obj.tempKeys = store?.tempKeys;
}
cb(obj);
}, !!data.neverDrive);
}
@ -3285,7 +2764,13 @@ const factory = (Sortify, UserObject, ProxyManager,
startModules(clientId, ret, onInit);
});
});
onJoinedEvt.reg(next);
Store.pad.onCacheReady(() => {
// If we're online, wait for onJoined
if (store.network) { return; }
// Otherwise, continue with cached drive
next();
});
Store.pad.onJoined(next);
onPadRejectedEvt.reg(next);
});
}
@ -3402,8 +2887,9 @@ const factory = (Sortify, UserObject, ProxyManager,
});
}
// If this is not the first tab (initialized is true), it means either we don't
// support offline or we're already online
// If this is not the first tab (initialized is true),
// it means either we don't support offline or we're
// already online
if (initialized) {
if (store.networkTimeout) {
postMessage(clientId, "LOADING_DRIVE", {
@ -3471,48 +2957,41 @@ const factory = (Sortify, UserObject, ProxyManager,
};
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('json.sortify'),
require('../common/user-object'),
require('../common/proxy-manager'),
require('./components/migrate-user-object'),
require('../common/common-hash'),
require('../common/common-util'),
require('../common/common-constants'),
require('../common/common-feedback'),
require('../common/common-realtime'),
require('./components/messaging'),
require('../common/pinpad'),
require('../common/rpc'),
require('./components/merge-drive'),
require('../common/cache-store'),
require('./components/sharedfolder'),
require('./components/account'), // .ts
require('./components/drive'), // .ts
require('./modules/cursor'),
require('./modules/support'),
require('./modules/integration'),
require('./modules/onlyoffice'),
require('./modules/mailbox'),
require('./modules/profile'),
require('./modules/team'),
require('./modules/messenger'),
require('./modules/history'),
require('./modules/calendar'),
require('./modules/badge'),
require('../common/login-block'),
require('../common/network-config'),
require('chainpad-crypto'),
require('chainpad'),
require('chainpad-netflux'),
require('chainpad-listmap'),
require('netflux-websocket'),
require('nthen')
);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('json.sortify'),
require('../common/user-object'),
require('../common/proxy-manager'),
require('./components/migrate-user-object'),
require('../common/common-hash'),
require('../common/common-util'),
require('../common/common-constants'),
require('../common/common-feedback'),
require('../common/common-realtime'),
require('./components/messaging'),
require('../common/pinpad'),
require('../common/rpc'),
require('../common/cache-store'),
require('./components/sharedfolder'),
require('./components/account'), // .ts
require('./components/drive'), // .ts
require('./components/pad'), // .ts
require('./modules/cursor'),
require('./modules/support'),
require('./modules/integration'),
require('./modules/onlyoffice'),
require('./modules/mailbox'),
require('./modules/profile'),
require('./modules/team'),
require('./modules/messenger'),
require('./modules/history'),
require('./modules/calendar'),
require('./modules/badge'), // .ts
require('../common/outer/login-block'),
require('../common/network-config'),
require('chainpad-crypto'),
require('chainpad'),
require('chainpad-netflux'),
require('chainpad-listmap'),
require('netflux-websocket'),
require('nthen')
);

View File

@ -36,10 +36,12 @@ const init = (config) => {
// Create account secret from hash
const secret = Hash.getSecrets('drive', hash);
const network = config.store?.network ||
config.store?.networkPromise;
const listmapConfig = {
data: {},
websocketURL: NetConfig.getWebsocketURL(),
network: config.store?.network,
network: network,
channel: secret.channel,
readOnly: false,
validateKey: secret.keys?.validateKey || undefined,
@ -71,10 +73,14 @@ const init = (config) => {
}).on('cacheready', function (info) {
store.realtime = info.realtime;
store.offline = true;
store.networkPromise = info.networkPromise;
const hadPromise = !!store.networkPromise;
store.networkPromise ||= info.networkPromise;
store.cacheReturned = returned;
if (store.networkPromise && store.networkPromise.then) {
// Show error if we can't connect, but only if the accounts
// was required first
if (store.networkPromise && store.networkPromise.then
&& !hadPromise) {
// Check if we can connect
const to = setTimeout(function () {
store.networkTimeout = true;

View File

@ -3,7 +3,8 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import nacl from 'tweetnacl/nacl-fast';
import { Drive } from '../types'
import { Drive } from '../types';
import * as Merge from './merge-drive';
// node modules
import * as Listmap from 'chainpad-listmap';
@ -21,10 +22,59 @@ const onReadyEvt: any = Util.mkEvent(true);
const onDisconnectEvt: any = Util.mkEvent();
const onReconnectEvt: any = Util.mkEvent();
const init = (config) => {
const { broadcast, store, account } = config;
const copyObject = (src, target) => {
Object.keys(src).forEach(k => {
delete src[k];
});
Object.keys(target).forEach(k => {
src[k] = Util.clone(target[k]);
});
};
const getProxy = (ctx, teamId) => {
const store = ctx.store;
const Store = ctx.Store;
let proxy;
if (!teamId) {
proxy = store.drive?.proxy;
} else {
const s = Store.getStore(teamId);
proxy = s?.proxy?.drive;
}
return proxy;
};
const drive = store.drive = store.drive || {};
const initAPI = (config) => {
const { broadcast, store, Store, account } = config;
const ctx = {
store,
Store
};
return {
exists: (clientId, data, cb) => {
cb({ state: Boolean(store.proxy) });
},
get: (clientId, data, cb) => {
let proxy = getProxy(ctx, data.teamId);
if (!proxy) { return void cb({error: 'ENOTFOUND'}); }
cb({drive: proxy});
},
set: (clientId, data, cb) => {
let proxy = getProxy(ctx, data.teamId);
if (!proxy) { return void cb({error: 'ENOTFOUND'}); }
copyObject(proxy, data.value);
Store.onSync(data.teamId, cb);
},
migrateAnon: (clientId, data, cb) => {
Merge.anonDriveIntoUser(store, data.anonHash, cb);
}
};
};
const init = (config) => {
const { broadcast, store, Store, account } = config;
const drive = store.drive ||= {};
let data = store.proxy?.drive;
const hash:string = data?.hash || Hash.createRandomHash('drive');
@ -112,7 +162,8 @@ const init = (config) => {
};
const Drive: Drive = {
init: init,
init,
initAPI
};
export { Drive }

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(function () {
var factory = function (Util, Cred, Nacl, Crypto) {
var Invite = {};
@ -102,21 +101,9 @@ var factory = function (Util, Cred, Nacl, Crypto) {
return Invite;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require("../../common/common-util"),
require("../../common/common-credential"),
require("tweetnacl/nacl-fast"),
require("chainpad-crypto")
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/common/common-credential.js',
'/components/chainpad-crypto/crypto.js',
'/components/tweetnacl/nacl-fast.min.js',
], function (Util, Cred, Crypto) {
return factory(Util, Cred, window.nacl, Crypto);
});
}
}());
module.exports = factory(
require("../../common/common-util"),
require("../../common/common-credential"),
require("tweetnacl/nacl-fast"),
require("chainpad-crypto")
);

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Messaging, Hash, Util, Crypto, Block) => {
// Random timeout between 10 and 30 times your sync time (lag + chainpad sync)
@ -222,14 +221,30 @@ const factory = (Messaging, Hash, Util, Crypto, Block) => {
var curve = msg.author;
var friend = ctx.store.proxy.friends && ctx.store.proxy.friends[curve];
if (!friend || typeof msg.content !== "object") { return void cb(true); }
Object.keys(msg.content).forEach(function (key) {
friend[key] = msg.content[key];
});
if (ctx.store.messenger) {
ctx.store.messenger.onFriendUpdate(curve);
const edPublic = msg.content.edPublic;
const todo = () => {
Object.keys(msg.content).forEach(function (key) {
friend[key] = msg.content[key];
});
if (ctx.store.messenger) {
ctx.store.messenger.onFriendUpdate(curve);
}
ctx.updateMetadata();
cb(true);
};
if (msg.content.badge && ctx.store.modules['badge']) {
return ctx.store.modules['badge'].listBadges({
edPublic
}, (list => {
if (!list.includes(msg.content.badge)) {
delete msg.content['badge'];
}
todo();
}));
}
ctx.updateMetadata();
cb(true);
todo();
};
// Encrypt the password under the right key before sending it via URL hash
@ -983,24 +998,10 @@ const factory = (Messaging, Hash, Util, Crypto, Block) => {
};
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('./messaging'),
require('../../common/common-hash'),
require('../../common/common-util'),
require('chainpad-crypto'),
require('../../common/login-block')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/outer/messaging.js',
'/common/common-hash.js',
'/common/common-util.js',
'/components/chainpad-crypto/crypto.js',
'/common/outer/login-block.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('./messaging'),
require('../../common/common-hash'),
require('../../common/common-util'),
require('chainpad-crypto'),
require('../../common/outer/login-block')
);

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Crypt, FO, Hash, Realtime) => {
var exp = {};
@ -91,22 +90,9 @@ const factory = (Crypt, FO, Hash, Realtime) => {
return exp;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('../../common/cryptget'),
require('../../common/user-object'),
require('../../common/common-hash'),
require('../../common/common-realtime')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/cryptget.js',
'/common/user-object.js',
'/common/common-hash.js',
'/common/common-realtime.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/cryptget'),
require('../../common/user-object'),
require('../../common/common-hash'),
require('../../common/common-realtime')
);

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Crypto, Hash, Util, Constants, Realtime) => {
var Msg = {};
@ -140,23 +139,10 @@ const factory = (Crypto, Hash, Util, Constants, Realtime) => {
return Msg;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('chainpad-crypto'),
require('../../common/common-hash'),
require('../../common/common-util'),
require('../../common/common-constants'),
require('../../common/common-realtime')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/components/chainpad-crypto/crypto.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-constants.js',
'/common/common-realtime.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('chainpad-crypto'),
require('../../common/common-hash'),
require('../../common/common-util'),
require('../../common/common-constants'),
require('../../common/common-realtime')
);

View File

@ -2,11 +2,11 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Feedback, Hash, Util,
Messaging, Crypt, Mailbox, Messages = {},
Messaging, Crypt, Mailbox,
Realtime, nThen, Crypto) => {
let Messages = {};
const setCustomize = data => {
Messages = data.Messages;
};
@ -507,35 +507,14 @@ const factory = (Feedback, Hash, Util,
return migrate;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('../../common/common-feedback'),
require('../../common/common-hash'),
require('../../common/common-util'),
require('./messaging'),
require('../../common/cryptget'),
require('../modules/mailbox'),
undefined,
require('../../common/common-realtime'),
require('nthen'),
require('chainpad-crypto')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-feedback.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/outer/messaging.js',
'/common/cryptget.js',
'/common/outer/mailbox.js',
'/customize/messages.js',
'/common/common-realtime.js',
'/components/nthen/index.js',
'/components/chainpad-crypto/crypto.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/common-feedback'),
require('../../common/common-hash'),
require('../../common/common-util'),
require('./messaging'),
require('../../common/cryptget'),
require('../modules/mailbox'),
require('../../common/common-realtime'),
require('nthen'),
require('chainpad-crypto')
);

View File

@ -0,0 +1,545 @@
// SPDX-FileCopyrightText: 2025 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import nacl from 'tweetnacl/nacl-fast';
import { Pad, RpcCall, Callback } from '../types'
// node modules
import * as ChainPad from 'chainpad';
import * as Crypto from 'chainpad-crypto';
import * as CpNetflux from 'chainpad-netflux';
import nThen from 'nthen';
import Sortify from 'json.sortify';
// custom modules
import * as NetConfig from '../../common/network-config.js';
import * as Hash from '../../common/common-hash.js';
import * as Util from '../../common/common-util.js';
import * as Cache from '../../common/cache-store.js';
import * as UserObject from '../../common/user-object.js';
import * as SF from './sharedfolder.js';
const onJoinedEvt: any = Util.mkEvent(true);
const onCacheReadyEvt: any = Util.mkEvent(true);
const _getMetadata: Callback = (ctx, clientId, data, _cb) => {
const cb = Util.once(Util.mkAsync(_cb));
const { store, Store } = ctx;
if (store.offline || !store.anon_rpc) {
return void cb({ error: 'OFFLINE' });
}
if (!data.channel) {
return void cb({ error: 'ENOTFOUND'});
}
if (data.channel.length !== 32) {
return void cb({ error: 'EINVAL'});
}
if (!Hash.isValidChannel(data.channel)) {
return void cb({ error: 'EINVAL' });
}
store.anon_rpc.send('GET_METADATA', data.channel, (err, obj) => {
if (err) { return void cb({error: err}); }
const metadata = (obj && obj[0]) || {};
cb(metadata);
// If you don't have access to the metadata, stop here
// (we can't update the local data)
if (metadata.rejected) { return; }
// Update owners and expire time in the drive
Store.getAllStores().forEach(s => {
const allData = s.manager.findChannel(data.channel, true);
let changed = false;
allData.forEach(obj => {
if (Sortify(obj.data.owners) !== Sortify(metadata.owners)) {
changed = true;
}
obj.data.owners = metadata.owners;
obj.data.atime = +new Date();
if (metadata.expire) {
obj.data.expire = +metadata.expire;
}
});
// If we had to change the "owners" field,
// redraw the drive UI
if (!changed) { return; }
var send = s.sendEvent || store.sendDriveEvent;
send('DRIVE_CHANGE', {
path: ['drive', UserObject.FILES_DATA]
});
});
});
};
const _setMetadata: Callback = (ctx, clientId, data, cb) => {
if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); }
if (!data.command) { return void cb({ error: 'EINVAL' }); }
const { Store } = ctx;
const s = Store.getStore(data.teamId);
if (!s) { return void cb({ error: 'ENOTFOUND' }); }
const otherChannels = data.channels;
delete data.channels;
s.rpc.setMetadata(data, (err, res) => {
if (err) { return void cb({ error: err }); }
if (!Array.isArray(res) || !res.length) { return void cb({}); }
cb(res[0]);
});
// If we have other related channels, send the command for them too
if (Array.isArray(otherChannels)) {
otherChannels.forEach(chan => {
var _d = Util.clone(data);
_d.channel = chan;
Store.setPadMetadata(clientId, _d, () => {});
});
}
};
const _getVersionHash: Callback = (ctx, clientId, data) => {
let validateKey;
const { Store, store, postMessage } = ctx;
const channel = data.channel;
const toFind = data.versionHash;
// create fake history keeper to avoid validate
const fakeNetflux = Hash.createChannelId();
nThen(waitFor => {
_getMetadata(ctx, clientId, { channel }, waitFor(md => {
if (md && md.rejected) {
postMessage(clientId, "PAD_ERROR", {
type: "ERESTRICTED"
});
waitFor.abort();
return;
}
validateKey = md.validateKey;
}));
}).nThen(() => {
Store.getHistoryRange(clientId, {
cpCount: 1,
channel,
lastKnownHash: toFind
}, obj => {
if (obj && obj.error) {
return postMessage(clientId, "PAD_ERROR", obj.error);
}
const msgs = obj.messages || [];
if (msgs[msgs.length - 1]?.serverHash !== toFind) {
return postMessage(clientId, "PAD_ERROR", {
type: "HASH_NOT_FOUND"
});
}
postMessage(clientId, "PAD_CONNECT", {
myID: fakeNetflux,
id: channel,
members: [fakeNetflux]
});
(obj.messages || []).forEach(data => {
postMessage(clientId, "PAD_MESSAGE", {
msg: data.msg,
time: data.time,
user: fakeNetflux.slice(0,16)
});
});
if (validateKey) {
store?.messenger?.storeValidateKey(channel, validateKey);
}
postMessage(clientId, "PAD_READY");
});
});
};
const _join: Callback = (ctx, clientId, data) => {
if (data.versionHash) {
return _getVersionHash(ctx, clientId, data);
}
const { channels, store, Store, myDeletions, postMessage } = ctx;
const channelId = data.channel;
if (!Hash.isValidChannel(channelId)) {
return postMessage(clientId, "PAD_ERROR", 'INVALID_CHAN');
}
const isNew = typeof channels[channelId] === "undefined";
// Create or get existing channel object
const channel = channels[channelId] ||= {
queue: [],
data: {},
clients: [],
bcast: (cmd, data, notMe) => {
channel.clients.forEach(function (cId) {
if (cId === notMe) { return; }
postMessage(cId, cmd, data);
});
},
history: [],
pushHistory: (msg, isCp) => {
if (isCp) {
// Checkpoint, re-add "cp|" and clear older history
channel.history.push('cp|' + msg);
let i;
for (i = channel.history.length - 101; i > 0; i--) {
if (/^cp\|/.test(channel.history[i])) { break; }
}
channel.history = channel.history.slice(i);
return;
}
channel.history.push(msg);
}
};
// Add new client
if (channel.clients.indexOf(clientId) === -1) {
channel.clients.push(clientId);
}
// Existing pad already loaded: send userlist and history
if (!isNew && channel.wc) {
postMessage(clientId, "PAD_CONNECT", { // Initialize
myID: channel.wc.myID,
id: channel.wc.id,
members: channel.wc.members
});
channel.wc.members.forEach(m => { // Userlist
postMessage(clientId, "PAD_JOIN", m);
});
channel.history.forEach(msg => { // History
postMessage(clientId, "PAD_MESSAGE", {
msg: CpNetflux.removeCp(msg),
user: channel.wc.myID,
validateKey: channel.data.validateKey
});
});
postMessage(clientId, "PAD_READY"); // Ready
return;
}
// chainpad-netflux config
const onError = err => {
const type = err?.type;
// Deletion started from this worker => different UI message
if (type === "EDELETED" && myDeletions[channelId]) {
delete myDeletions[channelId];
err.ownDeletion = true;
}
channel.bcast("PAD_ERROR", err);
if (type === "EDELETED" && Cache?.clearChannel) {
Cache.clearChannel(channelId);
}
// DELETED, EXPIRED or RESTRICTED pad => leave the channel
if (!["EDELETED","EEXPIRED","ERESTRICTED"].includes(type)) {
return;
}
ctx.leavePad(null, data, function () {});
};
const conf = {
Cache: store.neverCache ? undefined : Cache,
priority: 1,
onCacheStart: () => {
postMessage(clientId, "PAD_CACHE");
},
onCacheReady: () => {
postMessage(clientId, "PAD_CACHE_READY");
onCacheReadyEvt.fire();
},
onReady: pad => {
const padData = pad.metadata || {};
channel.data = padData;
if (padData?.validateKey && store.messenger) {
store.messenger.storeValidateKey(channelId, padData.validateKey);
}
postMessage(clientId, "PAD_READY", pad.noCache);
},
onMessage: function (m, user, validateKey, isCp, hash) {
channel.lastHash = hash;
channel.pushHistory(m, isCp);
channel.bcast("PAD_MESSAGE", {
user: user,
msg: m,
validateKey: validateKey
});
},
onJoin: function (m) {
channel.bcast("PAD_JOIN", m);
},
onLeave: function (m) {
channel.bcast("PAD_LEAVE", m);
},
onError: onError,
onChannelError: onError,
onRejected: Store.onRejected,
onConnectionChange: info => {
if (!info.state) {
channel.bcast("PAD_DISCONNECT");
}
},
onMetadataUpdate: metadata => {
channel.data = metadata || {};
Store.getAllStores().forEach(s => {
let allData = s.manager.findChannel(channelId, true);
allData.forEach(obj => {
obj.data.owners = metadata.owners;
obj.data.atime = +new Date();
if (metadata.expire) {
obj.data.expire = +metadata.expire;
}
});
const send = s.sendEvent || store.sendDriveEvent;
send('DRIVE_CHANGE', {
path: ['drive', UserObject.FILES_DATA]
});
});
channel.bcast("PAD_METADATA", metadata);
},
crypto: {
// Encryption and decryption is done in the outer window
// Async-store only deals with encrypted messages
encrypt: function (m) { return m; },
decrypt: function (m) { return m; }
},
noChainPad: true,
channel: channelId,
metadata: data.metadata,
network: store.network || store.networkPromise,
websocketURL: NetConfig.getWebsocketURL(),
onInit: function () {
onJoinedEvt.fire();
},
//readOnly: data.readOnly,
onConnect: (wc, sendMessage) => {
channel.sendMessage = (msg, cId, cb) => {
// Send to server
sendMessage(msg, err => {
if (err) { return void cb({ error: err }); }
// Broadcast to other tabs
channel.lastHash = msg.slice(0,64);
channel.pushHistory(CpNetflux.removeCp(msg), /^cp\|/.test(msg));
channel.bcast("PAD_MESSAGE", {
user: wc.myID,
msg: CpNetflux.removeCp(msg),
validateKey: channel.data.validateKey
}, cId);
cb();
});
};
channel.wc = wc;
channel.queue.forEach(function (data) {
channel.sendMessage(data.message, clientId);
});
channel.queue = [];
channel.bcast("PAD_CONNECT", {
myID: wc.myID,
id: wc.id,
members: wc.members
});
}
};
channel.cpNf = CpNetflux.start(conf);
};
// Send a message to a pad we already joined
const _sendMessage: Callback = (ctx, clientId, data, cb) => {
var msg = data.msg;
var channel = ctx.channels[data.channel];
if (!channel) { return; }
if (!channel.wc) {
channel.queue.push(msg);
return void cb();
}
channel.sendMessage(msg, clientId, cb);
};
const _onCorruptedCache: Callback = (ctx, clientId, channel) => {
var chan = ctx.channels[channel];
if (!chan || !chan.cpNf) { return; }
Cache.clearChannel(channel);
if (!chan.cpNf.resetCache) { return; }
chan.cpNf.resetCache();
};
const _getLastHash: Callback = (ctx, clientId, data, cb) => {
var chan = ctx.channels[data.channel];
if (!chan) { return void cb({error: 'ENOCHAN'}); }
if (!chan.lastHash) { return void cb({error: 'EINVAL'}); }
cb({
hash: chan.lastHash
});
};
// clearOwnedChannel is only used for private chat and forms
const _clear: Callback = (ctx, clientId, data, cb) => {
const { Store } = ctx;
const s = Store.getStore(data && data.teamId);
if (!s.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
s.rpc.clearOwnedChannel(data.channel, err => {
cb({error:err});
});
};
const _destroy: Callback = (ctx, clientId, data, cb) => {
const { store, Store, channels, myDeletions } = ctx;
// "data" used to be a channelId, now it can also be an object
// data.force tells us we can safely remove the drive ID
let channel = data;
let force = false;
let teamId, reason;
if (data && typeof(data) === "object") {
({ channel, force, teamId, reason } = data);
}
if (channel === store.driveChannel && !force) {
return void cb({error: 'User drive removal blocked!'});
}
const s = Store.getStore(teamId);
if (!s) { return void cb({ error: 'ENOTFOUND' }); }
if (!s.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
// If this channel is loaded, store that we deleted it ourselves
if (channels[channel]) { myDeletions[channel] = true; }
s.rpc.removeOwnedChannel(channel, err => {
if (err) { delete myDeletions[channel]; }
cb({error:err});
}, reason);
};
const alwaysOnline = (ctx, chanId) => {
const { store } = ctx;
if (!store) { return false; }
// Drive
if (store.driveChannel === chanId) { return true; }
// Shared folders
if (SF.isSharedFolderChannel(chanId)) { return true; }
// Teams
if (Util.find(store, ['proxy', 'teams'])) {
var t = Util.find(store, ['proxy', 'teams']) || {};
return Object.keys(t).some(id => {
return t[id].channel === chanId;
});
}
// Profile
if (Util.find(store, ['proxy', 'profile', 'href'])) {
let href = Util.find(store, ['proxy', 'profile', 'href']);
return Hash.hrefToHexChannelId(href) === chanId;
}
};
const dropChannel = (ctx, chanId) => {
const store = ctx.store;
store.messenger?.leavePad?.(chanId);
store.onlyoffice?.leavePad?.(chanId);
Object.keys(store.modules).forEach(key => {
store.modules[key]?.leavePad?.(chanId);
});
if (!alwaysOnline(ctx, chanId)) {
try {
Cache.leaveChannel(chanId);
} catch (e) { console.error(e); }
ctx.channels[chanId]?.cpNf?.stop();
}
delete ctx.channels[chanId];
};
const init = (config) => {
const { broadcast, postMessage, store, Store } = config;
/** XXX
* Variables
* Store.channels, store.channels
*
* Methods
* sendPadMsg, corruptedCache, getLastHash
**/
const ctx = {
channels: [],
postMessage,
store,
Store,
myDeletions: [],
leavePad: (clientId, data, cb) => {}
};
const join: RpcCall = (clientId, data, cb) => {
_join(ctx, clientId, data, cb);
};
const destroy: RpcCall = (clientId, data, cb) => {
_destroy(ctx, clientId, data, cb);
};
const clear: RpcCall = (clientId, data, cb) => {
_clear(ctx, clientId, data, cb);
};
const setMetadata: RpcCall = (clientId, data, cb) => {
_setMetadata(ctx, clientId, data, cb);
};
const getMetadata: RpcCall = (clientId, data, cb) => {
_getMetadata(ctx, clientId, data, cb);
};
const sendMessage: RpcCall = (clientId, data, cb) => {
_sendMessage(ctx, clientId, data, cb);
};
const onCorruptedCache: RpcCall = (clientId, data, cb) => {
_onCorruptedCache(ctx, clientId, data, cb);
};
const getLastHash: RpcCall = (clientId, data, cb) => {
_getLastHash(ctx, clientId, data, cb);
};
const removeClient = clientId => {
Object.keys(ctx.channels).forEach(chanId => {
let idx = ctx.channels[chanId].clients.indexOf(clientId);
if (idx !== -1) {
ctx.channels[chanId].clients.splice(idx, 1);
}
if (ctx.channels[chanId].clients.length === 0) {
dropChannel(ctx, chanId);
}
});
};
const leave: RpcCall = (clientId, data, cb) => {
const channel = ctx.channels[data.channel];
if (!channel?.cpNf) { return void cb ({error: 'EINVAL'}); }
dropChannel(ctx, data.channel);
cb();
};
ctx.leavePad = leave;
const getChannels = () => {
return ctx.channels;
};
return {
join,
destroy,
clear,
setMetadata,
getMetadata,
sendMessage,
onCorruptedCache,
getLastHash,
leave,
removeClient,
onJoined: onJoinedEvt.reg,
onCacheReady: onCacheReadyEvt.reg,
getChannels
};
};
const Pad: Pad = {
init: init
};
export { Pad }

View File

@ -2,8 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(function () {
var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto, Feedback) {
var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
var Roster = {};
// this constant is somewhat arbitrary.
@ -618,7 +617,6 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto, Feedback)
// deleted while you are open
// emit an event
var onChannelError = function (info) {
if (Feedback) { Feedback.send('ROSTER_CHANNEL_ERROR='+(info && info.type)); }
if (info && info.type === "EUNKNOWN") {
// chainpad-netflux should recover by itself
return;
@ -926,39 +924,11 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto, Feedback)
return Roster;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require("../../common/common-util"),
require("../../common/common-hash"),
require('chainpad-netflux'),
require('json.sortify'),
require("nthen"),
require("chainpad-crypto"),
null // no feedback here
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
require.config({ paths: { 'json.sortify': '/components/json.sortify/dist/JSON.sortify' } });
define([
'/common/common-util.js',
'/common/common-hash.js',
'chainpad-netflux',
'json.sortify',
'/components/nthen/index.js',
'/components/chainpad-crypto/crypto.js',
'/common/common-feedback.js',
//'/components/tweetnacl/nacl-fast.min.js',
], function (Util, Hash, CPNF, Sortify, nThen, Crypto, Feedback) {
return factory.apply(null, [
Util,
Hash,
CPNF,
Sortify,
nThen,
Crypto,
Feedback
]);
});
} else {
// I'm not gonna bother supporting any other kind of instanciation
}
}());
module.exports = factory(
require("../../common/common-util"),
require("../../common/common-hash"),
require('chainpad-netflux'),
require('json.sortify'),
require("nthen"),
require("chainpad-crypto")
);

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Hash, Util, UserObject, Cache,
nThen, Crypto, Listmap, ChainPad) => {
var SF = {};
@ -387,31 +386,14 @@ const factory = (Hash, Util, UserObject, Cache,
return SF;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('../../common/common-hash'),
require('../../common/common-util'),
require('../../common/user-object'),
require('../../common/cache-store'),
require('nthen'),
require('chainpad-crypto'),
require('chainpad-listmap'),
require('chainpad')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-hash.js',
'/common/common-util.js',
'/common/user-object.js',
'/common/outer/cache-store.js',
module.exports = factory(
require('../../common/common-hash'),
require('../../common/common-util'),
require('../../common/user-object'),
require('../../common/cache-store'),
require('nthen'),
require('chainpad-crypto'),
require('chainpad-listmap'),
require('chainpad')
);
'/components/nthen/index.js',
'/components/chainpad-crypto/crypto.js',
'chainpad-listmap',
'/components/chainpad/chainpad.dist.js',
], factory);
} else {
// unsupported initialization
}
})();

View File

@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (SRpc, Channel, Util) => {
const Interface = {};
let store;
@ -92,6 +91,7 @@ const factory = (SRpc, Channel, Util) => {
cb(data);
});
});
// XXX allow multiple pads in same tab
chan.on('JOIN_PAD', function (data, cb) {
client.channelId = data.channel;
try {
@ -122,19 +122,8 @@ const factory = (SRpc, Channel, Util) => {
return Interface;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
require('./store-rpc'),
require('../../common/worker-channel'),
require('../../common/common-util')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/outer/store-rpc.js',
'/common/outer/worker-channel.js',
'/common/common-util.js'
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('./store-rpc'),
require('../../common/events-channel'),
require('../../common/common-util')
);

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = AStore => {
var create = function (config) {
var Store = AStore.create(config);
@ -13,23 +12,18 @@ const factory = AStore => {
// Ready
CONNECT: Store.init,
DISCONNECT: Store.disconnect,
MIGRATE_ANON_DRIVE: Store.migrateAnonDrive,
PING: function (cId, data, cb) { cb(); },
CACHE_DISABLE: Store.disableCache,
HAS_DRIVE: Store.hasDrive,
// RPC
UPDATE_PIN_LIMIT: Store.updatePinLimit,
GET_PIN_LIMIT: Store.getPinLimit,
CLEAR_OWNED_CHANNEL: Store.clearOwnedChannel,
REMOVE_OWNED_CHANNEL: Store.removeOwnedChannel,
PIN_PADS: Store.pinPads,
UNPIN_PADS: Store.unpinPads,
GET_PINNED_USAGE: Store.getPinnedUsage,
GET_DELETED_PADS: Store.getDeletedPads,
UPLOAD_CHUNK: Store.uploadChunk,
UPLOAD_COMPLETE: Store.uploadComplete,
UPLOAD_STATUS: Store.uploadStatus,
UPLOAD_CANCEL: Store.uploadCancel,
PIN_PADS: Store.pinPads,
UNPIN_PADS: Store.unpinPads,
GET_DELETED_PADS: Store.getDeletedPads,
GET_PINNED_USAGE: Store.getPinnedUsage,
// ANON RPC
ANON_RPC_MESSAGE: Store.anonRpcMsg,
GET_FILE_SIZE: Store.getFileSize,
@ -37,8 +31,6 @@ const factory = AStore => {
// Store
GET: Store.get,
SET: Store.set,
GET_DRIVE: Store.drive.get,
SET_DRIVE: Store.drive.set,
ADD_PAD: Store.addPad,
SET_PAD_TITLE: Store.setPadTitle,
MOVE_TO_TRASH: Store.moveToTrash,
@ -74,9 +66,13 @@ const factory = AStore => {
// Universal
UNIVERSAL_COMMAND: Store.universal.execCommand,
// Pad
SEND_PAD_MSG: Store.sendPadMsg,
JOIN_PAD: Store.joinPad,
LEAVE_PAD: Store.leavePad,
SEND_PAD_MSG: Store.pad.sendMessage,
JOIN_PAD: Store.pad.join,
LEAVE_PAD: Store.pad.leave,
REMOVE_OWNED_CHANNEL: Store.pad.destroy,
CLEAR_OWNED_CHANNEL: Store.pad.clear,
CORRUPTED_CACHE: Store.pad.onCorruptedCache,
GET_LAST_HASH: Store.pad.getLastHash,
GET_FULL_HISTORY: Store.getFullHistory,
GET_HISTORY: Store.getHistory,
GET_HISTORY_RANGE: Store.getHistoryRange,
@ -84,15 +80,17 @@ const factory = AStore => {
CONTACT_PAD_OWNER: Store.contactPadOwner,
GIVE_PAD_ACCESS: Store.givePadAccess,
BURN_PAD: Store.burnPad,
GET_PAD_METADATA: Store.getPadMetadata,
SET_PAD_METADATA: Store.setPadMetadata,
GET_PAD_METADATA: Store.pad?.getMetadata,
SET_PAD_METADATA: Store.pad?.setMetadata,
CHANGE_PAD_PASSWORD_PIN: Store.changePadPasswordPin,
GET_LAST_HASH: Store.getLastHash,
GET_SNAPSHOT: Store.getSnapshot,
CORRUPTED_CACHE: Store.corruptedCache,
DELETE_MAILBOX_MESSAGE: Store.deleteMailboxMessage,
// Drive
DRIVE_USEROBJECT: Store.userObjectCommand,
GET_DRIVE: Store.drive.get,
SET_DRIVE: Store.drive.set,
MIGRATE_ANON_DRIVE: Store.drive.migrateAnon,
HAS_DRIVE: Store.drive.exists,
// Settings,
DELETE_ACCOUNT: Store.deleteAccount,
REMOVE_OWNED_PADS: Store.removeOwnedPads,
@ -118,16 +116,6 @@ const factory = AStore => {
return { create };
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('../async-store')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/outer/async-store.js'
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../async-store')
);

View File

@ -93,10 +93,34 @@ const Badge: BadgeModule<ModuleObject> = {
updateMetadata: config.updateMetadata
};
const _listBadges = (data, cb) => {
listBadges(ctx, data, undefined, cb);
};
// On ready, check if our badge is still valid
const Store = ctx.Store;
Store.onReadyEvt.reg(() => {
const md = Store.getMetadata(void 0, 'drive', () => {});
const myBadge:string = md?.user?.badge || "";
if (!myBadge) { return; }
listBadges(ctx, {}, undefined, all => {
if (!all.includes(myBadge)) {
const profile = ctx?.store?.modules?.profile;
profile?.execCommand(void 0, {
cmd: 'SET',
data: {
key: 'badge',
value: ''
}
}, () => {});
}
});
});
return {
listBadges: _listBadges,
removeClient: () => {},
execCommand: (clientId, obj, cb) => {
console.log('Exex command', clientId, obj);
const cmd = obj.cmd;
const data = obj.data;
if (cmd === 'LIST_BADGES') {

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Util, Hash, Constants, Realtime, Cache, Rec,
nThen, Listmap, FP, Crypto, ChainPad) => {
var Calendar = {};
@ -1208,38 +1207,16 @@ const factory = (Util, Hash, Constants, Realtime, Cache, Rec,
return Calendar;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-constants'),
require('../../common/common-realtime'),
require('../../common/cache-store'),
require('../../common/recurrence'),
require('nthen'),
require('chainpad-listmap'),
require('../../../www/lib/datepicker/flatpickr'),
require('chainpad-crypto'),
require('chainpad'),
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-constants.js',
'/common/common-realtime.js',
'/common/outer/cache-store.js',
'/calendar/recurrence.js',
'/components/nthen/index.js',
'chainpad-listmap',
'/lib/datepicker/flatpickr.js',
'/components/chainpad-crypto/crypto.js',
'/components/chainpad/chainpad.dist.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-constants'),
require('../../common/common-realtime'),
require('../../common/cache-store'),
require('../../common/recurrence'),
require('nthen'),
require('chainpad-listmap'),
require('../../../www/lib/datepicker/flatpickr'),
require('chainpad-crypto'),
require('chainpad'),
);

View File

@ -2,10 +2,10 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Util, Constants, Messages = {},
AppConfig = {}, Crypto) => {
var Cursor = {};
const factory = (Util, Constants, Crypto) => {
const Cursor = {};
let Messages = {};
let AppConfig = {};
Cursor.setCustomize = data => {
Messages = data.Messages;
@ -286,25 +286,8 @@ const factory = (Util, Constants, Messages = {},
return Cursor;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-constants'),
undefined,
undefined,
require('chainpad-crypto')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/common/common-constants.js',
'/customize/messages.js',
'/customize/application_config.js',
'/components/chainpad-crypto/crypto.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-constants'),
require('chainpad-crypto')
);

View File

@ -2,10 +2,9 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Util, Hash, UserObject, nThen) => {
var History = {};
var commands = {};
const History = {};
const commands = {};
var getAccountChannels = function (ctx) {
var channels = [];
@ -133,7 +132,7 @@ const factory = (Util, Hash, UserObject, nThen) => {
history = messages.join('\n').length;
}), true);
// Metadata
Store.getPadMetadata(null, {
Store.pad.getMetadata(null, {
channel: channel
}, waitFor(function (obj) {
if (obj && obj.error) { return; }
@ -257,22 +256,9 @@ const factory = (Util, Hash, UserObject, nThen) => {
return History;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/user-object'),
require('nthen')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/common/common-hash.js',
'/common/user-object.js',
'/components/nthen/index.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/user-object'),
require('nthen')
);

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Crypto) => {
var Integration = {};
@ -203,17 +202,6 @@ const factory = (Crypto) => {
return Integration;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('chainpad-crypto')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/components/chainpad-crypto/crypto.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('chainpad-crypto')
);

View File

@ -2,10 +2,10 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (BCast = {}, Util, Hash,
const factory = (Util, Hash,
Realtime, Messaging, Notify, Handlers, CpNetflux, Crypto) => {
var Mailbox = {};
const Mailbox = {};
let BCast = {};
Mailbox.setCustomize = data => {
BCast = data.Broadcast;
@ -673,32 +673,13 @@ proxy.mailboxes = {
return Mailbox;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
undefined,
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-realtime'),
require('../components/messaging'),
require('../../common/notify'),
require('../components/mailbox-handlers'),
require('chainpad-netflux'),
require('chainpad-crypto')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/api/broadcast',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/outer/messaging.js',
'/common/notify.js',
'/common/outer/mailbox-handlers.js',
'chainpad-netflux',
'/components/chainpad-crypto/crypto.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-realtime'),
require('../components/messaging'),
require('../../common/notify'),
require('../components/mailbox-handlers'),
require('chainpad-netflux'),
require('chainpad-crypto')
);

View File

@ -2,12 +2,12 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Crypto, Hash, Util, Realtime, Messaging,
Constants, Messages = {}, PadTypes, nThen) => {
Constants, PadTypes, nThen) => {
var Curve = Crypto.Curve;
var Msg = {};
const Msg = {};
let Messages = {};
Msg.setCustomize = data => {
Messages = data.Messages;
@ -1119,34 +1119,13 @@ const factory = (Crypto, Hash, Util, Realtime, Messaging,
return Msg;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('chainpad-crypto'),
require('../../common/common-hash'),
require('../../common/common-util'),
require('../../common/common-realtime'),
require('../components/messaging'),
require('../../common/common-constants'),
undefined,
require('../../common/pad-types'),
require('nthen')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/components/chainpad-crypto/crypto.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-realtime.js',
'/common/outer/messaging.js',
'/common/common-constants.js',
'/customize/messages.js',
'/common/pad-types.js',
'/components/nthen/index.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('chainpad-crypto'),
require('../../common/common-hash'),
require('../../common/common-util'),
require('../../common/common-realtime'),
require('../components/messaging'),
require('../../common/common-constants'),
require('../../common/pad-types'),
require('nthen')
);

View File

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = () => {
var OO = {};
@ -354,12 +353,4 @@ const factory = () => {
return OO;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory();
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory();

View File

@ -2,11 +2,12 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Util, Hash, Constants, Realtime,
Listmap, Crypto, ChainPad) => {
Messaging, Listmap, Crypto, ChainPad) => {
var Profile = {};
const onReady = Util.mkEvent(true);
var initializeProfile = function (ctx, cb) {
var profile = ctx.profile;
if (!profile.edit || !profile.view) {
@ -55,6 +56,15 @@ const factory = (Util, Hash, Constants, Realtime,
if (!lm.proxy.edPublic) {
lm.proxy.edPublic = ctx.store.proxy.edPublic;
}
if (!lm.proxy.proof) {
let str = secret.channel;
let myIDu8 = Util.decodeUTF8(str);
let k = Util.decodeBase64(ctx.store.proxy.edPrivate);
let nacl = Crypto.Nacl;
let s = nacl.sign(myIDu8, k);
let signature = Util.encodeBase64(s);
lm.proxy.proof = signature;
}
if (ctx.onReadyHandlers.length) {
ctx.onReadyHandlers.forEach(function (f) {
try {
@ -63,6 +73,7 @@ const factory = (Util, Hash, Constants, Realtime,
});
ctx.onReadyHandlers = [];
}
onReady.fire();
}).on('change', [], function () {
ctx.emit('UPDATE', lm.proxy, ctx.clients);
});
@ -91,21 +102,26 @@ const factory = (Util, Hash, Constants, Realtime,
};
var setValue = function (ctx, data, cId, cb) {
var key = data.key;
var value = data.value;
if (!key) { return; }
ctx.listmap.proxy[key] = value;
Realtime.whenRealtimeSyncs(ctx.listmap.realtime, function () {
ctx.emit('UPDATE', ctx.listmap.proxy, ctx.clients.filter(function (clientId) {
return clientId !== cId;
}));
if (key === 'badge') {
ctx.Store.set(null, {
key: ['profile', 'badge'],
value: value || undefined
}, () => {});
}
cb(ctx.listmap.proxy);
onReady.reg(() => {
var key = data.key;
var value = data.value;
if (!key) { return; }
ctx.listmap.proxy[key] = value;
Realtime.whenRealtimeSyncs(ctx.listmap.realtime, function () {
ctx.emit('UPDATE', ctx.listmap.proxy, ctx.clients.filter(function (clientId) {
return clientId !== cId;
}));
if (key === 'badge') {
ctx.Store.set(null, {
key: ['profile', 'badge'],
value: value || undefined
}, () => {
Messaging.updateMyData(ctx.store);
ctx.updateMetadata();
});
}
cb(ctx.listmap.proxy);
});
});
};
@ -163,29 +179,13 @@ const factory = (Util, Hash, Constants, Realtime,
return Profile;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-constants'),
require('../../common/common-realtime'),
require('chainpad-listmap'),
require('chainpad-crypto'),
require('chainpad')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-constants.js',
'/common/common-realtime.js',
'chainpad-listmap',
'/components/chainpad-crypto/crypto.js',
'/components/chainpad/chainpad.dist.js',
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-constants'),
require('../../common/common-realtime'),
require('../components/messaging'),
require('chainpad-listmap'),
require('chainpad-crypto'),
require('chainpad')
);

View File

@ -2,10 +2,10 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (ApiConfig = {}, Util, Hash, Realtime, Pinpad, Crypt,
const factory = (Util, Hash, Realtime, Pinpad, Crypt,
nThen, Crypto, Listmap, ChainPad, CpNetflux) => {
var Support = {};
const Support = {};
let ApiConfig = {};
Support.setCustomize = data => {
ApiConfig = data.ApiConfig;
@ -1420,37 +1420,15 @@ const factory = (ApiConfig = {}, Util, Hash, Realtime, Pinpad, Crypt,
return Support;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
undefined,
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-realtime'),
require('../../common/pinpad'),
require('../../common/cryptget'),
require('nthen'),
require('chainpad-crypto'),
require('chainpad-listmap'),
require('chainpad'),
require('chainpad-netflux')
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/api/config',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/pinpad.js',
'/common/cryptget.js',
'/components/nthen/index.js',
'/components/chainpad-crypto/crypto.js',
'chainpad-listmap',
'/components/chainpad/chainpad.dist.js',
'chainpad-netflux'
], factory);
} else {
// unsupported initialization
}
})();
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-realtime'),
require('../../common/pinpad'),
require('../../common/cryptget'),
require('nthen'),
require('chainpad-crypto'),
require('chainpad-listmap'),
require('chainpad'),
require('chainpad-netflux')
);

View File

@ -2,12 +2,11 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Util, Hash, Constants, Realtime, ProxyManager,
UserObject, SF, Roster, Messaging, Feedback,
Invite, Crypt, Cache, Pinpad, Listmap, Crypto,
CpNetflux, ChainPad, nThen, Nacl) => {
var Team = {};
const Team = {};
Nacl = Nacl || (typeof(window) !== "undefined" && window.nacl);
var onStoreReady = Util.mkEvent(true);
@ -298,7 +297,7 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
teamId: id
};
}
ctx.Store.removeOwnedChannel('', data, cb);
ctx.Store.pad.destroy('', data, cb);
},
Store: ctx.Store,
store: ctx.store
@ -880,7 +879,7 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
}));
}).nThen(function (_w) {
if (otherOwners) {
ctx.Store.setPadMetadata(null, {
ctx.Store.pad.setMetadata(null, {
channel: c,
command: 'RM_OWNERS',
value: [teamEdPublic],
@ -984,7 +983,7 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
var md;
nThen(function (waitFor) {
// Get pending owners
ctx.Store.getPadMetadata(null, {
ctx.Store.pad.getMetadata(null, {
channel: teamData.channel
}, waitFor(function (obj) {
if (obj && obj.error) {
@ -1007,7 +1006,7 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
});
if (!member && teamData.owner) {
var removeOwnership = function (chan) {
ctx.Store.setPadMetadata(null, {
ctx.Store.pad.setMetadata(null, {
channel: chan,
command: 'RM_PENDING_OWNERS',
value: [ed],
@ -1120,7 +1119,7 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
}
};
var addPendingOwner = function (chan) {
ctx.Store.setPadMetadata(null, {
ctx.Store.pad.setMetadata(null, {
channel: chan,
command: 'ADD_PENDING_OWNERS',
value: [user.edPublic],
@ -1176,7 +1175,7 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
}
};
var removeOwnership = function (chan) {
ctx.Store.setPadMetadata(null, {
ctx.Store.pad.setMetadata(null, {
channel: chan,
command: cmd,
value: [user.edPublic],
@ -1393,7 +1392,7 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
var md;
nThen(function (waitFor) {
// Get pending owners
ctx.Store.getPadMetadata(null, {
ctx.Store.pad.getMetadata(null, {
channel: teamData.channel
}, waitFor(function (obj) {
if (obj && obj.error) {
@ -2241,59 +2240,27 @@ const factory = (Util, Hash, Constants, Realtime, ProxyManager,
return Team;
};
if (typeof(module) !== 'undefined' && module.exports) {
// Code from customize can't be laoded directly in the build
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-constants'),
require('../../common/common-realtime'),
module.exports = factory(
require('../../common/common-util'),
require('../../common/common-hash'),
require('../../common/common-constants'),
require('../../common/common-realtime'),
require('../../common/proxy-manager'),
require('../../common/user-object'),
require('../components/sharedfolder'),
require('../components/roster'),
require('../components/messaging'),
require('../../common/common-feedback'),
require('../components/invitation'),
require('../../common/cryptget'),
require('../../common/cache-store'),
require('../../common/pinpad'),
require('../../common/proxy-manager'),
require('../../common/user-object'),
require('../components/sharedfolder'),
require('../components/roster'),
require('../components/messaging'),
require('../../common/common-feedback'),
require('../components/invitation'),
require('../../common/cryptget'),
require('../../common/cache-store'),
require('../../common/pinpad'),
require('chainpad-listmap'),
require('chainpad-crypto'),
require('chainpad-netflux'),
require('chainpad'),
require('nthen'),
require('tweetnacl/nacl-fast'),
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-constants.js',
'/common/common-realtime.js',
'/common/proxy-manager.js',
'/common/user-object.js',
'/common/outer/sharedfolder.js',
'/common/outer/roster.js',
'/common/outer/messaging.js',
'/common/common-feedback.js',
'/common/outer/invitation.js',
'/common/cryptget.js',
'/common/outer/cache-store.js',
'/common/pinpad.js',
'chainpad-listmap',
'/components/chainpad-crypto/crypto.js',
'chainpad-netflux',
'/components/chainpad/chainpad.dist.js',
'/components/nthen/index.js',
'/components/tweetnacl/nacl-fast.min.js',
], factory);
} else {
// unsupported initialization
}
})();
require('chainpad-listmap'),
require('chainpad-crypto'),
require('chainpad-netflux'),
require('chainpad'),
require('nthen'),
require('tweetnacl/nacl-fast'),
);

View File

@ -23,7 +23,7 @@ import * as UOSetter from '../common/user-object-setter.js';
import * as Pinpad from '../common/pinpad.js';
import * as PadTypes from '../common/pad-types.js';
import * as NetworkConfig from '../common/network-config.js';
import * as LoginBlock from '../common/login-block.js';
import * as LoginBlock from '../common/outer/login-block.js';
// Core
import * as Store from './async-store.js';

View File

@ -5,6 +5,7 @@
// Default CryptPad worker module, extended with
// specific methods for each module
export type Callback = (...args: any[]) => void
export type RpcCall = (clientId: string, data: any, cb: Callback) => void;
export type ModuleConfig = {
store: any,
@ -53,6 +54,7 @@ export interface Account {
export type DriveConfig = {
store: any,
Store: any,
broadcast: (exclude: object, cmd: string, data?: any, cb?: any) => void,
postMessage: (clientId: string, cmd: string, data?: any, cb?: any) => void
}
@ -64,5 +66,31 @@ export interface DriveObject {
onReconnect: any
}
export interface Drive {
initAPI: (config: DriveConfig) => any
init: (config: DriveConfig) => DriveObject
}
export type PadConfig = {
Store: any,
store: any,
broadcast: (exclude: object, cmd: string, data?: any, cb?: any) => void,
postMessage: (clientId: string, cmd: string, data?: any, cb?: any) => void
}
export interface PadObject {
join: RpcCall,
destroy: RpcCall,
clear: RpcCall,
setMetadata: RpcCall,
getMetadata: RpcCall,
leave: RpcCall,
removeClient: (clientId: string) => void,
sendMessage: RpcCall,
getLastHash: RpcCall,
onCorruptedCache: RpcCall,
getChannels: () => string[],
onJoined: any,
onCacheReady: any,
}
export interface Pad {
init: (config: PadConfig) => PadObject
}

View File

@ -734,20 +734,6 @@ define([
return tableObj.table;
};
// Msg.admin_updateLimitHint, .admin_updateLimitTitle, .admin_updateLimitButton
sidebar.addItem('update-limit', function (cb) {
var button = blocks.activeButton('primary', '',
Messages.admin_updateLimitButton, done => {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'Q_UPDATE_LIMIT',
}, function (e, data) {
done(!!data);
UI.alert(data ? Messages.admin_updateLimitDone || 'done' : 'error' + e);
});
});
cb(button);
});
// Msg.admin_enableembedsHint, .admin_enableembedsTitle
sidebar.addCheckboxItem({
key: 'enableembeds',

View File

@ -24,11 +24,6 @@ define([
sframeChan.on('Q_ADMIN_RPC', function (data, cb) {
Cryptpad.adminRpc(data, cb);
});
sframeChan.on('Q_UPDATE_LIMIT', function (data, cb) {
Cryptpad.updatePinLimit(function (e) {
cb({error: e});
});
});
};
var category;
if (window.location.hash) {

View File

@ -171,7 +171,7 @@
color: @cryptpad_text_col;
border-radius: @variables_radius_L;
font-weight: normal;
.tui-full-calendar-icon:not(.tui-full-calendar-calendar-dot):not(.tui-full-calendar-dropdown-arrow):not(.tui-full-calendar-ic-checkbox):not(.fa-map-marker) {
.tui-full-calendar-icon:not(.tui-full-calendar-calendar-dot):not(.tui-full-calendar-dropdown-arrow):not(.tui-full-calendar-ic-checkbox):not(.fa-map-marker):not(.fa-align-justify) {
display: none;
}
.tui-full-calendar-icon {
@ -179,9 +179,46 @@
flex-shrink: 0;
}
.tui-full-calendar-popup-detail-item {
display: flex;
margin-bottom: 0.3rem;
align-items: center;
.event-location {
display: flex;
align-items: center;
}
.fa-align-justify {
margin-top: 3px;
}
&.tui-full-calendar-popup-detail-item-separate {
&>span {
display: flex;
width: 100%;
}
.event-description {
display: block;
line-height: 1.1em;
p, ul {
margin:0.1rem;
}
}
}
a {
margin:0.1rem;
border-radius: @variables_radius;
color: @cryptpad_color_link;
text-decoration: underline;
&:focus-visible{
outline: @variables_focus_style;
}
}
span {
overflow: hidden;
word-break: break-word;
white-space: nowrap;
text-overflow: ellipsis;
.event-description{
white-space: normal;
}
}
}
.tui-full-calendar-section-button-save {

View File

@ -7,7 +7,7 @@
define([
'/customize/pages.js',
'/common/common-util.js',
'/calendar/recurrence.js',
'/common/recurrence.js',
'/lib/ical.min.js'
], function (Pages, Util, Rec) {
@ -97,6 +97,10 @@ define([
});
return;
}
if (k === "until") {
rrule += ";"+k.toUpperCase()+"="+getICSDate(r[k]);
return;
}
rrule += ";"+k.toUpperCase()+"="+r[k];
});
return rrule;

View File

@ -21,7 +21,7 @@ define([
'/customize/application_config.js',
'/lib/calendar/tui-calendar.min.js',
'/calendar/export.js',
'/calendar/recurrence.js',
'/common/recurrence.js',
'/lib/datepicker/flatpickr.js',
'tui-date-picker',
@ -408,13 +408,16 @@ define([
str = `<a href="${l}" id="${uid}">${str}</a>`;
APP.nextLocationUid = uid;
}
let location_icon = h('i.fa.fa-map-marker.tui-full-calendar-icon', { 'aria-label': Messages.calendar_loc }, []);
return location_icon.outerHTML + str;
let location_icon = h('i.fa.fa-map-marker.tui-full-calendar-icon', { 'aria-hidden': true }, []);
return `<div class="event-location"> ${location_icon.outerHTML} ${str} </div>`;
},
popupDetailBody: function(schedule) {
var str = schedule.body;
delete APP.eventBody;
return diffMk.render(str, true);
let description_icon = h('i.fa.fa-align-justify.tui-full-calendar-icon', { 'aria-hidden': true }, []);
let description = diffMk.render(str, true);
return `${description_icon.outerHTML}<div class="event-description">${description}</div>`;
},
popupIsAllDay: function() { return Messages.calendar_allDay; },
titlePlaceholder: function() { return Messages.calendar_title; },
@ -997,7 +1000,8 @@ define([
// Mark selected months as done
todo.forEach(function (monthId) { APP.recurringDone.push(monthId); });
cal.createSchedules(applyUpdates(toAdd));
//cal.createSchedules(applyUpdates(toAdd));
cal.createSchedules(toAdd);
};
updateRecurring = function () {
try {
@ -2354,6 +2358,14 @@ APP.recurrenceRule = {
};
var onCalendarEditPopup = function (el) {
var $el = $(el);
const $header = $el.find('.tui-full-calendar-section-header');
$header.attr('id', 'tui-full-calendar-section-header');
$el.attr('aria-labelledby', 'tui-full-calendar-section-header');
const $desc = $el.find('.tui-full-calendar-section-detail');
$desc.attr('id', 'tui-full-calendar-section-detail');
$el.attr('aria-describedby', 'tui-full-calendar-section-detail');
$el.find('.tui-full-calendar-popup-edit').addClass('btn btn-primary');
$el.find('.tui-full-calendar-popup-edit .tui-full-calendar-icon').addClass('fa fa-pencil').removeClass('tui-full-calendar-icon');
$el.find('.tui-full-calendar-content').removeClass('tui-full-calendar-content');

View File

@ -1,908 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = (Util) => {
var Rec = {};
const window = globalThis;
var debug = function () {};
// Get week number with any "WKST" (firts day of the week)
// Week 1 is the first week of the year containing at least 4 days in this year
// It depends on which day is considered the first day of the week (default Monday)
// In our case, wkst is a number matching the JS rule: 0 == Sunday
var getWeekNo = Rec.getWeekNo = function (date, wkst) {
if (typeof(wkst) !== "number") { wkst = 1; } // Default monday
var newYear = new Date(date.getFullYear(),0,1);
var day = newYear.getDay() - wkst; //the day of week the year begins on
day = (day >= 0 ? day : day + 7);
var daynum = Math.floor((date.getTime() - newYear.getTime())/86400000) + 1;
var weeknum;
// Week 1 / week 53
if (day < 4) {
weeknum = Math.floor((daynum+day-1)/7) + 1;
if (weeknum > 52) {
var nYear = new Date(date.getFullYear() + 1,0,1);
var nday = nYear.getDay() - wkst;
nday = nday >= 0 ? nday : nday + 7;
weeknum = nday < 4 ? 1 : 53;
}
}
else {
weeknum = Math.floor((daynum+day-1)/7);
}
return weeknum;
};
var getYearDay = function (date) {
var start = new Date(date.getFullYear(), 0, 0);
var diff = (date - start) +
((start.getTimezoneOffset() - date.getTimezoneOffset()) * 60 * 1000);
var oneDay = 1000 * 60 * 60 * 24;
return Math.floor(diff / oneDay);
};
var setYearDay = function (date, day) {
if (typeof(day) !== "number" || Math.abs(day) < 1 || Math.abs(day) > 366) { return; }
if (day < 0) {
var max = getYearDay(new Date(date.getFullYear(), 11, 31));
day = max + day + 1;
}
date.setMonth(0);
date.setDate(day);
return true;
};
var getEndData = function (s, e) {
if (s > e) { return void console.error("Wrong data"); }
var days;
if (e.getFullYear() === s.getFullYear()) {
days = getYearDay(e) - getYearDay(s);
} else { // eYear < sYear
var tmp = new Date(s.getFullYear(), 11, 31);
var d1 = getYearDay(tmp) - getYearDay(s); // Number of days before December 31st
var de = getYearDay(e);
days = d1 + de;
while ((tmp.getFullYear()+1) < e.getFullYear()) {
tmp.setFullYear(tmp.getFullYear()+1);
days += getYearDay(tmp);
}
}
return {
h: e.getHours(),
m: e.getMinutes(),
days: days
};
};
var setEndData = function (s, e, data) {
e.setTime(+s);
if (!data) { return; }
e.setHours(data.h);
e.setMinutes(data.m);
e.setSeconds(0);
e.setDate(s.getDate() + data.days);
};
var DAYORDER = Rec.DAYORDER = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"];
var getDayData = function (str) {
var pos = Number(str.slice(0,-2));
var day = DAYORDER.indexOf(str.slice(-2));
return pos ? [pos, day] : day;
};
var goToFirstWeekDay = function (date, wkst) {
var d = date.getDay();
wkst = typeof(wkst) === "number" ? wkst : 1;
if (d >= wkst) {
date.setDate(date.getDate() - (d-wkst));
} else {
date.setDate(date.getDate() - (7+d-wkst));
}
};
var getDateStr = function (date) {
return date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate();
};
var FREQ = {};
FREQ['daily'] = function (s, i) {
s.setDate(s.getDate()+i);
};
FREQ['weekly'] = function (s,i) {
s.setDate(s.getDate()+(i*7));
};
FREQ['monthly'] = function (s,i) {
s.setMonth(s.getMonth()+i);
};
FREQ['yearly'] = function (s,i) {
s.setFullYear(s.getFullYear()+i);
};
// EXPAND is used to create iterations added from a BYxxx rule
// dateA is the start date and b is the number or id of the BYxxx rule item
var EXPAND = {};
EXPAND['month'] = function (dateS, origin, b) {
var oS = new Date(origin.start);
var a = dateS.getMonth() + 1;
var toAdd = (b-a+12)%12;
var m = dateS.getMonth() + toAdd;
dateS.setMonth(m);
dateS.setDate(oS.getDate());
if (dateS.getMonth() !== m) { return; } // Day 31 may move us to the next month
return true;
};
EXPAND['weekno'] = function (dateS, origin, week, rule) {
var wkst = rule && rule.wkst;
if (typeof(wkst) !== "number") { wkst = 1; } // Default monday
var oS = new Date(origin.start);
var lastD = new Date(dateS.getFullYear(), 11, 31); // December 31st
var lastW = getWeekNo(lastD, wkst); // Last week of the year is either 52 or 53
var doubleOne = lastW === 1;
if (lastW === 1) { lastW = 52; }
var a = getWeekNo(dateS, wkst);
if (!week || week > lastW) { return false; } // Week 53 may not exist this year
if (week < 0) { week = lastW + week + 1; } // Turn negative week number into positive
var toAdd = week - a;
var weekS = new Date(+dateS);
// Go to the selected week
weekS.setDate(weekS.getDate() + (toAdd * 7));
goToFirstWeekDay(weekS, wkst);
// Then make sure we are in the correct start day
var all = 'aaaaaaa'.split('').map(function (o, i) {
var date = new Date(+weekS);
date.setDate(date.getDate() + i);
if (date.getFullYear() !== dateS.getFullYear()) { return; }
return date.toLocaleDateString() !== oS.toLocaleDateString() && date;
}).filter(Boolean);
// If we're looking for week 1 and the last week is a week 1, add the days
if (week === 1 && doubleOne) {
goToFirstWeekDay(lastD, wkst);
'aaaaaaa'.split('').some(function (o, i) {
var date = new Date(+lastD);
date.setDate(date.getDate() + i);
if (date.toLocaleDateString() === oS.toLocaleDateString()) { return; }
if (date.getFullYear() > dateS.getFullYear()) { return true; }
all.push(date);
});
}
return all.length ? all : undefined;
};
EXPAND['yearday'] = function (dateS, origin, b) {
var y = dateS.getFullYear();
var state = setYearDay(dateS, b);
if (!state) { return; } // Invalid day "b"
if (dateS.getFullYear() !== y) { return; } // Day 366 make move us to the next year
return true;
};
EXPAND['monthday'] = function (dateS, origin, b, rule) {
if (typeof(b) !== "number" || Math.abs(b) < 1 || Math.abs(b) > 31) { return false; }
var setMonthDay = function (date, day) {
var m = date.getMonth();
if (day < 0) {
var tmp = new Date(date.getFullYear(), date.getMonth()+1, 0); // Last day
day = tmp.getDate() + day + 1;
}
date.setDate(day);
return date.getMonth() === m; // Don't push if day 31 moved us to the next month
};
// Monthly events
if (rule.freq === 'monthly') {
return setMonthDay(dateS, b);
}
var all = 'aaaaaaaaaaaa'.split('').map(function (o, i) {
var date = new Date(dateS.getFullYear(), i, 1);
var ok = setMonthDay(date, b);
return ok ? date : undefined;
}).filter(Boolean);
return all.length ? all : undefined;
};
EXPAND['day'] = function (dateS, origin, b, rule) {
// Here "b" can be a single day ("TU") or a position and a day ("1MO")
var day = getDayData(b);
var pos;
if (Array.isArray(day)) {
pos = day[0];
day = day[1];
}
var all = [];
if (![0,1,2,3,4,5,6].includes(day)) { return false; }
var filterPos = function (m) {
if (!pos) { return; }
var _all = [];
'aaaaaaaaaaaa'.split('').some(function (a, i) {
if (typeof(m) !== "undefined" && i !== m) { return; }
var _pos;
var tmp = all.filter(function (d) {
return d.getMonth() === i;
});
if (pos < 0) {
_pos = tmp.length + pos;
} else {
_pos = pos - 1; // An array starts at 0 but the recurrence rule starts at 1
}
_all.push(tmp[_pos]);
return typeof(m) !== "undefined" && i === m;
});
all = _all.filter(Boolean); // The "5th" {day} won't always exist
};
var tmp;
if (rule.freq === 'yearly') {
tmp = new Date(+dateS);
var y = dateS.getFullYear();
while (tmp.getDay() !== day) { tmp.setDate(tmp.getDate()+1); }
while (tmp.getFullYear() === y) {
all.push(new Date(+tmp));
tmp.setDate(tmp.getDate()+7);
}
filterPos();
return all;
}
if (rule.freq === 'monthly') {
tmp = new Date(+dateS);
var m = dateS.getMonth();
while (tmp.getDay() !== day) { tmp.setDate(tmp.getDate()+1); }
while (tmp.getMonth() === m) {
all.push(new Date(+tmp));
tmp.setDate(tmp.getDate()+7);
}
filterPos(m);
return all;
}
if (rule.freq === 'weekly') {
while (dateS.getDay() !== day) { dateS.setDate(dateS.getDate()+1); }
}
return true;
};
var LIMIT = {};
LIMIT['month'] = function (events, rule) {
return events.filter(function (s) {
return rule.includes(s.getMonth()+1);
});
};
LIMIT['weekno'] = function (events, weeks, rules) {
return events.filter(function (s) {
var wkst = rules && rules.wkst;
if (typeof(wkst) !== "number") { wkst = 1; } // Default monday
var lastD = new Date(s.getFullYear(), 11, 31); // December 31st
var lastW = getWeekNo(lastD, wkst); // Last week of the year is either 52 or 53
if (lastW === 1) { lastW = 52; }
var w = getWeekNo(s, wkst);
return weeks.some(function (week) {
if (week > 0) { return week === w; }
return w === (lastW + week + 1);
});
});
};
LIMIT['yearday'] = function (events, days) {
return events.filter(function (s) {
var d = getYearDay(s);
var max = getYearDay(new Date(s.getFullYear(), 11, 31));
return days.some(function (day) {
if (day > 0) { return day === d; }
return d === (max + day + 1);
});
});
};
LIMIT['monthday'] = function (events, rule) {
return events.filter(function (s) {
var r = Util.clone(rule);
// Transform the negative monthdays into positive for this specific month
r = r.map(function (b) {
if (b < 0) {
var tmp = new Date(s.getFullYear(), s.getMonth()+1, 0); // Last day
b = tmp.getDate() + b + 1;
}
return b;
});
return r.includes(s.getDate());
});
};
LIMIT['day'] = function (events, days, rules) {
return events.filter(function (s) {
var dayStr = s.toLocaleDateString();
// Check how to handle position in BYDAY rules (last day of the month or the year?)
var type = 'yearly';
if (rules.freq === 'monthly' ||
(rules.freq === 'yearly' && rules.by && rules.by.month)) {
type = 'monthly';
}
// Check if this event matches one of the allowed days
return days.some(function (r) {
// rule elements are strings with pos and day
var day = getDayData(r);
var pos;
if (Array.isArray(day)) {
pos = day[0];
day = day[1];
}
if (!pos) {
return s.getDay() === day;
}
// If we have a position, we can use EXPAND.day to get the nth {day} of the
// year/month and compare if it matches with
var d = new Date(s.getFullYear(), s.getMonth(), 1);
if (type === 'yearly') { d.setMonth(0); }
var res = EXPAND["day"](d, {}, r, {freq: type});
return res.some(function (date) {
return date.toLocaleDateString() === dayStr;
});
});
});
};
LIMIT['setpos'] = function (events, rule) {
var init = events.slice();
var rules = Util.deduplicateString(rule.slice().map(function (n) {
if (n > 0) { return (n-1); }
if (n === 0) { return; }
return init.length + n;
}));
return events.filter(function (ev) {
var idx = init.indexOf(ev);
return rules.includes(idx);
});
};
var BYORDER = ['month','weekno','yearday','monthday','day'];
var BYDAYORDER = ['month','monthday','day'];
Rec.getMonthId = function (d) {
return d.getFullYear() + '-' + d.getMonth();
};
var cache = window.CP_calendar_cache = {};
var recurringAcross = {};
Rec.resetCache = function () {
cache = window.CP_calendar_cache = {};
recurringAcross = {};
};
var iterate = function (rule, _origin, s) {
// "origin" is the original event to detect the start of BYxxx
var origin = Util.clone(_origin);
var oS = new Date(origin.start);
var id = origin.id.split('|')[0]; // Use same cache when updating recurrence rule
// "uid" is used for the cache
var uid = s.toLocaleDateString();
cache[id] = cache[id] || {};
var inter = rule.interval || 1;
var freq = rule.freq;
var all = [];
var limit = function (byrule, n) {
all = LIMIT[byrule](all, n, rule);
};
var expand = function (byrule) {
return function (n) {
// Set the start date at the beginning of the current FREQ
var _s = new Date(+s);
if (rule.freq === 'yearly') {
// January 1st
_s.setMonth(0);
_s.setDate(1);
} else if (rule.freq === 'monthly') {
_s.setDate(1);
} else if (rule.freq === 'weekly') {
goToFirstWeekDay(_s, rule.wkst);
} else if (rule.freq === 'daily') {
// We don't have < byday rules so we can't expand daily rules
}
var add = EXPAND[byrule](_s, origin, n, rule);
if (!add) { return; }
if (Array.isArray(add)) {
add = add.filter(function (dateS) {
return dateS.toLocaleDateString() !== oS.toLocaleDateString();
});
Array.prototype.push.apply(all, add);
} else {
if (_s.toLocaleDateString() === oS.toLocaleDateString()) { return; }
all.push(_s);
}
};
};
// Manage interval for the next iteration
var it = Util.once(function () {
FREQ[freq](s, inter);
});
var addDefault = function () {
if (freq === "monthly") {
s.setDate(15);
} else if (freq === "yearly" && oS.getMonth() === 1 && oS.getDate() === 29) {
s.setDate(28);
}
it();
var _s = new Date(+s);
if (freq === "monthly" || freq === "yearly") {
_s.setDate(oS.getDate());
if (_s.getDate() !== oS.getDate()) { return; } // If 31st or Feb 29th doesn't exist
if (freq === "yearly" && _s.getMonth() !== oS.getMonth()) { return; }
// FIXME if there is a recUpdate that moves the 31st to the 30th, the event
// will still only be displayed on months with 31 days
}
all.push(_s);
};
if (Array.isArray(cache[id][uid])) {
debug('Get cache', id, uid);
if (freq === "monthly") {
s.setDate(15);
} else if (freq === "yearly" && oS.getMonth() === 1 && oS.getDate() === 29) {
s.setDate(28);
}
it();
return cache[id][uid];
}
if (rule.by && freq === 'yearly') {
var order = BYORDER.slice();
var monthLimit = false;
if (rule.by.weekno || rule.by.yearday || rule.by.monthday || rule.by.day) {
order.shift();
monthLimit = true;
}
var first = true;
order.forEach(function (_order) {
var r = rule.by[_order];
if (!r) { return; }
if (first) {
r.forEach(expand(_order));
first = false;
} else if (_order === "day") {
if (rule.by.yearday || rule.by.monthday || rule.by.weekno) {
limit('day', rule.by.day);
} else {
rule.by.day.forEach(expand('day'));
}
} else {
limit(_order, r);
}
});
if (rule.by.month && monthLimit) {
limit('month', rule.by.month);
}
}
if (rule.by && freq === 'monthly') {
// We're going to compute all the entries for the coming month
if (!rule.by.monthday && !rule.by.day) {
addDefault();
} else if (rule.by.monthday) {
rule.by.monthday.forEach(expand('monthday'));
} else if (rule.by.day) {
rule.by.day.forEach(expand('day'));
}
if (rule.by.month) {
limit('month', rule.by.month);
}
if (rule.by.day && rule.by.monthday) {
limit('day', rule.by.day);
}
}
if (rule.by && freq === 'weekly') {
// We're going to compute all the entries for the coming week
if (!rule.by.day) {
addDefault();
} else {
rule.by.day.forEach(expand('day'));
}
if (rule.by.month) {
limit('month', rule.by.month);
}
}
if (rule.by && freq === 'daily') {
addDefault();
BYDAYORDER.forEach(function (_order) {
var r = rule.by[_order];
if (!r) { return; }
limit(_order, r);
});
}
all.sort(function (a, b) {
return a-b;
});
if (rule.by && rule.by.setpos) {
limit('setpos', rule.by.setpos);
}
if (!rule.by || !Object.keys(rule.by).length) {
addDefault();
} else {
it();
}
var done = [];
all = all.filter(function (newS) {
var start = new Date(+newS).toLocaleDateString();
if (done.includes(start)) { return false; }
done.push(start);
return true;
});
debug('Set cache', id, uid);
cache[id][uid] = all;
return all;
};
var getNextRules = function (obj) {
if (!obj.recUpdate) { return []; }
var _allRules = {};
var _obj = obj.recUpdate.from;
Object.keys(_obj || {}).forEach(function (d) {
var u = _obj[d];
if (u.recurrenceRule) { _allRules[d] = u.recurrenceRule; }
});
return Object.keys(_allRules).sort(function (a, b) { return Number(a)-Number(b); })
.map(function (k) {
var r = Util.clone(_allRules[k]);
if (!FREQ[r.freq]) { return; }
if (r.interval && r.interval < 1) { return; }
r._start = Number(k);
return r;
}).filter(Boolean);
};
var fixTimeZone = function (evTimeZone, origin, target) {
var getOffset = function (date, tz) {
// Get an ISO string using Canadian local format
let iso = date.toLocaleString('en-CA', { timeZone:tz, hour12: false }).replace(', ', 'T');
iso += '.' + date.getMilliseconds().toString().padStart(3, '0');
// Get a UTC version of this time
let utcDate = new Date(iso + 'Z');
// Return the difference in timestamps, as minutes (60*1000)
return -(utcDate - date);
};
var myTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
var offset = getOffset(origin, evTimeZone) - getOffset(target, evTimeZone);
var myOffset = getOffset(origin, myTimeZone) - getOffset(target, myTimeZone);
return myOffset - offset;
};
Rec.getRecurring = function (months, events) {
if (window.CP_DEV_MODE) { debug = console.warn; }
var toAdd = [];
months.forEach(function (monthId) {
// from 1st day of the month at 00:00 to last day at 23:59:59:999
var ms = monthId.split('-');
var _startMonth = new Date(ms[0], ms[1]);
var _endMonth = new Date(+_startMonth);
_endMonth.setMonth(_endMonth.getMonth() + 1);
_endMonth.setMilliseconds(-1);
debug('Compute month', _startMonth.toLocaleDateString());
var rec = events || [];
rec.forEach(function (obj) {
var _start = new Date(obj.start);
var _end = new Date(obj.end);
var _origin = obj;
var rule = obj.recurrenceRule;
if (!rule) { return; }
var nextRules = getNextRules(obj);
var nextRule = nextRules.shift();
if (_start >= _endMonth) { return; }
// Check the "until" date of the latest rule we can use and stop now
// if the recurrence ends before the current month
var until = rule.until;
var _nextRules = nextRules.slice();
var _nextRule = nextRule;
while (_nextRule && _nextRule._start && _nextRule._start < _startMonth) {
until = nextRule.until;
_nextRule = _nextRules.shift();
}
if (until < _startMonth) { return; }
var endData = getEndData(_start, _end);
if (rule.interval && rule.interval < 1) { return; }
if (!FREQ[rule.freq]) { return; }
/*
// Rule examples
rule.by = {
//month: [1, 4, 5, 8, 12],
//weekno: [1, 2, 4, 5, 32, 34, 35, 50],
//yearday: [1, 2, 29, 30, -2, -1, 250],
//monthday: [1, 2, 3, -3, -2, -1],
//day: ["MO", "WE", "FR"],
//setpos: [1, 2, -1, -2]
};
rule.wkst = 0;
rule.interval = 2;
rule.freq = 'yearly';
rule.count = 10;
*/
debug('Iterate over', obj.title, obj);
debug('Use rule', rule);
var count = rule.count;
var c = 1;
var next = function (start) {
var evS = new Date(+start);
if (count && c >= count) { return; }
debug('Start iteration', evS.toLocaleDateString());
var _toAdd = iterate(rule, obj, evS);
debug('Iteration results', JSON.stringify(_toAdd.map(function (o) { return new Date(o).toLocaleDateString();})));
// Make sure to continue if the current year doesn't provide any result
if (!_toAdd.length) {
if (evS.getFullYear() < _startMonth.getFullYear() ||
evS < _endMonth) {
return void next(evS);
}
return;
}
var stop = false;
var newrule = false;
_toAdd.some(function (_newS) {
// Make event with correct start and end time
var _ev = Util.clone(obj);
_ev.id = _origin.id + '|' + (+_newS);
var _evS = new Date(+_newS);
var _evE = new Date(+_newS);
setEndData(_evS, _evE, endData);
_ev.start = +_evS;
_ev.end = +_evE;
_ev._count = c;
if (_ev.isAllDay && _ev.startDay) { _ev.startDay = getDateStr(_evS); }
if (_ev.isAllDay && _ev.endDay) { _ev.endDay = getDateStr(_evE); }
if (nextRule && _ev.start === nextRule._start) {
newrule = true;
}
var useNewRule = function () {
if (!newrule) { return; }
debug('Use new rule', nextRule);
_ev._count = c;
count = nextRule.count;
c = 1;
evS = +_evS;
obj = _ev;
rule = nextRule;
nextRule = nextRules.shift();
};
if (c >= count) { // Limit reached
debug(_evS.toLocaleDateString(), 'count');
stop = true;
return true;
}
if (_evS >= _endMonth) { // Won't affect us anymore
debug(_evS.toLocaleDateString(), 'endMonth');
stop = true;
return true;
}
if (rule.until && _evS > rule.until) {
debug(_evS.toLocaleDateString(), 'until');
stop = true;
return true;
}
if (_evS < _start) { // "Expand" rules may create events before the _start
debug(_evS.toLocaleDateString(), 'start');
return;
}
c++;
if (_evE < _startMonth) { // Ended before the current month
// Nothing to display but continue the recurrence
debug(_evS.toLocaleDateString(), 'startMonth');
if (newrule) { useNewRule(); }
return;
}
// If a recurring event start and end in different months, make sure
// it is only added once
if ((_evS < _endMonth && _evE >= _endMonth) ||
(_evS < _startMonth && _evE >= _startMonth)) {
if (recurringAcross[_ev.id] && recurringAcross[_ev.id].includes(_ev.start)) {
return;
} else {
recurringAcross[_ev.id] = recurringAcross[_ev.id] || [];
recurringAcross[_ev.id].push(_ev.start);
}
}
// Add this event
if (_origin.timeZone && !_ev.isAllDay) {
var offset = fixTimeZone(_origin.timeZone, _start, _evS);
_ev.start += offset;
_ev.end += offset;
}
toAdd.push(_ev);
if (newrule) {
useNewRule();
return true;
}
});
if (!stop) { next(evS); }
};
next(_start);
debug('Added this month (all events)', toAdd.map(function (ev) {
return new Date(ev.start).toLocaleDateString();
}));
});
});
return toAdd;
};
Rec.getAllOccurrences = function (ev) {
if (!ev.recurrenceRule) { return [ev.start]; }
var r = ev.recurrenceRule;
// In case of infinite recursion, we can't get all
if (!r.until && !r.count) { return false; }
var all = [ev.start];
var d = new Date(ev.start);
d.setDate(15); // Make sure we won't skip a month if the event starts on day > 28
var toAdd = [];
var i = 0;
var check = function () {
return r.count ? (all.length < r.count) : (+d <= r.until);
};
while ((toAdd = Rec.getRecurring([Rec.getMonthId(d)], [ev])) && check() && i < (r.count*12)) {
Array.prototype.push.apply(all, toAdd.map(function (_ev) { return _ev.start; }));
d.setMonth(d.getMonth() + 1);
i++;
}
return all;
};
Rec.diffDate = function (oldTime, newTime) {
var n = new Date(newTime);
var o = new Date(oldTime);
// Diff Days
var d = 0;
var mult = n < o ? -1 : 1;
while (n.toLocaleDateString() !== o.toLocaleDateString() || mult >= 10000) {
n.setDate(n.getDate() - mult);
d++;
}
d = mult * d;
// Diff hours
n = new Date(newTime);
var h = n.getHours() - o.getHours();
// Diff minutes
var m = n.getMinutes() - o.getMinutes();
return {
d: d,
h: h,
m: m
};
};
var sortUpdate = function (obj) {
return Object.keys(obj).sort(function (d1, d2) {
return Number(d1) - Number(d2);
});
};
Rec.applyUpdates = function (events) {
events.forEach(function (ev) {
ev.raw = {
start: ev.start,
end: ev.end,
};
if (!ev.recUpdate) { return; }
var from = ev.recUpdate.from || {};
var one = ev.recUpdate.one || {};
var s = ev.start;
// Add "until" date to our recurrenceRule if it has been modified in future occurences
var nextRules = getNextRules(ev).filter(function (r) {
return r._start > s;
});
var nextRule = nextRules.shift();
var applyDiff = function (obj, k) {
var diff = obj[k]; // Diff is always compared to origin start/end
var d = new Date(ev.raw[k]);
d.setDate(d.getDate() + diff.d);
d.setHours(d.getHours() + diff.h);
d.setMinutes(d.getMinutes() + diff.m);
ev[k] = +d;
};
sortUpdate(from).forEach(function (d) {
if (s < Number(d)) { return; }
Object.keys(from[d]).forEach(function (k) {
if (k === 'start' || k === 'end') { return void applyDiff(from[d], k); }
if (k === "recurrenceRule" && !from[d][k]) { return; }
ev[k] = from[d][k];
});
});
Object.keys(one[s] || {}).forEach(function (k) {
if (k === 'start' || k === 'end') { return void applyDiff(one[s], k); }
if (k === "recurrenceRule" && !one[s][k]) { return; }
ev[k] = one[s][k];
});
if (ev.deleted) {
Object.keys(ev).forEach(function (k) {
delete ev[k];
});
}
if (nextRule && ev.recurrenceRule) {
ev.recurrenceRule._next = nextRule._start - 1;
}
if (ev.reminders) {
ev.raw.reminders = ev.reminders;
}
});
return events;
};
return Rec;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(require('./common-util'));
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define(['/common/common-util.js'], factory);
} else {
// unsupported initialization
}
})();

View File

@ -15,7 +15,7 @@ define([
'/common/common-hash.js',
'/common/common-util.js',
'/common/pinpad.js',
'/common/outer/network-config.js',
'/common/network-config.js',
'/common/outer/login-block.js',
'/customize/pages.js',
'/checkup/checkup-tools.js',
@ -82,7 +82,7 @@ define([
var trimmedSafe = trimSlashes(ApiConfig.httpSafeOrigin);
var trimmedUnsafe = trimSlashes(ApiConfig.httpUnsafeOrigin);
var fileHost = ApiConfig.fileHost;
var accounts_api = ApiConfig.accounts_api || AppConfig.accounts_api || undefined;
var accounts_api = ApiConfig.accounts_api || undefined;
var getAPIPlaceholderPath = function (relative) {
var absolute;
@ -113,15 +113,6 @@ define([
} catch (e) {}
}
var ACCOUNTS_URL;
try {
if (typeof(AppConfig.upgradeURL) === 'string') {
ACCOUNTS_URL = new URL(AppConfig.upgradeURL, trimmedUnsafe).origin;
}
} catch (err) {
console.error(err);
}
var debugOrigins = {
httpUnsafeOrigin: trimmedUnsafe,
httpSafeOrigin: trimmedSafe,
@ -1026,8 +1017,7 @@ define([
(HTTP_API_URL && HTTP_API_URL !== $outer) ? HTTP_API_URL : undefined,
isHTTPS(fileHost)? fileHost: undefined,
// support for cryptpad.fr configuration
accounts_api,
![trimmedUnsafe, trimmedSafe].includes(ACCOUNTS_URL)? ACCOUNTS_URL: undefined,
accounts_api
],
'img-src': ["'self'", 'data:', 'blob:', $outer],
@ -1066,8 +1056,7 @@ define([
API_URL.origin,
(HTTP_API_URL && HTTP_API_URL !== $outer) ? HTTP_API_URL : undefined,
isHTTPS(fileHost)? fileHost: undefined,
accounts_api,
![trimmedUnsafe, trimmedSafe].includes(ACCOUNTS_URL)? ACCOUNTS_URL: undefined,
accounts_api
],
'img-src': ["'self'", 'data:', 'blob:', $outer],
'media-src': ['blob:'],

View File

@ -179,6 +179,9 @@ define([
var isSmallScreen = () => window.innerWidth <= 600;
const showEditor = Util.once(() => {
$('#cp-app-code-editor').css('display', '');
});
var mkPreviewPane = function (editor, CodeMirror, framework, isPresentMode) {
var $previewContainer = $('#cp-app-code-preview');
var $preview = $('#cp-app-code-preview-content');
@ -345,8 +348,8 @@ define([
$codeMirrorContainer.addClass('cp-app-code-fullpage');
};
framework.onReady(function () {
showEditor();
handleResize();
window.addEventListener('resize', handleResize);
@ -536,6 +539,7 @@ define([
}
framework.onContentUpdate(function (newContent) {
showEditor();
var highlightMode = newContent.highlightMode;
if (highlightMode && highlightMode !== CodeMirror.highlightMode) {
CodeMirror.setMode(highlightMode, evModeChange.fire);
@ -578,6 +582,13 @@ define([
framework.setCursorGetter(CodeMirror.getCursor);
editor.on('cursorActivity', updateCursor);
editor.setOption("extraKeys", {
"Esc": function(cm, event) {
event.preventDefault();
document.body.focus();
}
});
framework.onEditableChange(function () {
editor.setOption('readOnly', framework.isLocked() || framework.isReadOnly());
});
@ -696,7 +707,7 @@ define([
h('div#cp-app-code-preview-content'),
h('div#cp-app-code-print')
])
]);
]).hide();
nThen(function (waitFor) {
$(waitFor());

View File

@ -1,47 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(() => {
const factory = function (AppConfig = {}) {
return {
setCustomize: data => {
AppConfig = data.AppConfig;
},
// localStorage
userHashKey: 'User_hash',
userNameKey: 'User_name',
blockHashKey: 'Block_hash',
fileHashKey: 'FS_hash',
sessionJWT: 'Session_JWT',
ssoSeed: 'SSO_seed',
// Store
displayNameKey: 'cryptpad.username',
oldStorageKey: 'CryptPad_RECENTPADS',
storageKey: 'filesData',
tokenKey: 'loginToken',
prefersDriveRedirectKey: 'prefersDriveRedirect',
isPremiumKey: 'isPremiumUser',
displayPadCreationScreen: 'displayPadCreationScreen',
deprecatedKey: 'deprecated',
MAX_TEAMS_SLOTS: AppConfig.maxTeamsSlots || 5,
MAX_TEAMS_OWNED: AppConfig.maxOwnedTeams || 5,
MAX_PREMIUM_TEAMS_SLOTS: Math.max(AppConfig.maxTeamsSlots || 0, AppConfig.maxPremiumTeamsSlots || 0) || 5,
MAX_PREMIUM_TEAMS_OWNED: Math.max(AppConfig.maxOwnedTeams || 0, AppConfig.maxPremiumTeamsOwned || 0) || 5,
// Apps
criticalApps: ['profile', 'settings', 'debug', 'admin', 'support', 'notifications', 'calendar', 'moderation', 'oldadmin'], // XXX oldadmin
earlyAccessApps: []
};
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(undefined);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define(['/customize/application_config.js'], factory);
} else {
// unsupported initialization
}
})();

View File

@ -1,116 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
(function () {
var factory = function (AppConfig = {}, Scrypt) {
var Cred = {};
Cred.setCustomize = data => {
AppConfig = data.AppConfig;
};
Cred.MINIMUM_PASSWORD_LENGTH = typeof(AppConfig.minimumPasswordLength) === 'number'?
AppConfig.minimumPasswordLength: 8; // TODO 14 or higher is a decent default for 2023
Cred.MINIMUM_NAME_LENGTH = 1;
Cred.MAXIMUM_NAME_LENGTH = 64;
// https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript
Cred.isEmail = function (email) {
var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
};
Cred.isLongEnoughPassword = function (passwd) {
return passwd.length >= Cred.MINIMUM_PASSWORD_LENGTH;
};
var isString = Cred.isString = function (x) {
return typeof(x) === 'string';
};
// Maximum username length is enforced at registration time
// rather than in this function
// in order to maintain backwards compatibility with accounts
// that might have already registered with a longer name.
Cred.isValidUsername = function (name) {
return !!(isString(name) && name.length >= Cred.MINIMUM_NAME_LENGTH);
};
Cred.isValidPassword = function (passwd) {
return !!(passwd && isString(passwd));
};
Cred.passwordsMatch = function (a, b) {
return isString(a) && isString(b) && a === b;
};
Cred.customSalt = function () {
return typeof(AppConfig.loginSalt) === 'string'?
AppConfig.loginSalt: '';
};
Cred.deriveFromPassphrase = function (username, password, len, cb) {
Scrypt(password,
username + Cred.customSalt(), // salt
8, // memoryCost (n)
1024, // block size parameter (r)
len || 128, // dkLen
200, // interruptStep
cb,
undefined); // format, could be 'base64'
};
Cred.dispenser = function (bytes) {
var entropy = {
used: 0,
};
// crypto hygeine
var consume = function (n) {
// explode if you run out of bytes
if (entropy.used + n > bytes.length) {
throw new Error('exceeded available entropy');
}
if (typeof(n) !== 'number') { throw new Error('expected a number'); }
if (n <= 0) {
throw new Error('expected to consume a positive number of bytes');
}
// grab an unused slice of the entropy
// Note: Internet Explorer doesn't support .slice on Uint8Array
var A;
if (bytes.slice) {
A = bytes.slice(entropy.used, entropy.used + n);
} else {
A = bytes.subarray(entropy.used, entropy.used + n);
}
// account for the bytes you used so you don't reuse bytes
entropy.used += n;
//console.info("%s bytes of entropy remaining", bytes.length - entropy.used);
return A;
};
return consume;
};
return Cred;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(
undefined, //require("../../customize.dist/application_config.js"),
require("scrypt-async")
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/customize/application_config.js',
'/components/scrypt-async/scrypt-async.min.js',
], function (AppConfig) {
return factory(AppConfig, window.scrypt);
});
}
}());

Some files were not shown because too many files have changed in this diff Show More