mirror of
https://github.com/cryptpad/cryptpad.git
synced 2026-09-12 19:49:59 +05:00
Merge branch 'staging' into moderation
This commit is contained in:
commit
93d0fbb7d8
3
.gitignore
vendored
3
.gitignore
vendored
@ -35,6 +35,9 @@ www/common/onlyoffice/v*
|
||||
www/common/worker.bundle.js
|
||||
_build
|
||||
|
||||
FUNDING.json
|
||||
funding.json
|
||||
|
||||
# ---> Node
|
||||
# Logs
|
||||
logs
|
||||
|
||||
49
CHANGELOG.md
49
CHANGELOG.md
@ -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
7
FUNDING.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"drips": {
|
||||
"ethereum": {
|
||||
"ownedBy": "0xa117114b3a22e038c1545043d610dbd4029355b1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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">
|
||||
|
||||
63
customize.dist/images/forum.svg
Normal file
63
customize.dist/images/forum.svg
Normal 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 |
3
customize.dist/images/forum.svg.license
Normal file
3
customize.dist/images/forum.svg.license
Normal file
@ -0,0 +1,3 @@
|
||||
SPDX-FileCopyrightText: Font Awesome by Dave Gandy
|
||||
|
||||
SPDX-License-Identifier: CC-BY-SA-3.0
|
||||
@ -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 |
@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
@ -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 |
@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
@ -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 |
@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
@ -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>',
|
||||
|
||||
@ -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();
|
||||
});
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
});
|
||||
|
||||
@ -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]),
|
||||
|
||||
@ -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(),
|
||||
]);
|
||||
|
||||
@ -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',[
|
||||
|
||||
@ -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
|
||||
])
|
||||
])
|
||||
]),
|
||||
|
||||
@ -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>',
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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%);
|
||||
|
||||
@ -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%);
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
---
|
||||
services:
|
||||
cryptpad:
|
||||
image: "cryptpad/cryptpad:version-2025.3.0"
|
||||
image: "cryptpad/cryptpad:version-2025.6.0"
|
||||
hostname: cryptpad
|
||||
|
||||
environment:
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -1,10 +1,5 @@
|
||||
{
|
||||
"version": "v1.0.0",
|
||||
"drips": {
|
||||
"ethereum": {
|
||||
"ownedBy": "0xa117114b3a22e038c1545043d610dbd4029355b1"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"type": "organisation",
|
||||
"role": "owner",
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -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});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -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(); }
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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");
|
||||
|
||||
13
lib/env.js
13
lib/env.js
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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
3228
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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);
|
||||
@ -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',
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
})();
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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')
|
||||
);
|
||||
@ -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')
|
||||
@ -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
|
||||
|
||||
@ -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); }
|
||||
|
||||
|
||||
@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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")
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
545
src/worker/components/pad.ts
Normal file
545
src/worker/components/pad.ts
Normal 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 }
|
||||
|
||||
|
||||
@ -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")
|
||||
);
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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') {
|
||||
|
||||
@ -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'),
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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'),
|
||||
);
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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
|
||||
}
|
||||
})();
|
||||
@ -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:'],
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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
|
||||
}
|
||||
})();
|
||||
|
||||
@ -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
Loading…
Reference in New Issue
Block a user