Compare commits

..

20 commits

Author SHA1 Message Date
yflory
bc6765d9f6 Load test with sodium 2024-06-24 11:47:51 +02:00
yflory
43cfc926bb Merge branch 'monitoring' into load 2024-06-24 11:22:38 +02:00
yflory
1d639a7653 update package.json and package-lock.json 2024-06-12 15:32:26 +02:00
yflory
1310979994 lint compliance 2024-06-12 15:01:57 +02:00
yflory
2de0a0d4b9 Merge branch 'stats' into monitoring 2024-06-12 14:59:55 +02:00
yflory
8c6acf9578 Guard against null channels 2024-05-23 17:41:26 +02:00
yflory
73891d819e Add new monitoring data 2024-05-23 16:01:38 +02:00
yflory
83dd6fa16c Add response time to load testing results 2024-05-23 11:57:46 +02:00
yflory
a8e03cef9d Fix offset with load test data creation 2024-05-22 18:16:12 +02:00
yflory
c17312c3fb More config options for load test 2024-05-22 16:28:37 +02:00
yflory
cf153b8474 Load testing script 2024-05-22 13:54:50 +02:00
yflory
eb1249b6cd Fix cpu usage monitoring 2024-05-17 11:22:32 +02:00
yflory
332edec162 Merge branch 'main' into monitoring 2024-05-17 10:45:37 +02:00
yflory
bde6ef8044 Add CPU monitoring 2024-05-17 10:30:39 +02:00
yflory
9b8e487b70 Merge branch 'main' into monitoring 2024-05-16 17:19:40 +02:00
yflory
99f1cf650e Fix package.json 2024-05-16 17:18:00 +02:00
yflory
9c22a25ba2 Merge branch 'main' into monitoring 2024-05-16 17:15:09 +02:00
yflory
fff4e7581e Add heap memory data to the monitoring tools 2023-08-30 16:13:09 +02:00
yflory
49af0533b5 lint compliance 2023-08-22 18:36:48 +02:00
yflory
4f2a48a72e Add memory monitoring tools 2023-08-22 18:09:22 +02:00
131 changed files with 9662 additions and 8019 deletions

View file

@ -7,7 +7,6 @@ www/components/
www/bower_components/
www/common/onlyoffice/dist
www/common/onlyoffice/x2t
onlyoffice-dist/
www/scratch
www/accounts
@ -16,8 +15,6 @@ www/accounts
www/worker
www/todo
#lib/plugins/
www/common/hyperscript.js
www/pad/wysiwygarea-plugin.js

View file

@ -49,10 +49,13 @@ module.exports = {
// TODO remove these exceptions from the eslint defaults
'no-irregular-whitespace': ['off'],
'no-unused-vars': ['warn'],
'no-self-assign': ['off'],
'no-empty': ['off'],
'no-useless-escape': ['off'],
'no-redeclare': ['off'],
'no-extra-boolean-cast': ['off'],
'no-global-assign': ['off'],
'no-prototype-builtins': ['off'],
}
};

View file

@ -89,9 +89,6 @@ body:
label: Version
description: What version of CryptPad are you running?
options:
- 2024.6.1
- 2024.6.0
- 2024.3.1
- 2024.3.0
- 5.7.0
- 5.6.0
@ -103,6 +100,8 @@ body:
- 5.2.0
- 5.1.0
- 5.0.0
- 4.14.1
- 4.14.0
- Other
validations:
required: true

60
.lesshintrc Normal file
View file

@ -0,0 +1,60 @@
{
"fileExtensions": [".less"],
// These rules are almost certainly crap and will not catch bugs (Caleb)
"newlineAfterBlock": { "enabled": false }, // not just a newline but an entire empty line after each block
"spaceAroundOperator": { "enabled": false }, // disallow calc(10px+10px);
"hexLength": { "enabled": false }, // require long hex color codes or require short where possible
"hexNotation": { "enabled": false }, // require hex lowercase
"propertyOrdering": { "enabled": false }, // require attributes to be in alphabetical order D:
"stringQuotes": { "enabled": false }, // force quoting of strings with ' or " (silly)
"importPath": { "enabled": false }, // require imports to not have .less, ridiculous
"qualifyingElement": { "enabled": false }, // disallow div.xxx and require .xxx
"decimalZero": { "enabled": false }, // disallow .5em
"borderZero": { "enabled": false }, // disallow border: none;
"selectorNaming": { "enabled": false }, // this would be crap because classes are what they are.
"zeroUnit": { "enabled": false },
"singleLinePerProperty": { "enabled": false },
"_singleLinePerProperty": {
"enabled": true,
"allowSingleLineRules": true
},
"spaceAroundComma": { "enabled": false },
"importantRule": { "enabled": false },
"universalSelector": { "enabled": false },
"idSelector": { "enabled": false },
"singleLinePerSelector": { "enabled": false },
"spaceBetweenParens": { "enabled": false },
"maxCharPerLine": { "enabled": false }, // using lesshint flags can cause long lines
"comment": { "enabled": false }, // ban multi-line comments ?
// These rules should be discussed, if they're crap then they should be moved up.
"colorVariables": { "enabled": false }, // require all colors to be stored as variables first...
"variableValue": { "enabled": false }, // any attribute types which should always be variables ? color?
"spaceBeforeBrace": { "enabled": true },//{ "enabled": true, "style": "one_space" },
// Turn everything else on
"spaceAfterPropertyColon": { "enabled": true },
"finalNewline": { "enabled": true }, // require an empty line at the end of the file (enabled for now)
"attributeQuotes": { "enabled": true },
"depthLevel": {
"depth": 1 // TODO(cjd) This is obviously not triggering, even with 1
},
"duplicateProperty": { "enabled": false },
"emptyRule": { "enabled": true },
"hexValidation": { "enabled": true }, // disallow actual garbage color hex codes (e.g. #ab)
"propertyUnits": {
"valid": ["rem", "vw", "em", "px", "ch"], // These units are allowed for all properties
"invalid": ["pt"], // The 'pt' unit is not allowed under any circumstances
"properties": {
//"line-height": [] // No units are allowed for line-height
}
},
"spaceAfterPropertyName": { "enabled": true, "style": "no_space" },
"spaceAfterPropertyValue": { "enabled": true, "style": "no_space" },
"spaceAroundBang": { "enabled": true, "style": "before" },
"trailingSemicolon": { "enabled": true },
"trailingWhitespace": { "enabled": true },
"urlFormat": { "enabled": true, "style": "relative" },
"urlQuotes": { "enabled": true }
}

View file

@ -25,7 +25,7 @@ Files: .jshintrc
Copyright: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
License: AGPL-3.0-or-later
Files: .stylelintrc.js
Files: .lesshintrc
Copyright: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
License: AGPL-3.0-or-later

View file

@ -1,43 +0,0 @@
module.exports = {
"extends": "stylelint-config-standard-less",
"rules": {
"no-descending-specificity": null,
"length-zero-no-unit": null,
"no-duplicate-selectors": null,
"declaration-block-no-duplicate-properties": null,
"comment-empty-line-before": null,
"rule-empty-line-before": null,
"declaration-empty-line-before": null,
"at-rule-empty-line-before": null,
"custom-property-empty-line-before": null,
"font-family-name-quotes": null,
"font-family-no-missing-generic-family-keyword": null,
"declaration-block-no-redundant-longhand-properties": null,
"shorthand-property-no-redundant-values": null,
"declaration-block-no-shorthand-property-overrides": null,
"comment-whitespace-inside": null,
"property-no-vendor-prefix": null,
"selector-no-vendor-prefix": null,
"function-name-case": null,
"selector-class-pattern": null,
"custom-property-pattern": null,
"selector-id-pattern": null,
"selector-pseudo-element-colon-notation": null,
"media-feature-range-notation": null,
"selector-not-notation": null,
"color-function-notation": null,
"alpha-value-notation": null,
"number-max-precision": null,
"at-rule-no-unknown": null, // FIXME
"less/no-duplicate-variables": null,
"less/color-no-invalid-hex": null
}
};

View file

@ -4,168 +4,6 @@ SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and cont
SPDX-License-Identifier: AGPL-3.0-or-later
-->
# 2024.6.1
## Goals
This is a bugfix release to address issues that were reported by Cryptpad.fr users. We took the opportunity to update the translations with some new languages contributed by the community.
## Improvements
- Translations update from CryptPad Translations [#1575](https://github.com/cryptpad/cryptpad/pull/1575)
- Added: Español cubano, اَلْعَرَبِيَّةُ Arabic, Svenska
- Removed some languages without enough coverage
- Greek (16%)
- Romanian (36%)
## Fixes
- Calendar events sometimes dont appear when created [#1551](https://github.com/cryptpad/cryptpad/issues/1551) fixed by [072dba2](https://github.com/cryptpad/cryptpad/commit/072dba254e3c2be32cd6b261d84510909deb713f)
- Revert the new method of counting registered users in the admin panel [4544be6](https://github.com/cryptpad/cryptpad/commit/4544be6b4d9fa7291b19cb366f7dd492dfe07340)
- Fix broken OnlyOffice Document [#1572](https://github.com/cryptpad/cryptpad/issues/1572)
- Fix printing in Code documents [#1557](https://github.com/cryptpad/cryptpad/pull/1557) [#1478](https://github.com/cryptpad/cryptpad/pull/1478)
- Fix OnlyOffice undefined functions [#1550](https://github.com/cryptpad/cryptpad/pull/1550)
- Fix keyboard operation of confirm modals [#1576](https://github.com/cryptpad/cryptpad/issues/1576)
- Pressing Enter on the "Cancel" button triggered the "OK" button instead
## Upgrade notes
If you are upgrading from a version older than `2024.6.0` please read the upgrade notes of all versions between yours and `2024.6.1` to avoid configuration issues.
To upgrade:
1. Stop your server
2. Get the latest code with git
```bash
git fetch origin --tags
git checkout 2024.6.1
npm ci
npm run install:components
./install-onlyoffice.sh
```
3. Restart your server
4. Review your instance's checkup page to ensure that you are passing all tests
# 2024.6.0
## Goals
This release introduces a new onboarding flow to guide administrators through the setup of an instance. After creating the first admin account, 3 screens guide them through the customization of the instance title, logo, accent color, available applications, and security features. We also include a new language, some fixes on accessibility, deployment, OnlyOffice and more.
## Features
- Onboarding screens & app configuration [#1513](https://github.com/cryptpad/cryptpad/pull/1513)
- Bahasa Indonesia is a new available language [fe78b6a](https://github.com/cryptpad/cryptpad/commit/fe78b6ab1dc76ce9eb8d5361c309db8e92117fa8)
- Thanks to our [Weblate](https://weblate.cryptpad.org) contributors who made that happen!
## Improvements
- Improve plugins API [#1511](https://github.com/cryptpad/cryptpad/pull/1511)
## Fixes
- Accessibility
- Kanban accessibility fixes [#1488](https://github.com/cryptpad/cryptpad/pull/1488)
- Fix modal focus [#1483](https://github.com/cryptpad/cryptpad/pull/1483)
- Fix locked focus on text editors [#1473](https://github.com/cryptpad/cryptpad/pull/1473)
- Frames must have accessible names [#1123](https://github.com/cryptpad/cryptpad/issues/1123)
- Focus trapped on notifications menu [#1430](https://github.com/cryptpad/cryptpad/issues/1430)
- Add page language [#1125](https://github.com/cryptpad/cryptpad/issues/1125)
- Can not open folder via "▼" -> "Open". [#1089](https://github.com/cryptpad/cryptpad/issues/1089)
- Images must have alternate text [#1449](https://github.com/cryptpad/cryptpad/issues/1449)
- OnlyOffice
- Remove x2t from the CryptPad repo [#1454](https://github.com/cryptpad/cryptpad/issues/1454)
- Other OnlyOffice users are shown as "Guest" [#1446](https://github.com/cryptpad/cryptpad/issues/1446)
- Document PDF exports are empty when remote embedding is disabled [#1472](https://github.com/cryptpad/cryptpad/issues/1472)
- Sometimes images of a presentation are not exported to PDF [#1500](https://github.com/cryptpad/cryptpad/issues/1500)
- Automatic upgrade of an OnlyOffice document fails sometimes [#1534](https://github.com/cryptpad/cryptpad/issues/1534)
- Import/Export is broken [#1532](https://github.com/cryptpad/cryptpad/issues/1532)
- Print is broken [#1533](https://github.com/cryptpad/cryptpad/issues/1533)
- Deployment / Hosting
- Upgrade CryptPad version in docker-compose.yml [#1529](https://github.com/cryptpad/cryptpad/pull/1529)
- Optimize HTTPd example config [#1498](https://github.com/cryptpad/cryptpad/pull/1498)
- Tidy up HTTPd config [#1527](https://github.com/cryptpad/cryptpad/pull/1527)
- Clarify sandbox `httpSafePort` use in `config.example.js` [#1518](https://github.com/cryptpad/cryptpad/pull/1518)
- Switch to new `http2` Nginx option [#1516](https://github.com/cryptpad/cryptpad/pull/1516)
- Server fixes and aggregated stats [#1509](https://github.com/cryptpad/cryptpad/pull/1509)
- Create the block folder at boot [#911](https://github.com/cryptpad/cryptpad/pull/911)
- Remove obsolete `version` from `docker-compose.yml` [2e716eb](https://github.com/cryptpad/cryptpad/commit/2e716eb4e39fb835f95a1fa1a340e01142d11b1c)
- Other
- Unsharp the corners when hovering the dismiss button on notification drop-down menu [#1466](https://github.com/cryptpad/cryptpad/pull/1466)
- Fix contextual menu `Open` on anonymous drive [#1464](https://github.com/cryptpad/cryptpad/pull/1464)
- Tighten eslint rules [#1456](https://github.com/cryptpad/cryptpad/pull/1456)
- Remove mediatag subfolder [#844](https://github.com/cryptpad/cryptpad/pull/844)
## Dependencies
- Upgrade CryptPad version in `package.json`, update description as well [#1530](https://github.com/cryptpad/cryptpad/pull/1530)
- Remove deprecated and unmaintained `lesshint` library and use `stylelint` and its `stylelint-less` plugin instead
## Upgrade notes
If you are upgrading from a version older than `2024.3.1` please read the upgrade notes of all versions between yours and `2024.3.1` to avoid configuration issues.
To upgrade:
1. Stop your server
2. Get the latest code with git
```bash
git fetch origin --tags
git checkout 2024.6.0
npm ci
npm run install:components
./install-onlyoffice.sh
```
3. Restart your server
4. Review your instance's checkup page to ensure that you are passing all tests
# 2024.3.1
## Goals
This minor release introduces a workaround to recover corrupted OnlyOffice documents alongside other fixes, with some improvements.
## Fixes
- Workarounds for missing OnlyOffice methods: [#1492](https://github.com/cryptpad/cryptpad/pull/1492)
- Fix HTTP server issue with NodeJs >= v20.13.0: [4483b84](https://github.com/cryptpad/cryptpad/commit/4483b848ff2ba23176cb05dacf073f3e0581ba7b)
- Fix merge issues with `package.json`: [7f45d59](https://github.com/cryptpad/cryptpad/commit/7f45d598cbf230002863bbd84004c38252b97031)
- Fix Docker ports: [#1485](https://github.com/cryptpad/cryptpad/pull/1485)
- Change _inactive_ to _archived_ in `config.example.js` file: [#1474](https://github.com/cryptpad/cryptpad/pull/1474)
## Improvements
- New translations from our Weblate contributors: [#1491](https://github.com/cryptpad/cryptpad/pull/1491)
- Polish
- French
- Bulgarian
- Hungarian
- Basque
- Optimize default Nginx example config: [#1486](https://github.com/cryptpad/cryptpad/pull/1486)
- Add `.mjs` support in HTTPd example config: [#1471](https://github.com/cryptpad/cryptpad/pull/1471)
## Upgrade notes
If you are upgrading from a version older than `2024.3.0` please read the upgrade notes of all versions between yours and `2024.3.1` to avoid configuration issues.
To upgrade:
1. Stop your server
2. Get the latest code with git
```bash
git fetch origin --tags
git checkout 2024.3.1
npm ci
npm run install:components
./install-onlyoffice.sh
```
3. Restart your server
4. Review your instance's checkup page to ensure that you are passing all tests
# 2024.3.0
## Goals

View file

@ -22,15 +22,14 @@ RUN npm install --production \
# Create actual CryptPad image
FROM node:lts-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && apt install -y git rdfind && rm -rf /var/lib/apt/lists/*
# Create user and group for CryptPad so it does not run as root
RUN groupadd cryptpad -g 4001
RUN useradd cryptpad -u 4001 -g 4001 -d /cryptpad
# Install curl for healthcheck
# Install git, rdfind and unzip for install-onlyoffice.sh
RUN apt-get update && apt-get install --no-install-recommends -y \
curl ca-certificates git rdfind unzip && \
# Install wget for healthcheck
RUN apt-get update && apt-get install --no-install-recommends -y wget && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
@ -57,10 +56,10 @@ VOLUME /cryptpad/datastore
ENTRYPOINT ["/bin/bash", "/cryptpad/docker-entrypoint.sh"]
# Healthcheck
HEALTHCHECK --interval=1m CMD curl -f http://localhost:3000/ || exit 1
HEALTHCHECK --interval=1m CMD wget --no-verbose --tries=1 http://localhost:3000/ -q -O /dev/null || exit 1
# Ports
EXPOSE 3000 3003
EXPOSE 3000 3001 3003
# Run cryptpad on startup
CMD ["npm", "start"]

View file

@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* globals module */
/* DISCLAIMER:
There are two recommended methods of running a CryptPad instance:
@ -87,10 +89,9 @@ module.exports = {
*/
//httpPort: 3000,
/* httpSafePort purpose is to emulate another origin for the sandbox when
* you don't have two domains at hand (i.e. when httpSafeOrigin not defined).
* It is meant to be used only in case where you are working on a local
* development instance. The default value is your httpPort + 1.
/* httpSafePort allows you to specify an alternative port from which
* the node process should serve sandboxed assets. The default value is
* that of your httpPort + 1. You probably don't need to change this.
*
*/
//httpSafePort: 3001,
@ -191,7 +192,7 @@ module.exports = {
* This archived data still takes up space and so you'll probably still want to
* remove these files after a brief period.
*
* cryptpad/scripts/evict-archived.js is intended to be run daily
* cryptpad/scripts/evict-inactive.js is intended to be run daily
* from a crontab or similar scheduling service.
*
* The intent with this feature is to provide a safety net in case of accidental

View file

@ -9,9 +9,7 @@
* If you want to check all the configurable values, you can open the internal configuration file
but you should not change it directly (/common/application_config_internal.js)
*/
define(['/common/application_config_internal.js'], function (AppConfig) {
// Example: If you want to remove the survey link in the menu:
// AppConfig.surveyURL = "";

View file

@ -22,13 +22,11 @@ define([
'/common/outer/login-block.js',
'/common/common-hash.js',
'/common/outer/http-command.js',
'/api/config',
'/components/tweetnacl/nacl-fast.min.js',
'/components/scrypt-async/scrypt-async.min.js', // better load speed
], function ($, Listmap, Crypto, Util, NetConfig, Login, Cred, ChainPad, Realtime, Constants, UI,
Feedback, h, LocalStore, Messages, nThen, Block, Hash, ServerCommand,
ApiConfig) {
Feedback, h, LocalStore, Messages, nThen, Block, Hash, ServerCommand) {
var Exports = {
Cred: Cred,
Block: Block,
@ -220,11 +218,6 @@ define([
proxy.edPublic = result.edPublic;
}
if (ApiConfig && Array.isArray(ApiConfig.adminKeys) &&
ApiConfig.adminKeys.includes(proxy.edPublic)) {
localStorage.CP_admin = "1";
}
setTimeout(function () {
Realtime.whenRealtimeSyncs(result.realtime, function () {
proceed(result);

View file

@ -5,18 +5,15 @@
(function () {
// add your module to this map so it gets used
var map = {
'ar': 'اَلْعَرَبِيَّةُ',
'ca': 'Català',
'cs': 'Čeština',
'de': 'Deutsch',
//'el': 'Ελληνικά',
'el': 'Ελληνικά',
'es': 'Español',
'es_CU': 'Español cubano',
'eu': 'Euskara',
'fi': 'Suomi',
'fr': 'Français',
//'hi': 'हिन्दी',
'id': 'Bahasa Indonesia',
'it': 'Italiano',
'ja': '日本語',
'nb': 'Norwegian Bokmål',
@ -24,9 +21,9 @@ var map = {
'pl': 'Polski',
'pt-br': 'Português do Brasil',
'pt-pt': 'Português do Portugal',
//'ro': 'Română',
'ro': 'Română',
'ru': 'Русский',
'sv': 'Svenska',
//'sv': 'Svenska',
//'te': 'తెలుగు',
'uk': 'Українська',
'zh': '中文(簡體)',
@ -48,7 +45,6 @@ var getLanguage = Messages._getLanguage = function () {
(map[l.split('_')[0]] ? l.split('_')[0] : 'en'));
};
var language = getLanguage();
window.cryptpadLanguage = language;
// Translations files were migrated from requirejs modules to json.
// To avoid asking every administrator to update their customized translation files,
@ -92,9 +88,6 @@ define(req, function(AppConfig, Default, Language) {
});
}
let html = typeof(document) !== "undefined" && document.documentElement;
if (html) { html.setAttribute('lang', language); }
var extend = function (a, b) {
for (var k in b) {
if (Array.isArray(b[k])) {
@ -136,6 +129,7 @@ define(req, function(AppConfig, Default, Language) {
}
};
return Messages;
});

View file

@ -15,8 +15,7 @@ define([
'/common/outer/local-store.js',
'/customize/pages.js',
'/common/pad-types.js',
'/common/extensions.js'
], function ($, Config, h, Hash, Constants, Util, TextFit, Msg, AppConfig, LocalStore, Pages, PadTypes, Extensions) {
], function ($, Config, h, Hash, Constants, Util, TextFit, Msg, AppConfig, LocalStore, Pages, PadTypes) {
var urlArgs = Config.requireConf.urlArgs;
var checkEarlyAccess = function (x) {
@ -165,19 +164,9 @@ define([
};
let popup = h('div.cp-extensions-popups');
let utils = { h, Util, Hash };
Extensions.getExtensions('HOMEPAGE_POPUP').forEach(ext => {
if (typeof(ext.check) === "function" && !ext.check()) { return; }
ext.getContent(utils, content => {
$(popup).append(h('div.cp-extensions-popup', content));
});
});
return [
h('div#cp-main', [
Pages.infopageTopbar(),
popup,
notice,
h('div.container.cp-container', [
h('div.row.cp-home-hero', [

View file

@ -18,6 +18,8 @@ define([
return;
}
Msg.install_token = "Install token";
document.title = Msg.install_header;
var frame = function (content) {
@ -25,7 +27,8 @@ define([
h('div#cp-main', [
//Pages.infopageTopbar(),
h('div.container.cp-container', [
h('div.row.cp-page-title', h('h1', Msg.install_header)),
//h('div.row.cp-page-title', h('h1', Msg.install_header)),
h('div.row.cp-page-title', h('h1', Msg.register_header)),
].concat(content)),
Pages.infopageFooter(),
]),
@ -36,12 +39,17 @@ define([
h('div.row.cp-register-det', [
h('div#data.hidden.col-md-6', [
h('h2', Msg.register_notes_title),
Pages.setHTML(h('div.cp-register-notes'), Msg.install_notes)
//Pages.setHTML(h('div.cp-register-notes'), Msg.install_notes)
Pages.setHTML(h('div.cp-register-notes'), Msg.register_notes)
]),
h('div.cp-reg-form.col-md-6', [
h('div#userForm.form-group.hidden', [
h('div.cp-register-instance', [
Msg.install_instance,
Msg._getKey('register_instance', [ Pages.Instance.name ]),
/*h('br'),
h('a', {
href: '/features.html'
}, Msg.register_whyRegister)*/
]),
h('input.form-control#installtoken', {
type: 'text',
@ -67,7 +75,7 @@ define([
/*h('div.checkbox-container', [
UI.createCheckbox('import-recent', Msg.register_importRecent, true)
]),*/
h('button#register', Msg.install_launch)
h('button#register', Msg.login_register)
])
]),
])

View file

@ -1,98 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@import (reference) "./colortheme-all.less";
@import (reference) "./forms.less";
@import (reference) './icon-colors.less';
.admin_main() {
--LessLoader_require: LessLoader_currentFile();
}
& {
// Instance accent color presets
@palette-colors:
#0087FF,
#de0064,
#8c52bc,
#3d7672;
div.cp-palette-container {
.cp-palette-nocolor {
display: none;
}
.instance-colors(@palette-colors; @index) when (@index > 0) {
// loop through the @colors
.instance-colors(@palette-colors; (@index - 1));
@color: extract(@palette-colors, @index);
// make a numbered class selector for each color
.cp-palette-color@{index} {
background-color: @color !important;
color: contrast(@color, @cryptpad_color_grey_800, @cryptpad_color_grey_200) !important;
}
}
.instance-colors(@palette-colors; length(@palette-colors));
}
.cp-admin-customize-apps-grid, .cp-admin-customize-options-grid {
display: grid;
gap: 0.5rem;
}
.cp-admin-customize-apps-grid {
grid-template-columns: 1fr 1fr 1fr;
.cp-appblock {
padding: 0.5rem;
border-radius: @variables_radius;
font-size: 1.2em;
display: flex;
flex-direction: row;
align-items: center;
gap: 0.75rem;
.iconColors_main();
&:hover {
cursor: pointer;
}
i.cp-icon {
font-size: 2.8rem;
}
.cp-app-name {
flex-grow: 1;
}
}
.cp-inactive-app {
background-color: transparent;
opacity: 0.75;
.cp-on-enabled {
visibility: hidden;
}
}
.cp-active-app {
background-color: fade(@cryptpad_text_col, 10%);
.cp-on-enabled {
visibility: visible;
}
}
}
.cp-admin-customize-options-grid {
grid-template-columns: 1fr 1fr;
.cp-optionblock {
padding: 0.5rem;
border-radius: @variables_radius;
background-color: fade(@cryptpad_text_col, 10%);
align-self: start;
.cp-checkmark-label {
font-weight: bold;
}
.cp-option-hint {
margin-left: 30px;
display: inline-block;
}
}
}
}

View file

@ -198,12 +198,6 @@
text-decoration: none;
}
}
.cp-usergrid-user, textarea, a, .fa-times {
outline: none;
&:focus {
outline: @cryptpad_color_brand solid 2px;
}
}
}
.cp-alertify-type-container {
overflow: visible !important;
@ -243,10 +237,6 @@
}
}
}
outline: none;
&:focus {
outline: @cryptpad_color_brand solid 2px;
}
}
span.alertify-tabs-active {
background-color: @cp_alertify-fg !important;
@ -273,10 +263,6 @@
input {
.tools_placeholder-color();
outline: none;
&:focus-visible {
outline: @cryptpad_color_brand solid 2px;
}
}
span.cp-password-container {

View file

@ -128,9 +128,9 @@
position: absolute;
box-sizing: border-box;
}
outline: none;
&:focus {
outline: @cryptpad_color_brand solid 2px;
box-shadow: 0px 0px 5px @cp_checkmark-back1;
outline: none;
}
}
@ -216,9 +216,9 @@
height: @checkmark-dim1;
height: var(--checkmark-dim1);
}
outline: none;
&:focus {
outline: @cryptpad_color_brand solid 2px;
box-shadow: 0px 0px 5px @cp_checkmark-back1;
outline: none;
}
}

View file

@ -119,34 +119,10 @@
}
}
// The following palette container is just for the UI components
// The specific colors you want to show have to be defined in your app
// using the classes .cp-palette-nocolor .cp-palette-color1 .cp-palette-color2 etc.
div.cp-palette-container {
display: flex;
justify-content: space-between;
.cp-palette-color {
display: inline-block;
border-radius: 50%;
height: 30px;
width: 30px;
text-align: center;
line-height: 30px;
color: @cp_kanban-fg;
border: 1px solid fade(@cp_kanban-fg, 40%);
&.fa-check { // tick on selected color
color: @cryptpad_text_col;
}
outline: none;
&:focus {
outline: @cryptpad_color_brand solid 2px;
}
}
}
button.btn {
background-color: @cp_buttons-cancel;
box-sizing: border-box;
outline: 0;
align-items: center;
padding: 0 6px;
line-height: 36px;
@ -256,9 +232,11 @@
}
outline: none;
&:focus {
outline: @cryptpad_color_brand solid 2px;
//border: 1px dotted @alertify-base;
box-shadow: 0px 0px 5px @cp_buttons-primary !important;
outline: none;
}
&::-moz-focus-inner {
border: 0;

View file

@ -36,6 +36,9 @@
}
.cp-reminder, .cp-avatar {
cursor: pointer;
&:hover {
background-color: @cp_dropdown-bg-hover;
}
}
.cp-avatar {
.avatar_main(30px);
@ -58,6 +61,9 @@
}
&.cp-clickable {
cursor: pointer;
&:hover {
background-color: @cp_dropdown-bg-hover;
}
}
}
.cp-notification-dismiss {
@ -67,6 +73,9 @@
align-items: center;
justify-content: center;
cursor: pointer;
&:hover {
background-color: @cp_dropdown-bg-hover;
}
}
}
}

View file

@ -80,10 +80,6 @@
text-overflow: ellipsis;
padding-left: 4px;
vertical-align: middle;
outline: none;
&:focus {
outline: @cryptpad_color_brand solid 2px;
}
}
.close {
opacity: 1;

View file

@ -77,12 +77,6 @@
&:hover {
background-color: contrast(@cp_toolbar-bg, darken(@cp_toolbar-bg, 5%), lighten(@cp_toolbar-bg, 5%));
}
&:focus {
outline: @cryptpad_color_brand solid 2px;
}
}
button:nth-of-type(1) {
margin-left: 0.3rem;
}
}
@ -780,7 +774,7 @@
padding: 10px;
color: @toolbar-bg-color;
color: var(--toolbar-bg-color);
border-radius: @variables_radius;
border-radius: 5px;
span {
font-size: 45px;

View file

@ -115,8 +115,7 @@
}
}
.fa-times {
border-radius: @variables_radius;
margin-left: 5px;
padding-left: 5px;
cursor: pointer;
height: 100%;
line-height: 25px;

View file

@ -297,29 +297,6 @@
}
}
.cp-extensions-popups {
width: 100%;
.cp-extensions-popup {
background-color: @cp_alertify-bg;
border-radius: @infopages-radius-L;
padding: 10px;
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.2);
width: 400px;
max-width: 100%;
color: @cryptpad_text_col;
margin-left: 40px;
}
}
@media (max-width: 700px) {
.cp-extensions-popups {
max-width: 90%;
.cp-extensions-popup {
margin-left: 0;
}
}
}
@media (min-width: 576px) and (max-width: 767px) {
.container {
padding-left: 0;

View file

@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@import (reference) "../include/infopages.less";
@import (reference) "../include/colortheme-all.less";
@import (reference) "../include/alertify.less";
@import (reference) "../include/checkmark.less";
@import (reference) "../include/forms.less";
&.cp-page-load {
.infopages_main();
.forms_main();
.alertify_main();
.checkmark_main(20px);
.form {
max-width: 400px;
padding: 20px;
}
.alertify {
// workaround for alertify making empty p
p:empty {
display: none;
}
nav {
display: flex;
align-items: center;
justify-content: flex-end;
}
@media screen and (max-width: 600px) {
nav .btn-danger {
line-height: inherit;
}
}
}
}

View file

@ -8,8 +8,3 @@
margin: 3cm;
size: A4 portrait;
}
@media print {
body {
background: white !important;
}
}

View file

@ -1,18 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/*
* You can override the translation text using this file.
* The recommended method is to make a copy of this file (/customize.dist/translations/messages.{LANG}.js)
in a 'customize' directory (/customize/translations/messages.{LANG}.js).
* If you want to check all the existing translation keys, you can open the internal language file
but you should not change it directly (/common/translations/messages.{LANG}.js)
*/
define(['/common/translations/messages.ar.js'], function (Messages) {
// Replace the existing keys in your copied file here:
// Messages.button_newpad = "New Rich Text Document";
return Messages;
});

View file

@ -1,18 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/*
* You can override the translation text using this file.
* The recommended method is to make a copy of this file (/customize.dist/translations/messages.{LANG}.js)
in a 'customize' directory (/customize/translations/messages.{LANG}.js).
* If you want to check all the existing translation keys, you can open the internal language file
but you should not change it directly (/common/translations/messages.{LANG}.js)
*/
define(['/common/translations/messages.es_CU.js'], function (Messages) {
// Replace the existing keys in your copied file here:
// Messages.button_newpad = "New Rich Text Document";
return Messages;
});

View file

@ -1,18 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/*
* You can override the translation text using this file.
* The recommended method is to make a copy of this file (/customize.dist/translations/messages.{LANG}.js)
in a 'customize' directory (/customize/translations/messages.{LANG}.js).
* If you want to check all the existing translation keys, you can open the internal language file
but you should not change it directly (/common/translations/messages.{LANG}.js)
*/
define(['/common/translations/messages.id.js'], function (Messages) {
// Replace the existing keys in your copied file here:
// Messages.button_newpad = "New Rich Text Document";
return Messages;
});

View file

@ -3,9 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
---
version: '3.8'
services:
cryptpad:
image: "cryptpad/cryptpad:version-2024.6.1"
image: "cryptpad/cryptpad:version-2024.3.0"
hostname: cryptpad
environment:
@ -28,6 +30,7 @@ services:
ports:
- "3000:3000"
- "3001:3001"
- "3003:3003"
ulimits:

View file

@ -31,7 +31,7 @@ fi
cd $CPAD_HOME
if [ "$CPAD_INSTALL_ONLYOFFICE" == "yes" ]; then
./install-onlyoffice.sh --accept-license --trust-repository
./install-onlyoffice.sh --accept-license
fi
npm run build

View file

@ -9,13 +9,11 @@
# in production and require professional support please contact sales@cryptpad.fr
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
listen 443 ssl http2;
listen [::]:443 ssl http2;
# Let's Encrypt webroot
include letsencrypt-webroot;
# Include mime.types to be able to support .mjs files (see "types" below)
include mime.types;
@ -84,9 +82,6 @@ server {
# replace with the IP address of your resolver
resolver 8.8.8.8 8.8.4.4 1.1.1.1 1.0.0.1 9.9.9.9 149.112.112.112 208.67.222.222 208.67.220.220;
# OnlyOffice fonts may be loaded from both domains
if ($uri ~ ^\/common\/onlyoffice\/.*\/fonts\/.*$) { set $allowed_origins "*"; }
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options nosniff;
add_header Access-Control-Allow-Origin "${allowed_origins}";
@ -234,20 +229,6 @@ server {
add_header Cross-Origin-Embedder-Policy require-corp;
}
location ~ ^/extensions.js {
proxy_pass http://localhost:3000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# These settings prevent both NGINX and the API server
# from setting the same headers and creating duplicates
proxy_hide_header Cross-Origin-Resource-Policy;
add_header Cross-Origin-Resource-Policy cross-origin;
proxy_hide_header Cross-Origin-Embedder-Policy;
add_header Cross-Origin-Embedder-Policy require-corp;
}
# Requests for blobs and blocks are now proxied to the API server
# This simplifies NGINX path configuration in the event they are being hosted in a non-standard location
# or with odd unexpected permissions. Serving blobs in this manner also means that it will be possible to

View file

@ -33,14 +33,8 @@ SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
Protocols h2 http/1.1
AddType application/javascript mjs
<Location "/">
LimitRequestBody 157286400
ProxyPass http://localhost:3000/ upgrade=websocket
ProxyPassReverse http://localhost:3000/
</Location>
<Location "/cryptpad_websocket">
ProxyPass http://localhost:3003/ upgrade=websocket
ProxyPassReverse http://localhost:3003/
</Location>
AddType application/javascript mjs
ProxyPass / http://localhost:3000/ upgrade=websocket
ProxyPassReverse / http://localhost:3000/
</VirtualHost>

View file

@ -9,9 +9,8 @@
# in production and require professional support please contact sales@cryptpad.fr
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
listen 443 ssl http2;
listen [::]:443 ssl http2;
# Let's Encrypt webroot
include letsencrypt-webroot;
@ -71,15 +70,4 @@ server {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
}
location ^~ /cryptpad_websocket {
proxy_pass http://localhost:3003;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
}
}

View file

@ -6,7 +6,7 @@
set -euo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
CONF_DIR=$SCRIPT_DIR/onlyoffice-conf
BUILDS_DIR=$CONF_DIR/onlyoffice-builds.git
OO_DIR=$SCRIPT_DIR/www/common/onlyoffice/dist
@ -14,7 +14,7 @@ PROPS_FILE="$CONF_DIR"/onlyoffice.properties
declare -A PROPS
main() {
main () {
mkdir -p "$CONF_DIR"
load_props
@ -33,46 +33,41 @@ main() {
install_version v4 6ebc6938
install_version v5 88a356f0
install_version v6 abd8a309
install_version v7 e1267803
install_x2t v7.3+1 ab0c05b0e4c81071acea83f0c6a8e75f5870c360ec4abc4af09105dd9b52264af9711ec0b7020e87095193ac9b6e20305e446f2321a541f743626a598e5318c1
install_version v7 9d8b914a
rm -rf "$BUILDS_DIR"
if command -v rdfind &>/dev/null; then
if command -v rdfind &> /dev/null; then
rdfind -makehardlinks true -makeresultsfile false $OO_DIR/v*
fi
}
load_props() {
load_props () {
if [ -e "$PROPS_FILE" ]; then
while IFS='=' read -r key value; do
PROPS["$key"]="$value"
done <"$PROPS_FILE"
done < "$PROPS_FILE"
fi
}
set_prop() {
set_prop () {
PROPS["$1"]="$2"
for i in "${!PROPS[@]}"; do
echo "$i=${PROPS[$i]}"
done >"$PROPS_FILE"
done > "$PROPS_FILE"
}
parse_arguments() {
parse_arguments () {
while [[ $# -gt 0 ]]; do
case $1 in
-h | --help)
-h|--help)
show_help
shift
;;
-a | --accept-license)
-a|--accept-license)
ACCEPT_LICENSE="1"
shift
;;
-t | --trust-repository)
TRUST_REPOSITORY="1"
shift
;;
*)
show_help
shift
@ -81,26 +76,23 @@ parse_arguments() {
done
}
ask_for_license() {
ask_for_license () {
if [ ${ACCEPT_LICENSE+x} ] || [ "${PROPS[agree_license]:-no}" == yes ]; then
return
fi
ensure_command_available curl
(
echo -e "Please review the license of OnlyOffice:\n\n"
curl https://raw.githubusercontent.com/ONLYOFFICE/web-apps/master/LICENSE.txt 2>/dev/null
) | less
(echo -e "Please review the license of OnlyOffice:\n\n" ; curl https://raw.githubusercontent.com/ONLYOFFICE/web-apps/master/LICENSE.txt 2>/dev/null) | less
read -rp "Do you accept the license? (Y/N): " confirm &&
[[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || exit 1
read -rp "Do you accept the license? (Y/N): " confirm \
&& [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || exit 1
set_prop "agree_license" yes
}
show_help() {
cat <<EOF
show_help () {
cat << EOF
install-onlyoffice installs or upgrades OnlyOffice.
NOTE: When you have rdfind installed, it will be used to save ~650MB of disk
@ -114,29 +106,20 @@ OPTIONS:
Accept the license of OnlyOffice and do not ask when running this
script. Read and accept this before using this option:
https://github.com/ONLYOFFICE/web-apps/blob/master/LICENSE.txt
-t, --trust-repository
Automatically configure the cloned onlyoffice-builds repository
as a safe.directory.
https://git-scm.com/docs/git-config/#Documentation/git-config.txt-safedirectory
EOF
exit 1
}
ensure_oo_is_downloaded() {
ensure_oo_is_downloaded () {
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
}
install_version() {
install_version () {
local DIR=$1
local COMMIT=$2
local FULL_DIR=$OO_DIR/$DIR
@ -153,55 +136,21 @@ install_version() {
cd "$LAST_DIR"
echo "$COMMIT" >"$FULL_DIR"/.commit
echo "$COMMIT" > "$FULL_DIR"/.commit
echo "$DIR updated"
else
echo "$DIR was up to date"
fi
if [ ${CLEAR+x} ]; then
rm -f "$FULL_DIR"/.git
fi
}
install_x2t() {
ensure_command_available curl
ensure_command_available sha512sum
ensure_command_available unzip
local VERSION=$1
local HASH=$2
local LAST_DIR
LAST_DIR=$(pwd)
local X2T_DIR=$OO_DIR/x2t
if [ ! -e "$X2T_DIR"/.version ] || [ "$(cat "$X2T_DIR"/.version)" != "$VERSION" ]; then
rm -rf "$X2T_DIR"
mkdir -p "$X2T_DIR"
cd "$X2T_DIR"
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"
exit 1
fi
unzip x2t.zip
rm x2t.zip*
echo "$VERSION" >"$X2T_DIR"/.version
echo "x2t updated"
else
echo "x2t was up to date"
fi
}
ensure_command_available() {
if ! command -v "$1" &>/dev/null; then
ensure_command_available () {
if ! command -v "$1" &> /dev/null; then
echo "$1 needs to be installed to run this script"
exit 1
fi

View file

@ -8,10 +8,8 @@ const Decrees = require("./decrees");
const nThen = require("nthen");
const Fs = require("fs");
const Fse = require("fs-extra");
const Path = require("path");
const Nacl = require("tweetnacl/nacl-fast");
const Hash = require('./common-hash');
module.exports.create = function (Env) {
var log = Env.Log;
@ -27,41 +25,6 @@ nThen(function (w) {
console.error(err);
}
}));
}).nThen(function (w) {
let admins = Env.admins || [];
// If we don't have any admin on this instance, print an onboarding link
if (Array.isArray(admins) && admins.length) { return; }
let token = Env.installToken;
let printLink = () => {
let url = `${Env.httpUnsafeOrigin}/install/#${token}`;
console.log('=============================');
console.log('Create your first admin account and customize your instance by visiting');
console.log(url);
console.log('=============================');
};
// If we already have a token, print it
if (token) { return void printLink(); }
// Otherwise create a new token
let decreeName = Path.join(Env.paths.decree, 'decree.ndjson');
token = Hash.createChannelId() + Hash.createChannelId();
let decree = ["ADD_INSTALL_TOKEN",[token],"",+new Date()];
Fs.appendFile(decreeName, JSON.stringify(decree) + '\n', w(function (err) {
if (err) { console.log(err); return; }
Env.installToken = token;
Env.envUpdated.fire();
printLink();
}));
}).nThen(function () {
if (!Env.admins.length) {
Env.Log.info('NO_ADMIN_CONFIGURED', {
message: `Your instance is not correctly configured for production usage. Review its checkup page for more information.`,
details: new URL('/checkup/', Env.httpUnsafeOrigin).href,
});
}
}).nThen(function (w) {
// we assume the server has generated a secret used to validate JWT tokens
if (typeof(Env.bearerSecret) === 'string') { return; }
@ -77,19 +40,9 @@ nThen(function (w) {
], w(function (err) {
if (err) { throw err; }
}));
}).nThen(function (w) {
Fse.mkdirp(Env.paths.block, w(function (err) {
if (err) {
log.error("BLOCK_FOLDER_CREATE_FAILED", err);
}
}));
}).nThen(function (w) {
var fullPath = Path.join(Env.paths.block, 'placeholder.txt');
Fs.writeFile(fullPath, 'PLACEHOLDER\n', w(function (err) {
if (err) {
log.error('BLOCK_PLACEHOLDER_CREATE_FAILED', err);
}
}));
Fs.writeFile(fullPath, 'PLACEHOLDER\n', w());
}).nThen(function () {
// asynchronously create a historyKeeper and RPC together
require('./historyKeeper.js').create(Env, function (err, historyKeeper) {
@ -108,7 +61,7 @@ nThen(function (w) {
};
// spawn ws server and attach netflux event handlers
let Server = NetfluxSrv.create(new WebSocketServer({ server: Env.httpServer}))
let Server = Env.Server = NetfluxSrv.create(new WebSocketServer({ server: Env.httpServer}))
.on('channelClose', historyKeeper.channelClose)
.on('channelMessage', historyKeeper.channelMessage)
.on('channelOpen', historyKeeper.channelOpen)

View file

@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* globals Buffer */
const B32 = require("thirty-two");
const OTP = require("notp");
const nThen = require("nthen");

View file

@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* globals process */
const nThen = require("nthen");
const getFolderSize = require("get-folder-size");
const Util = require("../common-util");
@ -104,8 +105,10 @@ var shutdown = function (Env, Server, cb) {
var getRegisteredUsers = Admin.getRegisteredUsers = function (Env, Server, cb) {
Env.batchRegisteredUsers('', cb, function (done) {
var dir = Env.paths.pin;
var folders;
var dirB = Env.paths.block;
var folders, foldersB;
var users = 0;
var blocks = 0;
nThen(function (waitFor) {
Fs.readdir(dir, waitFor(function (err, list) {
if (err) {
@ -114,6 +117,13 @@ var getRegisteredUsers = Admin.getRegisteredUsers = function (Env, Server, cb) {
}
folders = list;
}));
Fs.readdir(dirB, waitFor(function (err, list) {
if (err) {
waitFor.abort();
return void done(err);
}
foldersB = list;
}));
}).nThen(function (waitFor) {
folders.forEach(function (f) {
var dir = Env.paths.pin + '/' + f;
@ -126,8 +136,20 @@ var getRegisteredUsers = Admin.getRegisteredUsers = function (Env, Server, cb) {
users += list.length;
}));
});
}).nThen(function (waitFor) {
foldersB.forEach(function (f) {
var dir = Env.paths.block + '/' + f;
Fs.readdir(dir, waitFor(function (err, list) {
if (err) { return; }
// Don't count placeholders
list = list.filter(name => {
return !/\.placeholder$/.test(name);
});
blocks += list.length;
}));
});
}).nThen(function () {
done(void 0, {users});
done(void 0, {users, blocks});
});
});
};
@ -470,8 +492,6 @@ var setLastEviction = function (Env, Server, cb, data, unsafeKey) {
// CryptPad_AsyncStore.rpc.send('ADMIN', ['INSTANCE_STATUS], console.log)
var instanceStatus = function (Env, Server, cb) {
cb(void 0, {
appsToDisable: Env.appsToDisable,
restrictRegistration: Env.restrictRegistration,
restrictSsoRegistration: Env.restrictSsoRegistration,
dontStoreSSOUsers: Env.dontStoreSSOUsers,

View file

@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* globals Buffer*/
const Block = module.exports;
const Nacl = require("tweetnacl/nacl-fast");
const nThen = require("nthen");

View file

@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* globals process */
const Core = module.exports;
const Util = require("../common-util");
const escapeKeyCharacters = Util.escapeKeyCharacters;

View file

@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* globals Buffer*/
const Quota = module.exports;
//const Util = require("../common-util");
@ -161,11 +162,13 @@ var queryAccountServer = function (Env, cb) {
nThen(waitFor => {
Admin.getRegisteredUsers(Env, null, waitFor((err, data) => {
if (err) { return; }
stats.registered = data.users;
stats.registered = data.blocks;
if (Env.lastPingRegisteredUsers) {
stats.usersDiff = stats.registered - Env.lastPingRegisteredUsers;
}
Env.lastPingRegisteredUsers = stats.registered;
let teams = (data.users - data.blocks);
if (teams > 0) { stats.teams = teams; }
}));
}).nThen(() => {
if (Env.maxConcurrentWs) {

View file

@ -1,6 +0,0 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
module.exports = require("../www/common/common-hash");

View file

@ -169,7 +169,6 @@ var isInteger = function (n) {
var args_isString = function (args) {
return !(!Array.isArray(args) || !isString(args[0]));
};
var args_isInteger = function (args) {
return !(!Array.isArray(args) || !isInteger(args[0]));
};
@ -212,6 +211,10 @@ commands.SET_ARCHIVE_RETENTION_TIME = makeIntegerSetter('archiveRetentionTime');
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_ACCOUNT_RETENTION_TIME', [365]]], console.log)
commands.SET_ACCOUNT_RETENTION_TIME = makeIntegerSetter('accountRetentionTime');
var args_isString = function (args) {
return Array.isArray(args) && typeof(args[0]) === "string";
};
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_ADMIN_EMAIL', ['admin@website.tld']]], console.log)
commands.SET_ADMIN_EMAIL = makeGenericSetter('adminEmail', args_isString);
@ -220,15 +223,6 @@ commands.SET_SUPPORT_MAILBOX = makeGenericSetter('supportMailbox', function (arg
return args_isString(args) && Core.isValidPublicKey(args[0]);
});
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_SUPPORT_KEYS', ["Tdz6+fE9N9XXBY93rW5qeNa/k27yd40c0vq7EJyt7jA=", "Tdz6+fE9N9XXBY93rW5qeNa/k27yd40c0vq7EJyt7jA="]]], console.log)
commands.DISABLE_APPS = function (Env, args) {
if (!Array.isArray(args)) { throw new Error("INVALID_ARGS"); }
if (JSON.stringify(args) === JSON.stringify(Env.appsToDisable)) { return false; }
Env.appsToDisable = args;
return true;
};
commands.SET_SUPPORT_KEYS = function (Env, args) {
const curvePublic = args[0]; // Support mailbox key
const edPublic = args[1]; // Support pin log
@ -241,7 +235,7 @@ commands.SET_SUPPORT_KEYS = function (Env, args) {
Env.supportMailboxKey = curvePublic;
Env.supportPinKey = edPublic;
return true;
};
};
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_INSTANCE_PURPOSE', ["development"]]], console.log)
commands.SET_INSTANCE_PURPOSE = makeGenericSetter('instancePurpose', args_isString);
@ -299,7 +293,7 @@ var args_isMaintenance = function (args) {
// whenever that happens we can relax validation a bit to support more formats
var makeBroadcastSetter = function (attr, validation) {
return function (Env, args) {
if ((validation && !validation(args)) && !args_isString(args)) {
if ((validation && !validation(args)) && !args_isString(args)) {
throw new Error('INVALID_ARGS');
}
var str = args[0];

View file

@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* globals process */
const { existsSync, readdirSync } = require('node:fs');
const Crypto = require('crypto');
@ -252,6 +254,7 @@ module.exports.create = function (config) {
curvePublic: Nacl.util.encodeBase64(curve.publicKey),
selfDestructTo: {},
monitoring: {}
};
(function () {
@ -415,6 +418,7 @@ const BAD = [
'limits',
'customLimits',
'scheduleDecree',
'monitoring',
'httpServer',

View file

@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* global Buffer */
var HK = module.exports;
const nThen = require('nthen');

View file

@ -78,26 +78,15 @@ COMMANDS.TOTP_REVOKE = TOTP.TOTP_REVOKE;
COMMANDS.TOTP_WRITE_BLOCK = TOTP.TOTP_WRITE_BLOCK; // Password change only for now (v5.5.0)
COMMANDS.TOTP_REMOVE_BLOCK = TOTP.TOTP_REMOVE_BLOCK;
// Load challenges added by plugins
Object.keys(plugins || {}).forEach(id => {
try {
let plugin = plugins[id];
if (!plugin.challenge) { return; }
let commands = plugin.challenge;
Object.keys(commands).forEach(cmd => {
if (COMMANDS[cmd]) { return; } // Don't overwrite
COMMANDS[cmd] = commands[cmd];
});
} catch (e) {}
});
/*
const SSO = plugins.SSO && plugins.SSO.challenge;
COMMANDS.SSO_AUTH = SSO.SSO_AUTH;
COMMANDS.SSO_AUTH_CB = SSO.SSO_AUTH_CB;
COMMANDS.SSO_WRITE_BLOCK = SSO.SSO_WRITE_BLOCK; // Account creation only
COMMANDS.SSO_UPDATE_BLOCK = SSO.SSO_UPDATE_BLOCK; // Password change
COMMANDS.SSO_VALIDATE = SSO.SSO_VALIDATE;
*/
try {
// SSO plugin may not be installed
const SSO = plugins.SSO && plugins.SSO.challenge;
COMMANDS.SSO_AUTH = SSO.SSO_AUTH;
COMMANDS.SSO_AUTH_CB = SSO.SSO_AUTH_CB;
COMMANDS.SSO_WRITE_BLOCK = SSO.SSO_WRITE_BLOCK; // Account creation only
COMMANDS.SSO_UPDATE_BLOCK = SSO.SSO_UPDATE_BLOCK; // Password change
COMMANDS.SSO_VALIDATE = SSO.SSO_VALIDATE;
} catch (e) {}
var randomToken = () => Nacl.util.encodeBase64(Nacl.randomBytes(24)).replace(/\//g, '-');

View file

@ -19,6 +19,9 @@ const BlobStore = require("./storage/blob");
const BlockStore = require("./storage/block");
const plugins = require("./plugin-manager");
const Prometheus = require('prom-client');
const Monitoring = require('./monitoring');
const DEFAULT_QUERY_TIMEOUT = 5000;
const PID = process.pid;
@ -66,6 +69,102 @@ Env.incrementBytesWritten = function () {};
const EVENTS = {};
// XXX Store in monitoring.js
const rssMetric = new Prometheus.Gauge({
name: `memory_rss`,
help: 'The amount of space occupied in the main memory device for the process.',
labelNames: ['pid', 'type']
});
const heapTotalMetric = new Prometheus.Gauge({
name: `memory_heap_total`,
help: "Total heap memory.",
labelNames: ['pid', 'type']
});
const heapUsedMetric = new Prometheus.Gauge({
name: `memory_heap_used`,
help: 'Used heap memory.',
labelNames: ['pid', 'type']
});
const externalMetric = new Prometheus.Gauge({
name: `memory_external`,
help: 'Memory usage of C++ objects bound to JavaScript objects managed by V8.',
labelNames: ['pid', 'type']
});
const arrayBufferMetric = new Prometheus.Gauge({
name: `memory_array_buffers`,
help: 'Memory allocated for ArrayBuffers and SharedArrayBuffers.',
labelNames: ['pid', 'type']
});
const cpuUserMetric = new Prometheus.Gauge({
name: `process_cpu_user_seconds_total`,
help: 'Total user CPU time spent in seconds during the configured interval.',
labelNames: ['pid', 'type']
});
const cpuSystemMetric = new Prometheus.Gauge({
name: `process_cpu_system_seconds_total`,
help: 'Total system CPU time spent in seconds during the configured interval.',
labelNames: ['pid', 'type']
});
const cpuTotalMetric = new Prometheus.Gauge({
name: `process_cpu_seconds_total`,
help: 'Total user and system CPU time spent in seconds during the configured interval',
labelNames: ['pid', 'type']
});
const cpuPercentMetric = new Prometheus.Gauge({
name: `process_cpu_percent`,
help: 'Total user and system CPU time spent divided by the interval duration',
labelNames: ['pid', 'type']
});
const wsMetric = new Prometheus.Gauge({
name: `active_websockets`,
help: 'Number of active websocket connections',
});
const regMetric = new Prometheus.Gauge({
name: `active_registered_users`,
help: 'Number of registered users online',
});
const chanMetric = new Prometheus.Gauge({
name: `active_channels`,
help: 'Number of active pads',
});
EVENTS.MONITORING = function (data) {
/*
{
main: {
rss: 1234
...
},
pid1: {
rss: 234
...
}
}
*/
Object.keys(data).forEach(pid => {
let val = data[pid];
let type = val.type;
rssMetric.set({pid, type}, val.mem?.rss || 0);
heapTotalMetric.set({pid, type}, val.mem?.heapTotal || 0);
heapUsedMetric.set({pid, type}, val.mem?.heapUsed || 0);
externalMetric.set({pid, type}, val.mem?.external || 0);
arrayBufferMetric.set({pid, type}, val.mem?.arrayBuffers || 0);
let userSeconds = (val.cpu?.user || 0) / 1000000;
let systemSeconds = (val.cpu?.system || 0) / 1000000;
cpuUserMetric.set({pid, type}, userSeconds);
cpuSystemMetric.set({pid, type}, systemSeconds);
let sum = userSeconds + systemSeconds;
let percent = sum / (Monitoring.interval/1000);
cpuTotalMetric.set({pid, type}, sum);
cpuPercentMetric.set({pid, type}, percent);
if (type === 'main') {
wsMetric.set(val.ws || 0);
regMetric.set(val.registered || 0);
chanMetric.set(val.channels || 0);
}
});
};
EVENTS.ENV_UPDATE = function (data /*, cb */) {
try {
Env = JSON.parse(data);
@ -161,13 +260,6 @@ var setHeaders = function (req, res) {
}
var h = getHeaders(Env, type);
// Allow main domain to load resources from the sandbox URL
if (!Env.enableEmbedding && req.get('origin') === Env.httpUnsafeOrigin &&
/^\/common\/onlyoffice\/dist\/.*\/fonts\/.*/.test(req.url)) {
h['Access-Control-Allow-Origin'] = Env.httpUnsafeOrigin;
}
applyHeaderMap(res, h);
};
@ -219,6 +311,13 @@ const wsProxy = createProxyMiddleware({
app.use('/cryptpad_websocket', wsProxy);
app.get('/metrics', (req, res) => {
Prometheus.register.metrics().then((data) => {
res.set('Content-Type', Prometheus.register.contentType);
res.send(data);
});
});
app.use('/ssoauth', (req, res, next) => {
if (SSOUtils && req && req.body && req.body.SAMLResponse) {
req.method = 'GET';
@ -513,11 +612,6 @@ app.use("/block", (req, res, next) => {
next();
});
Object.keys(plugins || {}).forEach(name => {
let plugin = plugins[name];
if (!plugin.addHttpEndpoints) { return; }
plugin.addHttpEndpoints(Env, app);
});
app.use("/customize", Express.static('customize'));
app.use("/customize", Express.static('customize.dist'));
@ -595,7 +689,6 @@ var serveConfig = makeRouteCache(function () {
maxUploadSize: Env.maxUploadSize,
premiumUploadSize: Env.premiumUploadSize,
restrictRegistration: Env.restrictRegistration,
appsToDisable: Env.appsToDisable,
restrictSsoRegistration: Env.restrictSsoRegistration,
httpSafeOrigin: Env.httpSafeOrigin,
enableEmbedding: Env.enableEmbedding,
@ -631,35 +724,6 @@ var serveBroadcast = makeRouteCache(function () {
app.get('/api/config', serveConfig);
app.get('/api/broadcast', serveBroadcast);
(function () {
let extensions = plugins._extensions;
let styles = plugins._styles;
let str = JSON.stringify(extensions);
let str2 = JSON.stringify(styles);
let js = `let extensions = ${str};
let styles = ${str2};
let lang = window.cryptpadLanguage;
let paths = [];
extensions.forEach(name => {
paths.push(\`optional!/\${name}/extensions.js\`);
paths.push(\`optional!json!/\${name}/translations/messages.json\`);
paths.push(\`optional!json!/\${name}/translations/messages.\${lang}.json\`);
});
styles.forEach(name => {
paths.push(\`optional!less!/\${name}/style.less\`);
});
define(paths, function () {
let args = Array.prototype.slice.apply(arguments);
return args;
}, function () {
// ignore missing files
});`;
app.get('/extensions.js', (req, res) => {
res.setHeader('Content-Type', 'text/javascript');
res.send(js);
});
})();
var Define = function (obj) {
return `define(function (){
return ${JSON.stringify(obj, null, '\t')};
@ -753,7 +817,7 @@ app.post('/api/auth', function (req, res, next) {
});
app.use(function (req, res /*, next */) {
if (/^(\/favicon\.ico\/|.*\.js\.map|.*\/translations\/.*\.json)/.test(req.url)) {
if (/^(\/favicon\.ico\/|.*\.js\.map)$/.test(req.url)) {
// ignore common 404s
} else {
Log.info('HTTP_404', req.url);
@ -799,7 +863,14 @@ nThen(function (w) {
}));
}).nThen(function () {
// TODO inform the parent process that this worker is ready
setInterval(() => {
sendMessage({
command: 'MONITORING',
data: Monitoring.getData('http-worker')
}, () => {
// Done
});
}, Monitoring.interval);
});
process.on('uncaughtException', function (err) {

View file

@ -39,6 +39,8 @@ var handlers = {};
handlers[level] = function (ctx, content) { console.error(content); };
});
var noop = function () {};
var createLogType = function (ctx, type) {
if (logLevels.indexOf(type) < logLevels.indexOf(ctx.logLevel)) {
return noop;

45
lib/monitoring.js Normal file
View file

@ -0,0 +1,45 @@
/*
globals process
*/
const VALUES = {};
VALUES.mem = () => {
return process.memoryUsage();
};
let oldCpu;
VALUES.cpu = () => {
if (!oldCpu) {
oldCpu = process.cpuUsage();
return {user:0,system:0};
}
let val = process.cpuUsage(oldCpu);
oldCpu = process.cpuUsage();
return val;
};
const applyToEnv = (Env, data) => {
if (!Env) { return; }
Env.monitoring[data.pid] = data;
};
const getData = (type) => {
const value = {
pid: process.pid,
type: type
};
Object.keys(VALUES).forEach(key => {
value[key] = VALUES[key]();
});
return value;
};
const remove = (Env, pid) => {
if (Env && Env.monitoring && pid && Env.monitoring[pid]) {
delete Env.monitoring[pid];
}
};
module.exports = {
interval: 5000,
applyToEnv,
getData,
remove
};

View file

@ -4,8 +4,6 @@
const fs = require('node:fs');
const plugins = {};
const extensions = plugins._extensions = [];
const styles = plugins._styles = [];
try {
let pluginsDir = fs.readdirSync(__dirname + '/plugins');
@ -14,18 +12,6 @@ try {
try {
let plugin = require(`./plugins/${name}/index`);
plugins[plugin.name] = plugin.modules;
try {
let hasExt = fs.existsSync(`lib/plugins/${name}/client/extensions.js`);
if (hasExt) {
extensions.push(plugin.name.toLowerCase());
}
} catch (e) {}
try {
let hasStyle = fs.existsSync(`lib/plugins/${name}/client/style.less`);
if (hasStyle) {
styles.push(plugin.name.toLowerCase());
}
} catch (e) {}
} catch (err) {
console.error(err);
}

View file

@ -170,6 +170,15 @@ var rpc = function (Env, Server, userId, data, respond) {
var command = msg[1];
/*
// TODO get data from lib/upload.js to be able to get the size of the uploaded file
if (command === 'UPLOAD_COMPLETE' || command === 'OWNED_UPLOAD_COMPLETE') {
let m = Env.monitoring = Env.monitoring || {};
let b = m.upload = m.upload || {};
let id = msg[2];
if (id) { b[id] = +new Date(); }
}
*/
if (command === 'UPLOAD') {
// UPLOAD is a special case that skips signature validation
// intentional fallthrough behaviour

View file

@ -83,6 +83,7 @@ Stats.instanceData = function (Env) {
data.providesAggregateStatistics = true;
data.statistics = {}; // Filled in lib/commands/quota.js because of async calls
}
return data;
};

View file

@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* globals Buffer */
var Fs = require("fs");
var Fse = require("fs-extra");
var Path = require("path");

View file

@ -3,6 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
/*@flow*/
/* globals Buffer */
var Fs = require("fs");
var Fse = require("fs-extra");
var Path = require("path");

View file

@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* global Buffer */
const ToPull = require('stream-to-pull-stream');
const Pull = require('pull-stream');

View file

@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* globals process, Buffer */
const HK = require("../hk-util");
const Store = require("../storage/file");
const BlobStore = require("../storage/blob");
@ -15,7 +17,12 @@ const Saferphore = require("saferphore");
const Logger = require("../log");
const Tasks = require("../storage/tasks");
const Nacl = require('tweetnacl/nacl-fast');
const Sodium = require('sodium');
const LW = require('libsodium-wrappers');
const Eviction = require("../eviction");
const Monitoring = require('../monitoring');
const withSodium = false;
const withLibSodium = true;
const Env = {
Log: {},
@ -56,6 +63,13 @@ const init = function (config, _cb) {
Env.archiveRetentionTime = config.archiveRetentionTime;
Env.accountRetentionTime = config.accountRetentionTime;
setInterval(() => {
process.send({
monitoring: true,
data: Monitoring.getData('db-worker')
});
}, Monitoring.interval);
nThen(function (w) {
Store.create(config, w(function (err, _store) {
if (err) {
@ -679,11 +693,29 @@ COMMANDS.INLINE = function (data, cb) {
} catch (e) {
return void cb("E_BADKEY");
}
// validate the message
if (!withSodium && !withLibSodium) {
const validated = Nacl.sign.open(signedMsg, validateKey);
if (!validated) {
return void cb("FAILED");
}
} else if (withLibSodium) {
const validated = LW.crypto_sign_open(signedMsg, validateKey);
if (!validated) {
console.log("validation failed");
return void cb("failed");
}
} else {
var input = {
sign: signedMsg,
publicKey: validateKey
};
const validated = Sodium.Sign.verify(input);
if (!validated) {
console.log("validation failed");
return void cb("failed");
}
}
cb();
};
@ -720,9 +752,31 @@ const checkDetachedSignature = function (signedMsg, signature, publicKey) {
throw new Error("INVALID_SIGNATURE_LENGTH");
}
if (!withSodium && !withLibSodium) {
if (Nacl.sign.detached.verify(signedBuffer, signatureBuffer, pubBuffer) !== true) {
throw new Error("FAILED");
}
} else {
try {
let sign = Buffer.concat([ signatureBuffer, signedBuffer]);
var input = {
sign,
publicKey: pubBuffer
};
if (withLibSodium) {
const validated = LW.crypto_sign_open(sign, pubBuffer);
if (!validated) {
throw new Error('FAILED');
}
} else {
if (!Sodium.Sign.verify(input)) {
throw new Error("FAILED");
}
}
} catch (e) {
throw new Error("SODIUM FAILED");
}
}
};
COMMANDS.DETACHED = function (data, cb) {

View file

@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* global process */
const Util = require("../common-util");
const nThen = require('nthen');
const OS = require("os");
@ -9,6 +10,7 @@ const { fork } = require('child_process');
const Workers = module.exports;
const PID = process.pid;
const Block = require("../storage/block");
const Monitoring = require('../monitoring');
const DB_PATH = 'lib/workers/db-worker';
const MAX_JOBS = 16;
@ -162,6 +164,13 @@ Workers.initialize = function (Env, config, _cb) {
if (res.log) {
return void handleLog(res.log, res.label, res.info);
}
// handle monitoring data
if (res.monitoring) {
Monitoring.applyToEnv(Env, res.data);
return;
}
// but don't bother handling things addressed to other processes
// since it's basically guaranteed not to work
if (res.pid !== PID) {
@ -226,7 +235,9 @@ Workers.initialize = function (Env, config, _cb) {
handleResponse(state, res);
});
let pid = worker.pid;
var substituteWorker = Util.once(function () {
Monitoring.remove(Env, pid);
Env.Log.info("SUBSTITUTE_DB_WORKER", '');
var idx = workers.indexOf(state);
if (idx !== -1) {
@ -263,9 +274,10 @@ Workers.initialize = function (Env, config, _cb) {
};
nThen(function (w) {
var limit = Env.maxWorkers;
var limit = Env.maxWorkers || OS.cpus().length;
var logged;
/*
OS.cpus().forEach(function (cpu, index) {
if (limit && index >= limit) {
if (!logged) {
@ -281,6 +293,14 @@ Workers.initialize = function (Env, config, _cb) {
return void cb(err);
}));
});
*/
for (let i = 0; i<limit; i++) {
initWorker(fork(DB_PATH), w(function (err) {
if (!err) { return; }
w.abort();
return void cb(err);
}));
}
}).nThen(function () {
Env.computeIndex = function (Env, channel, cb) {
Env.store.getWeakLock(channel, function (next) {

3873
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "a collaborative office suite that is end-to-end encrypted and open-source",
"version": "2024.6.1",
"description": "realtime collaborative visual editor with zero knowledge server",
"version": "2024.3.0",
"license": "AGPL-3.0+",
"repository": {
"type": "git",
@ -20,9 +20,9 @@
"bootstrap-tokenfield": "^0.12.0",
"chainpad": "^5.2.6",
"chainpad-crypto": "^0.2.5",
"chainpad-listmap": "^1.1.0",
"chainpad-netflux": "^1.2.0",
"chainpad-server": "^5.2.0",
"chainpad-listmap": "^1.0.0",
"chainpad-netflux": "^1.0.0",
"chainpad-server": "^5.2.1",
"ckeditor": "npm:ckeditor4@~4.22.1",
"codemirror": "^5.19.0",
"components-font-awesome": "^4.6.3",
@ -41,15 +41,17 @@
"json.sortify": "~2.1.0",
"jsonwebtoken": "^9.0.0",
"jszip": "3.10.1",
"libsodium-wrappers": "^0.7.13",
"localforage": "^1.5.2",
"marked": "^4.3.0",
"mathjax": "3.0.5",
"netflux-websocket": "^1.2.0",
"netflux-websocket": "^1.0.0",
"notp": "^2.0.3",
"nthen": "0.1.8",
"open-sans-fontface": "^1.4.0",
"openid-client": "^5.4.2",
"pako": "^2.1.0",
"prom-client": "^14.2.0",
"prompt-confirm": "^2.0.4",
"pull-stream": "^3.6.1",
"require-css": "0.1.10",
@ -57,38 +59,40 @@
"requirejs-plugins": "^1.0.2",
"saferphore": "0.0.1",
"scrypt-async": "1.2.0",
"sodium": "^3.0.2",
"sortablejs": "^1.6.0",
"sortify": "^1.0.4",
"stream-to-pull-stream": "^1.7.2",
"thirty-two": "^1.0.2",
"tweetnacl": "~0.12.2",
"ulimit": "0.0.2",
"ws": "^8.17.1",
"ws": "^3.3.1",
"x2js": "^3.4.4"
},
"devDependencies": {
"eslint": "^8.57.0",
"eslint-plugin-compat": "^4.2.0",
"stylelint": "^16.6.1",
"stylelint-config-standard-less": "^3.0.1"
"lesshint": "6.3.7"
},
"overrides": {
"glob-parent": "5.1.2",
"set-value": "4.0.1",
"minimist": "~1.2.3",
"minimatch": "~3.1.2",
"ws": "^8.17.1",
"jquery": "3.6.0"
},
"scripts": {
"install:components": "node scripts/copy-components.js",
"start": "node server.js",
"dev": "DEV=1 node server.js",
"prof": "NODE_ENV=production node --prof server.js",
"fresh": "FRESH=1 node server.js",
"offline": "FRESH=1 OFFLINE=1 node server.js",
"offlinedev": "DEV=1 OFFLINE=1 node server.js",
"package": "PACKAGE=1 node server.js",
"lint": "eslint . && stylelint \"./customize.dist/src/less2/**/*.less\"",
"lint": "eslint . && ./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/",
"lint:js": "eslint .",
"lint:less": "stylelint \"./customize.dist/src/less2/**/*.less\"",
"lint:less": "./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/",
"lint:translations": "node ./scripts/translations/lint-translations.js",
"unused-translations": "node ./scripts/translations/unused-translations.js",
"test": "node scripts/TestSelenium.js",

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
# CryptPad
CryptPad is a collaboration suite that is end-to-end-encrypted and open-source. It is built to enable collaboration, synchronizing changes to documents in real time. Because all data are encrypted, in the eventuality of a breach, attackers have no way of seeing the stored content. Moreover, if the administrators dont alter the code, they and the service also cannot infer any piece of information about the users' content.
CryptPad is a collaboration suite that is end-to-end-encrypted and open-source. It is built to enable collaboration, synchronizing changes to documents in real time. Because all data is encrypted, the service and its administrators have no way of seeing the content being edited and stored.
![Drive screenshot](screenshot.png "preview of the CryptDrive")
@ -24,7 +24,7 @@ Configuring CryptPad for production requires a little more work, but the process
## Current version
The most recent version and all past release notes can be found on the [releases page on GitHub](https://github.com/cryptpad/cryptpad/releases/).
The most recent version and all past release notes can be found [here](https://github.com/cryptpad/cryptpad/releases/).
## Setup using Docker
@ -36,7 +36,7 @@ Previously, Docker images were community maintained, had their own repository an
CryptPad offers a variety of collaborative tools that encrypt your data in your browser
before it is sent to the server and your collaborators. In the event that the server is
compromized, the database holds encrypted data that is not of much value to attackers.
compromized the database holds encrypted data that is not of much value to attackers.
The code which performs the encryption is still loaded from the host server like any
other web page, so you still need to trust the administrator to keep their server secure
@ -44,29 +44,23 @@ and to send you the right code. An expert can download code from the server and
that it isn't doing anything malicious like leaking your encryption keys, which is why
this is considered an [active attack].
The platform is designed to minimize what data is exposed to its operators. User
registration and account access are based on cryptographic keys that are derived from your
username and password. Hence, the server never needs to see either, and you don't need to
worry about whether they are being stored securely. It is impossible to verify whether a
server's operators are logging your IP or other activity, so if you consider this
information sensitive it is safest to assume it is being recorded and access your
preferred instance via [Tor browser].
The platform is designed to minimize what data is exposed to its operators. User registration
and account access is based on a cryptographic key that is derived from your username
and password so the server never needs to see either and you don't need to worry about
whether they are being stored securely. It is impossible to verify whether a server's
operators are logging your IP or other activity, so if you consider this information
sensitive it is safest to assume it is being recorded and access your preferred instance
via [Tor browser].
A correctly configured instance has safeguards to prevent collaborators from doing some
nasty things like injecting scripts into collaborative documents or uploads. The project
is actively maintained and bugs that our safeguards don't catch tend to get fixed quickly.
For this reason it is best to only use instances that are running the most recent version,
which is currently on a three-month release cycle. It is difficult for a non-expert to
which is currently on a three-week release cycle. It is difficult for a non-expert to
determine whether an instance is otherwise configured correctly, so we are actively
working on allowing administrators to opt in to a [public directory of
servers](https://cryptpad.org/instances/) that
working on allowing administrators to opt in to a public directory of servers that
meet our strict criteria for safety.
For end users, a [guide](https://blog.cryptpad.org/2024/03/14/Most-Secure-CryptPad-Usage/)
is provided in our blog to help understand the security of CryptPad. This blog post
also explains and show the best practices when using CryptPad and clarify what end-to-end
encryption entails and not.
# Translations
CryptPad can be translated with nothing more than a web browser via our

View file

@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* global process */
var WebDriver = require("selenium-webdriver");
var nThen = require('nthen');

View file

@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* globals process */
var Fs = require("fs");
var Fse = require("fs-extra");
var Path = require("path");

View file

@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* globals Buffer */
var Https = require('https');
var Config = require("../lib/load-config");
var Package = require("../package.json");

View file

@ -40,4 +40,5 @@ nThen(function (w) {
console.log(token);
var url = config.httpUnsafeOrigin + '/install/';
console.log(`Please visit ${url} to create your first admin user`);
});

View file

@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* globals process */
const jwt = require("jsonwebtoken");
const Sessions = require("../lib/storage/sessions.js");

View file

@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* globals process */
var Client = require("../../lib/client");
var Nacl = require("tweetnacl/nacl-fast");
var nThen = require("nthen");

View file

@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* globals process */
var Client = require("../../lib/client/");
var Crypto = require("../../www/components/chainpad-crypto");
var Mailbox = Crypto.Mailbox;

View file

@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/* globals process */
var Client = require("../../lib/client/");
var Crypto = require("../../www/components/chainpad-crypto");
var Mailbox = Crypto.Mailbox;

View file

@ -2,6 +2,9 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later
/*
globals process
*/
var Express = require('express');
var Http = require('http');
var Fs = require('fs');
@ -15,6 +18,7 @@ var config = require("./lib/load-config");
var Environment = require("./lib/env");
var Env = Environment.create(config);
var Default = require("./lib/defaults");
var Monitoring = require('./lib/monitoring');
var app = Express();
@ -49,6 +53,11 @@ COMMANDS.GET_PROFILING_DATA = function (msg, cb) {
cb(void 0, Env.bytesWritten);
};
COMMANDS.MONITORING = function (msg, cb) {
Monitoring.applyToEnv(Env, msg.data);
cb();
};
nThen(function (w) {
require("./lib/log").create(config, w(function (_log) {
Env.Log = _log;
@ -68,6 +77,13 @@ nThen(function (w) {
Env.Log.info("WEBSERVER_LISTENING", {
origin: url,
});
if (!Env.admins.length) {
Env.Log.info('NO_ADMIN_CONFIGURED', {
message: `Your instance is not correctly configured for production usage. Review its checkup page for more information.`,
details: new URL('/checkup/', Env.httpUnsafeOrigin).href,
});
}
} catch (err) {
Env.Log.error("INVALID_ORIGIN", {
httpUnsafeOrigin: Env.httpUnsafeOrigin,
@ -93,6 +109,7 @@ nThen(function (w) {
var launchWorker = (online) => {
var worker = Cluster.fork(workerState);
var pid = worker.process.pid;
worker.on('online', () => {
online();
});
@ -122,6 +139,7 @@ nThen(function (w) {
});
worker.on('exit', (code, signal) => {
Monitoring.remove(Env, pid);
if (!signal && code === 0) { return; }
// relaunch http workers if they crash
Env.Log.error('HTTP_WORKER_EXIT', {
@ -163,6 +181,19 @@ nThen(function (w) {
broadcast('FLUSH_CACHE', Env.FRESH_KEY);
}, 250);
setInterval(() => {
// Add main process data to monitoring
let monitoring = Monitoring.getData('main');
let Server = Env.Server;
let stats = Server.getSessionStats();
monitoring.ws = stats.total;
monitoring.channels = Server.getActiveChannelCount();
monitoring.registered = Object.keys(Env.netfluxUsers).length;
// Send updated values
Monitoring.applyToEnv(Env, monitoring);
broadcast('MONITORING', Env.monitoring);
}, Monitoring.interval);
Env.envUpdated.reg(throttledEnvChange);
Env.cacheFlushed.reg(throttledCacheFlush);

View file

@ -9,14 +9,12 @@
@import (reference) "../../customize/src/less2/include/creation.less";
@import (reference) '../../customize/src/less2/include/framework.less';
@import (reference) '../../customize/src/less2/include/export.less';
@import (reference) '../../customize/src/less2/include/admin.less';
&.cp-app-admin {
.framework_min_main();
.sidebar-layout_main();
.limit-bar_main();
.creation_main();
.admin_main();
display: flex;
flex-flow: column;
@ -34,16 +32,6 @@
border-radius: 5px;
background-color: @cryptpad_color_brand;
}
input.cp-admin-color-picker {
vertical-align: middle;
}
.cp-palette-container {
display: inline-flex;
width: ~"calc(100% - 5rem)";
padding-left: 0.5rem;
vertical-align: middle;
}
.cp-admin-color-preview {
& > div {
margin-top: @sidebar_base-margin;

View file

@ -5,7 +5,6 @@
define([
'jquery',
'/common/toolbar.js',
'/common/pad-types.js',
'/components/nthen/index.js',
'/common/sframe-common.js',
'/common/common-interface.js',
@ -18,11 +17,11 @@ define([
'/common/hyperscript.js',
'/common/clipboard.js',
'json.sortify',
'/customize/application_config.js',
'/api/config',
'/api/instance',
'/lib/datepicker/flatpickr.js',
'/install/onboardscreen.js',
'/common/hyperscript.js',
'css!/lib/datepicker/flatpickr.min.css',
'css!/components/bootstrap/dist/css/bootstrap.min.css',
'css!/components/components-font-awesome/css/font-awesome.min.css',
@ -30,7 +29,6 @@ define([
], function(
$,
Toolbar,
PadTypes,
nThen,
SFCommon,
UI,
@ -43,12 +41,11 @@ define([
h,
Clipboard,
Sortify,
AppConfig,
ApiConfig,
Instance,
Flatpickr,
Onboarding,
Flatpickr
) {
var APP = window.APP = {};
var Nacl = window.nacl;
@ -93,12 +90,6 @@ define([
'forcemfa',
]
},
'apps': { // Msg.admin_cat_apps
icon: 'fa fa-wrench',
content: [
'apps',
]
},
'users' : { // Msg.admin_cat_users
icon : 'fa fa-address-card-o',
content : [
@ -171,22 +162,6 @@ define([
const blocks = sidebar.blocks;
// EXTENSION_POINT:ADMIN_CATEGORY
common.getExtensions('ADMIN_CATEGORY').forEach(ext => {
if (!ext || !ext.id || !ext.name || !ext.content) {
return console.error('Invalid extension point', 'ADMIN_CATEGORY', ext);
}
if (categories[ext.id]) {
return console.error('Extension point ID already used', ext);
}
console.error(ext);
categories[ext.id] = {
icon: ext.icon,
name: ext.name,
content: ext.content
};
});
const flushCache = (cb) => {
cb = cb || function () {};
sFrameChan.query('Q_ADMIN_RPC', {
@ -634,6 +609,7 @@ define([
UI.log(Messages._getKey('ui_saved', [Messages.admin_emailTitle]));
});
});
var nav = blocks.nav([button]);
var form = blocks.form([
@ -645,35 +621,6 @@ define([
cb(form);
});
sidebar.addItem('apps', function (cb) {
const appsToDisable = ApiConfig.appsToDisable || [];
const grid = Onboarding.createAppsGrid(appsToDisable);
var save = blocks.activeButton('primary', '', Messages.settings_save, function (done) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['DISABLE_APPS', appsToDisable]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
done(false);
return;
}
flushCache();
done(true);
UI.log(Messages._getKey('ui_saved', [Messages.admin_appSelection]));
});
});
let form = blocks.form([
grid
], blocks.nav([save]));
cb(form);
});
sidebar.addItem('instance-info-notice', function(cb){
var key = 'instance-info-notice';
var notice = blocks.alert('info', key, [Messages.admin_infoNotice1, ' ', Messages.admin_infoNotice2]);
@ -843,7 +790,7 @@ define([
var currentContainer = blocks.block([], 'cp-admin-customize-logo');
let redraw = () => {
var current = h('img', {src: '/api/logo?'+(+new Date()),alt:'Custom logo'}); // XXX
var current = h('img', {src: '/api/logo?'+(+new Date())});
$(currentContainer).empty().append(current);
};
redraw();
@ -958,7 +905,7 @@ define([
setColor(color, done);
});
let onColorPicked = () => {
let $input = $(input).on('change', () => {
require(['/lib/less.min.js'], (Less) => {
let color = $input.val();
let lColor = Less.color(color.slice(1));
@ -978,8 +925,7 @@ define([
$preview.find('.cp-admin-color-preview-dark a').attr('style', `color: ${lightColor} !important`);
$preview.find('.cp-admin-color-preview-light a').attr('style', `color: ${color} !important`);
});
};
let $input = $(input).on('change', onColorPicked).addClass('cp-admin-color-picker');
});
UI.confirmButton($remove, {
classes: 'btn-danger',
@ -989,18 +935,9 @@ define([
setColor('', () => {});
});
var colors = UIElements.makePalette(4, (color, $color) => {
// onselect
let rgb = $color.css('background-color');
let hex = Util.rgbToHex(rgb);
$input.val(hex);
onColorPicked();
});
$(label).append(colors);
let form = blocks.form([
labelCurrent,
label,
label
], blocks.nav([btn, remove, btn.spinner]));
cb([form, labelPreview]);
@ -2713,7 +2650,8 @@ define([
}, function (e, arr) {
pre.innerText = '';
let data = arr[0];
pre.append(String(data.users));
pre.append(String(data.blocks));
pre.append(' (old value including teams: ' + String(data.users) + ')'); // XXX
});
};
onRefresh();
@ -3288,6 +3226,10 @@ define([
$active.empty();
if (Broadcast && Broadcast.surveyURL) {
var a = blocks.link(Messages.admin_surveyActive, Broadcast.surveyURL);
$(a).click(function (e) {
e.preventDefault();
common.openUnsafeURL(Broadcast.surveyURL);
});
$active.append([a, removeButton]);
}
});
@ -3895,32 +3837,6 @@ define([
cb(opts);
});
// EXTENSION_POINT:ADMIN_ITEM
let utils = {
h, Util, Hash
};
common.getExtensions('ADMIN_ITEM').forEach(ext => {
if (!ext || !ext.id || typeof(ext.getContent) !== "function") {
return console.error('Invalid extension point', 'ADMIN_CATEGORY', ext);
}
if (sidebar.hasItem(ext.id)) {
return console.error('Extension point ID already used', ext);
}
sidebar.addItem(ext.id, cb => {
ext.getContent(common, blocks, utils, content => {
cb(content);
});
}, {
noTitle: !ext.title,
noHint: !ext.description,
title: ext.title,
hint: ext.description
});
});
sidebar.makeLeftside(categories);
};

View file

@ -106,7 +106,7 @@ define([
framework._.toolbar.$theme.append($showAuthorColors);
markers.setButton($showAuthorColors);
};
var mkPrintButton = function (framework, $content, $print) {
var mkPrintButton = function (framework, $content) {
var $printButton = framework._.sfCommon.createButton('print', true);
$printButton.click(function () {
$print.html($content.html());
@ -115,8 +115,8 @@ define([
framework.feedback('PRINT_CODE');
UI.clearTooltipsDelay();
});
var $dropdownEntry = UIElements.getEntryFromButton($printButton);
framework._.toolbar.$drawer.append($dropdownEntry);
var $print = UIElements.getEntryFromButton($printButton);
framework._.toolbar.$drawer.append($print);
};
var mkMarkdownTb = function (editor, framework) {
var $codeMirrorContainer = $('#cp-app-code-container');

View file

@ -240,15 +240,11 @@ define([
if (!(tab.content || tab.disabled) || !tab.title) { return; }
var content = h('div.alertify-tabs-content', tab.content);
var title = h('span.alertify-tabs-title'+ (tab.disabled ? '.disabled' : ''), h('span.tab-title-text',{id: 'cp-tab-' + tab.title.toLowerCase(), 'aria-hidden':"true"}, tab.title));
$(title).attr('tabindex', '0');
if (tab.icon) {
var icon = h('i', {class: tab.icon, 'aria-labelledby': 'cp-tab-' + tab.title.toLowerCase()});
$(title).prepend(' ').prepend(icon);
}
Util.onClickEnter($(title), function (event) {
event.preventDefault();
event.stopPropagation();
$(title).click(function () {
if (tab.disabled) { return; }
var old = tabs[active];
if (old.onHide) { old.onHide(); }
@ -304,17 +300,14 @@ define([
var $root = $t.parent();
var $input = $root.find('.token-input');
$input.attr('tabindex', 0);
var $button = $(h('button.btn.btn-primary', [
h('i.fa.fa-plus'),
h('span', Messages.tag_add)
]));
Util.onClickEnter($button, function (e) {
$button.click(function () {
$t.tokenfield('createToken', $input.val());
e.stopPropagation();
});
var $container = $(h('span.cp-tokenfield-container'));
@ -332,47 +325,27 @@ define([
if (!$tokens.length) {
$container.prepend(h('span.tokenfield-empty', Messages.kanban_noTags));
}
$tokens.find('.close').attr('tabindex', 0).on('keydown', e => {
e.stopPropagation();
});
$tokens.find('.token-label').attr('tabindex', 0).on('keydown', function (e) {
if (e.which === 13 || e.which === 32) {
$(this).dblclick();
}
e.stopPropagation();
});
$form.append($input);
$form.append($button);
if (isEdit) { $button.find('span').text(Messages.tag_edit); }
else { $button.find('span').text(Messages.add); }
$container.append($form);
$input.focus();
isEdit = false;
called = false;
});
};
resetUI();
const focusInput = () => {
let active = document.activeElement;
if ($.contains($container[0], active)) {
setTimeout(() => {
$input.focus();
});
}
};
$t.on('tokenfield:removedtoken', function () {
resetUI();
focusInput();
});
$t.on('tokenfield:editedtoken', function () {
resetUI();
focusInput();
});
$t.on('tokenfield:createdtoken', function () {
$input.val('');
resetUI();
focusInput();
});
$t.on('tokenfield:edittoken', function () {
isEdit = true;
@ -513,7 +486,7 @@ define([
var navs = [];
buttons.forEach(function (b) {
if (!b.name || !b.onClick) { return; }
var button = h('button', { 'class': b.className || '' }, [
var button = h('button', { tabindex: '1', 'class': b.className || '' }, [
b.iconClass ? h('i' + b.iconClass) : undefined,
b.name
]);
@ -536,8 +509,7 @@ define([
divClasses: 'left'
}, todo);
} else {
Util.onClickEnter($(button), function (e) {
e.stopPropagation();
$(button).click(function () {
todo();
});
}
@ -576,40 +548,6 @@ define([
if (opt.forefront) { $(frame).addClass('forefront'); }
return frame;
};
let addTabListener = frame => {
// find focusable elements
let modalElements = $(frame).find('a, button, input, [tabindex]:not([tabindex="-1"]), textarea').filter(':visible').filter(':not(:disabled)');
if (modalElements.length === 0) {
// there are no focusable elements -> nothing to do for us here
return;
}
// intialize with focus on first element
modalElements[0].focus();
$(frame).on('keydown', function (e) {
modalElements = $(frame).find('a, button, input, [tabindex]:not([tabindex="-1"]), textarea').filter(':visible').filter(':not(:disabled)'); // for modals with dynamic content
if (e.which === 9) { // Tab
if (e.shiftKey) {
// On the first element, shift+tab goes to last
if (document.activeElement === modalElements[0]) {
e.preventDefault();
modalElements[modalElements.length - 1].focus();
}
} else {
// On the last element, tab goes to first
if (document.activeElement === modalElements[modalElements.length - 1]) {
e.preventDefault();
modalElements[0].focus();
}
}
}
});
};
UI.openCustomModal = function (content, opt) {
var frame = dialog.frame([
content
@ -626,9 +564,6 @@ define([
setTimeout(function () {
Notifier.notify();
});
addTabListener(frame);
return frame;
};
@ -807,20 +742,13 @@ define([
var $ok = $(ok).click(function (ev) { close(true, ev); });
var $cancel = $(cancel).click(function (ev) { close(false, ev); });
document.body.appendChild(frame);
addTabListener(frame);
listener = listenForKeys(function () {
// Only trigger OK if cancel is not focused
if (document.activeElement === $cancel[0]) {
return void $cancel.click();
}
$ok.click();
}, function () {
$cancel.click();
}, frame);
document.body.appendChild(frame);
setTimeout(function () {
Notifier.notify();
$(frame).find('.ok').focus();
@ -887,19 +815,15 @@ define([
};
var newCls2 = config.new ? 'new' : '';
$(originalBtn).addClass('cp-button-confirm-placeholder').addClass(newCls2).on('click keydown', function (e) {
if (e.type === 'click' || (e.type === 'keydown' && e.key === 'Enter')) {
$(originalBtn).addClass('cp-button-confirm-placeholder').addClass(newCls2).click(function (e) {
e.stopPropagation();
// If we have a validation function, continue only if it's true
if (config.validate && !config.validate()) { return; }
i = 1;
to = setTimeout(todo, INTERVAL);
$(originalBtn).hide().after(content);
$(button).focus();
}
});
return {
reset: function () {
done(false);
@ -953,14 +877,12 @@ define([
opts = opts || {};
var attributes = merge({
type: 'password',
tabindex: '0',
tabindex: '1',
autocomplete: 'one-time-code', // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#values
}, opts);
var input = h('input.cp-password-input', attributes);
var eye = h('span.fa.fa-eye.cp-password-reveal', {
tabindex: 0
});
var eye = h('span.fa.fa-eye.cp-password-reveal');
var $eye = $(eye);
var $input = $(input);
@ -977,8 +899,7 @@ define([
$input.focus();
});
} else {
Util.onClickEnter($eye, function (e) {
e.stopPropagation();
$eye.click(function () {
if ($eye.hasClass('fa-eye')) {
$input.prop('type', 'text');
$input.focus();
@ -1004,8 +925,7 @@ define([
title: text,
href: href,
target: "_blank",
'data-tippy-placement': "right",
'aria-label': Messages.help_genericMore //TBC XXX
'data-tippy-placement': "right"
});
return q;
};
@ -1144,21 +1064,6 @@ define([
}
};
UI.getNewIcon = function (type) {
var icon = h('i.fa.fa-file-text-o');
if (AppConfig.applicationsIcon && AppConfig.applicationsIcon[type]) {
icon = AppConfig.applicationsIcon[type];
var font = icon.indexOf('cptools') === 0 ? 'cptools' : 'fa';
if (type === 'fileupload') { type = 'file'; }
if (type === 'folderupload') { type = 'file'; }
if (type === 'link') { type = 'drive'; }
var appClass = ' cp-icon cp-icon-color-'+type;
icon = h('i', {'class': font + ' ' + icon + appClass});
}
return icon;
};
var $defaultIcon = $('<span>', {"class": "fa fa-file-text-o"});
UI.getIcon = function (type) {
var $icon = $defaultIcon.clone();
@ -1300,18 +1205,18 @@ define([
if (labelOpts.class) { labelOpts.class += ' cp-checkmark'; }
// Mark properties
var markOpts = { tabindex: 0, role: 'checkbox', 'aria-checked': checked, 'aria-labelledby': inputOpts.id + '-label' };
var markOpts = { tabindex: 0 };
$.extend(markOpts, opts.mark || {});
var input = h('input', inputOpts);
var $input = $(input);
var mark = h('span.cp-checkmark-mark', markOpts);
var $mark = $(mark);
var label = h('span.cp-checkmark-label', {id: inputOpts.id + '-label'}, labelTxt);
var label = h('span.cp-checkmark-label', labelTxt);
$mark.keydown(function (e) {
if ($input.is(':disabled')) { return; }
if (e.which === 32 || e.which === 13){
if (e.which === 32) {
e.stopPropagation();
e.preventDefault();
$input.prop('checked', !$input.is(':checked'));
@ -1323,10 +1228,8 @@ define([
if (!opts.labelAlt) { return; }
if ($input.is(':checked') !== checked) {
$(label).text(opts.labelAlt);
$mark.attr('aria-checked', 'true');
} else {
$(label).text(labelTxt);
$mark.attr('aria-checked', 'false');
}
});
@ -1364,7 +1267,7 @@ define([
$(mark).keydown(function (e) {
if ($input.is(':disabled')) { return; }
if (e.which === 13 || e.which === 32) {
if (e.which === 32) {
e.stopPropagation();
e.preventDefault();
if ($input.is(':checked')) { return; }

View file

@ -173,12 +173,8 @@ define([
var removeBtn, el;
if (config.remove) {
removeBtn = h('span.fa.fa-times');
$(removeBtn).attr('tabindex', '0');
$(removeBtn).on('click keydown', function(event) {
if (event.type === 'click' || (event.type === 'keydown' && event.key === 'Enter')) {
event.preventDefault();
$(removeBtn).click(function () {
config.remove(el);
}
});
}
@ -188,7 +184,6 @@ define([
'data-curve': data.curvePublic || '',
'data-name': name.toLowerCase(),
'data-order': i,
'tabindex': config.noSelect ? '-1' : '0',
style: 'order:'+i+';'
},[
avatar,
@ -236,13 +231,6 @@ define([
}
onSelect();
});
$div.on('keydown', '.cp-usergrid-user', function (e) {
if (e.which === 13) {
e.preventDefault();
e.stopPropagation();
$(this).trigger('click');
}
});
}
return {
@ -2657,7 +2645,7 @@ define([
var urlArgs = (Config.requireConf && Config.requireConf.urlArgs) || '';
var logo = h('img', { src: '/customize/CryptPad_logo.svg?' + urlArgs });
var fill1 = h('div.cp-creation-fill.cp-creation-logo',{ role: 'presentation' }, logo);
var fill1 = h('div.cp-creation-fill.cp-creation-logo', logo);
var fill2 = h('div.cp-creation-fill');
var $creation = $('<div>', { id: 'cp-creation', tabindex:1 });
$creationContainer.append([fill1, $creation, fill2]);
@ -4297,76 +4285,5 @@ define([
return UI.errorLoadingScreen(msg, false, false);
};
UIElements.makePalette = (maxColors, onSelect) => {
let palette = [''];
for (var i=1; i<=maxColors; i++) { palette.push('color'+i); }
let offline = false;
let selectedColor = '';
let container = h('div.cp-palette-container');
let $container = $(container);
var all = [];
palette.forEach(function (color, i) {
var $color = $(h('button.cp-palette-color.fa'));
all.push($color);
$color.addClass('cp-palette-'+(color || 'nocolor'));
$color.keydown(function (e) {
if (e.which === 13) {
e.stopPropagation();
e.preventDefault();
$color.click();
}
});
$color.click(function () {
if (offline) { return; }
if (color === selectedColor) { return; }
selectedColor = color;
$container.find('.cp-palette-color').removeClass('fa-check');
$color.addClass('fa-check');
onSelect(color, $color);
}).appendTo($container);
$color.keydown(e => {
if (e.which === 37) {
e.preventDefault();
if (i === 0) {
all[all.length - 1].focus();
} else {
all[i - 1].focus();
}
}
if (e.which === 39) {
e.preventDefault();
if (i === (all.length - 1)) {
all[0].focus();
} else {
all[i + 1].focus();
}
}
if (e.which === 9) {
if (e.shiftKey) {
all[0].focus();
return;
}
all[all.length - 1].focus();
}
});
});
container.disable = state => {
offline = !!state;
};
container.getValue = () => {
return selectedColor;
};
container.setValue = color => {
$container.find('.cp-palette-color').removeClass('fa-check');
let $color = $container.find('.cp-palette-'+(color || 'nocolor'));
$color.addClass('fa-check');
selectedColor = color;
};
return container;
};
return UIElements;
});

View file

@ -606,9 +606,6 @@
parseInt(h.slice(4,6), 16),
];
};
Util.rgbToHex = function (rgb) {
return `#${rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/).slice(1).map(n => parseInt(n, 10).toString(16).padStart(2, '0')).join('')}`;
};
Util.isSmallScreen = function () {
return window.innerHeight < 800 || window.innerWidth < 800;

View file

@ -2822,15 +2822,6 @@ define([
initFeedback(data.feedback);
}
if (data.edPublic) {
if (Array.isArray(Config.adminKeys) &&
Config.adminKeys.includes(data.edPublic)) {
// Doesn't provides extra-rights but may show
// additional warnings in the UI
localStorage.CP_admin = "1";
}
}
if (data.loggedIn) {
window.CP_logged_in = true;
}

View file

@ -1197,12 +1197,11 @@ define([
});
};
// `app`: true (force open with the app), false (force open in preview),
// `app`: true (force open wiht the app), false (force open in preview),
// falsy (open in preview if default is not using the app)
var defaultInApp = ['application/pdf'];
var openFile = function (el, isRo, app) {
// In anonymous drives, `el` already contains file data
var data = el.channel ? el : manager.getFileData(el);
var data = manager.getFileData(el);
if (data.static) {
if (data.href) {
@ -2247,7 +2246,6 @@ define([
$element.prepend(img);
$(img).addClass('cp-app-drive-element-grid cp-app-drive-element-thumbnail');
$(img).attr("draggable", false);
$(img).attr("role", "presentation");
addTitleIcon(element, $name);
} else {
common.displayThumbnail(href || data.roHref, data.channel, data.password, $element, function ($thumb) {

View file

@ -1,63 +0,0 @@
// SPDX-FileCopyrightText: 2024 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
define([
'optional!/extensions.js'
], (Extensions) => {
const ext = {};
ext.getExtensions = id => {
let e = ext[id];
if (!Array.isArray(e)) { e = []; }
return e;
};
if (!Array.isArray(Extensions) || !Extensions.length) { return ext; }
let all = Extensions.slice();
while(all.length) {
let current = all.splice(0, 3);
let f = current[0];
if (typeof(f) !== "function") {
continue;
}
let defaultLang = current[1];
let lang = current[2];
if (!Object.keys(lang).length && Object.keys(defaultLang).length) {
// If our language doesn't exists, use default
lang = defaultLang;
} else if (Object.keys(defaultLang).length) {
// Otherwise fill our language with missing keys
Object.keys(defaultLang).forEach(key => {
if (typeof(lang[key]) !== "undefined") { return; }
lang[key] = defaultLang[key];
});
}
lang._getKey = function (key, argArray) {
if (!lang[key]) { return '?'; }
var text = lang[key];
if (typeof(text) === 'string') {
return text.replace(/\{(\d+)\}/g, function (str, p1) {
if (typeof(argArray[p1]) === 'string' || typeof(argArray[p1]) === "number") {
return argArray[p1];
}
return '';
});
} else {
return text;
}
};
let currentExt = f(lang) || {};
Object.keys(currentExt).forEach(key => {
ext[key] = ext[key] || [];
Array.prototype.push.apply(ext[key], currentExt[key]); // concat in place
});
}
return ext;
});

View file

@ -367,13 +367,6 @@ define([
UI.log(Messages.saved);
});
});
$(addBtn).on('keydown', function () {
if (event.keyCode === 13) {
event.preventDefault();
event.stopPropagation();
$(addBtn).click();
}
});
var called = false;
redrawAll = function (reload) {
@ -466,9 +459,6 @@ define([
var setLock = function (locked) {
$(link).find('.cp-overlay').toggle(locked);
$(link).find('.cp-usergrid-user').attr('tabindex', locked ? -1 : 0);
$(link).find('.cp-usergrid-filter input').prop('disabled', locked);
$(link).find('.cp-access-add').prop('disabled', locked);
};
// Remove owner column
@ -724,13 +714,6 @@ define([
UI.log(Messages.saved);
});
});
$(addBtn).on('keydown', function () {
if (event.keyCode === 13) {
event.preventDefault();
event.stopPropagation();
$(addBtn).click();
}
});
var called = false;
redrawAll = function (reload) {
@ -1042,13 +1025,6 @@ define([
});
});
});
$(passwordOk).on('keydown', function (e) {
if (e.keyCode === 13) {
e.preventDefault();
e.stopPropagation();
$(passwordOk).click();
}
});
$d.append(changePass);
}
if (owned) {

View file

@ -385,13 +385,13 @@ define([
h('label', Messages.sharedFolders_share),
h('br'),
] : [
UI.createCheckbox('cp-share-embed', Messages.share_linkEmbed, false, { mark: {tabindex:0} }),
UI.createCheckbox('cp-share-embed', Messages.share_linkEmbed, false, { mark: {tabindex:1} }),
];
if (opts.static) { linkContent = []; }
linkContent.push(h('div.cp-spacer'));
linkContent.push(UI.dialog.selectableArea('', { id: 'cp-share-link-preview', tabindex: 0, rows:3}));
linkContent.push(UI.dialog.selectableArea('', { id: 'cp-share-link-preview', tabindex: 1, rows:3}));
// Show alert if the pad is password protected
if (opts.hasPassword) {
@ -553,7 +553,7 @@ define([
var embedContent = [
h('p', Messages.viewEmbedTag),
UI.dialog.selectableArea(opts.getEmbedValue(), { id: 'cp-embed-link-preview', tabindex: 0, rows: 3})
UI.dialog.selectableArea(opts.getEmbedValue(), { id: 'cp-embed-link-preview', tabindex: 1, rows: 3})
];
// Show alert if the pad is password protected
@ -611,24 +611,24 @@ define([
labelEdit = Messages.share_formEdit;
labelView = Messages.share_formView;
auditor = UI.createRadio('accessRights', 'cp-share-form', Messages.share_formAuditor, false, {
mark: {tabindex:0},
mark: {tabindex:1},
});
}
var burnAfterReading = (hashes.viewHash && canBAR) ?
UI.createRadio('accessRights', 'cp-share-bar', Messages.burnAfterReading_linkBurnAfterReading, false, {
mark: {tabindex:0},
mark: {tabindex:1},
label: {style: "display: none;"}
}) : undefined;
var rights = h('div.msg.cp-inline-radio-group', [
h('label',{ for: 'cp-share-editable-true' }, Messages.share_linkAccess),
h('div.radio-group',[
UI.createRadio('accessRights', 'cp-share-editable-false',
labelView, true, { mark: {tabindex:0} }),
labelView, true, { mark: {tabindex:1} }),
canPresent ? UI.createRadio('accessRights', 'cp-share-present',
Messages.share_linkPresent, false, { mark: {tabindex:1} }) : undefined,
UI.createRadio('accessRights', 'cp-share-editable-true',
labelEdit, false, { mark: {tabindex:0} }),
labelEdit, false, { mark: {tabindex:1} }),
auditor]),
burnAfterReading,
]);
@ -921,7 +921,7 @@ define([
var cb = Util.once(Util.mkAsync(_cb));
var linkContent = [
UI.dialog.selectableArea(opts.getLinkValue(), {
id: 'cp-share-link-preview', tabindex: 0, rows:2
id: 'cp-share-link-preview', tabindex: 1, rows:2
})
];

View file

@ -4,7 +4,6 @@
define([
'jquery',
'/api/config',
'/components/nthen/index.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
@ -14,7 +13,6 @@ define([
'/common/hyperscript.js',
], function(
$,
ApiConfig,
nThen,
UI,
UIElements,
@ -24,26 +22,17 @@ define([
h
) {
const Sidebar = {};
const keyToCamlCase = (key) => {
return key.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
Sidebar.create = function (common, app, $container) {
const $leftside = $(h('div#cp-sidebarlayout-leftside')).appendTo($container);
const $rightside = $(h('div#cp-sidebarlayout-rightside')).appendTo($container);
const sidebar = {
$leftside,
$rightside
};
const items = {};
Sidebar.blocks = function (app, common) {
let blocks = {};
// sframe-common shim
if (!common) {
common = {
openURL: url => {
window.open(url);
},
openUnsafeURL: url => {
window.open(ApiConfig.httpSafeOrigin + '/bounce/#' + encodeURIComponent(url));
}
};
}
let blocks = sidebar.blocks = {};
blocks.labelledInput = (label, input, inputBlock) => {
let uid = Util.uid();
let id = `cp-${app}-item-${uid}`;
@ -231,7 +220,9 @@ define([
return button;
};
const keyToCamlCase = (key) => {
return key.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
};
blocks.activeCheckbox = (data) => {
const state = data.getState();
const key = data.key;
@ -255,52 +246,34 @@ define([
return box;
};
return blocks;
};
Sidebar.create = function (common, app, $container) {
const $leftside = $(h('div#cp-sidebarlayout-leftside')).appendTo($container);
const $rightside = $(h('div#cp-sidebarlayout-rightside')).appendTo($container);
const sidebar = {
$leftside,
$rightside
};
const items = {};
sidebar.blocks = Sidebar.blocks(app, common);
sidebar.addItem = (key, get, options) => {
const safeKey = keyToCamlCase(key);
const div = h(`div.cp-sidebarlayout-element`, {
'data-item': key,
style: 'display:none;'
});
items[key] = div;
$rightside.append(div);
get((content) => {
if (content === false) {
delete items[key];
return void $(div).remove();
}
if (content === false) { return; }
options = options || {};
const title = options.noTitle ? undefined : h('label.cp-item-label', {
id: `cp-${app}-${key}`
}, options.title || Messages[`${app}_${safeKey}Title`] || key);
}, Messages[`${app}_${safeKey}Title`] || key);
const hint = options.noHint ? undefined : h('span.cp-sidebarlayout-description',
options.hint || Messages[`${app}_${safeKey}Hint`] || 'Coming soon...');
Messages[`${app}_${safeKey}Hint`] || 'Coming soon...');
if (hint && options.htmlHint) {
hint.innerHTML = Messages[`${app}_${safeKey}Hint`];
}
$(div).append(title).append(hint).append(content);
const div = h(`div.cp-sidebarlayout-element`, {
'data-item': key,
style: 'display:none;'
}, [
title,
hint,
content
]);
items[key] = div;
$rightside.append(div);
});
};
sidebar.hasItem = key => {
return !key || !!items[key];
};
sidebar.addCheckboxItem = (data) => {
const key = data.key;
let blocks = sidebar.blocks;
let box = blocks.activeCheckbox(data);
sidebar.addItem(key, function (cb) {
cb(box);
@ -352,7 +325,7 @@ define([
'data-category': key
}, [
icon,
category.name || Messages[`${app}_cat_${key}`] || key,
Messages[`${app}_cat_${key}`] || key,
]);
var $item = $(item).appendTo(container);
Util.onClickEnter($item, function () {

View file

@ -107,11 +107,9 @@ define([
var myOOId;
var sessionId = Hash.createChannelId();
var cpNfInner;
let integrationChannel;
var evOnPatch = Util.mkEvent();
var evOnSync = Util.mkEvent();
var evIntegrationSave = Util.mkEvent();
// This structure is used for caching media data and blob urls for each media cryptpad url
var mediasData = {};
@ -181,11 +179,15 @@ define([
});
};
const getNewUserIndex = function () {
const ids = content.ids || {};
const indexes = Object.values(ids).map((user) => user.index);
const maxIndex = Math.max(...indexes);
return maxIndex === -Infinity ? 1 : maxIndex+1;
var getUserIndex = function () {
var i = 1;
var ids = content.ids || {};
Object.keys(ids).forEach(function (k) {
if (ids[k] && ids[k].index && ids[k].index >= i) {
i = ids[k].index + 1;
}
});
return i;
};
var setMyId = function () {
@ -196,7 +198,7 @@ define([
myOOId = Util.createRandomInteger();
// f: function used in .some(f) but defined outside of the while
var f = function (id) {
return ids[id].ooid === myOOId;
return ids[id] === myOOId;
};
while (Object.keys(ids).some(f)) {
myOOId = Util.createRandomInteger();
@ -205,7 +207,7 @@ define([
var myId = getId();
ids[myId] = {
ooid: myOOId,
index: getNewUserIndex(),
index: getUserIndex(),
netflux: metadataMgr.getNetfluxId()
};
oldIds = JSON.parse(JSON.stringify(ids));
@ -315,10 +317,7 @@ define([
isCp: cp
}
}, function (err, h) {
if (!err) {
evOnSync.fire();
evIntegrationSave.fire();
}
if (!err) { evOnSync.fire(); }
cb(err, h);
});
},
@ -913,18 +912,6 @@ define([
});
};
const findUserByOOId = function(ooId) {
return Object.values(content.ids)
.find((user) => user.ooid === ooId);
};
const getMyOOIndex = function() {
const user = findUserByOOId(myOOId);
return user
? user.index
: content.ids.length; // Assign an unused id to read-only users
};
var getParticipants = function () {
var users = metadataMgr.getMetadata().users;
var i = 1;
@ -956,19 +943,19 @@ define([
isCloseCoAuthoring:false,
view: false
});
const myOOIndex = getMyOOIndex();
if (!myUniqueOOId) { myUniqueOOId = String(myOOId) + myOOIndex; }
i++;
if (!myUniqueOOId) { myUniqueOOId = String(myOOId) + i; }
p.push({
id: String(myOOId),
id: myUniqueOOId,
idOriginal: String(myOOId),
username: metadataMgr.getUserData().name || Messages.anonymous,
indexUser: myOOIndex,
indexUser: i,
connectionId: metadataMgr.getNetfluxId() || Hash.createChannelId(),
isCloseCoAuthoring:false,
view: false
});
return {
index: myOOIndex,
index: i,
list: p.filter(Boolean)
};
};
@ -1431,9 +1418,6 @@ define([
debug(obj, 'toOO');
chan.event('CMD', obj);
if (obj && obj.type === "saveChanges") {
evIntegrationSave.fire();
}
};
chan.on('CMD', function (obj) {
@ -1481,10 +1465,6 @@ define([
send({ type: "message" });
break;
case "saveChanges":
if (readOnly) {
return;
}
// If we have unsaved data before reloading for a checkpoint...
if (APP.onStrictSaveChanges) {
delete APP.unsavedLocks;
@ -1612,15 +1592,13 @@ define([
var x2tConvertData = function (data, fileName, format, cb) {
var sframeChan = common.getSframeChannel();
var editor = getEditor();
var fonts = editor && editor.FontLoader.fontInfos;
var files = editor && editor.FontLoader.fontFiles.map(function (f) {
var e = getEditor();
var fonts = e && e.FontLoader.fontInfos;
var files = e && e.FontLoader.fontFiles.map(function (f) {
return { 'Id': f.Id, };
});
var type = common.getMetadataMgr().getPrivateData().ooType;
const images = editor
? structuredClone(window.frames[0].AscCommon.g_oDocumentUrls.getUrls())
: {};
var images = (e && window.frames[0].AscCommon.g_oDocumentUrls.urls) || {};
// Fix race condition which could drop images sometimes
// ==> make sure each image has a 'media/image_name.ext' entry as well
@ -1631,7 +1609,7 @@ define([
});
// Add theme images
var theme = editor && window.frames[0].AscCommon.g_image_loader.map_image_index;
var theme = e && window.frames[0].AscCommon.g_image_loader.map_image_index;
if (theme) {
Object.keys(theme).forEach(function (url) {
if (!/^(\/|blob:|data:)/.test(url)) {
@ -1645,7 +1623,7 @@ define([
type: type,
fileName: fileName,
outputFormat: format,
images: (editor && window.frames[0].AscCommon.g_oDocumentUrls.urls) || {},
images: (e && window.frames[0].AscCommon.g_oDocumentUrls.urls) || {},
fonts: fonts,
fonts_files: files,
mediasSources: getMediasSources(),
@ -1859,11 +1837,6 @@ define([
}
delete APP.oldCursor;
}
if (integrationChannel) {
APP.onDocumentUnlock = () => {
integrationChannel.event('EV_INTEGRATION_READY');
};
}
}
delete APP.startNew;
@ -2078,15 +2051,6 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
if (blobUrl) {
delete downloadImages[name];
debug("CryptPad Image already loaded " + blobUrl);
// Fix: https://github.com/cryptpad/cryptpad/issues/1500
// Maybe OO was reloaded, but the CryptPad cache is still intact?
// -> Add the image to OnlyOffice again.
const documentUrls = window.frames[0].AscCommon.g_oDocumentUrls;
if (!(data.name in documentUrls.getUrls())) {
documentUrls.addImageUrl(data.name, blobUrl);
}
return void callback(blobUrl);
}
@ -2600,22 +2564,6 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
});
};
sframeChan.on('EV_INTEGRATION_DOWNLOADAS', function (format) {
console.error('DOWNLOAD AS RECEIVED');
var data = getContent();
x2tConvertData(data, "document.bin", format, function (xlsData) {
UI.removeModals();
if (xlsData) {
var blob = new Blob([xlsData], {type: "application/bin;charset=utf-8"});
if (integrationChannel) {
integrationChannel.event('EV_INTEGRATION_ON_DOWNLOADAS',
blob, { raw: true });
}
return;
}
UI.warn(Messages.error);
});
});
sframeChan.on('EV_OOIFRAME_REFRESH', function (data) {
// We want to get the "bin" content of a sheet from its json in order to download
// something useful from a non-onlyoffice app (download from drive or settings).
@ -2720,6 +2668,7 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
};
var onCheckpoint = function (cp) {
// We want to load a checkpoint:
console.log('XXX onCheckpoint', JSON.stringify(cp));
loadCp(cp);
};
var setHistoryMode = function (bool) {
@ -3168,73 +3117,6 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
UI.removeLoadingScreen();
};
let convertImportBlob = (blob, title) => {
new Response(blob).arrayBuffer().then(function (buffer) {
var u8Xlsx = new Uint8Array(buffer);
x2tImportData(u8Xlsx, title, 'bin', function (bin) {
if (!bin) {
return void UI.errorLoadingScreen(Messages.error);
}
var blob = new Blob([bin], {type: 'text/plain'});
var file = getFileType();
resetData(blob, file);
//saveToServer(blob, title);
Title.updateTitle(title);
UI.removeLoadingScreen();
});
});
};
if (privateData.integration) {
let cfg = privateData.integrationConfig || {};
common.openIntegrationChannel(APP.onLocal);
integrationChannel = common.getSframeChannel();
var integrationSave = function (cb) {
var ext = cfg.fileType;
var upload = Util.once(function (_blob) {
integrationChannel.query('Q_INTEGRATION_SAVE', {
blob: _blob
}, cb, {
raw: true
});
});
var data = getContent();
x2tConvertData(data, "document.bin", ext, function (xlsData) {
UI.removeModals();
if (xlsData) {
var blob = new Blob([xlsData], {type: "application/bin;charset=utf-8"});
upload(blob);
return;
}
UI.warn(Messages.error);
});
};
const integrationHasUnsavedChanges = function(unsavedChanges, cb) {
integrationChannel.query('Q_INTEGRATION_HAS_UNSAVED_CHANGES', unsavedChanges, cb);
};
var inte = common.createIntegration(integrationSave,
integrationHasUnsavedChanges);
if (inte) {
evIntegrationSave.reg(function () {
inte.changed();
});
}
integrationChannel.on('Q_INTEGRATION_NEEDSAVE', function (data, cb) {
integrationSave(function (obj) {
if (obj && obj.error) { console.error(obj.error); }
cb();
});
});
if (privateData.initialState) {
var blob = privateData.initialState;
let title = `document.${cfg.fileType}`;
console.error(blob, title);
return convertImportBlob(blob, title);
}
}
if (privateData.isNewFile && privateData.fromFileData) {
try {
(function () {

View file

@ -11,15 +11,12 @@ define([
'/common/sframe-common-outer.js'
], function (nThen, ApiConfig, DomReady, Hash, SFCommonO) {
var isIntegration = Boolean(window.CP_integration_outer);
var integration = window.CP_integration_outer || {};
// Loaded in load #2
var hash, href, version;
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var obj = SFCommonO.initIframe(waitFor, true, integration.pathname);
var obj = SFCommonO.initIframe(waitFor, true);
href = obj.href;
hash = obj.hash;
var parsed = Hash.parsePadUrl(href);
@ -27,14 +24,9 @@ define([
var opts = parsed.getOptions();
version = opts.versionHash;
}
if (isIntegration) {
href = integration.href;
hash = integration.hash;
}
}).nThen(function (/*waitFor*/) {
var addData = function (obj) {
let path = (integration && integration.pathname) || window.location.pathname;
obj.ooType = path.replace(/^\//, '').replace(/\/$/, '');
obj.ooType = window.location.pathname.replace(/^\//, '').replace(/\/$/, '');
obj.ooVersionHash = version;
obj.ooForceVersion = localStorage.CryptPad_ooVersion || "";
};
@ -162,21 +154,18 @@ define([
Utils.initUnsafeIframe(obj, cb);
});
};
SFCommonO.start({
hash: hash,
href: href,
type: 'oo',
useCreationScreen: true,
addData: addData,
addRpc: addRpc,
getPropChannels: getPropChannels,
messaging: true,
useCreationScreen: !isIntegration,
noDrive: true,
integration: isIntegration,
integrationUtils: integration.utils,
integrationConfig: integration.config || {},
initialState: integration.initialState || undefined
messaging: true
});
});
});

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -789,7 +789,8 @@ define([
ownedPads.forEach(function (c) {
var w = waitFor();
sem.take(function (give) {
sem.take(function (_give) {
var give = _give();
var otherOwners = false;
nThen(function (_w) {
// Don't check server metadata for blobs
@ -3014,8 +3015,6 @@ define([
// Make sure we have a valid user object before emitting cacheready
if (rt.proxy && !rt.proxy.drive) { return; }
returned.edPublic = rt.proxy.edPublic;
onCacheReady(clientId, function () {
if (typeof(cb) === "function") { cb(returned); }
onCacheReadyEvt.fire();
@ -3049,8 +3048,6 @@ define([
drive[Constants.oldStorageKey] = [];
}
*/
returned.edPublic = rt.proxy.edPublic;
// Drive already exist: return the existing drive, don't load data from legacy store
if (store.manager) {
// If a cache is loading, make sure it is complete before calling onReady
@ -3160,7 +3157,6 @@ define([
loadUniversal(Cursor, 'cursor', function () {});
loadUniversal(Integration, 'integration', function () {});
loadUniversal(Messenger, 'messenger', function () {});
loadOnlyOffice();
store.messenger = store.modules['messenger'];
// And now we're ready

View file

@ -1055,7 +1055,7 @@ define([
var removeClient = function (ctx, cId) {
var idx = ctx.clients.indexOf(cId);
if (idx !== -1) { ctx.clients.splice(idx, 1); }
ctx.clients.splice(idx, 1);
Object.keys(ctx.calendars).forEach(function (id) {
var cal = ctx.calendars[id];

View file

@ -6,8 +6,8 @@
importScripts('/components/requirejs/require.js');
window = self; // eslint-disable-line no-global-assign
localStorage = { // eslint-disable-line no-global-assign
window = self;
localStorage = {
setItem: function (k, v) { localStorage[k] = v; },
getItem: function (k) { return localStorage[k]; }
};

View file

@ -6,8 +6,8 @@
importScripts('/components/requirejs/require.js');
window = self; // eslint-disable-line no-global-assign
localStorage = { // eslint-disable-line no-global-assign
window = self;
localStorage = {
setItem: function (k, v) { localStorage[k] = v; },
getItem: function (k) { return localStorage[k]; }
};

View file

@ -6,8 +6,8 @@
importScripts('/components/requirejs/require.js');
window = self; // eslint-disable-line no-global-assign
localStorage = { // eslint-disable-line no-global-assign
window = self;
localStorage = {
setItem: function (k, v) { localStorage[k] = v; },
getItem: function (k) { return localStorage[k]; }
};

View file

@ -12,7 +12,7 @@ define([
var CURRENT_VERSION = X2T.CURRENT_VERSION = CurrentVersion.currentVersion;
var debug = function (str) {
//if (localStorage.CryptPad_dev !== "1") { return; }
if (localStorage.CryptPad_dev !== "1") { return; }
console.debug(str);
};
@ -65,7 +65,7 @@ define([
};
var getX2T = function (cb) {
// Perform the x2t conversion
require(['/common/onlyoffice/dist/x2t/x2t.js'], function() { // FIXME why does this fail without an access-control-allow-origin header?
require(['/common/onlyoffice/x2t/x2t.js'], function() { // FIXME why does this fail without an access-control-allow-origin header?
var x2t = window.Module;
if (x2tInitialized) {
debug("x2t runtime already initialized");

View file

@ -12,22 +12,12 @@ define([
OOCurrentVersion.currentVersion,
);
let availablePadTypes = AppConfig.availablePadTypes.filter(
(t) => ooEnabled || !OO_APPS.includes(t)
let availableTypes = AppConfig.availablePadTypes.filter(
(t) => ooEnabled || !OO_APPS.includes(t),
);
let availableTypes;
if (ApiConfig.appsToDisable) {
availableTypes = availablePadTypes.filter(value => !ApiConfig.appsToDisable.includes(value));
} else {
availableTypes = availablePadTypes;
}
var appsToSelect = availablePadTypes.filter(value => !['drive', 'teams', 'file', 'contacts', 'convert'].includes(value));
return {
availableTypes,
appsToSelect,
isAvailable: function (type) {
return availableTypes.includes(type);

View file

@ -648,8 +648,8 @@ define([
const integrationHasUnsavedChanges = function(unsavedChanges, cb) {
integrationChannel.query('Q_INTEGRATION_HAS_UNSAVED_CHANGES', unsavedChanges, cb);
};
var inte = common.createIntegration(integrationSave,
integrationHasUnsavedChanges);
var inte = common.createIntegration(onLocal, cpNfInner.chainpad,
integrationSave, integrationHasUnsavedChanges);
if (inte) {
integration = true;
evIntegrationSave.reg(function () {

View file

@ -176,10 +176,6 @@ define([
else { editor.execCommand("insertTab"); }
}
},
//remove focus from editor
"Esc": function () {
editor.display.input.blur();
},
"Shift-Tab": function () {
editor.execCommand("indentLess");
},

View file

@ -9,6 +9,8 @@ define([
module.create = function (
Common,
onLocal,
chainpad,
saveHandler,
unsavedChangesHandler) {

View file

@ -78,7 +78,6 @@ define([
requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req)));
$i.attr('allowfullscreen', 'true');
$i.attr('allow', 'clipboard-write');
$i.attr('title', 'iframe');
$('iframe-placeholder').after($i).remove();
// This is a cheap trick to avoid loading sframe-channel in parallel with the
@ -2077,16 +2076,6 @@ define([
cfg.integrationUtils.save(obj, cb);
}
});
sframeChan.on('EV_INTEGRATION_READY', function () {
if (cfg.integrationUtils && cfg.integrationUtils.onReady) {
cfg.integrationUtils.onReady();
}
});
sframeChan.on('EV_INTEGRATION_ON_DOWNLOADAS', function (obj) {
if (cfg.integrationUtils && cfg.integrationUtils.onDownloadAs) {
cfg.integrationUtils.onDownloadAs(obj);
}
});
sframeChan.on('Q_INTEGRATION_HAS_UNSAVED_CHANGES', function (obj, cb) {
if (cfg.integrationUtils && cfg.integrationUtils.onHasUnsavedChanges) {
cfg.integrationUtils.onHasUnsavedChanges(obj, cb);
@ -2100,15 +2089,6 @@ define([
integrationSave = function (cb) {
sframeChan.query('Q_INTEGRATION_NEEDSAVE', null, cb);
};
if (cfg.integrationUtils) {
if (cfg.integrationUtils.setDownloadAs) {
cfg.integrationUtils.setDownloadAs(format => {
sframeChan.event('EV_INTEGRATION_DOWNLOADAS', format);
});
}
}
}
if (cfg.messaging) {

View file

@ -34,7 +34,6 @@ define([
'/common/common-constants.js',
'/components/localforage/dist/localforage.min.js',
'/common/hyperscript.js',
'/common/extensions.js'
], function (
$,
ApiConfig,
@ -65,8 +64,7 @@ define([
Language,
Constants,
localForage,
h,
Ext
h
) {
// Chainpad Netflux Inner
var funcs = {};
@ -777,8 +775,6 @@ define([
return Util.checkRestrictedApp(app, AppConfig, ea, priv.plan, priv.loggedIn);
};
funcs.getExtensions = Ext.getExtensions;
funcs.mailbox = {};
Object.freeze(funcs);

View file

@ -1195,7 +1195,6 @@ MessengerUI, Messages, Pages, PadTypes) {
if (![13,32,46].includes(e.which)) { return; }
e.stopPropagation();
if (e.which === 46) {
$('body').find('.cp-dropdown-content li').first().focus();
return $(el).find('.cp-notification-dismiss').click();
}
$(el).find('.cp-notification-content').click();

View file

@ -366,85 +366,5 @@
"login_invalUser": "Изисква се потребителско име",
"login_confirm": "Потвърдете паролата",
"login_unhandledError": "Възникна неочаквана грешка :(",
"settings_exportWarning": "Забележка: този инструмент все още е в бета версия и може да има проблеми с мащабируемостта. За по-добра производителност се препоръчва да оставите този раздел на фокус - без превключване на друг раздел.",
"settings_export_reading": "Четене от вашият CryptDrive...",
"settings_export_download": "Изтегляне и декриптиране на вашите документи...",
"settings_export_compressing": "Данните се компресират...",
"settings_exportError": "Вижте грешките",
"settings_exportErrorEmpty": "Този документ не може да бъде експортиран (липсващо или невалидно съдържание).",
"settings_exportErrorOther": "Възникна грешка при опит за експортиране на този документ: {0}",
"settings_resetNewTitle": "Почистване на CryptDrive",
"settings_resetButton": "Премахване",
"settings_resetDone": "Вашият диск вече е празен!",
"settings_resetError": "Неправилен текст за потвърждение. Вашият CryptDrive не е променен.",
"settings_resetTipsAction": "Нулиране",
"settings_resetTips": "Съвети",
"settings_resetTipsButton": "Нулиране на наличните съвети в CryptDrive",
"settings_resetTipsDone": "Всички съвети вече са видими отново.",
"settings_thumbnails": "Миниатюри",
"settings_disableThumbnailsAction": "Деактивиране на създаването на миниатюри във вашия CryptDrive",
"settings_resetThumbnailsAction": "Почистване",
"settings_resetThumbnailsDescription": "Почистване на всички миниатюри на документи, съхранени във вашия браузър.",
"settings_resetThumbnailsDone": "Всички миниатюри са изтрити.",
"settings_importTitle": "Импортиране на последните документи в браузъра във вашия CryptDrive",
"settings_export_done": "Вашето сваляне приключи!",
"settings_exportErrorDescription": "Не успяхме да добавим следните документи в експортирания файл:",
"settings_exportErrorMissing": "Този документ липсва на нашите сървъри (изтекъл му е срокът на съхранение или е изтрит от собственика му)",
"settings_reset": "Премахване на всички файлове и папки от вашия CryptDrive",
"settings_resetPrompt": "С това действие ще премахне всички документи от вашето устройство.<br>Сигурни ли сте, че искате да продължите?<br>Напишете “<em>Обичам CryptPad</em>”, за да потвърдите.",
"settings_disableThumbnailsDescription": "Миниатюрите се създават автоматично и се съхраняват във вашия браузър, когато влезете в нов документ. Можете да деактивирате тази функция тук.",
"settings_autostoreTitle": "Съхранение на документи в CryptDrive",
"settings_import": "Импортиране",
"settings_importConfirm": "Сигурни ли сте, че искате да импортирате последните документи от този браузър в CryptDrive във потребителския ви акаунт?",
"settings_importDone": "Импортирането завърши",
"settings_autostoreHint": "<b>Автоматично</b> Всички документи, които ползвате, се съхраняват във вашия CryptDrive.<br><b>Ръчно (винаги се иска потвърждаване)</b> Ако все още не сте съхранили документ, ще бъдете попитани дали искате, да ги съхранявате във вашия CryptDrive.<br><b>Ръчно (никога не се иска потвърждаване)</b> Документите не се съхраняват автоматично във вашия CryptDrive. Опцията за съхраняването им ще бъде скрита.",
"settings_autostoreYes": "Автоматично",
"settings_autostoreNo": "Ръчно (никога не се иска потвърждаване)",
"settings_autostoreMaybe": "Ръчно (вининаги се иска потвърждаване)",
"settings_userFeedbackTitle": "Обратна връзка",
"settings_userFeedbackHint1": "CryptPad предоставя обратна връзка на сървъра, за да ни уведоми как да подобрим вашия опит. ",
"settings_userFeedbackHint2": "Съдържанието на вашите документи никога няма да бъде споделено със сървъра.",
"settings_userFeedback": "Активиране на обратната връзка с потребителя",
"settings_deleteTitle": "Изтриване на акаунта",
"settings_deleteHint": "Изтриването на акаунта е окончателно. Вашият CryptDrive и вашият списък с документи ще бъдат изтрити от сървъра. Останалите ви документи ще бъдат изтрити след 90 дни, ако никой друг не ги е съхранил в своя CryptDrive.",
"settings_deleteButton": "Изтриване на акаунта си",
"settings_deleteModal": "Споделете следната информация с вашия администратор на CryptPad, за да бъдат премахнати данните ви от сървъра им.",
"settings_publicSigningKey": "Публичен ключ за подписване",
"settings_deleted": "Вашият потребителски акаунт вече е изтрит. Натиснете OK, за да се върнете на началната страница.",
"settings_anonymous": "Не сте влезли. Настройките тук са специфични за този браузър.",
"settings_logoutEverywhereButton": "Излизане",
"settings_logoutEverywhereTitle": "Затваряне на отдалечената сесия",
"settings_logoutEverywhere": "Принудително излизане от всички други сесии",
"settings_driveDuplicateTitle": "Дубликати на притежавани документи",
"settings_driveDuplicateLabel": "Скриване на дубликатите",
"settings_codeIndentation": "Отстъп при писане на код (интервали)",
"settings_codeUseTabs": "Отстъп с помощта на раздели (вместо интервали)",
"settings_codeFontSize": "Размер на шрифта в редактора за код",
"settings_padWidth": "Максималната ширина на редактора",
"settings_padWidthLabel": "Намалете ширината на редактора",
"settings_padSpellcheckTitle": "Проверка на правописа",
"settings_logoutEverywhereConfirm": "Сигурен ли си? Ще трябва да влезеш на всичките си устройства.",
"settings_driveDuplicateHint": "Когато преместите вашите документи в споделена папка, копие от тях се запазва във вашия CryptDrive, за да се гарантира, контрола ви върху него. Можете да скриете дублираните файлове. Само споделената версия ще бъде видима, освен ако не бъде изтрита, в този случай оригиналът ще се покаже на предишното си място.",
"settings_padWidthHint": "Превключване между режим на страница (по подразбиране), който ограничава ширината на текстовия редактор, и използване на цялата ширина на екрана.",
"settings_padSpellcheckHint": "Тази опция ви позволява да активирате проверка на правописа в документи с форматиран текст. Правописните грешки ще бъдат подчертани в червено и ще трябва да задържите клавиша Ctrl или Meta, докато щраквате с десния бутон, за да видите опциите.",
"settings_padSpellcheckLabel": "Активиране на проверката на правописа в документи с форматиран текст",
"settings_padOpenLinkTitle": "Отваряне на връзки при първо щракване",
"settings_padOpenLinkHint": "С тази възможност можете да отваряте вградени връзки при щракване без изскачащ прозорец за показване",
"settings_padOpenLinkLabel": "Разрешаване на отварянето на директната връзка",
"settings_ownDriveTitle": "Актуализиране на профила",
"settings_ownDriveHint": "По-старите акаунти нямат достъп до най-новите функции поради технически причини. Безплатното обновяване ще активира текущите функции и ще подготви вашия CryptDrive за бъдещи обновявания.",
"settings_ownDriveButton": "Надграждане на профила ви",
"settings_ownDriveConfirm": "Надграждането на вашия акаунт може да отнеме известно време. Ще трябва да влезете отново през всичките си устройства. Сигурен ли сте?",
"settings_ownDrivePending": "Вашият профил се надгражда. Моля, не затваряйте и не презареждайте тази страница, докато процесът не приключи.",
"settings_changePasswordTitle": "Промяна на паролата",
"settings_changePasswordHint": "Променете паролата на профила си. Въведете текущата си парола и потвърдете новата парола, като я въведете два пъти.<br><b>Не можем да възстановим паролата ви, ако я забравите, така че бъдете много внимателни!</b>",
"settings_changePasswordButton": "Промяна на парола",
"settings_changePasswordCurrent": "Текуща парола",
"settings_changePasswordNew": "Нова парола",
"settings_changePasswordNewConfirm": "Потвърждаване на новата парола",
"settings_changePasswordConfirm": "Сигурни ли сте, че искате да промените паролата си? Ще трябва да влезете отново през всичките си устройства.",
"settings_changePasswordPending": "Вашата парола се обновява. Моля, не затваряйте и не презареждайте тази страница, докато процесът не приключи.",
"settings_cursorColorTitle": "Цвят на курсора",
"settings_changePasswordError": "Възникна неочаквана грешка. Ако не можете да влезете или да промените паролата си, свържете се с администраторите на CryptPad.",
"settings_changePasswordNewPasswordSameAsOld": "Вашата нова парола трябва да е различна от настоящата."
"settings_exportWarning": "Забележка: този инструмент все още е в бета версия и може да има проблеми с мащабируемостта. За по-добра производителност се препоръчва да оставите този раздел на фокус - без превключване на друг раздел."
}

View file

@ -534,8 +534,8 @@
"features_f_file1": "Importar i compartir fitxers",
"features_f_reg": "Avantatges dels usuaris/es registrats/des",
"features_f_reg_note": "Amb avantatges addicionals",
"features_f_support": "Assistència més ràpida",
"features_f_supporter_note": "Ajudeu el CryptPad a ser econòmicament viable i demostreu que el programari pot respectar la privadesa i ser finançat voluntàriament pels usuaris",
"features_f_support": "Suport més ràpid",
"features_f_supporter_note": "Ajudeu a CryptPad a ser financerament viable i demostreu que el programari pot respectar la privadesa i ser finançat voluntàriament pels usuaris",
"features_f_storage2_note": "De 5GB a 50GB segons el pla, s'ha augmentat el límit a {0}MB per a les baixades de fitxers",
"features_f_subscribe": "Subscriure's",
"features_f_supporter": "Donar suport a la defensa de la privadesa",
@ -544,7 +544,7 @@
"features_f_storage1": "Emmagatzematge personal ({0})",
"features_f_storage1_note": "Els documents emmagatzemats al vostre CryptDrive no s'eliminen a causa de la inactivitat",
"features_f_register": "Registreu-vos gratuïtament",
"features_f_support_note": "Resposta prioritària de l'equip d'administració per correu electrònic i sistema de tiquets integrat",
"features_f_support_note": "Resposta prioritària de l'equip d'administració per correu electrònic i sistema de tiquets de suport integrat",
"features_f_subscribe_note": "Cal un compte registrat per subscriure's",
"view": "mostra",
"four04_pageNotFound": "No hem pogut trobar la pàgina que cercàveu.",
@ -632,7 +632,7 @@
"form_text_text": "Text",
"form_default": "La teva pregunta aquí?",
"form_type_input": "Text",
"support_cat_abuse": "Informa d'un abús",
"support_cat_abuse": "Informa d'abús",
"form_type_radio": "Elecció",
"broadcast_translations": "Traduccions",
"form_type_sort": "Llista ordenada",
@ -740,7 +740,7 @@
"form_anonAnswer": "Les respostes a aquest formulari són anònimes",
"pad_settings_title": "Configuració del document",
"fc_openIn": "Obre a {0}",
"supportPage": "Assistència",
"supportPage": "Suport",
"fm_filterBy": "Filtre",
"undo": "Desfés",
"support_answer": "Respon",
@ -811,7 +811,7 @@
"autostore_settings": "Podeu activar l'emmagatzematge automàtic de documents a la pàgina <a>Configuració</a>.",
"autostore_store": "Desa",
"autostore_hide": "No ho desis",
"crowdfunding_button": "Doneu suport al CryptPad",
"crowdfunding_button": "Doneu suport a CryptPad",
"settings_codeSpellcheckLabel": "Activa la verificació ortogràfica a l'editor de codi",
"admin_activeSessionsTitle": "Connexions actives",
"admin_registeredTitle": "Usuaris registrats",
@ -894,7 +894,7 @@
"history_fastPrev": "Sessió d'edició anterior",
"history_userNext": "Autor següent",
"snaphot_title": "Instantània",
"unableToDisplay": "No s'ha pogut mostrar el document. Premeu Esc per tornar a carregar la pàgina. Si el problema persisteix, contacteu amb l'assistència.",
"unableToDisplay": "No s'ha pogut mostrar el document. Premeu Esc per tornar a carregar la pàgina. Si el problema persisteix, contacteu amb el suport.",
"admin_archiveButton": "Arxiu",
"register_notes_title": "Notes importants",
"settings_cacheTitle": "Memòria cau",
@ -960,7 +960,7 @@
"fm_link_name_placeholder": "El meu enllaç",
"form_anonName": "El vostre nom",
"fm_link_warning": "Avís: l'URL excedeix els 200 caràcters",
"admin_support_premium": "Tiquets prèmium:",
"admin_support_premium": "Tiquets de recompensa:",
"fm_link_invalid": "URL no vàlid",
"calendar_str_monthly": "{0} mesos",
"form_willClose": "Aquest formulari es tancarà el {0}",
@ -974,7 +974,7 @@
"calendar_rec_yearly_nth": "Cada {0} {1} de {2}",
"calendar_rec_monthly_nth": "Cada {0} {1} del mes",
"calendar_nth_last": "últim",
"admin_cat_support": "Assistència",
"admin_cat_support": "Suport",
"friendRequest_later": "Decideix més tard",
"history_restoreDrivePrompt": "Esteu segur que voleu substituir la versió actual de CryptDrive per la versió mostrada?",
"owner_add": "{0} vol que sigueu propietari del document <b>{1}</b>. Ho accepteu?",
@ -1076,7 +1076,7 @@
"support_cat_drives": "Unitat o equip",
"comments_edited": "Editat",
"comments_submit": "Envia",
"support_debuggingDataHint": "La informació següent s'inclou en els tiquets d'assistència que envieu. Cap d'ells permet als administradors accedir o desxifrar els vostres documents. Aquesta informació està xifrada de manera que només els administradors poden llegir-la.",
"support_debuggingDataHint": "La informació següent s'inclou en els tiquets de suport que envieu. Cap d'ells permet als administradors accedir o desencriptar els vostres documents. Aquesta informació està xifrada de manera que només els administradors poden llegir-la.",
"ui_ms": "mil·lisegons",
"autostore_pad": "document",
"ui_collapse": "Contrau",
@ -1170,24 +1170,24 @@
"autostore_saved": "El document s'ha desat correctament al vostre CryptDrive!",
"autostore_forceSave": "Desa el fitxer al vostre CryptDrive",
"autostore_notAvailable": "Heu de desar aquest document al vostre CryptDrive abans de poder utilitzar aquesta característica.",
"crowdfunding_popup_text": "<h3>Necessitem la vostra ajuda!</h3>Per assegurar que el CryptPad es desenvolupa activament, us convidem a donar suport al projecte a través de la pàgina OpenCollective, on podeu veure el nostre <b>Full de ruta</b> i <b>Objectius de finançament</b>.",
"admin_supportInitPrivate": "La vostra instància de CryptPad està configurada per utilitzar una bústia de correu d'assistència, però el vostre compte no té la clau privada correcta per accedir-hi. Utilitzeu el següent formulari per afegir o actualitzar la clau privada al vostre compte.",
"crowdfunding_popup_text": "<h3>Necessitem la vostra ajuda!</h3>Per assegurar que CryptPad es desenvolupa activament, us convidem a donar suport al projecte a través de la pàgina OpenCollective, on podeu veure el nostre <b>Full de ruta</b> i <b>Objectius de finançament</b>.",
"admin_supportInitPrivate": "La vostra instància de CryptPad està configurada per utilitzar una bústia de correu compatible, però el vostre compte no té la clau privada correcta per accedir-hi. Utilitzeu el següent formulari per afegir o actualitzar la clau privada al vostre compte.",
"admin_supportAddError": "Clau privada no vàlida",
"admin_supportInitTitle": "Inicialització de la bústia d'assistència",
"admin_supportInitHint": "Podeu configurar una bústia d'assistència per tal de donar als usuaris de la vostra instància de CryptPad una manera de contactar de manera segura si tenen algun problema amb el seu compte.",
"admin_supportListTitle": "Bústia de correu d'assistència",
"admin_supportListHint": "Aquesta és la llista de tiquets enviats pels usuaris a la bústia d'assistència. Tots els administradors poden veure els missatges i les seves respostes. No es pot tornar a obrir un tiquet tancat. Només podeu suprimir (amagar) tiquets tancats i els tiquets eliminats encara són visibles per altres administradors.",
"support_disabledTitle": "L'assistència no està activada",
"support_disabledHint": "Aquesta instància de CryptPad encara no està configurada per a utilitzar un formulari d'assistència.",
"admin_supportInitTitle": "Permet la inicialització de la bústia de correu",
"admin_supportInitHint": "Podeu configurar una bústia d'assistència per tal de donar als usuaris de la vostra instància de CryptPad una manera de contactar-vos de manera segura si tenen algun problema amb el seu compte.",
"admin_supportListTitle": "Bústia de correu de suport",
"admin_supportListHint": "Aquesta és la llista de tiquets enviats pels usuaris a la bústia de suport. Tots els administradors poden veure els missatges i les seves respostes. No es pot tornar a obrir un bitllet tancat. Només podeu eliminar (amagar) tiquets tancats i els tiquets eliminats encara són visibles per altres administradors.",
"support_disabledTitle": "El suport no està habilitat",
"support_disabledHint": "Aquesta instància de CryptPad encara no està configurada per utilitzar un formulari de suport.",
"support_cat_new": "Tiquet nou",
"support_formTitle": "Tiquet nou",
"support_formTitleError": "Error: el títol està buit",
"support_formContentError": "Error: el contingut està buit",
"support_cat_tickets": "Tiquets existents",
"support_listTitle": "Tiquets d'assistència",
"support_listHint": "Aquesta és la llista d'entrades enviades als administradors i les seves respostes. No es pot tornar a obrir un tiquet tancat, però es pot fer un de nou. Podeu amagar els bitllets que s'han tancat.",
"support_listTitle": "Tiquets de suport",
"support_listHint": "Aquesta és la llista d'entrades enviades als administradors i les seves respostes. No es pot tornar a obrir un bitllet tancat, però es pot fer un de nou. Podeu amagar els bitllets que s'han tancat.",
"support_close": "Tanca el tiquet",
"support_remove": "Suprimeix el tiquet",
"support_remove": "Elimina el tiquet",
"support_showData": "Mostra/oculta les dades de l'usuari",
"support_from": "<b>De:</b> {0}",
"support_closed": "Aquest tiquet s'ha tancat",
@ -1198,7 +1198,7 @@
"notifications_cat_pads": "Compartit amb mi",
"notifications_cat_archived": "Història",
"notifications_dismissAll": "Descarta-ho tot",
"support_notification": "Un administrador ha respost al vostre tiquet d'assistència",
"support_notification": "Un administrador ha respost al vostre tiquet de suport",
"team_title": "Equip: {0}",
"team_quota": "Límit d'emmagatzematge del vostre equip",
"settings_codeBrackets": "Tanca automàticament els claudàtors",
@ -1412,7 +1412,7 @@
"admin_broadcastButton": "Envia",
"admin_broadcastActive": "Missatge actiu",
"admin_broadcastCancel": "Suprimeix el missatge",
"settings_deleteWarning": "Avís: esteu subscrit a un pla prèmium (pagat o donat per un altre usuari). Cancel·leu el vostre pla abans d'esborrar el vostre compte, ja que no serà possible fer-ho sense contactar amb l'assistència un cop el vostre compte hagi estat eliminat.",
"settings_deleteWarning": "Avís: esteu subscrit a un pla premium (pagat o donat per un altre usuari). Cancel·leu el vostre pla abans d'esborrar el vostre compte, ja que no serà possible sense contactar amb el suport un cop el vostre compte hagi estat eliminat.",
"settings_deleteContinue": "Suprimeix el meu compte",
"oo_cantMigrate": "Aquest full excedeix la mida màxima de càrrega i és massa gran per a ser migrat.",
"calendar_import_temp": "Importa aquest calendari",
@ -1426,13 +1426,13 @@
"reminder_now": "<b>{0}</b> ha començat",
"reminder_minutes": "<b>{0}</b> començarà en {1} minut(s)",
"reminder_time": "<b>{0}</b> avui a {1}",
"oo_conversionSupport": "El navegador no pot gestionar la conversió a i des dels formats ofimàtics. Suggerim que utilitzeu una versió recent del Firefox o Chrome.",
"oo_conversionSupport": "El navegador no pot gestionar la conversió a i des dels formats ofimàtics. Suggerim utilitzar una versió recent del Firefox o Chrome.",
"oo_importBin": "Feu clic a D'acord per a importar el format .bin intern de CryptPad.",
"admin_emailTitle": "Correu electrònic de contacte de l'administrador",
"admin_emailHint": "Establiu aquí el correu electrònic de contacte per a la vostra instància",
"admin_supportPrivTitle": "Clau privada de la bústia de correu d'assistència",
"admin_supportInitGenerate": "Genera claus d'assistència",
"admin_supportPrivHint": "Mostra la clau privada que els altres administradors necessitaran per poder veure els tiquets d'assistència. Es mostrarà al panell d'administració un formulari per introduir aquesta clau.",
"admin_supportPrivTitle": "Dona suport a la clau privada de la bústia",
"admin_supportInitGenerate": "Genera claus de suport",
"admin_supportPrivHint": "Mostra la clau privada que els altres administradors hauran de veure els tiquets de suport. Es mostrarà un formulari per introduir aquesta clau al seu plafó d'administració.",
"admin_supportPrivButton": "Mostra la clau",
"share_formEdit": "Autor",
"form_sort_hint": "Arrossegueu aquests elements des de la majoria (1) fins al mínim ({0}) preferit.",
@ -1499,7 +1499,7 @@
"form_answerAs": "Respon com a",
"ui_expand": "Expandeix",
"form_totalResponses": "Total de respostes: {0}",
"support_premiumPriority": "Els usuaris prèmium ajuden a millorar la usabilitat de CryptPad i es beneficien de respostes prioritzades als seus tiquets d'assistència.",
"support_premiumPriority": "Els usuaris premium ajuden a millorar la usabilitat de CryptPad i es beneficien de respostes prioritzades als seus bitllets de suport.",
"support_premiumLink": "Mostra les opcions de subscripció",
"toolbar_collapse": "Redueix la barra d'eines",
"toolbar_expand": "Expandeix la barra d'eines",
@ -1528,15 +1528,15 @@
"admin_archiveNote": "Nota",
"admin_descriptionHint": "El text descriptiu mostrat per aquesta instància a la llista d'instàncies públiques a cryptpad.org",
"footer_source": "Codi font",
"support_warning_prompt": "Trieu la categoria més rellevant per al vostre problema. Això ajuda els administradors a prioritzar i proporciona més suggeriments per a quina informació proporcionar",
"support_warning_account": "Tingueu en compte que els administradors no poden restablir les contrasenyes. Si heu perdut les credencials al vostre compte, però encara hi esteu connectat, podeu <a>migrar les vostres dades a un compte nou</a>",
"support_warning_drives": "Tingueu en compte que els administradors no poden identificar carpetes i documents pel seu nom. Per a carpetes compartides, proporcioneu un <a>identificador de document </a>",
"support_warning_document": "Especifiqueu quin tipus de document està causant el problema i proporcioneu un <a>identificador de document </a> o un enllaç",
"support_warning_prompt": "Trieu la categoria més rellevant per al vostre problema. Això ajuda als administradors a trigar i proporciona més suggeriments per a quina informació proporcionar",
"support_warning_account": "Tingueu en compte que els administradors no poden restablir les contrasenyes. Si heu perdut les credencials al vostre compte però encara hi esteu connectat, podeu <a>migrar les vostres dades a un compte nou</a>",
"support_warning_drives": "Tingueu en compte que els administradors no poden identificar carpetes i documents per nom. Per a carpetes compartides, proporcioneu un identificador de document <a></a>",
"support_warning_document": "Especifiqueu quin tipus de document està causant el problema i proporcioneu un identificador de document <a></a> o un enllaç",
"calendar_list_end": "{0} o {1}",
"calendar_rec_every_date": "Cada {0}",
"support_warning_bug": "Especifiqueu en quin navegador es produeix el problema i si hi ha instal·lades extensions. Proporcioneu el màxim detall possible sobre el problema i els passos necessaris per reproduir-lo",
"support_warning_abuse": "Informeu del contingut que viola les <a>condicions del servei</a>. Proporcioneu enllaços als documents o perfils d'usuari infractors i descriviu com estan violant els termes. Qualsevol informació addicional sobre el context en què heu descobert el contingut o el comportament pot ajudar els administradors a prevenir futures violacions",
"support_warning_other": "Quina és la naturalesa de la vostra consulta? Proporcioneu tanta informació rellevant com us sigui possible per a facilitar-nos la gestió del problema",
"support_warning_other": "Quina és la naturalesa de la seva consulta? Si us plau, proporcioni la màxima informació rellevant possible per a facilitar-nos que abordem la seva qüestió ràpidament",
"support_cat_document": "Document",
"ui_openDirectly": "Aquesta funcionalitat no està disponible si el CryptPad està incrustat en un altre lloc web. Voleu obrir el document en una pestanya nova?",
"support_cat_debugging": "Depura les dades",
@ -1571,7 +1571,7 @@
"form_editable_on": "Una vegada i edita",
"chrome68": "Sembla que esteu utilitzant el navegador Chrome o Chromium versió 68. Conté un error que fa que la pàgina es torni completament blanca després d'uns segons o que la pàgina no respongui als clics. Per a solucionar aquest problema, podeu canviar a una altra pestanya i tornar enrere, o intentar desplaçar-vos per la pàgina. Aquest error s'ha de corregir en la versió següent del navegador.",
"admin_updateLimitHint": "Forçar una actualització dels límits d'emmagatzematge dels usuaris es pot fer en qualsevol moment, però només és necessari en cas d'error",
"admin_supportInitHelp": "El servidor encara no està configurat per tenir una bústia de correu d'assistència. Si voleu que una bústia d'assistència rebi missatges dels vostres usuaris, hauríeu de demanar a l'administrador del servidor que executi l'script ubicat a «./scripts/generate-admin-keys.js», després deseu la clau pública al fitxer «config.js» i envieu la clau privada.",
"admin_supportInitHelp": "El servidor encara no està configurat per tenir una bústia de correu compatible. Si voleu que una bústia de suport rebi missatges dels vostres usuaris, hauríeu de demanar a l'administrador del servidor que executi l'script ubicat a «./scripts/generate-admin-keys.js», després deseu la clau pública al fitxer «config.js» i envieu la clau privada.",
"support_formHint": "Utilitzeu aquest formulari per contactar amb seguretat amb els administradors sobre problemes i preguntes.<br>Tingueu en compte que alguns problemes/preguntes ja es poden abordar a la <a>Guia d'usuari de CryptPad</a>. No creeu un tiquet nou si ja teniu un tiquet obert sobre el mateix problema. En canvi, responeu al vostre missatge original amb qualsevol informació addicional.",
"requestEdit_confirm": "{1} ha demanat la possibilitat d'editar el document <b>{0}</b>. Voleu concedir l'accés?",
"owner_removedPending": "{0} ha cancel·lat la vostra oferta de propietat de <b>{1}</b>",
@ -1666,56 +1666,5 @@
"status": "Pàgina d'estat",
"admin_diskUsageWarning": "Utilitzeu-ho amb precaució! Depenent de la mida de les dades emmagatzemades a la instància, generar aquest informe pot consumir tota la memòria disponible al servidor i originar una fallada.",
"dph_pad_pw": "El document s'ha protegit amb una contrasenya nova",
"dph_sf_destroyed_team": "Un propietari ha destruït la carpeta compartida <b>{0}</b> a la unitat de l'equip <b>{1}</b>",
"admin_invitationEmail": "Adreça de l'usuari",
"admin_registrationSsoTitle": "Tanca el registre per SSO",
"admin_invitationCreate": "Crea un enllaç d'invitació",
"admin_invitationTitle": "Enllaços d'invitació",
"admin_invitationLink": "Enllaç d'invitació",
"admin_invitationDeleteConfirm": "Segur que voleu eliminar aquesta invitació?",
"admin_usersTitle": "Directori d'usuari",
"admin_invitationCopy": "Copia l'enllaç",
"admin_invitationAlias": "Nom d'usuari",
"admin_usersRemove": "Suprimeix",
"admin_usersAdd": "Afegeix l'usuari conegut",
"admin_usersRemoveConfirm": "Segur que voleu suprimir aquest usuari del directori? Encara podrà accedir i utilitzar el seu compte.",
"admin_storeSsoLabel": "Desa automàticament els usuaris SSO",
"register_invalidToken": "L'enllaç d'invitació no és vàlid",
"ssoauth_header": "Contrasenya CryptPad",
"ssoauth_form_hint_login": "Introduïu la contrasenya CryptPad",
"duplicate": "Duplica",
"calendar_desc": "Descripció",
"calendar_description": "Descripció: {0}{1}",
"sso_login_description": "Inicieu la sessió amb",
"sso_register_description": "Registreu-vos amb",
"kanban_showTags": "Mostra totes les etiquetes",
"kanban_hideTags": "Mostra menys etiquetes",
"admin_forcemfaTitle": "Autenticació de dos factors obligatòria",
"admin_forcemfaHint": "Es demanarà a tots els usuaris de la instància que estableixin una autenticació de dos factors per a iniciar la sessió.",
"calendar_rec_change_first": "S'està movent el primer esdeveniment de la repetició a un calendari diferent. També es mouran tots els esdeveniments repetits.",
"admin_logoTitle": "Logotip personalitzat",
"admin_cat_security": "Seguretat",
"admin_cat_customize": "Personalitza",
"admin_colorTitle": "Color d'èmfasi",
"admin_logoHint": "SVG, PNG o JPG, mida màxima 200 KB",
"admin_logoButton": "Puja el logotip",
"admin_colorHint": "Canvieu el color d'èmfasi de la instància del CryptPad. Comproveu que el text i els botons són llegibles amb prou contrast als temes clars i foscos.",
"admin_logoRemoveButton": "Restableix el predefinit",
"admin_colorCurrent": "Color d'èmfasi actual",
"admin_supportDisabled": "S'ha desactivat el sistema d'assistència.",
"admin_colorChange": "Canvia el color",
"admin_colorPick": "Trieu un color",
"admin_colorPreview": "Previsualització",
"admin_supportSetupHint": "Creeu o actualitzeu les claus d'assistència.",
"admin_supportSetupTitle": "Inicialitza l'assistència",
"admin_supportEnabled": "S'ha activat el sistema d'assistència.",
"admin_supportInit": "Inicialitza el help-desk en aquesta instància",
"admin_cat_users": "Directori d'usuari",
"calendar_rec_change": "S'està movent un esdeveniment repetit a un calendari diferent. Només podeu aplicar el canvi a aquest esdeveniment, o a tots els repetits.",
"loading_mfa_required": "Cal autenticació de dos factors en aquesta instància. Actualitzeu el vostre compte fent servir una aplicació d'autenticació i el formulari de sota.",
"admin_invitationHint": "Els enllaços d'invitació creen un compte cadascú, fins i tot si el registre es troba tancat. El nom d'usuari i l'adreça electrònica només són per poder identificar-vos. El CryptPad no enviarà per correu l'enllaç d'invitació (ni cap altra cosa), copieu-lo i envieu-lo fent servir el canal segur que trieu.",
"admin_usersHint": "Llista dels comptes coneguts d'aquesta instància. Seleccioneu a continuació per a afegir comptes automàticament, o introduïu la informació manualment amb el formulari.",
"admin_usersBlock": "URL de blocatge d'inici de sessió de l'usuari (opcional)",
"ssoauth_form_hint_register": "Afegiu una contrasenya CryptPad per a més seguretat, o deixeu-la buida i continueu. Si no afegiu una contrasenya, els administradors de la instància tindran a l'abast les claus que protegeixen les vostres dades.",
"admin_storeInvitedLabel": "Desa automàticament els usuaris convidats"
"dph_sf_destroyed_team": "Un propietari ha destruït la carpeta compartida <b>{0}</b> a la unitat de l'equip <b>{1}</b>"
}

View file

@ -607,8 +607,8 @@
"admin_activeSessionsHint": "Anzahl aktiver Websocket-Verbindungen (und verbundener IP-Adressen)",
"admin_activePadsTitle": "Aktive Dokumente",
"admin_activePadsHint": "Anzahl der Dokumente, die gerade angesehen oder bearbeitet werden",
"admin_registeredTitle": "Nutzer und Team-Drives",
"admin_registeredHint": "Anzahl aktiver Drives auf deiner Instanz",
"admin_registeredTitle": "Registrierte Nutzer",
"admin_registeredHint": "Anzahl der auf deiner Instanz registrierten Nutzer",
"admin_updateLimitTitle": "Nutzer-Quotas aktualisieren",
"admin_updateLimitHint": "Das Erzwingen einer Aktualisierung der Speicherbegrenzungen für Nutzer ist jederzeit möglich, aber nur im Fehlerfall notwendig",
"admin_updateLimitButton": "Quotas aktualisieren",
@ -1019,7 +1019,7 @@
"admin_getlimitsTitle": "Individuelle Regeln",
"admin_limit": "Aktuelle Begrenzung: {0}",
"admin_defaultlimitHint": "Speicherplatzbegrenzung für CryptDrives (Benutzer und Teams), sofern keine individuelle Regel festgelegt wurde",
"admin_registrationHint": "Besucher der Instanz können keine Accounts erstellen. Einladungen können von Administratoren erstellt werden.",
"admin_registrationHint": "Erlaube keine neuen Benutzerregistrierungen",
"admin_cat_quota": "Speicher für Benutzer",
"admin_invalLimit": "Wert für Speicherplatzbegrenzung ist ungültig",
"admin_limitSetNote": "Notiz",
@ -1668,7 +1668,7 @@
"admin_diskUsageWarning": "Mit Vorsicht zu verwenden! Je nach Größe der auf der Instanz gespeicherten Daten kann die Erstellung dieses Berichts den gesamten auf dem Server verfügbaren Speicher verbrauchen und zu einem Absturz führen.",
"dph_sf_destroyed": "Geteilter Ordner <b>{0}</b> wurde vom Eigentümer zerstört",
"calendar_desc": "Beschreibung",
"admin_forcemfaHint": "Alle Benutzer dieser Instanz werden aufgefordert, eine Zwei-Faktor-Authentifizierung für die Anmeldung bei ihrem Account einzurichten. Beachte, dass bestehende Benutzer nicht in der Lage sein werden, ihren Account weiterhin zu nutzen, ohne eine TOTP-Anwendung einzurichten.",
"admin_forcemfaHint": "Alle Benutzer dieser Instanz werden aufgefordert, eine Zwei-Faktor-Authentifizierung für die Anmeldung bei ihrem Account einzurichten.",
"ssoauth_form_hint_register": "Füge ein CryptPad-Passwort für zusätzliche Sicherheit hinzu oder lasse es leer und fahre fort. Wenn du kein Passwort hinzufügst, können die Administratoren der Instanz auf die Schlüssel zum Schutz deiner Daten zugreifen.",
"ssoauth_form_hint_login": "Bitte gib dein CryptPad-Passwort ein",
"loading_mfa_required": "Auf dieser Instanz ist eine Zwei-Faktor-Authentifizierung erforderlich. Bitte aktualisiere deinen Account mit einer Authentifizierungs-App und dem folgenden Formular.",
@ -1763,22 +1763,5 @@
"support_movePending": "In das Archiv verschieben",
"support_cat_legacy": "Alte Daten",
"support_legacyTitle": "Alte Support-Daten ansehen",
"support_userNotification": "Neues Support-Ticket oder neue Antwort: {0}",
"install_token": "Installations-Token",
"install_header": "Installation",
"admin_appSelection": "Konfiguration der Anwendungen",
"install_launch": "Einrichtung der Instanz",
"install_instance": "Erstelle den ersten Admin-Account und fahre dann mit der Anpassung dieser Instanz fort",
"admin_appsTitle": "Anwendungen der Instanz",
"onboarding_save_error": "Einige Einstellungen konnten nicht korrekt gespeichert werden. Bitte besuche den Administrationsbereich, um die Werte zu überprüfen.",
"admin_appsHint": "Wähle aus, welche Anwendungen auf dieser Instanz aktiviert werden sollen.",
"admin_cat_apps": "Anwendungen",
"admin_onboardingNameTitle": "Willkommen auf deiner CryptPad-Instanz",
"install_notes": "<ul class=\"cp-notes-list\"><li>Erstelle auf dieser Seite deinen ersten Administrator-Account. Administratoren verwalten die Einstellungen der Instanz einschließlich der Speicherplatzkontingente und haben Zugriff auf die Moderationswerkzeuge.</li><li>Dein Passwort ist der geheime Schlüssel, der alle deine Dokumente und Administratorrechte auf dieser Instanz verschlüsselt. <span class=\"red\">Wenn du es verlierst, gibt es keine Möglichkeit, deine Daten wiederherzustellen.</span></li><li>Wenn du einen gemeinsam genutzten Computer verwendest, <span class=\"red\">denke daran, dich abzumelden,</span> wenn du fertig bist. Wird das Browserfenster einfach geschlossen, bleibt dein Account zugänglich.</li></ul>",
"onboarding_upload": "Logo auswählen",
"admin_onboardingOptionsTitle": "Einstellungen der Instanz",
"admin_onboardingNamePlaceholder": "Name der Instanz",
"admin_onboardingDescPlaceholder": "Beschreibungstext für die Instanz",
"admin_onboardingOptionsHint": "Bitte wähle die geeignete Option für deine Instanz.<br>Diese Einstellungen können später im Administrationsbereich geändert werden.",
"admin_onboardingNameHint": "Bitte wähle einen Namen, eine Beschreibung, eine Akzentfarbe und ein Logo (alle Angaben sind optional)"
"support_userNotification": "Neues Support-Ticket oder neue Antwort: {0}"
}

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