mirror of
https://github.com/cryptpad/cryptpad.git
synced 2026-09-14 11:05:41 +05:00
Merge branch '2025.3.1-rc' into staging
This commit is contained in:
commit
c276ca758e
@ -134,7 +134,12 @@ define(req, function(AppConfig, Default, Language) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 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";
|
||||
|
||||
return Messages;
|
||||
|
||||
|
||||
28
customize.dist/src/less2/include/badges.less
Normal file
28
customize.dist/src/less2/include/badges.less
Normal file
@ -0,0 +1,28 @@
|
||||
@import (reference) "./colortheme-all.less";
|
||||
@import (reference) "./variables.less";
|
||||
.badges_vars(
|
||||
@width: 30px
|
||||
) {
|
||||
@badges-width: @width;
|
||||
}
|
||||
.badges_main(@width: 30px) {
|
||||
--LessLoader_require: LessLoader_currentFile();
|
||||
.badges_vars(@width);
|
||||
--badges-width: @badges-width;
|
||||
--badges-font: ceil(@badges-width*2/3);
|
||||
}
|
||||
& {
|
||||
.badges_vars();
|
||||
i.cp-badge {
|
||||
width: var(--badges-width);
|
||||
height: var(--badges-width);
|
||||
box-sizing: border-box;
|
||||
border: 1px solid @cryptpad_text_col;
|
||||
border-radius: 50%;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: var(--badges-font);
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,6 +263,14 @@
|
||||
.cp-toolbar-userlist-friend {
|
||||
padding: 0;
|
||||
}
|
||||
.cp-userlist-badge {
|
||||
i {
|
||||
color: @cryptpad_color_brand;
|
||||
}
|
||||
i.cp-badge-error {
|
||||
color: @cryptpad_color_warn_red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
151
funding.json
Normal file
151
funding.json
Normal file
@ -0,0 +1,151 @@
|
||||
{
|
||||
"version": "v1.0.0",
|
||||
"drips": {
|
||||
"ethereum": {
|
||||
"ownedBy": "0xa117114b3a22e038c1545043d610dbd4029355b1"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"type": "organisation",
|
||||
"role": "owner",
|
||||
"name": "XWiki SAS",
|
||||
"email": "funding@xwiki.com",
|
||||
"phone": "",
|
||||
"description": "XWiki SAS is a European Open Source organization, maintaining Open Source Collaboration projects, XWiki and CryptPad. We believe that Open Source projects are key to getting back control on software, and that companies are important to allow the maintenance of Open Source projects.",
|
||||
"webpageUrl": {
|
||||
"url": "https://xwiki.com",
|
||||
"wellKnown": "https://xwiki.com/.well-known/funding-manifest-urls"
|
||||
}
|
||||
},
|
||||
"projects": [
|
||||
{
|
||||
"guid": "cryptpad",
|
||||
"name": "CryptPad",
|
||||
"description": "CryptPad is an end-to-end encrypted Collaboration Platform focused on providing real-time editing tools while protecting privacy.",
|
||||
"webpageUrl": {
|
||||
"url": "https://cryptpad.org",
|
||||
"wellKnown": "https://cryptpad.org/.well-known/funding-manifest-urls"
|
||||
},
|
||||
"repositoryUrl": {
|
||||
"url": "https://github.com/cryptpad/cryptpad"
|
||||
},
|
||||
"licenses": [
|
||||
"spdx:AGPL-3.0-or-later"
|
||||
],
|
||||
"tags": [
|
||||
"privacy",
|
||||
"e2ee",
|
||||
"collaboration",
|
||||
"realtimeediting",
|
||||
"office",
|
||||
"drive"
|
||||
]
|
||||
}
|
||||
],
|
||||
"funding": {
|
||||
"channels": [
|
||||
{
|
||||
"guid": "opencollective-cryptpad",
|
||||
"type": "payment-provider",
|
||||
"address": "https://opencollective.com/cryptpad",
|
||||
"description": "OpenCollective CryptPad Account"
|
||||
},
|
||||
{
|
||||
"guid": "bank",
|
||||
"type": "bank",
|
||||
"address": "Contact XWiki SAS",
|
||||
"description": "Donation by Bank Transfer or other means with XWiki SAS"
|
||||
}
|
||||
],
|
||||
"plans": [
|
||||
{
|
||||
"guid": "cryptpad-individual-helper",
|
||||
"status": "active",
|
||||
"name": "Individual Donator - Starter",
|
||||
"description": "Individual Donator would like to help us fund the maintenance and further improvement of the software.\n\n35000 people donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- an occasional user of CryptPad\n- a regular user with limited funds\n- somebody just wanting to give us a nudge",
|
||||
"amount": 20,
|
||||
"currency": "EUR",
|
||||
"frequency": "yearly",
|
||||
"channels": [
|
||||
"opencollective-cryptpad"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guid": "cryptpad-individual-user",
|
||||
"status": "active",
|
||||
"name": "Individual Donator - User",
|
||||
"description": "Individual Donator would like to help us fund the maintenance and further improvement of the software.\n\n14000 people donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- an regular user of CryptPad\n- a privacy believer who would like to help us\n- a personal instance owner with limited funds",
|
||||
"amount": 50,
|
||||
"currency": "EUR",
|
||||
"frequency": "yearly",
|
||||
"channels": [
|
||||
"opencollective-cryptpad"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guid": "cryptpad-individual-sponsor",
|
||||
"status": "active",
|
||||
"name": "Individual Donator - Sponsor",
|
||||
"description": "Individual Donator would like to help us fund the maintenance and further improvement of the software.\n\n3500 people donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- an regular user of CryptPad who really likes CryptPad\n- a privacy believer who wants to accelerate privacy online\n- a personal or community small size instance owner",
|
||||
"amount": 200,
|
||||
"currency": "EUR",
|
||||
"frequency": "yearly",
|
||||
"channels": [
|
||||
"opencollective-cryptpad"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guid": "cryptpad-instanceowner-smallorg",
|
||||
"status": "active",
|
||||
"name": "Small Organization Using CryptPad",
|
||||
"description": "Small Organization which would like to help us fund the maintenance and further improvement of the software.\n\n1500 organizations donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- a non-profit organization having some funds hosting CryptPad for their members\n- a small company using CryptPad for its users\n- any organizations wanting Privacy software to progress",
|
||||
"amount": 500,
|
||||
"currency": "EUR",
|
||||
"frequency": "yearly",
|
||||
"channels": [
|
||||
"opencollective-cryptpad"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guid": "cryptpad-instanceowner-bigorg",
|
||||
"status": "active",
|
||||
"name": "Small Organization Using CryptPad",
|
||||
"description": "Small Organization which would like to help us fund the maintenance and further improvement of the software.\n\n350 organizations donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- a commercial company using CryptPad regularly internally or externally\n- any organization having sufficient funds wanting to support Open Source Software doing the right thing\n",
|
||||
"amount": 2000,
|
||||
"currency": "EUR",
|
||||
"frequency": "yearly",
|
||||
"channels": [
|
||||
"opencollective-cryptpad",
|
||||
"bank"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guid": "cryptpad-instanceowner-bigorg",
|
||||
"status": "active",
|
||||
"name": "Organization using CryptPad heavily or as part of their offering",
|
||||
"description": "Organization using CryptPad heavily or as part of it's commercial offering and which would like to significantly contribute to it's development.\n\n70 organizations donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- a commercial company using CryptPad heavily or as part of a commercial offering\n- foundations who would like to support the development.",
|
||||
"amount": 10000,
|
||||
"currency": "EUR",
|
||||
"frequency": "yearly",
|
||||
"channels": [
|
||||
"opencollective-cryptpad",
|
||||
"bank"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guid": "cryptpad-org-funder",
|
||||
"status": "active",
|
||||
"name": "Funding organization or large company supporting the maintenance and development of CryptPad",
|
||||
"description": "This is meant for an organization investing in Privacy Preserving technologies and/or Open Source software which is important for the internet and wanting to support CryptPad with a grant. The grant can be for specific development useful for our roadmap or just for maintenance of the software.\n\n12 such grants would be needed to fund the current team.",
|
||||
"amount": 50000,
|
||||
"currency": "EUR",
|
||||
"frequency": "one-time",
|
||||
"channels": [
|
||||
"opencollective-cryptpad",
|
||||
"bank"
|
||||
]
|
||||
}
|
||||
],
|
||||
"history": []
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,10 @@ PROPS_FILE="$CONF_DIR"/onlyoffice.properties
|
||||
declare -A PROPS
|
||||
|
||||
main() {
|
||||
|
||||
# clean build env in case a previous build has failed
|
||||
rm -rf "$BUILDS_DIR"
|
||||
|
||||
mkdir -p "$CONF_DIR"
|
||||
|
||||
load_props
|
||||
@ -23,18 +27,75 @@ main() {
|
||||
|
||||
ask_for_license
|
||||
|
||||
# Remeber the 1st version that is installed. This will help us install only
|
||||
# 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
|
||||
|
||||
mkdir -p "$OO_DIR"
|
||||
install_version v1 4f370beb
|
||||
install_version v2b d9da72fd
|
||||
install_version v4 6ebc6938
|
||||
install_version v5 88a356f0
|
||||
install_version v6 abd8a309
|
||||
install_version v7 e1267803
|
||||
install_x2t v7.3+1 ab0c05b0e4c81071acea83f0c6a8e75f5870c360ec4abc4af09105dd9b52264af9711ec0b7020e87095193ac9b6e20305e446f2321a541f743626a598e5318c1
|
||||
|
||||
echo "Do you want to install all available OnlyOffice versions? (Y/N)"
|
||||
read -r install_all
|
||||
|
||||
declare -a selected_versions
|
||||
|
||||
if [[ "$install_all" =~ ^[Yy] ]]; then
|
||||
selected_versions=(v1 v2b v4 v5 v6 v7 v8 x2t)
|
||||
else
|
||||
echo "Select which versions to install (space-separated), options are:"
|
||||
echo " v1 v2b v4 v5 v6 v7 v8 x2t"
|
||||
read -ra selected_versions
|
||||
fi
|
||||
|
||||
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+10 be3c926d534b0c77aa1ed83b170a38b9e56ebbbfd73a1a968f523d63c5ed69ecdb063fad08740812de82c1e79af7309faf67ffdd81b45451adce539192f3414c
|
||||
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.0.83+3 fbf77671ea8b960b6c65fdad025980894dfc9ed0b79d9540951f56c79871d0bde3361f29a35b9653764f7802ec74df01601b6c9961e0f3118988422667563470 ;;
|
||||
x2t) install_x2t v7.3+1 ab0c05b0e4c81071acea83f0c6a8e75f5870c360ec4abc4af09105dd9b52264af9711ec0b7020e87095193ac9b6e20305e446f2321a541f743626a598e5318c1 ;;
|
||||
*)
|
||||
echo "Unknown version: $version"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
rm -rf "$BUILDS_DIR"
|
||||
|
||||
@ -156,23 +217,22 @@ EOF
|
||||
}
|
||||
|
||||
ensure_oo_is_downloaded() {
|
||||
ensure_command_available git
|
||||
ensure_command_available git
|
||||
|
||||
if ! [ -d "$BUILDS_DIR" ]; then
|
||||
echo "Downloading OnlyOffice..."
|
||||
git clone --bare https://github.com/cryptpad/onlyoffice-builds.git "$BUILDS_DIR"
|
||||
fi
|
||||
if [ ${TRUST_REPOSITORY+x} ] || [ "${PROPS[trust_repository]:-no}" == yes ]; then
|
||||
git config --global --add safe.directory /cryptpad/onlyoffice-conf/onlyoffice-builds.git
|
||||
fi
|
||||
if ! [ -d "$BUILDS_DIR" ]; then
|
||||
echo "Downloading OnlyOffice..."
|
||||
git clone --bare https://github.com/cryptpad/onlyoffice-builds.git "$BUILDS_DIR"
|
||||
fi
|
||||
if [ ${TRUST_REPOSITORY+x} ] || [ "${PROPS[trust_repository]:-no}" == yes ]; then
|
||||
git config --global --add safe.directory /cryptpad/onlyoffice-conf/onlyoffice-builds.git
|
||||
fi
|
||||
}
|
||||
|
||||
install_version() {
|
||||
install_old_version() {
|
||||
local DIR=$1
|
||||
local COMMIT=$2
|
||||
local FULL_DIR=$OO_DIR/$DIR
|
||||
local LAST_DIR
|
||||
LAST_DIR=$(pwd)
|
||||
local LAST_DIR=$(pwd)
|
||||
|
||||
local ACTUAL_COMMIT="not installed"
|
||||
if [ -e "$FULL_DIR"/.commit ]; then
|
||||
@ -206,6 +266,40 @@ install_version() {
|
||||
fi
|
||||
}
|
||||
|
||||
install_version() {
|
||||
ensure_command_available curl
|
||||
ensure_command_available sha512sum
|
||||
ensure_command_available unzip
|
||||
|
||||
local DIR=$1
|
||||
local VERSION=$2
|
||||
local HASH=$3
|
||||
local FULL_DIR=$OO_DIR/$DIR
|
||||
local LAST_DIR=$(pwd)
|
||||
|
||||
if [ ! -e "$FULL_DIR"/.version ] || [ "$(cat "$FULL_DIR"/.version)" != "$VERSION" ]; then
|
||||
rm -rf "$FULL_DIR"
|
||||
mkdir -p "$FULL_DIR"
|
||||
|
||||
cd "$FULL_DIR"
|
||||
|
||||
curl "https://github.com/cryptpad/onlyoffice-editor/releases/download/$VERSION/onlyoffice-editor.zip" --location --output "onlyoffice-editor.zip"
|
||||
echo "$HASH onlyoffice-editor.zip" >onlyoffice-editor.zip.sha512
|
||||
if ! sha512sum --check onlyoffice-editor.zip.sha512; then
|
||||
echo "onlyoffice-editor.zip does not match expected checksum"
|
||||
exit 1
|
||||
fi
|
||||
unzip onlyoffice-editor.zip
|
||||
rm onlyoffice-editor.zip*
|
||||
|
||||
echo "$VERSION" >"$FULL_DIR"/.version
|
||||
|
||||
echo "$DIR updated"
|
||||
else
|
||||
echo "$DIR was up to date"
|
||||
fi
|
||||
}
|
||||
|
||||
install_x2t() {
|
||||
local VERSION=$1
|
||||
local HASH=$2
|
||||
@ -233,7 +327,6 @@ install_x2t() {
|
||||
ensure_command_available sha512sum
|
||||
ensure_command_available unzip
|
||||
curl "https://github.com/cryptpad/onlyoffice-x2t-wasm/releases/download/$VERSION/x2t.zip" --location --output x2t.zip
|
||||
# curl "https://github.com/cryptpad/onlyoffice-x2t-wasm/releases/download/v7.3%2B1/x2t.zip" --location --output x2t.zip
|
||||
echo "$HASH x2t.zip" >x2t.zip.sha512
|
||||
if ! sha512sum --check x2t.zip.sha512; then
|
||||
echo "x2t.zip does not match expected checksum"
|
||||
|
||||
@ -309,3 +309,9 @@ Pinning.getDeletedPads = function (Env, channels, cb) {
|
||||
Pinning.isChannelPinned = function (Env, channel, cb) {
|
||||
return void cb(void 0, true);
|
||||
};
|
||||
|
||||
Pinning.isPremium = function (Env, userKey, cb) {
|
||||
const limit = Env.limits[userKey];
|
||||
return void cb(void 0, !!limit?.plan);
|
||||
//return void cb(void 0, (limit?.plan && limit.plan !== "custom"));
|
||||
};
|
||||
|
||||
@ -18,6 +18,7 @@ const bodyParser = require('body-parser');
|
||||
const BlobStore = require("./storage/blob");
|
||||
const BlockStore = require("./storage/block");
|
||||
const plugins = require("./plugin-manager");
|
||||
const gzipStatic = require('connect-gzip-static');
|
||||
|
||||
const DEFAULT_QUERY_TIMEOUT = 5000;
|
||||
const PID = process.pid;
|
||||
@ -134,6 +135,7 @@ var applyHeaderMap = function (res, map) {
|
||||
|
||||
var EXEMPT = [
|
||||
/^\/common\/onlyoffice\/.*\.html.*/,
|
||||
/^\/common\/onlyoffice\/dist\/.*\/sdkjs\/common\/spell\/spell\/spell.js.*/, // OnlyOffice loads spell.wasm in a way that needs unsave-eval
|
||||
/^\/(sheet|presentation|doc)\/inner\.html.*/,
|
||||
/^\/unsafeiframe\/inner\.html.*$/,
|
||||
];
|
||||
@ -336,7 +338,7 @@ Object.keys(plugins || {}).forEach(name => {
|
||||
// serve custom app content from the customize directory
|
||||
// useful for testing pages customized with opengraph data
|
||||
app.use(Express.static(Path.resolve('./customize/www')));
|
||||
app.use(Express.static(Path.resolve('./www')));
|
||||
app.use(gzipStatic(Path.resolve('./www')));
|
||||
|
||||
var mainPages = Env.mainPages || Default.mainPages();
|
||||
var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$');
|
||||
|
||||
@ -24,6 +24,7 @@ const UNAUTHENTICATED_CALLS = {
|
||||
WRITE_PRIVATE_MESSAGE: Channel.writePrivateMessage,
|
||||
DELETE_MAILBOX_MESSAGE: Channel.deleteMailboxMessage,
|
||||
GET_METADATA: Metadata.getMetadata,
|
||||
IS_PREMIUM: Pinning.isPremium,
|
||||
ADD_FIRST_ADMIN: Admin.addFirstAdmin
|
||||
};
|
||||
|
||||
|
||||
@ -20,7 +20,6 @@ const map = {
|
||||
'./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-messaging.js': './www/common/common-messaging.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',
|
||||
|
||||
51
package-lock.json
generated
51
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "cryptpad",
|
||||
"version": "2025.3.0",
|
||||
"version": "2025.3.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cryptpad",
|
||||
"version": "2025.3.0",
|
||||
"version": "2025.3.1",
|
||||
"license": "AGPL-3.0+",
|
||||
"dependencies": {
|
||||
"@mcrowe/minibloom": "^0.2.0",
|
||||
@ -17,12 +17,13 @@
|
||||
"bootstrap-tokenfield": "^0.12.0",
|
||||
"chainpad": "^5.2.6",
|
||||
"chainpad-crypto": "^0.3.0",
|
||||
"chainpad-listmap": "^1.1.1",
|
||||
"chainpad-netflux": "^1.2.3",
|
||||
"chainpad-listmap": "^1.2.0",
|
||||
"chainpad-netflux": "^1.3.0",
|
||||
"chainpad-server": "^5.2.4",
|
||||
"ckeditor": "npm:ckeditor4@~4.22.1",
|
||||
"codemirror": "^5.19.0",
|
||||
"components-font-awesome": "^4.6.3",
|
||||
"connect-gzip-static": "^4.2.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"croppie": "^2.5.0",
|
||||
"dragula": "3.7.2",
|
||||
@ -1913,11 +1914,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/chainpad-listmap": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/chainpad-listmap/-/chainpad-listmap-1.1.1.tgz",
|
||||
"integrity": "sha512-SXb3iJqWyp2wOMa9FT4kqOuR1KgjMPcNCzLlYignu1GdxMI67ZDGTLDucmht4CD8yuYzUWzWmJgs1vs/acH+rw==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/chainpad-listmap/-/chainpad-listmap-1.2.0.tgz",
|
||||
"integrity": "sha512-PI7iwk/yKzEb1iaVmLKvMJYXjCjvIgUWzI07LeDGVvZPScnYbEzMso9YMQyxzjqrp8cyQxsL7sA8c/hZAz01cw==",
|
||||
"license": "AGPL-3.0+",
|
||||
"dependencies": {
|
||||
"chainpad-netflux": "^1.2.0",
|
||||
"chainpad-netflux": "^1.3.0",
|
||||
"json.sortify": "~2.1.0"
|
||||
}
|
||||
},
|
||||
@ -1930,12 +1932,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/chainpad-netflux": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/chainpad-netflux/-/chainpad-netflux-1.2.3.tgz",
|
||||
"integrity": "sha512-bXLUSxAsCu4kRnGorW+7fVofiCWSNGRkB+uFJ4kUV/zDJ3RJYU4J3+DZsL4rCTkLkAjgKt+YQCoYciCKLlTwqg==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/chainpad-netflux/-/chainpad-netflux-1.3.0.tgz",
|
||||
"integrity": "sha512-oc47vLt78a/Fp7acQJvLNuX1JRrH9oOWAbzo+yOO1asCWDuI4oesPY81wHzVySKpo58GGfNpZN7CSTI8EZjYrQ==",
|
||||
"license": "LGPL-2.1",
|
||||
"dependencies": {
|
||||
"netflux-websocket": "^1.2.0"
|
||||
"netflux-websocket": "^1.2.0",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"tweetnacl-util": "^0.15.1"
|
||||
}
|
||||
},
|
||||
"node_modules/chainpad-server": {
|
||||
@ -2063,6 +2067,22 @@
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"node_modules/connect-gzip-static": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/connect-gzip-static/-/connect-gzip-static-4.2.1.tgz",
|
||||
"integrity": "sha512-6qC1NYUEZU6mftg2gPspcoPjgBzADm+fB1fPXu3RxBVe59P/Q9HQGQEHnuDaGRDJUoJwl5NX2OgoaaZp7RdpfQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "~2||~3||~4",
|
||||
"mime-types": "~2",
|
||||
"parseurl": "~1",
|
||||
"send": "~0 || ~1",
|
||||
"serve-static": "~1 || ~2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
@ -3270,9 +3290,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/http-proxy-middleware": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz",
|
||||
"integrity": "sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz",
|
||||
"integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/http-proxy": "^1.17.15",
|
||||
"debug": "^4.3.6",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cryptpad",
|
||||
"description": "a collaborative office suite that is end-to-end encrypted and open-source",
|
||||
"version": "2025.3.0",
|
||||
"version": "2025.3.1",
|
||||
"license": "AGPL-3.0+",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -20,12 +20,13 @@
|
||||
"bootstrap-tokenfield": "^0.12.0",
|
||||
"chainpad": "^5.2.6",
|
||||
"chainpad-crypto": "^0.3.0",
|
||||
"chainpad-listmap": "^1.1.1",
|
||||
"chainpad-netflux": "^1.2.3",
|
||||
"chainpad-listmap": "^1.2.0",
|
||||
"chainpad-netflux": "^1.3.0",
|
||||
"chainpad-server": "^5.2.4",
|
||||
"ckeditor": "npm:ckeditor4@~4.22.1",
|
||||
"codemirror": "^5.19.0",
|
||||
"components-font-awesome": "^4.6.3",
|
||||
"connect-gzip-static": "^4.2.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"croppie": "^2.5.0",
|
||||
"dragula": "3.7.2",
|
||||
|
||||
@ -4,8 +4,10 @@
|
||||
|
||||
(() => {
|
||||
const factory = () => {
|
||||
const version = 8;
|
||||
return {
|
||||
currentVersion: 'v7'
|
||||
currentVersionNumber: version,
|
||||
currentVersion: 'v' + version
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ const factory = (AppConfig = {}, ApiConfig = {},
|
||||
setCustomize({AppConfig,ApiConfig});
|
||||
}
|
||||
|
||||
const Types = { setCustomize };
|
||||
const Types = { OO_APPS, setCustomize };
|
||||
|
||||
Types.__defineGetter__("availableTypes", function () {
|
||||
if (ApiConfig.appsToDisable) {
|
||||
|
||||
@ -3,18 +3,21 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
(() => {
|
||||
const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
|
||||
const factory = (Sortify, UserObject, ProxyManager,
|
||||
Migrate, Hash, Util, Constants, Feedback,
|
||||
Realtime, Messaging, Pinpad, Rpc, Merge, Cache,
|
||||
SF, AccountTS, DriveTS, Cursor,
|
||||
Support, Integration, OnlyOffice,
|
||||
Mailbox, Profile, Team, Messenger, History,
|
||||
Calendar, Block, NetConfig, AppConfig = {},
|
||||
Calendar, BadgeTS, Block, NetConfig,
|
||||
Crypto, ChainPad, CpNetflux, Listmap,
|
||||
Netflux, nThen) => {
|
||||
|
||||
let ApiConfig = {};
|
||||
let AppConfig = {};
|
||||
const Account = AccountTS.Account;
|
||||
const Drive = DriveTS.Drive;
|
||||
const Badge = BadgeTS.Badge;
|
||||
const window = globalThis;
|
||||
globalThis.nacl = globalThis.nacl || Crypto.Nacl;
|
||||
|
||||
@ -659,11 +662,15 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
|
||||
color: Store.getUserColor(),
|
||||
notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']),
|
||||
curvePublic: proxy.curvePublic,
|
||||
edPublic: proxy.edPublic,
|
||||
netfluxId: store?.network?.webChannels?.[0]?.myID,
|
||||
badge: Util.find(proxy, ['profile', 'badge'])
|
||||
},
|
||||
// "priv" is not shared with other users but is needed by the apps
|
||||
priv: {
|
||||
clientId: clientId,
|
||||
edPublic: proxy.edPublic,
|
||||
edPrivate: proxy.edPrivate,
|
||||
friends: proxy.friends || {},
|
||||
settings: proxy.settings || NEW_USER_SETTINGS,
|
||||
thumbnails: disableThumbnails === false,
|
||||
@ -2807,6 +2814,7 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
|
||||
loadUniversal(Integration, 'integration', waitFor);
|
||||
loadUniversal(Messenger, 'messenger', waitFor);
|
||||
loadUniversal(History, 'history', waitFor);
|
||||
loadUniversal(Badge, 'badge', waitFor);
|
||||
loadOnlyOffice();
|
||||
if (store) {
|
||||
store.messenger = store.modules['messenger'];
|
||||
@ -2833,7 +2841,7 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
|
||||
|
||||
// Call onCacheReady if the manager is not yet defined
|
||||
if (!manager) {
|
||||
onCacheReady(clientId, waitFor());
|
||||
startCacheModules(clientId, returned, waitFor());
|
||||
manager = store.manager;
|
||||
userObject = store.userObject;
|
||||
}
|
||||
@ -3193,10 +3201,11 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
|
||||
andThen();
|
||||
};
|
||||
|
||||
const startCacheModules = (clientId, returned, cb) => {
|
||||
const startCacheModules = (clientId, returned, _cb) => {
|
||||
const cb = Util.mkAsync(_cb);
|
||||
onCacheReady(clientId, function () {
|
||||
cb(returned);
|
||||
onCacheReadyEvt.fire();
|
||||
cb(returned);
|
||||
});
|
||||
};
|
||||
const startModules = (clientId, returned, cb) => {
|
||||
@ -3231,6 +3240,10 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
|
||||
|
||||
const requires = data.requires;
|
||||
|
||||
// data.noDrive indicates that we can use drive-less
|
||||
// mode. It will be false is we are loading a safe hash
|
||||
const requiresDrive = !data.noDrive;
|
||||
|
||||
// Mark initialized to true: new tabs will have to
|
||||
// wait for the entire worker to be ready
|
||||
initialized = true;
|
||||
@ -3247,7 +3260,7 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
|
||||
|
||||
// Now users will create a drive, but they may not
|
||||
// own an account
|
||||
if (requires === 'pad') {
|
||||
if (requires === 'pad' && !requiresDrive) {
|
||||
// Start with only the pad modules, callback
|
||||
// and then load the account and other modules
|
||||
return void onNoDrive(clientId, function (obj) {
|
||||
@ -3270,7 +3283,7 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
|
||||
onPadRejectedEvt.reg(next);
|
||||
});
|
||||
}
|
||||
if (requires === 'file') {
|
||||
if (requires === 'file' && !requiresDrive) {
|
||||
// Start with only the pad modules, callback
|
||||
// and then load the account and other modules
|
||||
return void onNoDrive(clientId, function (obj) {
|
||||
@ -3455,7 +3468,6 @@ const factory = (ApiConfig = {}, Sortify, UserObject, ProxyManager,
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
// Code from customize can't be laoded directly in the build
|
||||
module.exports = factory(
|
||||
undefined,
|
||||
require('json.sortify'),
|
||||
require('../common/user-object'),
|
||||
require('../common/proxy-manager'),
|
||||
@ -3465,7 +3477,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
|
||||
require('../common/common-constants'),
|
||||
require('../common/common-feedback'),
|
||||
require('../common/common-realtime'),
|
||||
require('../common/common-messaging'),
|
||||
require('./components/messaging'),
|
||||
require('../common/pinpad'),
|
||||
require('../common/rpc'),
|
||||
require('./components/merge-drive'),
|
||||
@ -3483,9 +3495,9 @@ if (typeof(module) !== 'undefined' && module.exports) {
|
||||
require('./modules/messenger'),
|
||||
require('./modules/history'),
|
||||
require('./modules/calendar'),
|
||||
require('./modules/badge'),
|
||||
require('../common/login-block'),
|
||||
require('../common/network-config'),
|
||||
undefined,
|
||||
require('chainpad-crypto'),
|
||||
require('chainpad'),
|
||||
require('chainpad-netflux'),
|
||||
|
||||
@ -985,7 +985,7 @@ const factory = (Messaging, Hash, Util, Crypto, Block) => {
|
||||
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = factory(
|
||||
require('../../common/common-messaging'),
|
||||
require('./messaging'),
|
||||
require('../../common/common-hash'),
|
||||
require('../../common/common-util'),
|
||||
require('chainpad-crypto'),
|
||||
@ -993,7 +993,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
|
||||
);
|
||||
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
|
||||
define([
|
||||
'/common/common-messaging.js',
|
||||
'/common/outer/messaging.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-util.js',
|
||||
'/components/chainpad-crypto/crypto.js',
|
||||
|
||||
@ -15,6 +15,7 @@ const factory = (Crypto, Hash, Util, Constants, Realtime) => {
|
||||
curvePublic: proxy.curvePublic,
|
||||
notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']),
|
||||
avatar: proxy.profile && proxy.profile.avatar,
|
||||
badge: proxy.profile && proxy.profile.badge,
|
||||
uid: proxy.uid,
|
||||
};
|
||||
if (hash === false) { delete data.channel; }
|
||||
@ -142,10 +143,10 @@ const factory = (Crypto, Hash, Util, Constants, Realtime) => {
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = factory(
|
||||
require('chainpad-crypto'),
|
||||
require('./common-hash'),
|
||||
require('./common-util'),
|
||||
require('./common-constants'),
|
||||
require('./common-realtime')
|
||||
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([
|
||||
@ -512,7 +512,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
|
||||
require('../../common/common-feedback'),
|
||||
require('../../common/common-hash'),
|
||||
require('../../common/common-util'),
|
||||
require('../../common/common-messaging'),
|
||||
require('./messaging'),
|
||||
require('../../common/cryptget'),
|
||||
require('../modules/mailbox'),
|
||||
undefined,
|
||||
@ -525,7 +525,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
|
||||
'/common/common-feedback.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-messaging.js',
|
||||
'/common/outer/messaging.js',
|
||||
'/common/cryptget.js',
|
||||
'/common/outer/mailbox.js',
|
||||
'/customize/messages.js',
|
||||
|
||||
114
src/worker/modules/badge.ts
Normal file
114
src/worker/modules/badge.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import nacl from 'tweetnacl/nacl-fast';
|
||||
import nThen from 'nthen';
|
||||
import { Module, ModuleObject, Callback } from '../types'
|
||||
import * as Util from '../../common/common-util.js';
|
||||
|
||||
export interface BadgeModule<T> extends Module<T> {
|
||||
setCustomize: (data: any) => void
|
||||
}
|
||||
|
||||
let ApiConfig:any = {};
|
||||
|
||||
const checkApi = (ctx, ed, cb) => {
|
||||
const badges = [];
|
||||
const origin:string = ApiConfig?.httpUnsafeOrigin;
|
||||
Util.fetchApi(origin, 'config', true, Config => {
|
||||
const isAdmin = Config?.adminKeys?.includes(ed);
|
||||
if (isAdmin) { badges.push('admin'); }
|
||||
const isModerator = Config?.moderatorKeys?.includes(ed);
|
||||
if (isModerator) { badges.push('moderator'); }
|
||||
cb(badges);
|
||||
});
|
||||
};
|
||||
const checkPremium = (ctx, ed, cb) => {
|
||||
ctx.Store.anonRpcMsg('', {
|
||||
msg: 'IS_PREMIUM',
|
||||
data: ed
|
||||
}, obj => {
|
||||
let premium = Array.isArray(obj) && obj[0];
|
||||
cb(premium ? ['premium'] : []);
|
||||
});
|
||||
};
|
||||
|
||||
const listBadges:Callback = (ctx, data, clientId, cb) => {
|
||||
const badges = [];
|
||||
const origin:string = ApiConfig?.httpUnsafeOrigin;
|
||||
const ed = data.edPublic || ctx.store?.proxy?.edPublic;
|
||||
nThen(waitFor => {
|
||||
checkApi(ctx, ed, waitFor(list => {
|
||||
Array.prototype.push.apply(badges, list);
|
||||
}));
|
||||
}).nThen(waitFor => {
|
||||
checkPremium(ctx, ed, waitFor(list => {
|
||||
Array.prototype.push.apply(badges, list);
|
||||
}));
|
||||
}).nThen(() => {
|
||||
cb(badges);
|
||||
});
|
||||
};
|
||||
|
||||
const allBadges = {
|
||||
'admin': checkApi,
|
||||
'moderator': checkApi,
|
||||
'premium': checkPremium
|
||||
};
|
||||
|
||||
const checkBadge:Callback = (ctx, data, clientId, cb) => {
|
||||
const { badge, ed, sig, nid/*, channel*/ } = data;
|
||||
|
||||
const publicKey = Util.decodeBase64(ed); // Public key uint8
|
||||
const signature = Util.decodeBase64(sig); // badge uint8
|
||||
|
||||
// Check signature
|
||||
const msg = nacl.sign.open(signature, publicKey);
|
||||
if (!msg) { return void cb({verified: false}); }
|
||||
|
||||
// Check channel (replay attack)
|
||||
const msgStr:string = Util.encodeUTF8(msg);
|
||||
const expected:string = nid; // + channel;
|
||||
if (msgStr !== expected) { return void cb({verified: false}); }
|
||||
|
||||
let f = allBadges[badge];
|
||||
if (!f) { return void cb({verified: false, error: 'EINVAL'}); }
|
||||
|
||||
f(ctx, ed, list => {
|
||||
cb({
|
||||
verified: list.includes(badge),
|
||||
badge: badge
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const Badge: BadgeModule<ModuleObject> = {
|
||||
|
||||
init: (config, cb, emit) => {
|
||||
|
||||
const ctx:any = {
|
||||
store: config.store,
|
||||
Store: config.Store,
|
||||
updateMetadata: config.updateMetadata
|
||||
};
|
||||
|
||||
return {
|
||||
removeClient: () => {},
|
||||
execCommand: (clientId, obj, cb) => {
|
||||
console.log('Exex command', clientId, obj);
|
||||
const cmd = obj.cmd;
|
||||
const data = obj.data;
|
||||
if (cmd === 'LIST_BADGES') {
|
||||
return void listBadges(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'CHECK_BADGE') {
|
||||
return void checkBadge(ctx, data, clientId, cb);
|
||||
}
|
||||
cb();
|
||||
},
|
||||
}
|
||||
},
|
||||
setCustomize: data => {
|
||||
ApiConfig = data?.ApiConfig;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export { Badge }
|
||||
@ -679,7 +679,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
|
||||
require('../../common/common-util'),
|
||||
require('../../common/common-hash'),
|
||||
require('../../common/common-realtime'),
|
||||
require('../../common/common-messaging'),
|
||||
require('../components/messaging'),
|
||||
require('../../common/notify'),
|
||||
require('../components/mailbox-handlers'),
|
||||
require('chainpad-netflux'),
|
||||
@ -691,7 +691,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-realtime.js',
|
||||
'/common/common-messaging.js',
|
||||
'/common/outer/messaging.js',
|
||||
'/common/notify.js',
|
||||
'/common/outer/mailbox-handlers.js',
|
||||
'chainpad-netflux',
|
||||
|
||||
@ -1126,7 +1126,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
|
||||
require('../../common/common-hash'),
|
||||
require('../../common/common-util'),
|
||||
require('../../common/common-realtime'),
|
||||
require('../../common/common-messaging'),
|
||||
require('../components/messaging'),
|
||||
require('../../common/common-constants'),
|
||||
undefined,
|
||||
require('../../common/pad-types'),
|
||||
@ -1138,7 +1138,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
|
||||
'/common/common-hash.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-realtime.js',
|
||||
'/common/common-messaging.js',
|
||||
'/common/outer/messaging.js',
|
||||
'/common/common-constants.js',
|
||||
'/customize/messages.js',
|
||||
'/common/pad-types.js',
|
||||
|
||||
@ -99,6 +99,12 @@ const factory = (Util, Hash, Constants, Realtime,
|
||||
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);
|
||||
});
|
||||
};
|
||||
@ -113,6 +119,7 @@ const factory = (Util, Hash, Constants, Realtime,
|
||||
var store = cfg.store;
|
||||
if (!store.loggedIn || !store.proxy.edPublic) { return; }
|
||||
var ctx = {
|
||||
Store: cfg.Store,
|
||||
store: store,
|
||||
pinPads: cfg.pinPads,
|
||||
updateMetadata: cfg.updateMetadata,
|
||||
|
||||
@ -2253,7 +2253,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
|
||||
require('../../common/user-object'),
|
||||
require('../components/sharedfolder'),
|
||||
require('../components/roster'),
|
||||
require('../../common/common-messaging'),
|
||||
require('../components/messaging'),
|
||||
require('../../common/common-feedback'),
|
||||
require('../components/invitation'),
|
||||
require('../../common/cryptget'),
|
||||
@ -2278,7 +2278,7 @@ if (typeof(module) !== 'undefined' && module.exports) {
|
||||
'/common/user-object.js',
|
||||
'/common/outer/sharedfolder.js',
|
||||
'/common/outer/roster.js',
|
||||
'/common/common-messaging.js',
|
||||
'/common/outer/messaging.js',
|
||||
'/common/common-feedback.js',
|
||||
'/common/outer/invitation.js',
|
||||
'/common/cryptget.js',
|
||||
|
||||
@ -12,7 +12,7 @@ export interface TestModuleObject extends ModuleObject {
|
||||
|
||||
const TestModule: Module<TestModuleObject> = {
|
||||
|
||||
init: (config, cb) => {
|
||||
init: (config, cb, emit) => {
|
||||
let myTest = {};
|
||||
|
||||
cb();
|
||||
|
||||
@ -15,7 +15,6 @@ import * as Util from '../common/common-util.js';
|
||||
import * as Hash from '../common/common-hash.js';
|
||||
import * as Feedback from '../common/common-feedback.js';
|
||||
import * as Realtime from '../common/common-realtime.js';
|
||||
import * as Messaging from '../common/common-messaging.js';
|
||||
import * as Constants from '../common/common-constants.js';
|
||||
import * as Credential from '../common/common-credential.js';
|
||||
import * as ProxyManager from '../common/proxy-manager.js';
|
||||
@ -36,6 +35,7 @@ import * as AsyncConnector from './core/async-connector.js';
|
||||
|
||||
// Components
|
||||
import * as Migrate from './components/migrate-user-object.js';
|
||||
import * as Messaging from './components/messaging.js';
|
||||
|
||||
// Modules
|
||||
import * as Mailbox from './modules/mailbox.js';
|
||||
@ -48,6 +48,7 @@ import * as Team from './modules/team.js';
|
||||
import * as Messenger from './modules/messenger.js';
|
||||
import * as History from './modules/history.js';
|
||||
import * as Calendar from './modules/calendar.js';
|
||||
import { Badge } from './modules/badge';
|
||||
|
||||
declare var WorkerGlobalScope: any
|
||||
declare var SharedWorkerGlobalScope: any
|
||||
@ -76,7 +77,8 @@ let start = (cfg: StoreConfig):void => {
|
||||
Calendar,
|
||||
Store,
|
||||
Account,
|
||||
Mailbox
|
||||
Mailbox,
|
||||
Badge
|
||||
].forEach(dep => {
|
||||
if (typeof(dep.setCustomize) === "function") {
|
||||
dep.setCustomize(cfg);
|
||||
|
||||
@ -7,7 +7,11 @@
|
||||
export type Callback = (...args: any[]) => void
|
||||
|
||||
export type ModuleConfig = {
|
||||
emit: Function
|
||||
store: any,
|
||||
Store: any,
|
||||
updateMetadata: Callback,
|
||||
pinPads: Callback,
|
||||
unpinPads: Callback
|
||||
}
|
||||
|
||||
export type CommandData = {
|
||||
@ -21,7 +25,7 @@ export interface ModuleObject {
|
||||
}
|
||||
|
||||
export interface Module<T> {
|
||||
init: (config: ModuleConfig, cb: Callback) => T
|
||||
init: (config: ModuleConfig, cb: Callback, emit: Callback) => T
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,161 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
(() => {
|
||||
const factory = (Crypto, Hash, Util, Constants, Realtime) => {
|
||||
var Msg = {};
|
||||
|
||||
var createData = Msg.createData = function (proxy, hash) {
|
||||
var data = {
|
||||
channel: hash || Hash.createChannelId(),
|
||||
displayName: proxy['cryptpad.username'],
|
||||
profile: proxy.profile && proxy.profile.view,
|
||||
edPublic: proxy.edPublic,
|
||||
curvePublic: proxy.curvePublic,
|
||||
notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']),
|
||||
avatar: proxy.profile && proxy.profile.avatar,
|
||||
uid: proxy.uid,
|
||||
};
|
||||
if (hash === false) { delete data.channel; }
|
||||
return data;
|
||||
};
|
||||
|
||||
var getFriend = Msg.getFriend = function (proxy, pubkey) {
|
||||
if (!pubkey) { return; }
|
||||
if (pubkey === proxy.curvePublic) {
|
||||
var data = createData(proxy);
|
||||
delete data.channel;
|
||||
return data;
|
||||
}
|
||||
return proxy.friends ? proxy.friends[pubkey] : undefined;
|
||||
};
|
||||
|
||||
var getFriendList = Msg.getFriendList = function (proxy) {
|
||||
if (!proxy.friends) { proxy.friends = {}; }
|
||||
return proxy.friends;
|
||||
};
|
||||
|
||||
var eachFriend = function (friends, cb) {
|
||||
Object.keys(friends).forEach(function (id) {
|
||||
if (id === 'me') { return; }
|
||||
cb(friends[id], id, friends);
|
||||
});
|
||||
};
|
||||
|
||||
Msg.getFriendChannelsList = function (proxy) {
|
||||
var list = [];
|
||||
eachFriend(proxy.friends, function (friend) {
|
||||
list.push(friend.channel);
|
||||
});
|
||||
return list;
|
||||
};
|
||||
|
||||
Msg.declineFriendRequest = function (store, data, cb) {
|
||||
store.mailbox.sendTo('DECLINE_FRIEND_REQUEST', {}, {
|
||||
channel: data.notifications,
|
||||
curvePublic: data.curvePublic
|
||||
}, function (obj) {
|
||||
cb(obj);
|
||||
});
|
||||
};
|
||||
Msg.acceptFriendRequest = function (store, data, cb) {
|
||||
var friend = getFriend(store.proxy, data.curvePublic) || {};
|
||||
var myData = createData(store.proxy, friend.channel || data.channel);
|
||||
store.mailbox.sendTo('ACCEPT_FRIEND_REQUEST', { user: myData }, {
|
||||
channel: data.notifications,
|
||||
curvePublic: data.curvePublic
|
||||
}, function (obj) {
|
||||
cb(obj);
|
||||
});
|
||||
};
|
||||
Msg.addToFriendList = function (cfg, data, cb) {
|
||||
var proxy = cfg.proxy;
|
||||
var friends = getFriendList(proxy);
|
||||
var pubKey = data.curvePublic; // todo validata data
|
||||
|
||||
if (pubKey === proxy.curvePublic) { return void cb("E_MYKEY"); }
|
||||
|
||||
friends[pubKey] = data;
|
||||
|
||||
Realtime.whenRealtimeSyncs(cfg.realtime, function () {
|
||||
cb();
|
||||
cfg.pinPads([data.channel], function (res) {
|
||||
if (res.error) { console.error(res.error); }
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Msg.updateMyData = function (store, curve) {
|
||||
var myData = createData(store.proxy, false);
|
||||
if (store.proxy.friends) {
|
||||
store.proxy.friends.me = Util.clone(myData);
|
||||
delete store.proxy.friends.me.channel;
|
||||
}
|
||||
if (store.modules['team']) {
|
||||
store.modules['team'].updateMyData(myData);
|
||||
}
|
||||
var todo = function (friend) {
|
||||
if (!friend || !friend.notifications) { return; }
|
||||
delete friend.user;
|
||||
myData.channel = friend.channel;
|
||||
store.mailbox.sendTo('UPDATE_DATA', myData, {
|
||||
channel: friend.notifications,
|
||||
curvePublic: friend.curvePublic
|
||||
}, function (obj) {
|
||||
if (obj && obj.error) { console.error(obj); }
|
||||
});
|
||||
};
|
||||
if (curve) {
|
||||
var friend = getFriend(store.proxy, curve);
|
||||
return void todo(friend);
|
||||
}
|
||||
eachFriend(store.proxy.friends || {}, todo);
|
||||
};
|
||||
|
||||
Msg.removeFriend = function (store, curvePublic, cb) {
|
||||
var proxy = store.proxy;
|
||||
var friend = proxy.friends[curvePublic];
|
||||
if (!friend) { return void cb({error: 'ENOENT'}); }
|
||||
if (!friend.notifications) { return void cb({error: 'EINVAL'}); }
|
||||
|
||||
store.mailbox.sendTo('UNFRIEND', {
|
||||
curvePublic: proxy.curvePublic
|
||||
}, {
|
||||
channel: friend.notifications,
|
||||
curvePublic: friend.curvePublic
|
||||
}, function (obj) {
|
||||
if (obj && obj.error) {
|
||||
return void cb(obj);
|
||||
}
|
||||
store.messenger.onFriendRemoved(curvePublic, friend.channel);
|
||||
delete proxy.friends[curvePublic];
|
||||
Realtime.whenRealtimeSyncs(store.realtime, function () {
|
||||
cb(obj);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return Msg;
|
||||
};
|
||||
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = factory(
|
||||
require('chainpad-crypto'),
|
||||
require('./common-hash'),
|
||||
require('./common-util'),
|
||||
require('./common-constants'),
|
||||
require('./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
|
||||
}
|
||||
})();
|
||||
@ -9,7 +9,6 @@ define([
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/outer/cache-store.js',
|
||||
'/common/common-messaging.js',
|
||||
'/common/common-constants.js',
|
||||
'/common/common-feedback.js',
|
||||
'/common/visible.js',
|
||||
@ -20,13 +19,14 @@ define([
|
||||
'/common/common-credential.js',
|
||||
'/customize/login.js',
|
||||
'/common/store-interface.js',
|
||||
'/common/pad-types.js',
|
||||
|
||||
'/customize/application_config.js',
|
||||
'/components/nthen/index.js',
|
||||
'/components/tweetnacl/nacl-fast.min.js'
|
||||
], function (Config, Broadcast, Messages, Util, Hash, Cache,
|
||||
Messaging, Constants, Feedback, Visible, UserObject, LocalStore, Channel, Block,
|
||||
Cred, Login, Store, AppConfig, nThen) {
|
||||
Constants, Feedback, Visible, UserObject, LocalStore, Channel, Block,
|
||||
Cred, Login, Store, Types, AppConfig, nThen) {
|
||||
|
||||
/* This file exposes functionality which is specific to Cryptpad, but not to
|
||||
any particular pad type. This includes functions for committing metadata
|
||||
@ -1606,7 +1606,7 @@ define([
|
||||
oldChannel = oldSecret.channel;
|
||||
var src = fileHost + Hash.getBlobPathFromHex(oldChannel);
|
||||
var key = oldSecret.keys && oldSecret.keys.cryptKey;
|
||||
var cryptKey = window.Util.encodeBase64(key);
|
||||
var cryptKey = Util.encodeBase64(key);
|
||||
|
||||
var mt = document.createElement('media-tag');
|
||||
mt.setAttribute('src', src);
|
||||
@ -1690,7 +1690,7 @@ define([
|
||||
if (!href) { return void cb({ error: 'EINVAL_HREF' }); }
|
||||
var parsed = Hash.parsePadUrl(href);
|
||||
if (!parsed.hash) { return void cb({ error: 'EINVAL_HREF' }); }
|
||||
if (parsed.type !== 'sheet') { return void cb({ error: 'EINVAL_TYPE' }); }
|
||||
if (!Types.OO_APPS.includes(parsed.type)) { return void cb({ error: 'EINVAL_TYPE' }); }
|
||||
|
||||
var warning = false;
|
||||
var newHash, newRoHref;
|
||||
@ -2468,9 +2468,9 @@ define([
|
||||
};
|
||||
|
||||
common.getAnonymousKeys = function (formSeed, channel) {
|
||||
var array = window.Util.decodeBase64(formSeed + channel);
|
||||
var array = Util.decodeBase64(formSeed + channel);
|
||||
var hash = window.nacl.hash(array);
|
||||
var secretKey = window.Util.encodeBase64(hash.subarray(32));
|
||||
var secretKey = Util.encodeBase64(hash.subarray(32));
|
||||
var publicKey = Hash.getCurvePublicFromPrivate(secretKey);
|
||||
return {
|
||||
curvePrivate: secretKey,
|
||||
|
||||
@ -8,11 +8,12 @@ define([
|
||||
'/common/common-hash.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-ui-elements.js',
|
||||
'/common/pad-types.js',
|
||||
'/common/inner/common-modal.js',
|
||||
'/common/hyperscript.js',
|
||||
'/customize/messages.js',
|
||||
'/components/nthen/index.js',
|
||||
], function ($, Util, Hash, UI, UIElements, Modal, h,
|
||||
], function ($, Util, Hash, UI, UIElements, Types, Modal, h,
|
||||
Messages, nThen) {
|
||||
var Access = {};
|
||||
|
||||
@ -908,7 +909,7 @@ define([
|
||||
// We should know it because the pad is stored, but it's better to check...
|
||||
//if (!data.noEditPassword && !opts.noEditPassword && owned && data.href) {
|
||||
if (!data.noEditPassword && !opts.noEditPassword && owned && data.href && parsed.type !== "form") { // TODO password change in forms block responses (validation & decryption)
|
||||
var isOO = parsed.type === 'sheet';
|
||||
var isOO = Types?.OO_APPS?.includes(parsed.type);
|
||||
var isFile = parsed.hashData.type === 'file';
|
||||
var isSharedFolder = parsed.type === 'drive';
|
||||
|
||||
|
||||
32
www/common/inner/badges.js
Normal file
32
www/common/inner/badges.js
Normal file
@ -0,0 +1,32 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
define([
|
||||
'/common/hyperscript.js',
|
||||
'/customize/messages.js',
|
||||
], function (h, Messages) {
|
||||
var Badges = {};
|
||||
|
||||
const badges = {
|
||||
admin: 'fa-star',
|
||||
moderator: 'fa-life-ring',
|
||||
premium: 'fa-check-circle',
|
||||
error: 'fa-exclamation-circle'
|
||||
};
|
||||
|
||||
Badges.render = id => {
|
||||
let icon = badges[id];
|
||||
if (!icon) { return; }
|
||||
let cls = icon.indexOf('cptools') === 0 ? 'cptools '+icon : 'fa '+icon;
|
||||
if (id === 'error') { cls += ' cp-badge-error'; }
|
||||
return h('i', {
|
||||
'class': `cp-badge ${cls}`,
|
||||
'title': Messages[`badges_${id}`] || id
|
||||
});
|
||||
};
|
||||
|
||||
return Badges;
|
||||
});
|
||||
|
||||
|
||||
@ -186,7 +186,7 @@ define([
|
||||
if (avatars[href]) {
|
||||
var nodes = $.parseHTML(avatars[href]);
|
||||
var $el = $(nodes[0]);
|
||||
$container.append($el);
|
||||
$container.empty().append($el);
|
||||
return void cb($el);
|
||||
}
|
||||
|
||||
@ -222,7 +222,7 @@ define([
|
||||
if (typeof data !== "number") { return void displayDefault(); }
|
||||
if (Util.bytesToMegabytes(data) > 0.5) { return void displayDefault(); }
|
||||
var mt = UI.mediaTag(src, cryptKey);
|
||||
var $img = $(mt).appendTo($container);
|
||||
var $img = $(mt).appendTo($container.empty());
|
||||
MT.displayMediatagImage(common, $img, function (err, $image) {
|
||||
if (err) { return void console.error(err); }
|
||||
centerImage($img, $image);
|
||||
|
||||
@ -4,8 +4,10 @@
|
||||
|
||||
(() => {
|
||||
const factory = () => {
|
||||
const version = 8;
|
||||
return {
|
||||
currentVersion: 'v7'
|
||||
currentVersionNumber: version,
|
||||
currentVersion: 'v' + version
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -27,7 +27,7 @@ const factory = (AppConfig = {}, ApiConfig = {},
|
||||
setCustomize({AppConfig,ApiConfig});
|
||||
}
|
||||
|
||||
const Types = { setCustomize };
|
||||
const Types = { OO_APPS, setCustomize };
|
||||
|
||||
Types.__defineGetter__("availableTypes", function () {
|
||||
if (ApiConfig.appsToDisable) {
|
||||
|
||||
@ -751,6 +751,7 @@ define([
|
||||
if (!parsed.type) { throw new Error(); }
|
||||
var defaultTitle = Utils.UserObject.getDefaultName(parsed);
|
||||
var edPublic, curvePublic, notifications, isTemplate;
|
||||
var edPrivate;
|
||||
var settings = {};
|
||||
var isSafe = ['debug', 'profile', 'drive', 'teams', 'calendar', 'file'].indexOf(currentPad.app) !== -1;
|
||||
var isOO = ['sheet', 'doc', 'presentation'].indexOf(parsed.type) !== -1;
|
||||
@ -760,6 +761,7 @@ define([
|
||||
if (isDeleted) {
|
||||
Utils.Cache.clearChannel(secret.channel);
|
||||
}
|
||||
let signature;
|
||||
|
||||
var updateMeta = function () {
|
||||
//console.log('EV_METADATA_UPDATE');
|
||||
@ -775,6 +777,9 @@ define([
|
||||
curvePublic = metaObj.user.curvePublic;
|
||||
notifications = metaObj.user.notifications;
|
||||
settings = metaObj.priv.settings;
|
||||
|
||||
edPrivate = metaObj.priv.edPrivate;
|
||||
delete metaObj.priv.edPrivate; // don't send to inner
|
||||
}));
|
||||
if (typeof(isTemplate) === "undefined") {
|
||||
Cryptpad.isTemplate(currentPad.href, waitFor(function (err, t) {
|
||||
@ -856,6 +861,18 @@ define([
|
||||
}
|
||||
}
|
||||
|
||||
if (metaObj?.user?.edPublic) {
|
||||
let str = metaObj?.user?.netfluxId;// + secret.channel;
|
||||
if (!signature && str) {
|
||||
let myIDu8 = Utils.Util.decodeUTF8(str);
|
||||
let k = Utils.Util.decodeBase64(edPrivate);
|
||||
let nacl = Utils.Crypto.Nacl;
|
||||
let s = nacl.sign(myIDu8, k);
|
||||
signature = Utils.Util.encodeBase64(s);
|
||||
}
|
||||
metaObj.user.signature = signature;
|
||||
}
|
||||
|
||||
// Early access
|
||||
var priv = metaObj.priv;
|
||||
var _plan = typeof(priv.plan) === "undefined" ? Utils.LocalStore.getPremium() : priv.plan;
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
(() => {
|
||||
const factory = function () {
|
||||
let USE_MIN = true;
|
||||
if (localStorage.CryptPad_dev === "1") { USE_MIN = false; }
|
||||
if (typeof(localStorage) !== "undefined" &&
|
||||
localStorage.CryptPad_dev === "1") { USE_MIN = false; }
|
||||
|
||||
let path = '/common/worker.bundle.js?';
|
||||
if (USE_MIN) { path = '/common/worker.bundle.min.js?'; }
|
||||
|
||||
@ -13,12 +13,13 @@ define([
|
||||
'/common/common-util.js',
|
||||
'/common/common-feedback.js',
|
||||
'/common/inner/common-mediatag.js',
|
||||
'/common/inner/badges.js',
|
||||
'/common/hyperscript.js',
|
||||
'/common/messenger-ui.js',
|
||||
'/customize/messages.js',
|
||||
'/customize/pages.js',
|
||||
'/common/pad-types.js',
|
||||
], function ($, Config, ApiConfig, Broadcast, UIElements, UI, Hash, Util, Feedback, MT, h,
|
||||
], function ($, Config, ApiConfig, Broadcast, UIElements, UI, Hash, Util, Feedback, MT, Badges, h,
|
||||
MessengerUI, Messages, Pages, PadTypes) {
|
||||
var Common;
|
||||
|
||||
@ -193,16 +194,14 @@ MessengerUI, Messages, Pages, PadTypes) {
|
||||
// Display only one time each user (if he is connected in multiple tabs)
|
||||
var uids = [];
|
||||
Object.keys(userData).forEach(function(user) {
|
||||
//if (user !== userNetfluxId) {
|
||||
var data = userData[user] || {};
|
||||
var userId = data.uid;
|
||||
if (!userId) { return; }
|
||||
//data.netfluxId = user;
|
||||
if (uids.indexOf(userId) === -1) {// && (!myUid || userId !== myUid)) {
|
||||
if (user !== data.netfluxId) { return; }
|
||||
if (uids.indexOf(userId) === -1) {
|
||||
uids.push(userId);
|
||||
list.push(data);
|
||||
} else { i++; }
|
||||
//}
|
||||
});
|
||||
return {
|
||||
list: list,
|
||||
@ -223,6 +222,7 @@ MessengerUI, Messages, Pages, PadTypes) {
|
||||
});
|
||||
};
|
||||
var showColors = false;
|
||||
const validatedBadges = {};
|
||||
var updateUserList = function (toolbar, config, forceOffline) {
|
||||
if (!config.displayed || config.displayed.indexOf('userlist') === -1) { return; }
|
||||
if (toolbar.isAlone) { return; }
|
||||
@ -427,6 +427,38 @@ MessengerUI, Messages, Pages, PadTypes) {
|
||||
$span.append($rightCol);
|
||||
}, data.uid);
|
||||
$span.data('uid', data.uid);
|
||||
if (false && data.badge && data.edPublic) { // XXX 2025.6
|
||||
const addBadge = (badge) => {
|
||||
let i = Badges.render(badge);
|
||||
if (!i) { return; }
|
||||
$rightCol.append(h('div.cp-userlist-badge', i));
|
||||
};
|
||||
const key = data.signature + '-' + data.badge;
|
||||
const v = validatedBadges[key];
|
||||
if (typeof (v) === "string") {
|
||||
addBadge(v);
|
||||
} else if (v === false) {
|
||||
addBadge('error');
|
||||
} else {
|
||||
let ev = validatedBadges[key] ||= Util.mkEvent(true);
|
||||
ev.reg(badge => { addBadge(badge); });
|
||||
toolbar.badges.execCommand('CHECK_BADGE', {
|
||||
badge: data.badge,
|
||||
channel: priv.channel,
|
||||
ed: data.edPublic,
|
||||
sig: data.signature,
|
||||
nid: data.netfluxId
|
||||
}, res => {
|
||||
if (!res?.verified) {
|
||||
validatedBadges[key] = false;
|
||||
return void addBadge('error');
|
||||
}
|
||||
validatedBadges[key] = res.badge;
|
||||
ev.fire(res.badge);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
$editUsersList.append($span);
|
||||
});
|
||||
|
||||
@ -1401,6 +1433,8 @@ MessengerUI, Messages, Pages, PadTypes) {
|
||||
toolbar.connected = false;
|
||||
toolbar.firstConnection = true;
|
||||
|
||||
toolbar.badges = Common.makeUniversal('badge');
|
||||
|
||||
if (Array.isArray(cfg.displayed) && cfg.displayed.includes('pad')) {
|
||||
cfg.addFileMenu = true;
|
||||
}
|
||||
|
||||
2
www/common/worker.bundle.min.js
vendored
2
www/common/worker.bundle.min.js
vendored
File diff suppressed because one or more lines are too long
@ -7,6 +7,7 @@
|
||||
@import (reference) '../../customize/src/less2/include/framework.less';
|
||||
@import (reference) '../../customize/src/less2/include/sidebar-layout.less';
|
||||
@import (reference) '../../customize/src/less2/include/variables.less';
|
||||
@import (reference) '../../customize/src/less2/include/badges.less';
|
||||
|
||||
&.cp-app-profile {
|
||||
|
||||
@ -26,6 +27,29 @@
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.cp-profile-badges {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
& > span {
|
||||
margin-right: 1em;
|
||||
}
|
||||
.cp-profile-badges-list {
|
||||
.badges_main(40px);
|
||||
i {
|
||||
cursor: pointer;
|
||||
&:focus-visible {
|
||||
outline: @variables_focus_style;
|
||||
}
|
||||
&:not(:last-child) {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
&.cp-selected {
|
||||
border-color: @cryptpad_color_brand;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#cp-app-profile-header {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
||||
@ -17,6 +17,7 @@ define([
|
||||
'/common/common-realtime.js',
|
||||
'/common/clipboard.js',
|
||||
'/common/inner/common-mediatag.js',
|
||||
'/common/inner/badges.js',
|
||||
'/common/hyperscript.js',
|
||||
'/customize/messages.js',
|
||||
'/customize/application_config.js',
|
||||
@ -48,6 +49,7 @@ define([
|
||||
Realtime,
|
||||
Clipboard,
|
||||
MT,
|
||||
Badges,
|
||||
h,
|
||||
Messages,
|
||||
AppConfig,
|
||||
@ -92,6 +94,7 @@ define([
|
||||
var LINK_ID = "cp-app-profile-link";
|
||||
var AVATAR_ID = "cp-app-profile-avatar";
|
||||
var DESCRIPTION_ID = "cp-app-profile-description";
|
||||
var BADGES_ID = "cp-app-profile-badges";
|
||||
var CREATE_ID = "cp-app-profile-create";
|
||||
var HEADER_ID = "cp-app-profile-header";
|
||||
var HEADER_RIGHT_ID = "cp-app-profile-rightside";
|
||||
@ -375,7 +378,7 @@ define([
|
||||
var displayAvatar = function (val) {
|
||||
var sframeChan = common.getSframeChannel();
|
||||
var $span = APP.$avatar;
|
||||
$span.html('');
|
||||
$span.empty();
|
||||
if (!val) {
|
||||
$('<img>', {
|
||||
src: '/customize/images/avatar.png',
|
||||
@ -447,6 +450,48 @@ define([
|
||||
displayAvatar(data.avatar);
|
||||
};
|
||||
|
||||
const addBadges = $container => {
|
||||
var $block = $('<div>', {id: BADGES_ID, class:'cp-sidebarlayout-element'}).appendTo($container);
|
||||
APP.$badges = $(h('span')).appendTo($block);
|
||||
};
|
||||
const refreshBadges = (obj) => {
|
||||
if (!APP.$badges) { return; }
|
||||
const metadataMgr = APP.common.getMetadataMgr();
|
||||
const privateData = metadataMgr.getPrivateData();
|
||||
let args = {};
|
||||
if (!privateData.isOwnProfile) { args.edPublic = obj.edPublic; }
|
||||
APP.badge.execCommand('LIST_BADGES', args, data => {
|
||||
APP.$badges.empty();
|
||||
let spinner;
|
||||
APP.$badges.toggle(!!data.length);
|
||||
let all = data.map(str => {
|
||||
const i = Badges.render(str);
|
||||
const $i = $(i).attr('tabindex', 0);
|
||||
if (APP.readOnly) { return i; }
|
||||
const selected = obj?.badge === str;
|
||||
if (selected) { $i.addClass('cp-selected'); }
|
||||
Util.onClickEnter($i, () => {
|
||||
let value = selected ? '' : str;
|
||||
spinner.spin();
|
||||
APP.module.execCommand('SET', {
|
||||
key: 'badge',
|
||||
value
|
||||
}, function (data) {
|
||||
spinner.hide();
|
||||
APP.updateValues(data);
|
||||
});
|
||||
});
|
||||
return i;
|
||||
});
|
||||
let content = h('div.cp-profile-badges', [
|
||||
h('span', Messages.profile_badges),
|
||||
h('div.cp-profile-badges-list', all)
|
||||
]);
|
||||
APP.$badges.append(content);
|
||||
spinner = UI.makeSpinner(APP.$badges.find('> div'));
|
||||
});
|
||||
};
|
||||
|
||||
var addDescription = function ($container) {
|
||||
var $block = $('<div>', {id: DESCRIPTION_ID, class: PROFILE_SECTION}).appendTo($container);
|
||||
|
||||
@ -462,7 +507,7 @@ define([
|
||||
}, [
|
||||
h('i.fa.fa-pencil', {'aria-hidden': 'true' }),
|
||||
h('span#cp-profile-add-description-button', Messages.profile_addDescription)
|
||||
]);
|
||||
]);
|
||||
APP.$descriptionEdit = $(button);
|
||||
var save = h('button.btn.btn-primary', Messages.settings_save);
|
||||
var text = h('textarea');
|
||||
@ -609,6 +654,7 @@ define([
|
||||
addLink($rightside);
|
||||
addFriendRequest($rightside);
|
||||
addMuteButton($rightside);
|
||||
//addBadges($rightside); // XXX 2025.6
|
||||
addPublicKey($rightside);
|
||||
addCopyData($rightside);
|
||||
addViewButton($rightside);
|
||||
@ -627,6 +673,7 @@ define([
|
||||
refreshName(data);
|
||||
refreshLink(data);
|
||||
refreshDescription(data);
|
||||
refreshBadges(data);
|
||||
refreshFriendRequest(data);
|
||||
refreshMute(data);
|
||||
setPublicKeyButton(data);
|
||||
@ -700,6 +747,9 @@ define([
|
||||
return;
|
||||
}
|
||||
|
||||
APP.badge = common.makeUniversal('badge', {
|
||||
onEvent: onEvent
|
||||
});
|
||||
if (privateData.isOwnProfile) {
|
||||
|
||||
APP.module = common.makeUniversal('profile', {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user