diff --git a/.eslintignore b/.eslintignore index 91c021b650..8a12978566 100644 --- a/.eslintignore +++ b/.eslintignore @@ -7,6 +7,7 @@ www/components/ www/bower_components/ www/common/onlyoffice/dist www/common/onlyoffice/x2t +onlyoffice-dist/ www/scratch www/accounts @@ -15,6 +16,8 @@ www/accounts www/worker www/todo +#lib/plugins/ + www/common/hyperscript.js www/pad/wysiwygarea-plugin.js diff --git a/.eslintrc.js b/.eslintrc.js index 523d0fb263..209a4718e5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -49,13 +49,10 @@ 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'], } }; diff --git a/.github/ISSUE_TEMPLATE/bug_resolution.yml b/.github/ISSUE_TEMPLATE/bug_resolution.yml index 9016f70ebb..da1a7c9d56 100644 --- a/.github/ISSUE_TEMPLATE/bug_resolution.yml +++ b/.github/ISSUE_TEMPLATE/bug_resolution.yml @@ -89,6 +89,8 @@ body: label: Version description: What version of CryptPad are you running? options: + - 2024.6.0 + - 2024.3.1 - 2024.3.0 - 5.7.0 - 5.6.0 @@ -100,8 +102,6 @@ body: - 5.2.0 - 5.1.0 - 5.0.0 - - 4.14.1 - - 4.14.0 - Other validations: required: true diff --git a/.lesshintrc b/.lesshintrc deleted file mode 100644 index 56a5f680d1..0000000000 --- a/.lesshintrc +++ /dev/null @@ -1,60 +0,0 @@ -{ - "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 } -} diff --git a/.reuse/dep5 b/.reuse/dep5 index f69423e84e..bd6e250833 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -25,7 +25,7 @@ Files: .jshintrc Copyright: 2023 XWiki CryptPad Team and contributors License: AGPL-3.0-or-later -Files: .lesshintrc +Files: .stylelintrc.js Copyright: 2023 XWiki CryptPad Team and contributors License: AGPL-3.0-or-later @@ -156,4 +156,4 @@ License: AGPL-3.0-or-later Files: www/common/onlyoffice/x2t/* Copyright: Ascensio System Limited 2010-2022 -License: AGPL-3.0-or-later \ No newline at end of file +License: AGPL-3.0-or-later diff --git a/.stylelintrc.js b/.stylelintrc.js new file mode 100644 index 0000000000..a1ea1103f6 --- /dev/null +++ b/.stylelintrc.js @@ -0,0 +1,43 @@ +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 + } +}; diff --git a/CHANGELOG.md b/CHANGELOG.md index 11d6e20452..be2161b236 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,80 @@ SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and cont SPDX-License-Identifier: AGPL-3.0-or-later --> +# 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) + - 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 @@ -50,9 +124,9 @@ npm run install:components ## Goals -This release is aimed at instance administrators with new features and changes in the way CryptPad is installed. This marks a major release and we are also taking the opportunity to change the way we number CryptPad versions, moving to a date-based format (from semver to [calver](https://calver.org/)). For full details on the reasons behind this change please read [our March 2024 status blog post](https://blog.cryptpad.org/2024/03/29/status-2024-03/). The short version is that this is our Spring 2024 release with number `2024.3.0` and that we are aiming for the following schedule going forward, sticking to the `YYYY.MM.micro` format: +This release is aimed at instance administrators with new features and changes in the way CryptPad is installed. This marks a major release and we are also taking the opportunity to change the way we number CryptPad versions, moving to a date-based format (from semver to [calver](https://calver.org/)). For full details on the reasons behind this change please read [our March 2024 status blog post](https://blog.cryptpad.org/2024/03/29/status-2024-03/). The short version is that this is our Spring 2024 release with number `2024.3.0` and that we are aiming for the following schedule going forward, sticking to the `YYYY.MM.micro` format: -- 💐 Spring `2024.3.0` +- 💐 Spring `2024.3.0` - 🌻 Summer `2024.6.0` end June 2024 - 🍁 Autumn `2024.9.0` end September 2024 - ❄️ Winter `2024.12.0` end December 2024 @@ -102,7 +176,7 @@ Starting with this version, OnlyOffice applications (Sheets, Document, Presentat ``` For Docker users that want to use OnlyOffice, please read our updated [Docker installation guide](https://docs.cryptpad.org/en/admin_guide/installation.html#admin-docker-install). - + ### Others @@ -167,7 +241,7 @@ This release includes some features that could not be included into 5.6.0, namel - Kanban item export [#1360](https://github.com/cryptpad/cryptpad/pull/1360) - Calendar - Calendar datepicker on mobile now easily toggled [#1368](https://github.com/cryptpad/cryptpad/pull/1368) - - Behaviour change: keep the offset between start and end date constant when updating the start date (otherwise it was possible to create events that end before even starting that thus don’t appear in the calendar) + - Behaviour change: keep the offset between start and end date constant when updating the start date (otherwise it was possible to create events that end before even starting that thus don’t appear in the calendar) - Calendar yearly recurring event - wrong month name [#1398](https://github.com/cryptpad/cryptpad/issues/1398) - Admin - Encoding issues in broadcast messages [#1379](https://github.com/cryptpad/cryptpad/issues/1379) @@ -213,18 +287,18 @@ index cb827b4b0..f2b32e959 100644 --- a/docs/example-advanced.nginx.conf +++ b/docs/example-advanced.nginx.conf @@ -14,6 +14,8 @@ server { - + # Let's Encrypt webroot include letsencrypt-webroot; + # Include mime.types to be able to support .mjs files (see "types" below) + include mime.types; - + # CryptPad serves static assets over these two domains. # `main_domain` is what users will enter in their address bar. @@ -166,11 +168,6 @@ server { # We've applied other sandboxing techniques to mitigate the risk of running WebAssembly in this privileged scope if ($uri ~ ^\/unsafeiframe\/inner\.html.*$) { set $unsafe 1; } - + - # draw.io uses inline script tags in it's index.html. The hashes are added here. - if ($uri ~ ^\/components\/drawio\/src\/main\/webapp\/index.html.*$) { - set $scriptSrc "'self' 'sha256-dLMFD7ijAw6AVaqecS7kbPcFFzkxQ+yeZSsKpOdLxps=' 'sha256-6g514VrT/cZFZltSaKxIVNFF46+MFaTSDTPB8WfYK+c=' resource: https://${main_domain}"; @@ -236,7 +310,7 @@ index cb827b4b0..f2b32e959 100644 @@ -179,6 +176,11 @@ server { # Finally, set all the rules you composed above. add_header Content-Security-Policy "default-src 'none'; child-src $childSrc; worker-src $workerSrc; media-src $mediaSrc; style-src $styleSrc; script-src $scriptSrc; connect-src $connectSrc; font-src $fontSrc; img-src $imgSrc; frame-src $frameSrc; frame-ancestors $frameAncestors"; - + + # Add support for .mjs files used by pdfjs + types { + application/javascript mjs; @@ -267,15 +341,15 @@ This release introduces support for integrating CryptPad instances with Single-S ## Improvements - Accessibility of toolbars and some drop-down menus [#1290](https://github.com/cryptpad/cryptpad/pull/1290) - - "+ New" drop-down menu in Drive and Team Drive #1191 - - New `Ctrl + e` modal #1192 - - Code contact request notifications as headings #1197 - - DOM order of toolbar #1198 - - Notifications menu not accessible via Keyboard #1201 - - Sidebar "tabs" not accessible via keyboard #1203 - - Implement keyboard navigation of toolbar menus #1209 - - CryptDrive page needs a logical tab order #1151 - - Elements not accessible using the keyboard #1162 + - "+ New" drop-down menu in Drive and Team Drive #1191 + - New `Ctrl + e` modal #1192 + - Code contact request notifications as headings #1197 + - DOM order of toolbar #1198 + - Notifications menu not accessible via Keyboard #1201 + - Sidebar "tabs" not accessible via keyboard #1203 + - Implement keyboard navigation of toolbar menus #1209 + - CryptDrive page needs a logical tab order #1151 + - Elements not accessible using the keyboard #1162 - Calendar event modal date-picker is cut-off at some screen resolutions #1280 - Visible focus #1206 - Rich Text @@ -537,7 +611,7 @@ git checkout 5.4.1 This release introduces two major new features: - New Diagram application - 2 factor authentication using time-based one-time passwords (TOTP) - + Also included are some improvements, dependency updates, and bug fixes ## Features @@ -606,7 +680,7 @@ To upgrade: ## Goals -This release updates OnlyOffice applications to version 7.1 It improves the Form application and other areas of CryptPad with minor features and bug fixes. +This release updates OnlyOffice applications to version 7.1 It improves the Form application and other areas of CryptPad with minor features and bug fixes. ## Features @@ -649,10 +723,10 @@ This release updates OnlyOffice applications to version 7.1 It improves the Form - Send response notifications to all owners - Rich Text - - Fix scroll issues when clicking on the table of contents + - Fix scroll issues when clicking on the table of contents - Fix double notification for mention + reply in a comment -- Fix issues with deprecated cache +- Fix issues with deprecated cache - Fix bug that kept certain documents from being "pinned" to the drive. This could lead them to be deleted for inactivity even though they were stored in the drive. Note that storage quotas may increase as a result @@ -672,7 +746,7 @@ git checkout 5.3.0 1. Restart your server 2. Review your instance's checkup page to ensure that you are passing all tests - + # 5.2.1 ## Goals @@ -722,14 +796,14 @@ This release is focused on addressing long-standing user feedback with new featu - New option for form authors/auditors to export responses as JSON (in addition to existing CSV and CryptPad Sheet) - Settings have been refactored in a modal with a summary in the main editor view - Display fixes for long questions/options in some question types - + - Calendar - New event settings to repeat periodically - quick default patterns (e.g. weekly on Mondays, yearly on December 14th, etc), and custom intervals - modify one, future, or all events - easily stop repetition from event preview -- Drive +- Drive - New button to filter the drive view by document type - Teams @@ -749,7 +823,7 @@ This release is focused on addressing long-standing user feedback with new featu To update from `5.1.0` to `5.2.0`: -1. Read the **Nginx** section below to ensure you are using the right version and update your reverse proxy configuration to match the settings in our current `./docs/example.nginx.conf` +1. Read the **Nginx** section below to ensure you are using the right version and update your reverse proxy configuration to match the settings in our current `./docs/example.nginx.conf` 2. Reload nginx 3. Stop your API server 4. Fetch the latest code with git @@ -970,7 +1044,7 @@ The reasons for blocking embedding will be described in the _Features_ section b We're also recommending a few more updates, but we don't expect that these will stop the service from loading: -* NodeJS `v12.14.0` (which we have recommended for some time) will be considered _End-Of-Life_ as of April 30th. +* NodeJS `v12.14.0` (which we have recommended for some time) will be considered _End-Of-Life_ as of April 30th. * We recommend updating to [NodeJS v16.14.2](https://nodejs.org/en/download/) via [NVM](https://github.com/nvm-sh/nvm). * The API server will check the version of its runtime when it launches. It will print a warning to your server logs and set a public flag in `/api/config` indicating that it should be updated. There is a corresponding test on the checkup page which checks for the presence of this flag for admins that aren't in the habit of reviewing their logs. * The recommended NGINX config file also includes some minor changes. You can compare the current version (in `cryptpad/docs/example.nginx.conf`) against your live config with a diff tool. There are also new tests on the checkup page which will identify whether the newly changed headers have been correctly applied. @@ -1136,25 +1210,25 @@ index 14a3d4fc2..ea21e3ba7 100644 --- a/docs/example.nginx.conf +++ b/docs/example.nginx.conf @@ -65,5 +65,5 @@ server { - + set $coop ''; - if ($uri ~ ^\/(sheet|presentation|doc|convert)\/.*$) { set $coop 'same-origin'; } + #if ($uri ~ ^\/(sheet|presentation|doc|convert)\/.*$) { set $coop 'same-origin'; } - + # Enable SharedArrayBuffer in Firefox (for .xlsx export) @@ -91,5 +91,5 @@ server { - + # connect-src restricts URLs which can be loaded using script interfaces - set $connectSrc "'self' https://${main_domain} ${main_domain} https://${api_domain} blob: wss://${api_domain} ${api_domain} ${files_domain}"; + set $connectSrc "'self' https://${main_domain} ${main_domain} https://${api_domain} blob: wss://${api_domain} ${api_domain} ${files_domain} https://${sandbox_domain}"; - + # fonts can be loaded from data-URLs or the main domain @@ -121,8 +121,13 @@ server { # they unfortunately still require exceptions to the sandboxing to work correctly. if ($uri ~ ^\/(sheet|doc|presentation)\/inner.html.*$) { set $unsafe 1; } - if ($uri ~ ^\/common\/onlyoffice\/.*\/index\.html.*$) { set $unsafe 1; } + if ($uri ~ ^\/common\/onlyoffice\/.*\/.*\.html.*$) { set $unsafe 1; } - + # everything except the sandbox domain is a privileged scope, as they might be used to handle keys if ($host != $sandbox_domain) { set $unsafe 0; } + # this iframe is an exception. Office file formats are converted outside of the sandboxed scope @@ -1162,7 +1236,7 @@ index 14a3d4fc2..ea21e3ba7 100644 + # the use of some modern APIs that we require when javascript is run in a cross-origin context. + # We've applied other sandboxing techniques to mitigate the risk of running WebAssembly in this privileged scope + if ($uri ~ ^\/unsafeiframe\/inner\.html.*$) { set $unsafe 1; } - + # privileged contexts allow a few more rights than unprivileged contexts, though limits are still applied ``` @@ -1436,15 +1510,15 @@ To update from 4.7.0 to 4.8.0: ## Goals -Our main goal for this release was to prepare a BETA version of our new forms app, however, it also includes a number of nice bug fixes and minor features. +Our main goal for this release was to prepare a BETA version of our new forms app, however, it also includes a number of nice bug fixes and minor features. ## Update notes -As this release includes a new app you'll want to compare your current NGINX config against our example (`cryptpad/docs/example.nginx.conf`) and update yours to match the updated sections which rewrites URLs to include trailing slashes. We've also introduced a number of new variables to our color scheme which might conflict with customizations you've made to your stylesheets. As always, it's recommended that you test your customizations on a updated non-production instance before deploying. +As this release includes a new app you'll want to compare your current NGINX config against our example (`cryptpad/docs/example.nginx.conf`) and update yours to match the updated sections which rewrites URLs to include trailing slashes. We've also introduced a number of new variables to our color scheme which might conflict with customizations you've made to your stylesheets. As always, it's recommended that you test your customizations on a updated non-production instance before deploying. -We've been steadily adding new tests to our recently developed checkup page each time we observe particular types of instance misconfigurations in the wild. Unfortunately, it seems the admins that have the most trouble with instance configuration are those that haven't read the numerous mentions of this page throughout the last few release notes. For that reason we've made it so the server prints a link to this page at launch time if it detects that some important value is left unconfigured. +We've been steadily adding new tests to our recently developed checkup page each time we observe particular types of instance misconfigurations in the wild. Unfortunately, it seems the admins that have the most trouble with instance configuration are those that haven't read the numerous mentions of this page throughout the last few release notes. For that reason we've made it so the server prints a link to this page at launch time if it detects that some important value is left unconfigured. -On the topic of instance configuration, admins that have enabled their instance's admin panel may notice that it contains a new "Network" tab. On this pane you may find a button that links to the instance's checkup page to make it even easier to identify configuration problems. You should also notice options for configuring a number of values, some of which could previously only be set by modifying the server's configuration file and restarting. +On the topic of instance configuration, admins that have enabled their instance's admin panel may notice that it contains a new "Network" tab. On this pane you may find a button that links to the instance's checkup page to make it even easier to identify configuration problems. You should also notice options for configuring a number of values, some of which could previously only be set by modifying the server's configuration file and restarting. * One checkbox allows you to opt out of the server telemetry which tells our server that your server exists. This is mostly so that we have a rough idea of how many admins are running CryptPad and what version they have installed. It was clearly documented in the config file, but now it's even easier to opt out if you don't want us to know you exist. In the interest of transparency, everything that is sent to our server as a part of this telemetry is also printed to your application server's logs, so you always check what information has been shared. * Another setting opts in to listing your server in public directories. At present there is no public directory of CryptPad instances that are suitable for public use, but we plan to launch one in the coming months. For now this checkbox will serve to inform us how many instance admins are interested in offering their server to the public. This setting will have no effect if you've disabled telemetry as that is how your server informs ours of your preferences. We reserve the right to exclude instances from our listing for _any reason_. @@ -1452,7 +1526,7 @@ On the topic of instance configuration, admins that have enabled their instance' * The option to disable crowdfunding notices in the UI can be disabled via a simple checkbox. * Starting with our next release (4.8.0) anyone running 4.7.0 should also notice that a button appears on this pane informing them that an update is available. We regularly fix security flaws and improve general safeguards against them, so if you aren't up to date you might be putting your users' data at risk. -To update from 4.6.0 to 4.7.0: +To update from 4.6.0 to 4.7.0: 1. Apply the documented NGINX configuration 2. Stop your server @@ -1460,7 +1534,7 @@ To update from 4.6.0 to 4.7.0: 4. Install the latest dependencies with `bower update` and `npm i` 5. Restart your server -Please note that the new _Forms_ app depends on an update to our cryptography library. If you omit `bower update` from the upgrade sequence above, the app will not work. +Please note that the new _Forms_ app depends on an update to our cryptography library. If you omit `bower update` from the upgrade sequence above, the app will not work. ## Features @@ -1470,7 +1544,7 @@ Please note that the new _Forms_ app depends on an update to our cryptography li * Participants can respond to forms and view responses if these are made public (this can be set by authors). * Auditors can view responses, but cannot necessarily add their own answers unless they have the correct participant key. - This new app addresses many of the shortcomings of our current _Polls_ and vastly expands the feature set. Polls are effectively one of the many question types now available in _Forms_. For this reason we are deprecating the _Polls_ app. It will remain available to view and respond to existing polls, but we discourage the creation of new polls and all future improvements will be focused on _Forms_. + This new app addresses many of the shortcomings of our current _Polls_ and vastly expands the feature set. Polls are effectively one of the many question types now available in _Forms_. For this reason we are deprecating the _Polls_ app. It will remain available to view and respond to existing polls, but we discourage the creation of new polls and all future improvements will be focused on _Forms_. * In response to a GitHub issue we've added an option to the toolbar's _File_ menu to add the current pad to your drive regardless of whether it is already stored in one of your teams' drives. * Likewise, we received some reports that some users found it frustrating that the home page automatically redirected them to their drive when they were logged in. We've disabled this behaviour by default but added an option in the settings page through which you may re-enable the old behaviour. This can be found at the top of the "CryptDrive" pane. @@ -1492,27 +1566,27 @@ Please note that the new _Forms_ app depends on an update to our cryptography li ## Goals -Our main goal for this release cycle was to get a strong start on our upcoming _Forms_ app. This is a big job which we didn't expect to finish in the course of a few weeks, so in the meantime we've taken the opportunity to address many minor issues, stabilize the codebase, and implement a number of new tests. +Our main goal for this release cycle was to get a strong start on our upcoming _Forms_ app. This is a big job which we didn't expect to finish in the course of a few weeks, so in the meantime we've taken the opportunity to address many minor issues, stabilize the codebase, and implement a number of new tests. ## Update notes -Over the years the example configuration file has grown to include a large number of parameters. We've seen that this can make it hard to pick out which configuration parameters are important for a newly installed or migrated instance. We're trying to address this by moving more configuration options to the admin panel. +Over the years the example configuration file has grown to include a large number of parameters. We've seen that this can make it hard to pick out which configuration parameters are important for a newly installed or migrated instance. We're trying to address this by moving more configuration options to the admin panel. -4.6.0 introduces the ability to generate credentials for your instance's support ticket mailbox and publish the corresponding public key with the push of a button. Previously it was necessary to run a script, copy its value, update the config file, restart the server, and enter the private component of the keypair into an input on the admin panel. The relevant button can be found in the admin panel's _Support_ tab. +4.6.0 introduces the ability to generate credentials for your instance's support ticket mailbox and publish the corresponding public key with the push of a button. Previously it was necessary to run a script, copy its value, update the config file, restart the server, and enter the private component of the keypair into an input on the admin panel. The relevant button can be found in the admin panel's _Support_ tab. -We've also introduced the ability to update your _adminEmail_ settings via a field on the _General_ tab of the admin panel. This value is used by the contact page so that your users can contact you (instead of us) in case they encounter any problems when using your instance. Both the `supportMailbox` and `adminEmail` values are distributed by the `/api/config` endpoint which is typically cached by clients. You probably need to use the _Flush cache_ button to ensure that everyone loads the latest value. This button can also found on the _General_ tab. +We've also introduced the ability to update your _adminEmail_ settings via a field on the _General_ tab of the admin panel. This value is used by the contact page so that your users can contact you (instead of us) in case they encounter any problems when using your instance. Both the `supportMailbox` and `adminEmail` values are distributed by the `/api/config` endpoint which is typically cached by clients. You probably need to use the _Flush cache_ button to ensure that everyone loads the latest value. This button can also found on the _General_ tab. -One admin reported difficulty customizing their instance because they copy-pasted code from `cryptpad/www/common/application_config_internal.js` directly into `cryptpad/customize/application_config.js`. Unfortunately the internal variable name for the configuration object in the former did not match the value in the latter, so this led to a reference error. We've updated the variable name in the internal configuration file which provides the default options to match the customizable one, making it easier to copy-paste code examples without understanding what it's really doing. +One admin reported difficulty customizing their instance because they copy-pasted code from `cryptpad/www/common/application_config_internal.js` directly into `cryptpad/customize/application_config.js`. Unfortunately the internal variable name for the configuration object in the former did not match the value in the latter, so this led to a reference error. We've updated the variable name in the internal configuration file which provides the default options to match the customizable one, making it easier to copy-paste code examples without understanding what it's really doing. -We also introduced a new configuration option in `application_config_internal.js` which prevents unregistered users from creating new pads. Add `AppConfig.disableAnonymousPadCreation = true;` to your `customize/application_config.js` to disable anonymous pad creation. If you read the adjacent comment above the default example you'll see that this barrier is only enforced on the client, so it will keep out honest users but won't stop malicious ones from messaging the server directly. +We also introduced a new configuration option in `application_config_internal.js` which prevents unregistered users from creating new pads. Add `AppConfig.disableAnonymousPadCreation = true;` to your `customize/application_config.js` to disable anonymous pad creation. If you read the adjacent comment above the default example you'll see that this barrier is only enforced on the client, so it will keep out honest users but won't stop malicious ones from messaging the server directly. -This release also includes a number of new tests on the `/checkup/` page. Most notably it now checks for headers on certain assets which can only be checked from within the sandboxed iframe. These new tests automate the manual checks we were performing when admins reported that everything was working except for sheets, and go a little bit further to report which particular headers are incorrect. We also fixed some bugs that were checking headers on resources which could be cached, added a test for the recently added anti-FLoC header, fixed the styles on the page to respond to both light and dark mode, and made sure that websocket connections that were opened by tests were closed when they finished. +This release also includes a number of new tests on the `/checkup/` page. Most notably it now checks for headers on certain assets which can only be checked from within the sandboxed iframe. These new tests automate the manual checks we were performing when admins reported that everything was working except for sheets, and go a little bit further to report which particular headers are incorrect. We also fixed some bugs that were checking headers on resources which could be cached, added a test for the recently added anti-FLoC header, fixed the styles on the page to respond to both light and dark mode, and made sure that websocket connections that were opened by tests were closed when they finished. -Some of the tests we implemented checked the headers on resources that were particularly prone to misconfiguration because its headers were set by both NGINX and the NodeJS application server (see [#694](https://github.com/cryptpad/cryptpad/issues/694)). We tested in a variety of configurations and ultimately decided that the most resilient solution was to give up on using heuristics in the application server and just update the example NGINX config to use a patch proposed by another admin which fully overrides the settings of the application server. You can find this patch in the `/api/(config|broadcast)` section of the example config. +Some of the tests we implemented checked the headers on resources that were particularly prone to misconfiguration because its headers were set by both NGINX and the NodeJS application server (see [#694](https://github.com/cryptpad/cryptpad/issues/694)). We tested in a variety of configurations and ultimately decided that the most resilient solution was to give up on using heuristics in the application server and just update the example NGINX config to use a patch proposed by another admin which fully overrides the settings of the application server. You can find this patch in the `/api/(config|broadcast)` section of the example config. -Finally, we've made some minor changes to the provided `package-lock.json` file because `npm` reported some "Regular Expression Denial of Service" vulnerabilities. One of these was easy to fix, but another two were reported shortly thereafter. These "vulnerabilities" only affect some developer dependencies and will have no effect on regular usage of our software. The "risk" is essentially that malicious modifications to our source code can be tailored to make our style linting software run particularly slowly. This can only be triggered by integrating such malicious changes into your local repository and running `npm run lint:less`, so maybe don't do that. +Finally, we've made some minor changes to the provided `package-lock.json` file because `npm` reported some "Regular Expression Denial of Service" vulnerabilities. One of these was easy to fix, but another two were reported shortly thereafter. These "vulnerabilities" only affect some developer dependencies and will have no effect on regular usage of our software. The "risk" is essentially that malicious modifications to our source code can be tailored to make our style linting software run particularly slowly. This can only be triggered by integrating such malicious changes into your local repository and running `npm run lint:less`, so maybe don't do that. -To update from 4.5.0 to 4.6.0: +To update from 4.5.0 to 4.6.0: 1. Apply the documented NGINX configuration 2. Stop your server @@ -1522,7 +1596,7 @@ To update from 4.5.0 to 4.6.0: ## Features -This release includes very few new features aside from those already mentioned in the _Update notes_ section. One very minor improvement is that formatted code blocks in the code editor's markdown preview use the full width of their parent container instead of being indented. +This release includes very few new features aside from those already mentioned in the _Update notes_ section. One very minor improvement is that formatted code blocks in the code editor's markdown preview use the full width of their parent container instead of being indented. ## Bug fixes @@ -1538,21 +1612,21 @@ This release includes very few new features aside from those already mentioned i ## Goals -This release cycle we aimed to complete three major milestones: the official release of our calendar app, the ability for admins to close registration on their instance, and the deployment of the admin section of our [official documentation](https://docs.cryptpad.org/en/admin_guide/index.html). We spent the remainder of our time addressing a growing backlog of issues on GitHub by fixing a number of weird bugs. +This release cycle we aimed to complete three major milestones: the official release of our calendar app, the ability for admins to close registration on their instance, and the deployment of the admin section of our [official documentation](https://docs.cryptpad.org/en/admin_guide/index.html). We spent the remainder of our time addressing a growing backlog of issues on GitHub by fixing a number of weird bugs. ## Update notes -This release includes a new GitHub issue template (`cryptpad/.github/ISSUE_TEMPLATE/initial-instance-configuration.md`). The intent of this file is to make it clear that _Bug Reports_ are for intended for bugs in the software itself, not for soliciting help in configuring your personal server. Such issues take away time that we'd rather spend improving the platform for everybody's benefit, rather than for single administrators. +This release includes a new GitHub issue template (`cryptpad/.github/ISSUE_TEMPLATE/initial-instance-configuration.md`). The intent of this file is to make it clear that _Bug Reports_ are for intended for bugs in the software itself, not for soliciting help in configuring your personal server. Such issues take away time that we'd rather spend improving the platform for everybody's benefit, rather than for single administrators. -Sometimes difficulty configuring an instance does stem from an actual bug, however, most of the time these issues relate to the use of an unsupported configuration or failure to correctly follow installation instructions. The issue template includes some basic debugging steps which should identify the vast majority of problems. Beyond its primary goal of narrowing the scope of our issue tracker, we hope it will also be useful as an offline reference for administrators attempting to debug their instance. +Sometimes difficulty configuring an instance does stem from an actual bug, however, most of the time these issues relate to the use of an unsupported configuration or failure to correctly follow installation instructions. The issue template includes some basic debugging steps which should identify the vast majority of problems. Beyond its primary goal of narrowing the scope of our issue tracker, we hope it will also be useful as an offline reference for administrators attempting to debug their instance. -This template references the /checkup/ page that we've been steadily improving over the last few releases. It now includes even more tests to diagnose instance configuration problems, each with their own messages that provide some fairly detailed hints about what is wrong when an error is detected. This release introduces a number of tests that print _warnings_ that won't break an instance but might detract from users' experience. We recommend checking this page on your instance with each release as we will continue to improve it on an regular basis, and it might detect some errors of which you were unaware. +This template references the /checkup/ page that we've been steadily improving over the last few releases. It now includes even more tests to diagnose instance configuration problems, each with their own messages that provide some fairly detailed hints about what is wrong when an error is detected. This release introduces a number of tests that print _warnings_ that won't break an instance but might detract from users' experience. We recommend checking this page on your instance with each release as we will continue to improve it on an regular basis, and it might detect some errors of which you were unaware. -Otherwise, this release includes some changes to the provided example NGINX config file. It now includes a header designed to disable clients' participation in Google's [FLoC network](https://www.eff.org/deeplinks/2021/03/googles-floc-terrible-idea), as well as some basic rules related to the addition of our calendar app and OnlyOffice's two remaining editors (which are still not officially supported despite their inclusion here). +Otherwise, this release includes some changes to the provided example NGINX config file. It now includes a header designed to disable clients' participation in Google's [FLoC network](https://www.eff.org/deeplinks/2021/03/googles-floc-terrible-idea), as well as some basic rules related to the addition of our calendar app and OnlyOffice's two remaining editors (which are still not officially supported despite their inclusion here). -Lastly, any instance administrators that have had to customize their instance in order to disable registration can instead rely on a built-in feature that is available on the main page of the admin panel. Checking the "Close registration" checkbox will cause the application server to reject the creation of new "login blocks" (which store users' encrypted account credentials) while permitting existing users to change their passwords. Clients will be informed that registration is closed via the `/api/config` endpoint, causing the registration page to display a notice instead of the usual form. You may need to use the `FLUSH CACHE` button which can found on the same page of the admin panel in order to force clients to load the updated server config. +Lastly, any instance administrators that have had to customize their instance in order to disable registration can instead rely on a built-in feature that is available on the main page of the admin panel. Checking the "Close registration" checkbox will cause the application server to reject the creation of new "login blocks" (which store users' encrypted account credentials) while permitting existing users to change their passwords. Clients will be informed that registration is closed via the `/api/config` endpoint, causing the registration page to display a notice instead of the usual form. You may need to use the `FLUSH CACHE` button which can found on the same page of the admin panel in order to force clients to load the updated server config. -To update from 4.4.0 to 4.5.0: +To update from 4.4.0 to 4.5.0: 1. Apply the documented NGINX configuration 2. Stop your server @@ -1584,21 +1658,21 @@ To update from 4.4.0 to 4.5.0: ## Goals -Our main goal for this release was to complete the first steps of our ["Dialogue" project](https://nlnet.nl/project/CryptPadForms/), which will introduce surveys into CryptPad. We've also put considerable effort towards addressing some configuration issues, correcting some inconsistently translated UI, and writing some new documentation. +Our main goal for this release was to complete the first steps of our ["Dialogue" project](https://nlnet.nl/project/CryptPadForms/), which will introduce surveys into CryptPad. We've also put considerable effort towards addressing some configuration issues, correcting some inconsistently translated UI, and writing some new documentation. ## Update notes -This release removes the default privacy policy that has been included in CryptPad up until now. It included some assertions that were true of our own instance (CryptPad.fr) which we couldn't guarantee on third-party instances. We've updated our custom configuration to link to a privacy policy that was written in a rich text pad. You can do the same on your instance by editing `cryptpad/customize/application_config.js` to include the absolute URL of your instance, like so: `AppConfig.privacy = "https://cryptpad.your.website/privacy.html";`. +This release removes the default privacy policy that has been included in CryptPad up until now. It included some assertions that were true of our own instance (CryptPad.fr) which we couldn't guarantee on third-party instances. We've updated our custom configuration to link to a privacy policy that was written in a rich text pad. You can do the same on your instance by editing `cryptpad/customize/application_config.js` to include the absolute URL of your instance, like so: `AppConfig.privacy = "https://cryptpad.your.website/privacy.html";`. -We've clarified a point about telemetry in the notes of our 4.3.1 release. The text suggested that users on your instance would send telemetry to OUR webserver. It has been clarified to reflect that telemetry from your users is only ever sent to your instance. +We've clarified a point about telemetry in the notes of our 4.3.1 release. The text suggested that users on your instance would send telemetry to OUR webserver. It has been clarified to reflect that telemetry from your users is only ever sent to your instance. -We've spent some time working on improving our (officially) unreleased integrations of OnlyOffice's presentation and document editors. We've advised against enabling these editors on your instance. This release includes changes that may not be fully backwards compatible. If your users rely on either editor we advise that you not update until they have had an opportunity to back up their documents. We still aren't officially supporting either editor and we may make further breaking changes in the future. Consider this a warning and not an advertizement of their readiness! +We've spent some time working on improving our (officially) unreleased integrations of OnlyOffice's presentation and document editors. We've advised against enabling these editors on your instance. This release includes changes that may not be fully backwards compatible. If your users rely on either editor we advise that you not update until they have had an opportunity to back up their documents. We still aren't officially supporting either editor and we may make further breaking changes in the future. Consider this a warning and not an advertizement of their readiness! -This release also includes changes to the recommended NGINX configuration. Compare your instance's config against `cryptpad/docs/example.nginx.conf` and apply all the new changes before updating. In particular, you'll want to pay attention to the configuration for a newly exposed server API (`/api/broadcast`). This should work much the same as `/api/config`, so if you're using a non-standard configuration that uses more than one server you may want to proxy it in a similar fashion. +This release also includes changes to the recommended NGINX configuration. Compare your instance's config against `cryptpad/docs/example.nginx.conf` and apply all the new changes before updating. In particular, you'll want to pay attention to the configuration for a newly exposed server API (`/api/broadcast`). This should work much the same as `/api/config`, so if you're using a non-standard configuration that uses more than one server you may want to proxy it in a similar fashion. -Lastly, we've made some big improvements to the `/checkup/` page which performs some basic tests to confirm that your instance is configured correctly. It now provides some much more detailed descriptions of what might be wrong and how you can start debugging any issues that were identified. If you experience any problems after updating please review this page to assess your instance for any known issues before asking for help. +Lastly, we've made some big improvements to the `/checkup/` page which performs some basic tests to confirm that your instance is configured correctly. It now provides some much more detailed descriptions of what might be wrong and how you can start debugging any issues that were identified. If you experience any problems after updating please review this page to assess your instance for any known issues before asking for help. -To update from 4.3.1 to 4.4.0: +To update from 4.3.1 to 4.4.0: 1. Apply the documented NGINX configuration 2. Stop your server @@ -1606,7 +1680,7 @@ To update from 4.3.1 to 4.4.0: 4. Install the latest dependencies with `bower update` and `npm i` 5. Restart your server -This release requires updates to both clientside and serverside dependencies. **You will experience problems if you skip any of the above steps.** +This release requires updates to both clientside and serverside dependencies. **You will experience problems if you skip any of the above steps.** ## Features @@ -1638,7 +1712,7 @@ This release requires updates to both clientside and serverside dependencies. ** # 4.3.1 -This minor release addresses some bugs discovered after deploying and tagging 4.3.0 +This minor release addresses some bugs discovered after deploying and tagging 4.3.0 * We found that some browser extensions interfered with checks to determine whether a registered user was correctly logged in, which resulted in some disabled functionality. If you are running extensions that actively delete the tokens that keep you logged your session should now stay alive until you close all its active tabs, after which you will have to log back in. * Our 4.2.0 update introduced a new internal format for spreadsheets which broke support for spreadsheet templates using the older format. This release implements a compatibility layer. @@ -1652,13 +1726,13 @@ This minor release addresses some bugs discovered after deploying and tagging 4. ## Goals -This release is a continuation of our recent efforts to stabilize the platform, fixing small bugs and inconsistencies that we missed when developing larger features. In the meantime we've received reports of the platform performing poorly under various unusual circumstances, so we've developed some targeted fixes to both improve user experience and decrease the load on our server. +This release is a continuation of our recent efforts to stabilize the platform, fixing small bugs and inconsistencies that we missed when developing larger features. In the meantime we've received reports of the platform performing poorly under various unusual circumstances, so we've developed some targeted fixes to both improve user experience and decrease the load on our server. ## Update notes -This release should be fairly simple for admins. +This release should be fairly simple for admins. -To update from 4.2.1 to 4.3.0: +To update from 4.2.1 to 4.3.0: 1. Stop your server 2. Get the latest code with git @@ -1692,7 +1766,7 @@ To update from 4.2.1 to 4.3.0: # 4.2.1 -This minor release addresses a few bugs discovered after deploying 4.2.0: +This minor release addresses a few bugs discovered after deploying 4.2.0: * The 4.2.0 release included major improvements to the sheet application. This introduced breaking changes to the "lock" system in the application. Existing spreadsheets (before 4.2.0) that were closed by a user without "unlocking" all cells first became impossible to open after the 4.2.0 changes. This has been fixed. * Team owners can now properly upload a team avatar. @@ -1706,21 +1780,21 @@ This minor release addresses a few bugs discovered after deploying 4.2.0: ## Goals -We've made a lot of big changes to the platform lately. This release has largely been an attempt to stabilize the codebase by fixing bugs and merging features that we hadn't had a chance to test until now, all while updating our documentation and removing unused or outdated code. +We've made a lot of big changes to the platform lately. This release has largely been an attempt to stabilize the codebase by fixing bugs and merging features that we hadn't had a chance to test until now, all while updating our documentation and removing unused or outdated code. ## Update notes -This release includes an update to the sheet editor which is not backwards-compatible. Clients running the new version will not be able to correctly communicate with clients running older versions. Clients will automatically detect that a new version is available upon reconnecting to the server after a restart, so as long as you follow the steps recommended below this should be fine. +This release includes an update to the sheet editor which is not backwards-compatible. Clients running the new version will not be able to correctly communicate with clients running older versions. Clients will automatically detect that a new version is available upon reconnecting to the server after a restart, so as long as you follow the steps recommended below this should be fine. -We've also updated a server-side dependency that is not backwards-compatible. Failure to update both the platform and its dependencies together will result in errors. +We've also updated a server-side dependency that is not backwards-compatible. Failure to update both the platform and its dependencies together will result in errors. -The `scripts` directory now includes a script to identify unused translations. We used this to reduce the size of our localization files (`cryptpad/www/common/translations/*.json`). We reviewed the changes carefully and did our best to test, but it's always possible that a string was erroneously removed. If you notice any bugs in the UI where text seems to be missing, please let us (the developers) know via a GitHub issue. +The `scripts` directory now includes a script to identify unused translations. We used this to reduce the size of our localization files (`cryptpad/www/common/translations/*.json`). We reviewed the changes carefully and did our best to test, but it's always possible that a string was erroneously removed. If you notice any bugs in the UI where text seems to be missing, please let us (the developers) know via a GitHub issue. -CryptPad.fr now stores more than a terabyte of data, making it quite intensive to run the scripts to remove inactive files from the disk. To help alleviate this strain we've moved the code responsible for deleting files that have been archived for longer than the configured retention period into its own script (`./scripts/evict-archived.js`). For the moment this script is not integrated into the server and will not automatically run in the background as the main eviction script does. It's recommended that you run it manually if you find you are low on disk space. +CryptPad.fr now stores more than a terabyte of data, making it quite intensive to run the scripts to remove inactive files from the disk. To help alleviate this strain we've moved the code responsible for deleting files that have been archived for longer than the configured retention period into its own script (`./scripts/evict-archived.js`). For the moment this script is not integrated into the server and will not automatically run in the background as the main eviction script does. It's recommended that you run it manually if you find you are low on disk space. -Since early in the pandemic we've been serving a custom home page on CryptPad.fr to inform users that we've increased the amount of storage provided for free. This was originally intended as a temporary measure, but since almost a year has passed we figured it was about time we integrate this custom code into the platform itself. Admins can now add a custom note to the home page, using customized HTML in `customize/application_config.js`. To do this, define an `AppConfig.homeNotice` attribute like so: `AppConfig.homeNotice = "pewpew";`. +Since early in the pandemic we've been serving a custom home page on CryptPad.fr to inform users that we've increased the amount of storage provided for free. This was originally intended as a temporary measure, but since almost a year has passed we figured it was about time we integrate this custom code into the platform itself. Admins can now add a custom note to the home page, using customized HTML in `customize/application_config.js`. To do this, define an `AppConfig.homeNotice` attribute like so: `AppConfig.homeNotice = "pewpew";`. -To update from 4.1.0 to 4.2.0: +To update from 4.1.0 to 4.2.0: 1. Stop your server 2. Get the latest code from the 4.2.0 tag (`git fetch origin && git checkout 4.2.0`, or just `git pull origin main`) @@ -1775,13 +1849,13 @@ To update from 4.1.0 to 4.2.0: ## Goals -Our recent 4.0.0 release introduced major changes to CryptPad's style-sheets which likely caused some difficulty for admins who'd made extensive changes to their instance's appearance. We figure it's best to make more changes now instead of making small breaking changes more frequently, so we decided now is a good time to refactor a lot of our styles to implement an often-requested dark mode in CryptPad. +Our recent 4.0.0 release introduced major changes to CryptPad's style-sheets which likely caused some difficulty for admins who'd made extensive changes to their instance's appearance. We figure it's best to make more changes now instead of making small breaking changes more frequently, so we decided now is a good time to refactor a lot of our styles to implement an often-requested dark mode in CryptPad. ## Update notes -As noted above, this release introduces some major changes to CryptPad styles. If you have customized the look of your instance we recommend testing this new version locally before deploying it to your server to ensure that there are no critical conflicts. +As noted above, this release introduces some major changes to CryptPad styles. If you have customized the look of your instance we recommend testing this new version locally before deploying it to your server to ensure that there are no critical conflicts. -Otherwise, to update from 4.0.0 to 4.1.0: +Otherwise, to update from 4.0.0 to 4.1.0: 1. Stop your server 2. Get the latest code from the 4.1.0 tag (`git fetch origin && git checkout 4.1.0`, or just `git pull origin main`) @@ -1818,25 +1892,25 @@ Otherwise, to update from 4.0.0 to 4.1.0: # 4.0.0 (A) -We're very happy to introduce CryptPad v4.0! +We're very happy to introduce CryptPad v4.0! -This release is the culmination of a great deal of work over the last year, in which we searched for the right metaphors and imagery to clearly represent what CryptPad is all about. We've reworked our logo, color theme, text on our static pages, and the icons throughout the platform to convey the calm and safety we want our users to feel. +This release is the culmination of a great deal of work over the last year, in which we searched for the right metaphors and imagery to clearly represent what CryptPad is all about. We've reworked our logo, color theme, text on our static pages, and the icons throughout the platform to convey the calm and safety we want our users to feel. -Our release schedule typically follows an alphabetical naming scheme, ranging from A for the first (or zero-th) release of the cycle to Z for the last, with a thematic name for each letter. In the rush of preparing translations and double-checking all of our changes we never found time to settle on a theme for this release, but we do find there's some value in maintaining the otherwise arbitrary rhythm we've followed all this time. The progression through the alphabet gives a sense of pace to what can otherwise seem like a endless stream of problems that need solving, and the end of the alphabet prompts us to build towards major milestones like this one. +Our release schedule typically follows an alphabetical naming scheme, ranging from A for the first (or zero-th) release of the cycle to Z for the last, with a thematic name for each letter. In the rush of preparing translations and double-checking all of our changes we never found time to settle on a theme for this release, but we do find there's some value in maintaining the otherwise arbitrary rhythm we've followed all this time. The progression through the alphabet gives a sense of pace to what can otherwise seem like a endless stream of problems that need solving, and the end of the alphabet prompts us to build towards major milestones like this one. -With that in mind, you can expect 25 more major releases in this cycle before version 5.0, roughly every three weeks or so depending on circumstances. +With that in mind, you can expect 25 more major releases in this cycle before version 5.0, roughly every three weeks or so depending on circumstances. ## Goals -The main intent of this release was to deploy our `rebrand` branch which had been in development for some time. Along the way we also made notable improvements to the sheet editor which will be mentioned below. +The main intent of this release was to deploy our `rebrand` branch which had been in development for some time. Along the way we also made notable improvements to the sheet editor which will be mentioned below. ## Update notes -In the process of redesigning the platform we started using some new features of the LESS CSS pre-processor language that were not supported by the version of lesshint that we were using to scan for errors. We've updated that dev dependency to a newer version (4.5.0 => 6.3.7) which introduced a rather large number of minor dependencies. These are only used during development, not by the server itself, so this is unlikely to have any impact on the software itself. +In the process of redesigning the platform we started using some new features of the LESS CSS pre-processor language that were not supported by the version of lesshint that we were using to scan for errors. We've updated that dev dependency to a newer version (4.5.0 => 6.3.7) which introduced a rather large number of minor dependencies. These are only used during development, not by the server itself, so this is unlikely to have any impact on the software itself. -Otherwise, this release includes lots of changes to the platform's style sheets and static pages. If you've applied heavy customizations to your instance you might notice errors due to incompatibilities with your local changes. We recommend that you test your customizations against the latest release locally before updating a public instance to avoid service outages. +Otherwise, this release includes lots of changes to the platform's style sheets and static pages. If you've applied heavy customizations to your instance you might notice errors due to incompatibilities with your local changes. We recommend that you test your customizations against the latest release locally before updating a public instance to avoid service outages. -To update from 3.25.1 to 4.0.0: +To update from 3.25.1 to 4.0.0: 1. Stop your server 2. Get the latest code from the 4.0.0 tag @@ -1849,7 +1923,7 @@ To update from 3.25.1 to 4.0.0: * We found that certain issues reported via the built-in support ticket system were not easy to debug without knowing the id of the user's drive. Support tickets now include a `driveChannel` attribute to simplify this process. * We've added a variety of settings for the control of how your browser uses a local database to speed up loading times and display cached versions of documents even when disconnected from our server. These are available in the "confidentiality" section of the settings page (https://cryptpad.fr/settings/#security). -Finally, the "rebrand" part of this release: +Finally, the "rebrand" part of this release: * Our home page features our new logo, a cleaner layout, new text (notably dropping the use of "zero-knowledge" from our explanation), new app icons, softer colors, neater fonts, and a custom illustration of a document shredder that hints at how CryptPad works. * We no longer include a FAQ page with each instance, and instead link to relevant parts of our dedicated documentation platform (https://docs.cryptpad.org) from any place that previously referenced the FAQ. This will make it easier for translators to focus on text for the platform's interface if they wish. An updated Frequently Asked Questions will be added to the documentation in the near future. @@ -1867,9 +1941,9 @@ Finally, the "rebrand" part of this release: # ZyzomysPedunculatus' revenge (3.25.1) -This minor release is primarily intended to fix some minor issues that were introduced or detected following our 3.25.0 release, but it also includes some major improvements that we want to test and stabilize before our upcoming 4.0.0 release. +This minor release is primarily intended to fix some minor issues that were introduced or detected following our 3.25.0 release, but it also includes some major improvements that we want to test and stabilize before our upcoming 4.0.0 release. -Features +Features * Our recent introduction of a clientside cache for document content now allows us to load and display a readable copy of a document before the most recent history has been fully loaded from the server. You might notice that your drive and some document typees are now displayed in a "DISCONNECTED" of "OFFLINE" state until they gets the latest history. For now this just means the loading screen is removed soon so you can start reading, but it's also an essential improvement that will become even more useful when we introduce the use of service-workers for offline usage. * We've added an `offline` mode to the server so that anyone developing features in CryptPad can test its offline and caching features by disabling the websocket components of the server. Use `npm run offline` to launch in this mode. @@ -1878,7 +1952,7 @@ Features * Errors that occur when loading teams now trigger some basic telemetry to the server to indicate the error code. This should help us determine the origin of some annoying teams issues that several users have reported. * Users of the rich text editor should now find that their scroll position is maintained when they are at the bottom of the document and a remote users adds more text. -Bug fixes +Bug fixes * Shortly after deploying 3.25.0 we identified several cases in which its cache invalidation logic was not correctly detecting corrupted cache entries. This caused some documents to fail to load. We quickly disabled most caching until we got the chance to review. Since then, we've tested it much more thoroughly under situations which made it more likely to become corrupt. Our new cache invalidation logic seems to catch all the known cases, so we're re-enabling the use of the cache for encrypted files and most of our supported document types. * We found that a race condition in the logout process prevented the document cache from being cleared correctly. We now wait until the asynchronous cache eviction process completes before redirecting users to the login page. @@ -1890,7 +1964,7 @@ Bug fixes * The recent replacement of a link to our faq with a link to our documentation platform violated some security headers and prevented the link from loading. We've fixed the inline link with some code to open this link in a compatible way. * Finally, we found a bug that caused custom colors in the slide app to revert to the default settings on page reloads. Custom slide colors should now be preserved. -To update from 3.25.0 to 3.25.1: +To update from 3.25.0 to 3.25.1: 1. Stop your server 2. Get the latest code with `git checkout 3.25.1` @@ -1901,17 +1975,17 @@ To update from 3.25.0 to 3.25.1: ## Goals -This is the last major release of our 3.0.0 release cycle. We wanted to mark the occasion with some big improvements to keep everyone happy in case we need to take some more time to prepare our upcoming 4.0.0 release. +This is the last major release of our 3.0.0 release cycle. We wanted to mark the occasion with some big improvements to keep everyone happy in case we need to take some more time to prepare our upcoming 4.0.0 release. ## Update notes -This update introduces some major database optimizations that should decrease both CPU and disk usage over time as users request resources and prime an on-disk cache for the next time. +This update introduces some major database optimizations that should decrease both CPU and disk usage over time as users request resources and prime an on-disk cache for the next time. -We've also introduce the ability to archive illegal or otherwise objectionable material from the admin panel assuming you possess the ability to load the content in question. It's also possible to restore archived content via an adjacent form field on the admin panel as long as it has not been permanently deleted. Due to a quirk in how ownership of uploaded files works, restored files will not retain their "owners" property. We hope to fix this in a future release. +We've also introduce the ability to archive illegal or otherwise objectionable material from the admin panel assuming you possess the ability to load the content in question. It's also possible to restore archived content via an adjacent form field on the admin panel as long as it has not been permanently deleted. Due to a quirk in how ownership of uploaded files works, restored files will not retain their "owners" property. We hope to fix this in a future release. -We've also made some minor changes to the example NGINX config file provided in `cryptpad/docs/example.nginx.confg`, specifically in [this commit](https://github.com/cryptpad/cryptpad/commit/2647acbb78643e651b71d2d4f74c2f66e264a258). CryptPad will probably work if you don't apply these changes to your nginx conf, but some functional improvements depend on the exposed headers. +We've also made some minor changes to the example NGINX config file provided in `cryptpad/docs/example.nginx.confg`, specifically in [this commit](https://github.com/cryptpad/cryptpad/commit/2647acbb78643e651b71d2d4f74c2f66e264a258). CryptPad will probably work if you don't apply these changes to your nginx conf, but some functional improvements depend on the exposed headers. -To upgrade from 3.24.0 to 3.25.0: +To upgrade from 3.24.0 to 3.25.0: 1. Update your NGINX config as mentioned above. 2. Stop your nodejs server. @@ -1957,13 +2031,13 @@ To upgrade from 3.24.0 to 3.25.0: ## Goals -We are once again working to develop some significant new features. This release is fairly small but includes some significant changes to detect and handle a variety of errors. +We are once again working to develop some significant new features. This release is fairly small but includes some significant changes to detect and handle a variety of errors. ## Update notes -This release includes some minor corrections the recommended NGINX configuration supplied in `cryptpad/docs/example.nginx.conf`. +This release includes some minor corrections the recommended NGINX configuration supplied in `cryptpad/docs/example.nginx.conf`. -To update from 3.23.2 to 3.24.0: +To update from 3.23.2 to 3.24.0: 1. Update your NGINX config to replicate the most recent changes and reload NGINX to apply them. 2. Stop the nodejs server. @@ -1992,13 +2066,13 @@ To update from 3.23.2 to 3.24.0: # XerusDaamsi reloaded (3.23.2) -A number of instance administrators reported issues following our 3.23.1 release. We suspect the issues were caused by applying the recommended update steps out of order which would result in the incorrect HTTP header values getting cached for the most recent version of a file. Since the most recently updated headers modified some security settings, this caused a catastrophic error on clients receiving the incorrect headers which caused them to fail to load under certain circumstances. +A number of instance administrators reported issues following our 3.23.1 release. We suspect the issues were caused by applying the recommended update steps out of order which would result in the incorrect HTTP header values getting cached for the most recent version of a file. Since the most recently updated headers modified some security settings, this caused a catastrophic error on clients receiving the incorrect headers which caused them to fail to load under certain circumstances. -Regardless of the reasons behind this, we want CryptPad to be resilient against misconfiguration. This minor release includes a number of measures to override the unruly caching mechanisms employed internally by two of our most stubborn dependencies (CKEditor and OnlyOffice). Deploying 3.23.2 should force these editors to load the most recent versions of these dependencies according to the same policies as the rest of CryptPad and instruct clients to ignore any incorrect server responses they might have cached over the last few updates. +Regardless of the reasons behind this, we want CryptPad to be resilient against misconfiguration. This minor release includes a number of measures to override the unruly caching mechanisms employed internally by two of our most stubborn dependencies (CKEditor and OnlyOffice). Deploying 3.23.2 should force these editors to load the most recent versions of these dependencies according to the same policies as the rest of CryptPad and instruct clients to ignore any incorrect server responses they might have cached over the last few updates. -This release also includes a number of bug fixes which had been tested in the meantime. +This release also includes a number of bug fixes which had been tested in the meantime. -Other bug fixes +Other bug fixes * We removed a hardcoded translation pertaining to the recently introduced "snapshot" functionality. * Inspection of our server logs revealed a number of rare race conditions and type errors that have since been addressed. These included: @@ -2009,11 +2083,11 @@ Other bug fixes * it was possible to click the buttons on the "team invitation response dialog" multiple times before the first action completed. In some cases this could result in attempting to join a single team multiple times. * it was also possible to activate trigger several actions that would modify your access rights for a team when the team had not fully synchronized with the server. Some of the time this was recoverable, but it could occasionally result in your team membership getting stuck in a bad state. -We've implemented some measures to correct any team data that might have become corrupted due to the issues described above. Access rights from duplicated teams should be merged back into one set of cryptographic keys wherever possible. In cases where this isn't possible your role in the team will be automatically downgraded to the rank conferred by the keys you still have. For instance, somebody listed as an administrator who only has the keys required to view the team will downgrade themself to be a viewer. Subsequent promotions back to your previous team role should restore your possession of the required keys. +We've implemented some measures to correct any team data that might have become corrupted due to the issues described above. Access rights from duplicated teams should be merged back into one set of cryptographic keys wherever possible. In cases where this isn't possible your role in the team will be automatically downgraded to the rank conferred by the keys you still have. For instance, somebody listed as an administrator who only has the keys required to view the team will downgrade themself to be a viewer. Subsequent promotions back to your previous team role should restore your possession of the required keys. -To update to 3.23.2 from 3.23.0 or 3.23.1: +To update to 3.23.2 from 3.23.0 or 3.23.1: -Perform the same upgrade steps listed for 3.23.0 including the most recent configuration changes listed in `cryptpad/docs/example.nginx.conf... +Perform the same upgrade steps listed for 3.23.0 including the most recent configuration changes listed in `cryptpad/docs/example.nginx.conf... 1. Modify your server's NGINX config file (but don't apply its changes until step 6) 2. Stop CryptPad's nodejs server @@ -2025,9 +2099,9 @@ Perform the same upgrade steps listed for 3.23.0 including the most recent confi # XerusDaamsi's revenge (3.23.1) -We discovered a number of minor bugs after deploying 3.23.0. This minor release addresses them. +We discovered a number of minor bugs after deploying 3.23.0. This minor release addresses them. -Features +Features * On instances with a lot of data (like our own) the background process responsible for evicting inactive data could time out. We've increased its permitted duration to a sufficient timeframe. * This process also aggregates some statistics about your database while it runs. Upon its completion a report is now stored in memory until it is overwritten by the next eviction process. This report will most likely be displayed on the admin panel in a future release. @@ -2037,14 +2111,14 @@ Features * When creating a shared folder we now indicate that the password field will be used to add a layer of protection to the folder. * The "destroy" button on the access modal now indicates that it will completely destroy the file or folder in question, rather than its access list or other parameters. -Bug fixes +Bug fixes * We received a number of support tickets related to users being unable to open rich text pads and sheets. We determined the issue to have been caused by our deployment of new HTTP headers to enable XLSX export on Firefox. These headers conflicted with the those on some cached files. The issue seemed to affect users randomly and did not occur when we tested the new features. We deployed some one-time cache-busting code to force clients to load the latest versions of these files (and their headers). * We addressed a regression introduced in 3.23.0 which incorrectly disabled the support ticket panels for users and admins. * We also fixed some layout issues on the admin panel's new _User storage_ pane. * Finally, we added a few guards against type errors in the drive which were most commonly triggered when viewing ranges of your drive's history which contained shared folders that had since been deleted. -To update from 3.23.0 to 3.23.1: +To update from 3.23.0 to 3.23.1: 0. Read the 3.23.0 release notes carefully and apply all configuration changes if you haven't already done so. 1. Stop your server @@ -2056,23 +2130,23 @@ To update from 3.23.0 to 3.23.1: ## Goals -We plan to produce an updated installation guide for CryptPad instance administrators to coincide with the release of our 4.0.0 release. As we get closer to the end of the alphabet we're working to simplify the process of configuring instances. This release features several new admin panel features intended to supersede the usage of the server configuration file and provide the ability to modify instance settings at runtime. +We plan to produce an updated installation guide for CryptPad instance administrators to coincide with the release of our 4.0.0 release. As we get closer to the end of the alphabet we're working to simplify the process of configuring instances. This release features several new admin panel features intended to supersede the usage of the server configuration file and provide the ability to modify instance settings at runtime. -We also spent some time finalizing some major improvements to the history mode which is available in most of our document editors. More on that in the _Features_ section. +We also spent some time finalizing some major improvements to the history mode which is available in most of our document editors. More on that in the _Features_ section. ## Update notes -This release introduces some behaviour which may require manual configuration on the part of the administrator. Read the following sections carefully or proceed at your own risk! +This release introduces some behaviour which may require manual configuration on the part of the administrator. Read the following sections carefully or proceed at your own risk! ### Automatic database maintenance -When a user employs the _destroy_ functionality to make a pad unavailable it isn't typically deleted. Instead it is made unavailable by moving it into the server's archive directory. Archived files are intended to be removed after another configurable amount of time (`archiveRetentionTime` in your config file). The deletion of old files from your archive is handled by `evict-inactive.js`, which can be found in `cryptpad/scripts/`. Up until now this script needed to be run manually (typically as a cron job) with `node ./scripts/evict-inactive.js`. Since this isn't widely known we decided to integrate it directly into the server by automatically running the script once per day. +When a user employs the _destroy_ functionality to make a pad unavailable it isn't typically deleted. Instead it is made unavailable by moving it into the server's archive directory. Archived files are intended to be removed after another configurable amount of time (`archiveRetentionTime` in your config file). The deletion of old files from your archive is handled by `evict-inactive.js`, which can be found in `cryptpad/scripts/`. Up until now this script needed to be run manually (typically as a cron job) with `node ./scripts/evict-inactive.js`. Since this isn't widely known we decided to integrate it directly into the server by automatically running the script once per day. -The same _eviction_ process is also responsible for scanning your server's database for inactive documents (defined as those which haven't been accessed in a number of days specified in your config under `inactiveTime`). Such inactive documents are archived unless they have been stored within a registered users drive. Starting with this release we have added the ability to specify the number of days before an account will be considered inactive (`accountRetentionTime`). This will take into account whether they added any new documents to their drive, or whether any of the existing documents were accessed or modified by other users. +The same _eviction_ process is also responsible for scanning your server's database for inactive documents (defined as those which haven't been accessed in a number of days specified in your config under `inactiveTime`). Such inactive documents are archived unless they have been stored within a registered users drive. Starting with this release we have added the ability to specify the number of days before an account will be considered inactive (`accountRetentionTime`). This will take into account whether they added any new documents to their drive, or whether any of the existing documents were accessed or modified by other users. -If you prefer to run the eviction script manually you can disable its integration into the server by adding `disableIntegratedEviction: true` to your config file. An example is given in `cryptpad/config/config.example.js`. If you want this process to run manually you may set the same value to `false`, or comment it out if you prefer. Likewise, if you prefer to never remove accounts and their data due to account inactivity, you may also comment it out. +If you prefer to run the eviction script manually you can disable its integration into the server by adding `disableIntegratedEviction: true` to your config file. An example is given in `cryptpad/config/config.example.js`. If you want this process to run manually you may set the same value to `false`, or comment it out if you prefer. Likewise, if you prefer to never remove accounts and their data due to account inactivity, you may also comment it out. -If you haven't been manually running the eviction scripts we recommend that you carefully review all of the values mentioned above to ensure that you will not be surprised by the sudden and unintended removal of any data. As a reminder, they are: +If you haven't been manually running the eviction scripts we recommend that you carefully review all of the values mentioned above to ensure that you will not be surprised by the sudden and unintended removal of any data. As a reminder, they are: * `inactiveTime` (number of days before a file is considered inactive) * `archiveRetentionTime` (number of days that an archived file will be retained before it is permanently deleted) @@ -2081,19 +2155,19 @@ If you haven't been manually running the eviction scripts we recommend that you ### NGINX Configuration update -After some testing on our part we've included an update to the example NGINX config file available in `cryptpad/docs/example.nginx.conf` which will enable a relatively new browser API which is required for XLSX export from our sheet editor. The relevant lines can be found beneath the comment `# Enable SharedArrayBuffer in Firefox (for .xlsx export)`. +After some testing on our part we've included an update to the example NGINX config file available in `cryptpad/docs/example.nginx.conf` which will enable a relatively new browser API which is required for XLSX export from our sheet editor. The relevant lines can be found beneath the comment `# Enable SharedArrayBuffer in Firefox (for .xlsx export)`. ### Quota management -Up until now the configuration file found in `cryptpad/config/config.js` has been the primary means of configuring a CryptPad instance. Unfortunately, as the server's behaviour becomes increasingly complex due to interest in a broad variety of use-cases this config file tends to grow. The kinds of questions that administrators ask via email, GitHub issues, and via our Matrix channel often suggest that admins haven't read through the comments in these files. Additionally, changes to the server's configuration can only be applied by restarting the server, which is increasingly disruptive as the service becomes more popular. To address these issues we've decided to start improving the instance admin panel such that it becomes the predominant means of modifying common server behaviours. +Up until now the configuration file found in `cryptpad/config/config.js` has been the primary means of configuring a CryptPad instance. Unfortunately, as the server's behaviour becomes increasingly complex due to interest in a broad variety of use-cases this config file tends to grow. The kinds of questions that administrators ask via email, GitHub issues, and via our Matrix channel often suggest that admins haven't read through the comments in these files. Additionally, changes to the server's configuration can only be applied by restarting the server, which is increasingly disruptive as the service becomes more popular. To address these issues we've decided to start improving the instance admin panel such that it becomes the predominant means of modifying common server behaviours. -We've started by making it possible to update storage settings from the _User storage_ section of the admin panel. Administrators can now update the default storage limit for users registered on the instance from the default quota of 50MB. It's also possible to allocate storage limits to particular users on the basis of their _Public Signing Key_, which can be found at the top of the _Accounts_ section on the settings page. +We've started by making it possible to update storage settings from the _User storage_ section of the admin panel. Administrators can now update the default storage limit for users registered on the instance from the default quota of 50MB. It's also possible to allocate storage limits to particular users on the basis of their _Public Signing Key_, which can be found at the top of the _Accounts_ section on the settings page. -Storage limits configured in this way will supercede those set via the server's config file, such that any modifications to a quota already set in the file will be ignored once you have modified or removed that user's quota via the admin panel. Admins are also able to view the parameters of all existing custom quotas loaded from either source. +Storage limits configured in this way will supercede those set via the server's config file, such that any modifications to a quota already set in the file will be ignored once you have modified or removed that user's quota via the admin panel. Admins are also able to view the parameters of all existing custom quotas loaded from either source. ### How to update -Once you've reviewed these settings and you're ready to update from 3.22.0 to 3.23.0: +Once you've reviewed these settings and you're ready to update from 3.22.0 to 3.23.0: 1. Modify your server's NGINX config file to include the new headers enabling XLSX export 2. Stop CryptPad's nodejs server @@ -2124,11 +2198,11 @@ Once you've reviewed these settings and you're ready to update from 3.22.0 to 3. ## Goals -We've been working on some long-term projects that we hope to deliver over the course of the next few releases. In the meantime, this release includes a number of minor improvements. +We've been working on some long-term projects that we hope to deliver over the course of the next few releases. In the meantime, this release includes a number of minor improvements. ## Update notes -To upgrade from 3.21.0 to 3.22.0: +To upgrade from 3.21.0 to 3.22.0: 1. Stop your server 2. Get the latest platform code with git @@ -2152,15 +2226,15 @@ To upgrade from 3.21.0 to 3.22.0: ## Goals -This release was developed over a longer period than usual due to holidays, our yearly company seminar, and generally working on some important software-adjacent projects. As such, we opted not to aim for any major features and instead introduce some minor improvements and address some users' complaints. +This release was developed over a longer period than usual due to holidays, our yearly company seminar, and generally working on some important software-adjacent projects. As such, we opted not to aim for any major features and instead introduce some minor improvements and address some users' complaints. ## Update notes -We've had a few disgruntled administrators contact us about our apparent _failure to provide a docker image_ or to otherwise support their preferred configuration. With that in mind, this is a periodic reminder that CryptPad is provided to the public under the terms of the AGPL (found within this repository in the [LICENSE file](./LICENSE)) which implies on our part no warranty, liability, or responsibility to configure your server for you. We do our best to provide the necessary information to correctly launch your own instance of the software given our limited budget, however, all such files are provided **AS IS** and are only intended to function under the narrow circumstances of usage which we recommend within the comments of the provided example configuration files. +We've had a few disgruntled administrators contact us about our apparent _failure to provide a docker image_ or to otherwise support their preferred configuration. With that in mind, this is a periodic reminder that CryptPad is provided to the public under the terms of the AGPL (found within this repository in the [LICENSE file](./LICENSE)) which implies on our part no warranty, liability, or responsibility to configure your server for you. We do our best to provide the necessary information to correctly launch your own instance of the software given our limited budget, however, all such files are provided **AS IS** and are only intended to function under the narrow circumstances of usage which we recommend within the comments of the provided example configuration files. -With that said, the vast majority of our community acts kindly and courteously towards us and each other. We really do appreciate it, and we'll continue to help you to the best of our ability. With that in mind, we're happy to announce that we've written and deployed a first version of our user guide, available at https://docs.cryptpad.org. The work that went into this was funded by NLnet foundation as an NGI Zero PET (Privacy-Enhancing Technology) grant. We are currently working on two more guides intended for developers and administrators, and will deploy them to the same domain as they are completed. In the meantime we have begun to update our README, GitHub wiki, and other resources to reflect the current recommended practices and remove references to unsupported configurations. +With that said, the vast majority of our community acts kindly and courteously towards us and each other. We really do appreciate it, and we'll continue to help you to the best of our ability. With that in mind, we're happy to announce that we've written and deployed a first version of our user guide, available at https://docs.cryptpad.org. The work that went into this was funded by NLnet foundation as an NGI Zero PET (Privacy-Enhancing Technology) grant. We are currently working on two more guides intended for developers and administrators, and will deploy them to the same domain as they are completed. In the meantime we have begun to update our README, GitHub wiki, and other resources to reflect the current recommended practices and remove references to unsupported configurations. -If you're only reading this for instructions on how to update your instance from 3.20.1 to 3.21.0: +If you're only reading this for instructions on how to update your instance from 3.20.1 to 3.21.0: 1. Stop your server 2. Get the latest platform code with git @@ -2186,10 +2260,10 @@ If you're only reading this for instructions on how to update your instance from # UplandMoa's revenge (3.20.1) -Once again we've decided to follow up our last major release with a minor "revenge" release that we wanted to make available as soon as possible. -We expect to deploy and release version 3.21.0 on Tuesday, July 28th, 2020. +Once again we've decided to follow up our last major release with a minor "revenge" release that we wanted to make available as soon as possible. +We expect to deploy and release version 3.21.0 on Tuesday, July 28th, 2020. -Features +Features * The _markmap_ rendering mode which was recently added to markdown preview pane implements some click event handlers which overlap with our existing handlers which open the embedded mindmap in our full screen "lightbox". You can now use _ctrl-click_ to trigger its built-in events (collapsing subtrees of the mindmap) without opening the lightbox. * We've made a few improvement to user and team drives: @@ -2199,7 +2273,7 @@ Features * Our rich text, code, slide, and poll apps now intercept pasted images and prompt the user to upload them, matching the existing experience of dragging an image into the same editable area. * We've received new contributions to our Romanian translation via [our weblate instance](https://weblate.cryptpad.fr/projects/cryptpad/app/). -Bug fixes +Bug fixes * We identified some race conditions in our spreadsheet app that were responsible for some corrupted data during the period leading up to our 3.20.0 release, however, we wanted to take a little more time to test before releasing the fixes. As of this release we're moving to a third version of our internal data format. This requires a client-side migration for each older sheet which will be performed by the first registered user to open a sheet in edit mode, after which a page reload will be required. Unregistered users with edit rights will only be able to view older sheets until they have been migrated by a registered user. * We now guard against empty _mathjax_ and _markmap_ code blocks in their respective markdown preview rendering extensions, as we discovered that empty inputs resulted in the display of "undefined" in the rendered element. @@ -2209,7 +2283,7 @@ Bug fixes * We've updated to a new version of `lodash` as a dependency of the linters that we use to validate our code. Unless you were actively using those linters while developing CryptPad this should have no effect for you. * Finally, when users open a link to a "self-destructing pad" we now check to make sure that the deletion key they possess has not been revoked before displaying a warning indicating that the pad in question will be deleted once they open it. -To update from 3.20.0 to 3.20.1: +To update from 3.20.0 to 3.20.1: 1. Stop your server 2. Get the latest code with `git checkout 3.20.1` @@ -2220,15 +2294,15 @@ To update from 3.20.0 to 3.20.1: ## Goals -We've held off on deploying any major features while we work towards deploying some documentation we've been busy organizing. This release features a wide range of minor features intended to address a number of github issues and frequent causes of support tickets. +We've held off on deploying any major features while we work towards deploying some documentation we've been busy organizing. This release features a wide range of minor features intended to address a number of github issues and frequent causes of support tickets. ## Update notes -This release features a modification to the recommended Content Security Policy headers as demonstrated in `./cryptpad/docs/example.nginx.conf`. CryptPad will work without making this change, however, we highly recommend updating your instance's nginx.conf as it will mitigate a variety of potential security vulnerabilities. +This release features a modification to the recommended Content Security Policy headers as demonstrated in `./cryptpad/docs/example.nginx.conf`. CryptPad will work without making this change, however, we highly recommend updating your instance's nginx.conf as it will mitigate a variety of potential security vulnerabilities. -Otherwise, we've introduced a new client-side dependency (_Mathjax_) and changed some server-side code that will require a server restart. +Otherwise, we've introduced a new client-side dependency (_Mathjax_) and changed some server-side code that will require a server restart. -To update from 3.19.1 to 3.20.0: +To update from 3.19.1 to 3.20.0: 1. Apply the recommended changes to your `nginx.conf` 2. Stop your server @@ -2277,9 +2351,9 @@ To update from 3.19.1 to 3.20.0: # Thylacine's revenge (3.19.1) -Our upcoming 3.20.0 release is planned for July 7th, 2020, but we are once again releasing a minor version featuring some nice bug fixes and usability improvements which are ready to be deployed now. In case you missed [our announcement](https://social.weho.st/@cryptpad/104360490068671089) we are phasing out our usage of the `master` and basing our releases on the `main` branch. For best results we recommend explicitly checking out code by its tag. +Our upcoming 3.20.0 release is planned for July 7th, 2020, but we are once again releasing a minor version featuring some nice bug fixes and usability improvements which are ready to be deployed now. In case you missed [our announcement](https://social.weho.st/@cryptpad/104360490068671089) we are phasing out our usage of the `master` and basing our releases on the `main` branch. For best results we recommend explicitly checking out code by its tag. -New features: +New features: * We've spent a little time making support tickets a little bit easier for both users and admins. * Users can now label their tickets with a set of predefined categories, making it easier for admins to sort through related reports. @@ -2287,7 +2361,7 @@ New features: * Teams now take advantage of the same "mailbox" functionality that powers user accounts' notification center. Team members with the "viewer" role can now use this feature to share documents with their team using the "share menu" as they already can with other users. Anyone with the ability to add a document to the team's drive will then be able to receive the notification and add the document to the team's drive for them. Going forward we'll use this functionality to implement more behaviour to make teams function more like shared user accounts. * The "pad creation screen" which is displayed to registered users when they first create a pad will no longer remember the settings used when they last created a pad. While this behaviour was intended to streamline the process of creating documents, in practice it led to some user's documents getting deleted because they didn't realize they were set to automatically expire. If you prefer not to use the defaults (owned, non-expiring) then you'll have to click a few more times to create a document, but we think that's a worthwhile tradeoff to avoid data loss. -Bug fixes: +Bug fixes: * Hitting _ctrl-A_ in the drive used to select lots of the page's elements which had no business being selected. Now it will select the contents of the directory currently being displayed. * Due to some complications in OnlyOffice (which we use for spreadsheets) remote updates made to a sheet were not displayed for users who had opened the document in "view mode". We still don't have the means to apply these remote changes in real-time, but we now prompt users to click a button to refresh the editor (not the full page) to display the latest document state. @@ -2296,25 +2370,25 @@ Bug fixes: * We noticed that the zip file constructed in the browser when you downloaded a subtree of a shared folder in your drive contained the correct directory structure but did not contain the files that were supposed to be there. This has been fixed. * Finally, we've tweaked our styles to use more specific CSS selectors to prevent a variety of styles from being accidentally applied to the wrong elements. This should make the platform a little easier to maintain and help us improve the visual consistency of a variety of elements on different pages. -To update from 3.19.0 to 3.19.1: +To update from 3.19.0 to 3.19.1: 1. Stop your server 2. Get the latest code with `git checkout 3.19.1` 3. Restart your server -If you're updating from anything other than 3.19.0 you may need other clientside dependencies (available with `bower update` and `npm i`). +If you're updating from anything other than 3.19.0 you may need other clientside dependencies (available with `bower update` and `npm i`). # Thylacine release (3.19.0) ## Goals -The intent of this release was to catch up on our backlog of bug fixes and minor usability improvements. +The intent of this release was to catch up on our backlog of bug fixes and minor usability improvements. ## Update notes -This release features an update to our clientside dependencies. +This release features an update to our clientside dependencies. -To update to 3.19.0 from 3.18.1: +To update to 3.19.0 from 3.18.1: 1. Stop your server 2. Get the latest code with git @@ -2359,17 +2433,17 @@ To update to 3.19.0 from 3.18.1: # Smilodon's revenge (3.18.1) -Our next major release (3.19.0) is still a few weeks away. -In the meantime we've been working on some minor improvements and bug fixes that we wanted to ship as soon as possible. +Our next major release (3.19.0) is still a few weeks away. +In the meantime we've been working on some minor improvements and bug fixes that we wanted to ship as soon as possible. -New features: +New features: * Rich text pads can now be exported to .doc format. A few features don't translate well to the exported format (some fonts, embedded videos and pdfs), but for the most part your documents should work * Items in the "Recent pads" section of your drive can now be dragged to other folders via the filesystem tree UI * The user admin menu (found in the top-right corner) now includes an option to display the current version of the CryptPad instance you're using. We plan to add some more information here in the near future. * The kanban app now offers better support for editing markdown within cards with autocompleted parentheses. We've also added support for embedded media, allowing users to drag images and other content into the card content editor. -Bug fixes: +Bug fixes: * Account deletion via the settings page works once again * Some small layout and usability issues in the drive have been addressed @@ -2381,7 +2455,7 @@ Bug fixes: * We've updated the parameters of the XLSX import/export functionality to prevent an "out of memory" error that primarily affected large spreadsheets. It should now allocate more memory instead of failing silently. * Finally, members of a team can now directly share or transfer ownership of a document owned by their team to their own account without having to go through the additional steps of offering it to themself and accepting the offer. -Updating from 3.18.0 to 3.18.1 is pretty standard: +Updating from 3.18.0 to 3.18.1 is pretty standard: 1. Stop your server 2. Get the latest code with git @@ -2391,17 +2465,17 @@ Updating from 3.18.0 to 3.18.1 is pretty standard: ## Goals -This is a big one! A lot of people are going to love it and a few are probably going to hate it. +This is a big one! A lot of people are going to love it and a few are probably going to hate it. -This release introduces some major changes to our apps' appearances with the intent of making it easier to use, easier for us to support, and easier to maintain. +This release introduces some major changes to our apps' appearances with the intent of making it easier to use, easier for us to support, and easier to maintain. ## Update notes -If you're using a mostly standard CryptPad installation this should be a rather easy update. +If you're using a mostly standard CryptPad installation this should be a rather easy update. -If you've customized your styles, particularly for the purpose of overriding the default colors, you may encounter some problems. **We recommend that you test this version in a staging environment** before deploying to ensure that it is compatible with your modifications. +If you've customized your styles, particularly for the purpose of overriding the default colors, you may encounter some problems. **We recommend that you test this version in a staging environment** before deploying to ensure that it is compatible with your modifications. -Otherwise, update to 3.18.0 from 3.17.0 in the following manner: +Otherwise, update to 3.18.0 from 3.17.0 in the following manner: 1. stop your server 2. fetch the latest code with git @@ -2428,13 +2502,13 @@ Otherwise, update to 3.18.0 from 3.17.0 in the following manner: # RedGazelle's revenge release (3.17.1) -In recent months a growing amount of our time has been going towards answering support tickets, emails, and GitHub issues. This has made it a little more difficult to also maintain a bi-weekly release schedule, since there's some overhead involved in deploying our latest code and producing release notes. +In recent months a growing amount of our time has been going towards answering support tickets, emails, and GitHub issues. This has made it a little more difficult to also maintain a bi-weekly release schedule, since there's some overhead involved in deploying our latest code and producing release notes. -To ease our workload, we've decided to switch to producing a full release every three weeks, with an optional patch release at some point in the middle. Patch releases may fix major issues that can't wait three weeks or may simply consist of a few minor fixes that are trivial to deploy. +To ease our workload, we've decided to switch to producing a full release every three weeks, with an optional patch release at some point in the middle. Patch releases may fix major issues that can't wait three weeks or may simply consist of a few minor fixes that are trivial to deploy. -This release fixes a few spreadsheet issues and introduces a more responsive layout for user drives in list mode. +This release fixes a few spreadsheet issues and introduces a more responsive layout for user drives in list mode. -Updating to 3.17.1 from 3.17.0 is pretty standard: +Updating to 3.17.1 from 3.17.0 is pretty standard: 1. Stop your server 2. Get the latest code with git @@ -2444,25 +2518,25 @@ Updating to 3.17.1 from 3.17.0 is pretty standard: ## Goals -Our goal for this release was to introduce a first version of comments and mentions in our rich text editor as a part of a second R&D project funded by [NLnet](https://nlnet.nl/). We also received the results of an "accessibility audit" that was conducted as a part of our first NLnet PET project and so we've begun to integrate the auditor's feedback into the platform. +Our goal for this release was to introduce a first version of comments and mentions in our rich text editor as a part of a second R&D project funded by [NLnet](https://nlnet.nl/). We also received the results of an "accessibility audit" that was conducted as a part of our first NLnet PET project and so we've begun to integrate the auditor's feedback into the platform. -Otherwise we've continued with our major goal of continuing to support a growing number of users on our instance via server improvements (without introducing any regressions). +Otherwise we've continued with our major goal of continuing to support a growing number of users on our instance via server improvements (without introducing any regressions). ## Update notes -The most drastic change in this release is that we've removed all docker-related files from the platform's repository. These files were all added via community contributions. Having them in the main repo gave the impression that we support installation via docker (which we do not). +The most drastic change in this release is that we've removed all docker-related files from the platform's repository. These files were all added via community contributions. Having them in the main repo gave the impression that we support installation via docker (which we do not). -Docker-related files can now be found in the community-support [cryptpad-docker](https://github.com/cryptpad/cryptpad-docker/) repository. -If you have an existing instance that you've installed using docker and you'd like to update, you may review the [migration guide](https://github.com/cryptpad/cryptpad-docker/blob/master/MIGRATION.md). If you encounter any problems in the process we advise that you create an issue in the repository's issue-tracker. +Docker-related files can now be found in the community-support [cryptpad-docker](https://github.com/cryptpad/cryptpad-docker/) repository. +If you have an existing instance that you've installed using docker and you'd like to update, you may review the [migration guide](https://github.com/cryptpad/cryptpad-docker/blob/master/MIGRATION.md). If you encounter any problems in the process we advise that you create an issue in the repository's issue-tracker. -Once again, this repository is **community-maintained**. If you are using this repository then _you are a part of the community_! Bug reports are useful, but fixes are even better! +Once again, this repository is **community-maintained**. If you are using this repository then _you are a part of the community_! Bug reports are useful, but fixes are even better! -Otherwise, this is a fairly standard release. We've updated two of our client-side dependencies: +Otherwise, this is a fairly standard release. We've updated two of our client-side dependencies: 1. ChainPad features a memory management optimization which is particularly relevant to editing very large documents or loading a drive with a large number of files. In one test we were able to reduce memory consumption in Chrome from 1.7GB to 20MB. 2. CKEditor (the third-party library we use for our rich-text editor) has been updated so that we could make use of some more recent APIs for the _comments_ feature. -To update from **3.16.0** to **3.17.0**: +To update from **3.16.0** to **3.17.0**: 1. Stop your server 2. Fetch the latest source with git @@ -2492,15 +2566,15 @@ To update from **3.16.0** to **3.17.0**: ## Goals -We've continued to keep a close eye on server performance since our last release while making minimal changes. Our goal for this release has been to improve server scalability further while also addressing user needs with updates to our client code. +We've continued to keep a close eye on server performance since our last release while making minimal changes. Our goal for this release has been to improve server scalability further while also addressing user needs with updates to our client code. -We were pleasantly surprised to receive a pull request implementing a basic version of [author colors](https://github.com/cryptpad/cryptpad/issues/41) in our code editor. Since it was nearly ready to go we set some time aside to polish it up a little bit to include it in this release. +We were pleasantly surprised to receive a pull request implementing a basic version of [author colors](https://github.com/cryptpad/cryptpad/issues/41) in our code editor. Since it was nearly ready to go we set some time aside to polish it up a little bit to include it in this release. ## Update notes -We've updated the example nginx config in order to include an `Access-Control-Allow-Origin` header that was not included. We've also added a new configuration point in response to [this issue](https://github.com/cryptpad/cryptpad/issues/529) about the server's child processes using too many threads. Administrators may not set a maximum number of child processes via `config.js` using `maxWorkers: `. We recommend using one less than the number of available cores, though one worker should be sufficient as long as your server is not under heavy load. +We've updated the example nginx config in order to include an `Access-Control-Allow-Origin` header that was not included. We've also added a new configuration point in response to [this issue](https://github.com/cryptpad/cryptpad/issues/529) about the server's child processes using too many threads. Administrators may not set a maximum number of child processes via `config.js` using `maxWorkers: `. We recommend using one less than the number of available cores, though one worker should be sufficient as long as your server is not under heavy load. -As usual, updating from the previous release can be accomplished by: +As usual, updating from the previous release can be accomplished by: 1. stopping your server 2. pulling the latest code with git @@ -2533,13 +2607,13 @@ As usual, updating from the previous release can be accomplished by: ## Goals -Our plan for this release was to allow our server's code to stabilize after a prologued period of major changes. The massive surge of new users on cryptpad.fr forced us to change our plans and focus instead on increasing performance and scalability of our serverside code and its supporting infrastructure. Most of this release's changes have been thoroughly tested as they've been deployed to our instance on an ongoing basis, however, we're still looking forward to stabilizing as planned. +Our plan for this release was to allow our server's code to stabilize after a prologued period of major changes. The massive surge of new users on cryptpad.fr forced us to change our plans and focus instead on increasing performance and scalability of our serverside code and its supporting infrastructure. Most of this release's changes have been thoroughly tested as they've been deployed to our instance on an ongoing basis, however, we're still looking forward to stabilizing as planned. -We also ended up making significant improvements to our clientside code, since the increased load on the server seemed to exacerbate a few race conditions which occurred less frequently under the previous circumstances. +We also ended up making significant improvements to our clientside code, since the increased load on the server seemed to exacerbate a few race conditions which occurred less frequently under the previous circumstances. ## Update notes -Updating from version 3.14.0 should follow the usual process: +Updating from version 3.14.0 should follow the usual process: 1. stop your server 2. fetch the latest code with git @@ -2547,7 +2621,7 @@ Updating from version 3.14.0 should follow the usual process: 4. install serverside dependencies with `npm i` 5. start your server -You may notice that the server now launches a number of child processes named `crypto-worker.js` and `db-worker.js`. These worker processes make use of however many cores your server has available to perform more CPU-intensive tasks in parallel. +You may notice that the server now launches a number of child processes named `crypto-worker.js` and `db-worker.js`. These worker processes make use of however many cores your server has available to perform more CPU-intensive tasks in parallel. ## Features @@ -2563,7 +2637,7 @@ You may notice that the server now launches a number of child processes named `c ## Bug fixes -This release contains fixes for a lot of bugs. We'll provide a brief overview, but in the interest of putting more time towards development I'll just put my strong recommendation that you update. +This release contains fixes for a lot of bugs. We'll provide a brief overview, but in the interest of putting more time towards development I'll just put my strong recommendation that you update. * The server process didn't always close file descriptors that it opened, resulting in an EMFILE error when the system ran out of available file descriptors. Now it closes them. * The server also kept an unbounded amount of data in an in-memory cache under certain circumstances. Now it doesn't. @@ -2580,15 +2654,15 @@ This release contains fixes for a lot of bugs. We'll provide a brief overview, b ## Goals -We planned a one-week release cycle in order to finish up some major features that were already in development during our last release. +We planned a one-week release cycle in order to finish up some major features that were already in development during our last release. -In the meantime, the reaction to the COVID-19 pandemic has resulted in a greatly increased load on our servers, so we've begun to focus on improving stability to ensure that we are able to keep up with demand. +In the meantime, the reaction to the COVID-19 pandemic has resulted in a greatly increased load on our servers, so we've begun to focus on improving stability to ensure that we are able to keep up with demand. ## Update notes -We had some trouble during the week of March 9th, 2020, as the CryptPad.fr server started throwing EMFILE errors. This means that it was trying to open new files (for reading or writing) but there were too many files open already. We've added some new code to help debug the issue, but there is not yet a fix in place. The maximum number of open files on our host OS had been increased by several orders of magnitude (several years ago) but we're now aware that the systemd service file that launches the API server does not respect this global limit. As such, we've updated the example service file to indicate how you can update this limit yourself. For an example of how to update this limit at the OS level, see this page: https://docs.oracle.com/cd/E19623-01/820-6168/file-descriptor-requirements.html +We had some trouble during the week of March 9th, 2020, as the CryptPad.fr server started throwing EMFILE errors. This means that it was trying to open new files (for reading or writing) but there were too many files open already. We've added some new code to help debug the issue, but there is not yet a fix in place. The maximum number of open files on our host OS had been increased by several orders of magnitude (several years ago) but we're now aware that the systemd service file that launches the API server does not respect this global limit. As such, we've updated the example service file to indicate how you can update this limit yourself. For an example of how to update this limit at the OS level, see this page: https://docs.oracle.com/cd/E19623-01/820-6168/file-descriptor-requirements.html -Otherwise, updating from 3.13.0 to 3.14.0 is as usual: +Otherwise, updating from 3.13.0 to 3.14.0 is as usual: 1. stop your server 2. fetch the latest source @@ -2598,7 +2672,7 @@ Otherwise, updating from 3.13.0 to 3.14.0 is as usual: ## Features -We're very happy to announce a major update to our kanban application! We've made a lot of changes, but the most notables ones are: +We're very happy to announce a major update to our kanban application! We've made a lot of changes, but the most notables ones are: * the ability to add markdown content to your cards and edit it collaboratively in real-time * tags on cards and the ability to filter cards by tags at the top of the application @@ -2608,7 +2682,7 @@ We're very happy to announce a major update to our kanban application! We've mad * a smaller palette of pre-chosen colors for cards and boards instead of a color-picker, to make it easier to choose matching colors for tasks * the ability to drag cards and boards to the trash instead of having to click a small X and confirm their deletion -We've also improved message throughput for our server by splitting cryptographic signature validation into separate processes. On a quad core server this means you should be able to handle (roughly) four times the messages. +We've also improved message throughput for our server by splitting cryptographic signature validation into separate processes. On a quad core server this means you should be able to handle (roughly) four times the messages. ## Bug fixes @@ -2627,39 +2701,39 @@ We've also improved message throughput for our server by splitting cryptographic ## Goals -This release cycle we prioritized the completion of "access lists", a major feature that we're excited to introduce. +This release cycle we prioritized the completion of "access lists", a major feature that we're excited to introduce. ## Update notes -Nearly every week (sometimes more than once) we end up taking time away from development to help administrators to configure their CryptPad instances. We're happy to see more instances popping up, but ideally we'd like to spend more of our time working on new features. With this in mind we devoted some time to simplify instance configuration and to clarify some points where people commonly have difficulty. +Nearly every week (sometimes more than once) we end up taking time away from development to help administrators to configure their CryptPad instances. We're happy to see more instances popping up, but ideally we'd like to spend more of our time working on new features. With this in mind we devoted some time to simplify instance configuration and to clarify some points where people commonly have difficulty. -If you review `cryptpad/config.example.js` you'll notice it is significantly smaller than it was last release. -Old configuration files should be backwards compatible (if you copied `config.example.js` to `config.js` in order to customize it). -The example has been reorganized so that the most important parts (which people seemed to miss most of the time) are at the top. -Most of the fields which were defined within the config file now have defaults defined within the server itself. -If you supply these values they will override the default, but for the most part they can be removed. +If you review `cryptpad/config.example.js` you'll notice it is significantly smaller than it was last release. +Old configuration files should be backwards compatible (if you copied `config.example.js` to `config.js` in order to customize it). +The example has been reorganized so that the most important parts (which people seemed to miss most of the time) are at the top. +Most of the fields which were defined within the config file now have defaults defined within the server itself. +If you supply these values they will override the default, but for the most part they can be removed. -We advise that you read the comments at the top of the example, in particular the points related to `httpUnsafeOrigin` and `httpSafeOrigin` which are used to protect users' cryptographic keys in the event of a cross-site scripting (XSS) vulnerability. -If these values are not correctly set then your users will not benefit from all the security measures we've spent lots of time implemented. +We advise that you read the comments at the top of the example, in particular the points related to `httpUnsafeOrigin` and `httpSafeOrigin` which are used to protect users' cryptographic keys in the event of a cross-site scripting (XSS) vulnerability. +If these values are not correctly set then your users will not benefit from all the security measures we've spent lots of time implemented. -A lot of the fields that were present as modifiable defaults have been removed or commented out in the example config. -If you supply them then they will override the default behaviour, however, you probably won't need to and doing so might break important functionality. -Content-Security Policy (CSP) definitions should be safe to remove, as should `httpAddress`, `httpPort`, and `httpSafePort` (unless you need to run the nodejs API server on an address other than `localhost` or port 3000. +A lot of the fields that were present as modifiable defaults have been removed or commented out in the example config. +If you supply them then they will override the default behaviour, however, you probably won't need to and doing so might break important functionality. +Content-Security Policy (CSP) definitions should be safe to remove, as should `httpAddress`, `httpPort`, and `httpSafePort` (unless you need to run the nodejs API server on an address other than `localhost` or port 3000. -Up until now it's been possible for administrators to allow users to pay for accounts (on their server) via https://accounts.cryptpad.fr. -Our intent was to securely handle payment and then split the proceeds between ourselves and the instance's administrator. -In practice this just created extra work for us because we ended up having to contact admins, all of whom have opted to treat the subscription as a donation to support development. -As such we have disabled the ability of users to pay for premium subscriptions (on https://accounts.cryptpad.fr) for any instance other than our own. +Up until now it's been possible for administrators to allow users to pay for accounts (on their server) via https://accounts.cryptpad.fr. +Our intent was to securely handle payment and then split the proceeds between ourselves and the instance's administrator. +In practice this just created extra work for us because we ended up having to contact admins, all of whom have opted to treat the subscription as a donation to support development. +As such we have disabled the ability of users to pay for premium subscriptions (on https://accounts.cryptpad.fr) for any instance other than our own. -Servers with premium subscriptions enabled were configured to check whether anyone had subscribed to a premium account by querying our accounts server on a daily basis. -We've left this daily check in place despite premium subscriptions being disabled because it informs us how many third-party instances exist and what versions they are running. -We don't sell or share this information with anyone, but it is useful to us because it informs us what older data structures we have to continue to support. -For instance, we retain code for migrating documents to newer data formats as long as we know that there are still instances that have not run those migrations. -We also cite the number of third-party instances when applying for grants as an indicator of the value of funding our project. -In any case, you can disable this daily check-in by setting `blockDailyCheck` to `true` in `config/config.js`. +Servers with premium subscriptions enabled were configured to check whether anyone had subscribed to a premium account by querying our accounts server on a daily basis. +We've left this daily check in place despite premium subscriptions being disabled because it informs us how many third-party instances exist and what versions they are running. +We don't sell or share this information with anyone, but it is useful to us because it informs us what older data structures we have to continue to support. +For instance, we retain code for migrating documents to newer data formats as long as we know that there are still instances that have not run those migrations. +We also cite the number of third-party instances when applying for grants as an indicator of the value of funding our project. +In any case, you can disable this daily check-in by setting `blockDailyCheck` to `true` in `config/config.js`. -Finally, we've implemented the ability to set a higher limit on the maximum size of uploaded files for premium users (paying users on CryptPad.fr and users with entries in `customLimits` on other instances). -Set this limit as a number (of bytes) with `premiumUploadSize` in your config file. +Finally, we've implemented the ability to set a higher limit on the maximum size of uploaded files for premium users (paying users on CryptPad.fr and users with entries in `customLimits` on other instances). +Set this limit as a number (of bytes) with `premiumUploadSize` in your config file. ## Features @@ -2681,21 +2755,21 @@ Set this limit as a number (of bytes) with `premiumUploadSize` in your config fi ## Goals -As of our last release our 'history trim' functionality was almost ready to go. We took this release period to do some extensive testing and to prepare the 'allow list' functionality which will be included in our next release. +As of our last release our 'history trim' functionality was almost ready to go. We took this release period to do some extensive testing and to prepare the 'allow list' functionality which will be included in our next release. -In the meantime, we also aimed to improve performance, add a few small but nice features, and fix a number of bugs. +In the meantime, we also aimed to improve performance, add a few small but nice features, and fix a number of bugs. ## Update notes -This release includes updates to: +This release includes updates to: 1. the server and its dependencies 2. the example nginx configuration which we recommend for production installations 3. the client code and its dependencies -Our ability to debug CryptPad's usage of shared workers (on the client) has been complicated by the fact that Firefox's shared worker debugging panel was not working for our instance. We finally traced the problem back to a Content-Security Policy setting in our configuration file. The issue can be addressed by adding a `resource:` entry in the `connect-src` header. We've updated the example nginx config to reflect this. You can deploy this version of CryptPad without this modification, but without it our ability to debug and fix issues related to shared worker will be extremely limited. +Our ability to debug CryptPad's usage of shared workers (on the client) has been complicated by the fact that Firefox's shared worker debugging panel was not working for our instance. We finally traced the problem back to a Content-Security Policy setting in our configuration file. The issue can be addressed by adding a `resource:` entry in the `connect-src` header. We've updated the example nginx config to reflect this. You can deploy this version of CryptPad without this modification, but without it our ability to debug and fix issues related to shared worker will be extremely limited. -Otherwise, updating from CryptPad v3.11.0 is pretty much the same as normal: +Otherwise, updating from CryptPad v3.11.0 is pretty much the same as normal: 1. stop your server 2. pull the latest code via git @@ -2737,21 +2811,21 @@ Otherwise, updating from CryptPad v3.11.0 is pretty much the same as normal: ## Goals -For this release we aimed to phase in two major features that we've been anticipating for a while: "history trim" and "safe links". +For this release we aimed to phase in two major features that we've been anticipating for a while: "history trim" and "safe links". -History trim will allow users to remove the old versions of their documents which continue to count against their storage quotas. It will be formally introduced in our next release, even though its server-side components are all ready. We had to reorganize and modify a lot of our server code, so we wanted to wait and make sure there were no regressions in our existing functionality before moving ahead. +History trim will allow users to remove the old versions of their documents which continue to count against their storage quotas. It will be formally introduced in our next release, even though its server-side components are all ready. We had to reorganize and modify a lot of our server code, so we wanted to wait and make sure there were no regressions in our existing functionality before moving ahead. -We're introducing the concept of "safe links" in CryptPad. Users can continue to share links to documents which include the cryptographic secrets necessary to read or edit them, but whenever possible we will replace those secrets with a document id. This will make it less likely for encryption keys to be exposed to third parties through invasive browser extensions or passive behaviour like history synchronization across devices. +We're introducing the concept of "safe links" in CryptPad. Users can continue to share links to documents which include the cryptographic secrets necessary to read or edit them, but whenever possible we will replace those secrets with a document id. This will make it less likely for encryption keys to be exposed to third parties through invasive browser extensions or passive behaviour like history synchronization across devices. ## Update notes -This release features a few changes to the server: +This release features a few changes to the server: 1. The "legal notice" feature which we included in the previous release turned out to be incorrect. We've since fixed it. We document this functionality [here](https://github.com/cryptpad/cryptpad/blob/e8b905282a2cde826ad9100dcad6b59a50c70e8b/www/common/application_config_internal.js#L35-L41), but you'll need to implement the recommended changes in `cryptpad/customize/application_config.js` for best effect. 2. We've dropped server-side support for the `retainData` attribute in `cryptpad/config/config.js`. Previously you could configure CryptPad to delete unpinned, inactive data immediately or to move it into an archive for a configurable retention period. We've removed the option to delete data outright, since it introduces additional complexity in the server which we don't regularly test. We also figure that administrators will appreciate this default in the event of a bug which incorrectly flags data as inactive. 3. We've fixed an incorrect line in [the example nginx configuration file](https://github.com/cryptpad/cryptpad/commit/1be01c07eee3431218d0b40a58164f60fec6df31). If you're using nginx as a reverse proxy for your CryptPad instance you should correct this line. It is used to set Content-Security Policy headers for the sandboxed-iframe which provides an additional layer of security for users in the event of a cross-site-scripting (XSS) vulnerability within CryptPad. If you find that your instance stops working after applying this change it is likely that you have not correctly configured your instance to use a secondary domain for its sandbox. See [this section of `cryptpad/config/config.example.js`](https://github.com/cryptpad/cryptpad/blob/c388641479128303363d8a4247f64230c08a7264/config/config.example.js#L94-L96) for more information. -Otherwise, deploying the new code should be fairly simple: +Otherwise, deploying the new code should be fairly simple: 1. stop your server 2. fetch the latest code from the git repository @@ -2783,19 +2857,19 @@ Otherwise, deploying the new code should be fairly simple: ## Goals -For this release we aimed to finish the last major feature of our CryptPad Teams project as well as some long-awaited features that we've planned to demo at FOSDEM 2020. +For this release we aimed to finish the last major feature of our CryptPad Teams project as well as some long-awaited features that we've planned to demo at FOSDEM 2020. ## Update notes -The CryptPad repository's _docs_ directory now includes a _systemd service file_ which you can use to ensure that CryptPad stays up and running. We're working on some step-by-step documentation to describe how to make use of it, but for now you can probably find some instructions by searching the web. +The CryptPad repository's _docs_ directory now includes a _systemd service file_ which you can use to ensure that CryptPad stays up and running. We're working on some step-by-step documentation to describe how to make use of it, but for now you can probably find some instructions by searching the web. -We've also updated the provided example.nginx.conf to include a minor but important change to the CSP settings for our OnlyOffice spreadsheet integration. +We've also updated the provided example.nginx.conf to include a minor but important change to the CSP settings for our OnlyOffice spreadsheet integration. -Up until now we have not been deleting unowned encrypted files from our server. As of this release `cryptpad/scripts/evict-inactive.js` includes logic to identify inactive, unpinned files. Identified files are first moved to your instance's _archive_ directory for a configurable period, after which they are deleted. This script is not run automatically, so if you haven't configured a cron job to run periodically then inactive files will not be removed. We recommend running the script once per day at a time when you expect your server to be relatively idle, since it consumes a non-negligible amount of server resources. +Up until now we have not been deleting unowned encrypted files from our server. As of this release `cryptpad/scripts/evict-inactive.js` includes logic to identify inactive, unpinned files. Identified files are first moved to your instance's _archive_ directory for a configurable period, after which they are deleted. This script is not run automatically, so if you haven't configured a cron job to run periodically then inactive files will not be removed. We recommend running the script once per day at a time when you expect your server to be relatively idle, since it consumes a non-negligible amount of server resources. -Finally, in case you live in a political jurisdiction that requires web site administrators to display their legal information, we've made it easier to add a link to a custom page. See `cryptpad/www/common/application_config_internal.js` for details, particularly the comments above `config.imprint`. +Finally, in case you live in a political jurisdiction that requires web site administrators to display their legal information, we've made it easier to add a link to a custom page. See `cryptpad/www/common/application_config_internal.js` for details, particularly the comments above `config.imprint`. -To update from v3.9.0: +To update from v3.9.0: 1. update the CSP settings in your reverse proxy's configuration file to match those in nginx.example.conf @@ -2840,17 +2914,17 @@ To update from v3.9.0: ## Goals -Over time we've added many small configuration values to CryptPad's `config/config.js`. -As the number of possible variations grew it became increasingly difficult to test the platform and to provide clear documentation. -Ultimately this has made the platform more difficult to understand and consequently to host. +Over time we've added many small configuration values to CryptPad's `config/config.js`. +As the number of possible variations grew it became increasingly difficult to test the platform and to provide clear documentation. +Ultimately this has made the platform more difficult to understand and consequently to host. -This release features relatively few bug fixes or features. -Instead, we took the calm period of the northern winter holidays to simplify the process of running a server and to begin working on some comprehensive documentation. +This release features relatively few bug fixes or features. +Instead, we took the calm period of the northern winter holidays to simplify the process of running a server and to begin working on some comprehensive documentation. ## Update notes -We have chosen to drop support for a number of parameters which we believe are not widely used. -Read the following list carefully before updating, as you could be relying on behaviour which no longer exists. +We have chosen to drop support for a number of parameters which we believe are not widely used. +Read the following list carefully before updating, as you could be relying on behaviour which no longer exists. * Due to reasons of security and performance we have long advised that administrators make their instance available only over HTTPS provided by a reverse proxy such as nginx instead of loading TLS certificates via the node process itself. We have removed the option of serving HTTPS traffic directly from node by removing all support for HTTPS in this process. * Over the years many administrators have had to migrate their instance from one machine to another and have had difficulty identifying which directories were responsible for storing user data. We are beginning to migrate all user-generated data from the repository's root into the `data` directory as a new default, allowing for admins to migrate content by copying this single directory. @@ -2879,7 +2953,7 @@ Read the following list carefully before updating, as you could be relying on be * if your instance is configured in the default manner you shouldn't actually need this value, as it will default to using `/cryptpad_websocket`. * if you have configured your instance to serve all static assets over one domain and to host your API server on another, set `externalWebsocketURL` to `wss://your-domain.tld/cryptpad_websocket` or whatever URL will be correctly forwarded to your API server. -Once you have reviewed your configuration files and ensured that they are correct, update to 3.9.0 with the following steps: +Once you have reviewed your configuration files and ensured that they are correct, update to 3.9.0 with the following steps: 1. take your server down 2. get the latest code with `git pull origin master` @@ -2901,26 +2975,26 @@ Once you have reviewed your configuration files and ensured that they are correc # IsolobodonPortoricensis release (3.8.0) -We had some trouble finding an extinct animal whose name started with "I", and we had to resort to using a scientific name. -Despite this long name, this was a very short release cycle. -It's the last release of 2019, so we hope you like it! +We had some trouble finding an extinct animal whose name started with "I", and we had to resort to using a scientific name. +Despite this long name, this was a very short release cycle. +It's the last release of 2019, so we hope you like it! ## Goals -During this release cycle we prioritized the mitigation of some social abuse vectors and the ability to invite users to a team via a link. -We have more improvements planned for both features, but we wanted to release what we had before the end of the year as our team is taking a little time off to recharge for 2020. +During this release cycle we prioritized the mitigation of some social abuse vectors and the ability to invite users to a team via a link. +We have more improvements planned for both features, but we wanted to release what we had before the end of the year as our team is taking a little time off to recharge for 2020. ## Update notes -This is a small and simple release. We made a very minor improvement to the server which will require a restart, but everything will still work if you choose not to. +This is a small and simple release. We made a very minor improvement to the server which will require a restart, but everything will still work if you choose not to. -Update from 3.7.0 to 3.8.0 with the following procedure: +Update from 3.7.0 to 3.8.0 with the following procedure: 1. Take your server down 2. Get the latest code with `git pull origin master` 3. Bring your server back up -Or if you've set up your admin interface: +Or if you've set up your admin interface: 1. Pull the latest code 2. Click the admin panel's "Flush cache" button @@ -2950,22 +3024,22 @@ Or if you've set up your admin interface: ## Goals -As we are getting closer to the end of our CryptPad Teams project we planned to spend this release addressing some of the difficulties that users have reported regarding the usage of our newer social features. +As we are getting closer to the end of our CryptPad Teams project we planned to spend this release addressing some of the difficulties that users have reported regarding the usage of our newer social features. ## Update notes -This release includes an upgrade to a newer version of JQuery which mitigates a minor vulnerability which could have contributed to the presence of an XSS attack. We weren't using the affected methods in the library, but there's no harm in updating as it will protect against the vulnerability affecting user data in the future. +This release includes an upgrade to a newer version of JQuery which mitigates a minor vulnerability which could have contributed to the presence of an XSS attack. We weren't using the affected methods in the library, but there's no harm in updating as it will protect against the vulnerability affecting user data in the future. -We've also made some non-critical fixes to the server code, so you'll need to restart after pulling the latest code to take advantage of these improvements. +We've also made some non-critical fixes to the server code, so you'll need to restart after pulling the latest code to take advantage of these improvements. -Update to 3.7.0 from 3.6.0 using the normal update procedure: +Update to 3.7.0 from 3.6.0 using the normal update procedure: 1. stop your server 2. pull the latest code via git 3. run `bower update` 4. restart your server -If you're using an up-to-date version of NPM you should find that running `npm update` prints a notice that one of the packages you've installed is seeking funding. Entering `npm fund` will print information about our OpenCollective funding campaign. If you're running a slightly older version of NPM and you wish to support CryptPad's development you can do so by visiting https://opencollective.com/cryptpad . +If you're using an up-to-date version of NPM you should find that running `npm update` prints a notice that one of the packages you've installed is seeking funding. Entering `npm fund` will print information about our OpenCollective funding campaign. If you're running a slightly older version of NPM and you wish to support CryptPad's development you can do so by visiting https://opencollective.com/cryptpad . ## Features @@ -2997,11 +3071,11 @@ If you're using an up-to-date version of NPM you should find that running `npm u ## Goals -We're following up our last few releases of major core developments with an effort to improve reliability in some unstable areas and make some superficial tweaks to improve usability of some critical interfaces. +We're following up our last few releases of major core developments with an effort to improve reliability in some unstable areas and make some superficial tweaks to improve usability of some critical interfaces. ## Update notes -Update to 3.6.0 from 3.5.0 using the normal update procedure: +Update to 3.6.0 from 3.5.0 using the normal update procedure: 1. stop your server 2. pull the latest code via git @@ -3033,13 +3107,13 @@ Update to 3.6.0 from 3.5.0 using the normal update procedure: ## Goals -This release features work that we've been planning for a long time centered around sharing collections of documents in a more granular way. +This release features work that we've been planning for a long time centered around sharing collections of documents in a more granular way. -This is our first release since David Benqué joined our team, so in addition to these team-centric updates we also worked on integrating some UI/UX improvements. +This is our first release since David Benqué joined our team, so in addition to these team-centric updates we also worked on integrating some UI/UX improvements. ## Update notes -Updating to 3.5.0 from 3.4.0 is simple. +Updating to 3.5.0 from 3.4.0 is simple. 1. stop your server 2. pull the latest code via git @@ -3072,11 +3146,11 @@ Updating to 3.5.0 from 3.4.0 is simple. ## Goals -This is a small release, focused on bug fixes and UI improvements, while we're finalizing bigger team-centric features planned for the next release. +This is a small release, focused on bug fixes and UI improvements, while we're finalizing bigger team-centric features planned for the next release. ## Update notes -This is a pretty basic release: +This is a pretty basic release: 1. stop your server 2. pull the latest source code @@ -3106,11 +3180,11 @@ This is a pretty basic release: ## Goals -We've continued to prioritize the development of team-centric features in CryptPad. This release was focused on stabilizing the code for Teams and making them available to the users. +We've continued to prioritize the development of team-centric features in CryptPad. This release was focused on stabilizing the code for Teams and making them available to the users. ## Update notes -This is a pretty basic release: +This is a pretty basic release: 1. stop your server 2. pull the latest source code @@ -3118,7 +3192,7 @@ This is a pretty basic release: 4. install the latest clientside dependencies with `bower update` 5. restart your server -Note: we've updated our Nginx configuration to fix any missing trailing slash in the URL for the newest applications: https://github.com/cryptpad/cryptpad/commit/d4e5b98c140c28417e008379ec7af7cdc235792b +Note: we've updated our Nginx configuration to fix any missing trailing slash in the URL for the newest applications: https://github.com/cryptpad/cryptpad/commit/d4e5b98c140c28417e008379ec7af7cdc235792b ## Features @@ -3151,13 +3225,13 @@ Note: we've updated our Nginx configuration to fix any missing trailing slash in ## Goals -We've continued to prioritize the development of team-centric features in CryptPad. This release implements most of the core functionality for fully-functional teams as a core part of CryptPad, though they're not quite ready for use just yet. +We've continued to prioritize the development of team-centric features in CryptPad. This release implements most of the core functionality for fully-functional teams as a core part of CryptPad, though they're not quite ready for use just yet. -Beyond teams we did a little work to standardize some serverside APIs related to storage. +Beyond teams we did a little work to standardize some serverside APIs related to storage. ## Update notes -This is a pretty basic release: +This is a pretty basic release: 1. stop your server 2. pull the latest source code @@ -3189,7 +3263,7 @@ This is a pretty basic release: ## Goals -For CryptPad 3.1.0 we prioritized our work on team-centric features. In particular we wanted to finish some improvements to make our notifications system more private and start making use of our prior work on editable pad metadata. +For CryptPad 3.1.0 we prioritized our work on team-centric features. In particular we wanted to finish some improvements to make our notifications system more private and start making use of our prior work on editable pad metadata. ## Update notes @@ -3214,9 +3288,9 @@ For CryptPad 3.1.0 we prioritized our work on team-centric features. In particul * checking disk usage (global and for particular users) * loading a user's pin log -Baiji depends on updates to clientside and serverside dependencies. +Baiji depends on updates to clientside and serverside dependencies. -To update: +To update: 1. Take down your server 2. Pull the latest code @@ -3258,18 +3332,18 @@ To update: # Aurochs release (v3.0.0) -The move to 3.0 is mostly because we ran out of letters in the alphabet for our 2.0 release cycle. -Releases in this cycle will be named according to a theme of "extinct animals", a list which is unfortunately getting longer all the time. +The move to 3.0 is mostly because we ran out of letters in the alphabet for our 2.0 release cycle. +Releases in this cycle will be named according to a theme of "extinct animals", a list which is unfortunately getting longer all the time. ## Goals -In this release, we took more time than usual to make some big changes to the way the platform works, taking great care to maintain or improve stability. +In this release, we took more time than usual to make some big changes to the way the platform works, taking great care to maintain or improve stability. -Up until now it has been necessary to create documents with the whatever settings they might require in the future, after which point it was not possible to change them. This release introduces the ability of the server to store and read amendments to document metadata. This will soon allow users of owned documents to delegate that ownership to their friends, add or modify expiration times, and make other modifications that will greatly improve their control over their data. +Up until now it has been necessary to create documents with the whatever settings they might require in the future, after which point it was not possible to change them. This release introduces the ability of the server to store and read amendments to document metadata. This will soon allow users of owned documents to delegate that ownership to their friends, add or modify expiration times, and make other modifications that will greatly improve their control over their data. ## Update notes -During this development period we performed an extensive audit of our existing features and discovered a few potential security issues which we've addressed. We plan to announce the details of these flaws once administrators have had sufficient time to update their instances. If you are running a CryptPad instance, we advise you to update to 3.0.0 at your earliest opportunity. +During this development period we performed an extensive audit of our existing features and discovered a few potential security issues which we've addressed. We plan to announce the details of these flaws once administrators have had sufficient time to update their instances. If you are running a CryptPad instance, we advise you to update to 3.0.0 at your earliest opportunity. * It was brought to our attention that while expired pads were not being served beyond their expiration time, they were not being removed as intended. The cause was due to our failure to document a configuration point (`enableTaskScheduling`) that was added to make expiration optional in the example configuration file. We've removed this configuration point so that tasks like expiration will always be scheduled. Expiration of tasks was already integrated into the main server process, but we have added a new configuration point to the server in case any administrators would like to run the expiration tasks in a dedicated process for performance reasons. To disable the integration, change `disableIntegratedTasks` from `false` to `true` in the server configuration file. * This release depends on updates to three clientside libraries (`netflux-websocket@0.1.20`, `chainpad-netflux@0.9.0`, and `chainpad-listmap@0.7.0`). These changes are **not compatible with older versions of the server**. To update: @@ -3353,11 +3427,11 @@ During this development period we performed an extensive audit of our existing f ## Goals -This release coincided with XWiki's yearly seminar, so our regular schedule was interrupted a bit. We spent the time we had working towards implementing components of "editable metadata", which will allow pad owners to add new owners or transfer ownership to friends, among other things. +This release coincided with XWiki's yearly seminar, so our regular schedule was interrupted a bit. We spent the time we had working towards implementing components of "editable metadata", which will allow pad owners to add new owners or transfer ownership to friends, among other things. -Otherwise we wanted to deploy a built-in support system to improve our ability to debug issues as well as to make it easier for users to report problems. Along the way we did our best to improve usability and fix small annoying bugs. +Otherwise we wanted to deploy a built-in support system to improve our ability to debug issues as well as to make it easier for users to report problems. Along the way we did our best to improve usability and fix small annoying bugs. -As this is the last release in our 2.0 cycle, we're going to take some extra time to prepare some big features for our 3.0.0 release, which we expect to deploy on August 20th, 2019. +As this is the last release in our 2.0 cycle, we're going to take some extra time to prepare some big features for our 3.0.0 release, which we expect to deploy on August 20th, 2019. ## Update notes @@ -3389,9 +3463,9 @@ As this is the last release in our 2.0 cycle, we're going to take some extra tim ## Goals -We've recently had an intern join our team, so this release and those until the end of summer are likely to feature a lot of small usability fixes. -Otherwise, we've continued to develop team-centric features, particularly the way that registered users share pads with friends. -Finally, we prioritized the ability to archive files for a period instead of deleting them, which we've been planning for a while. +We've recently had an intern join our team, so this release and those until the end of summer are likely to feature a lot of small usability fixes. +Otherwise, we've continued to develop team-centric features, particularly the way that registered users share pads with friends. +Finally, we prioritized the ability to archive files for a period instead of deleting them, which we've been planning for a while. ## Update notes @@ -3465,9 +3539,9 @@ Finally, we prioritized the ability to archive files for a period instead of del ## Goals -For this release we wanted to focus on releasing a small set of features built on top of some foundations established in our last release. Since we were able to complete this feature set in less than a week, we decided to bundle them together so users could take benefit from them sooner. +For this release we wanted to focus on releasing a small set of features built on top of some foundations established in our last release. Since we were able to complete this feature set in less than a week, we decided to bundle them together so users could take benefit from them sooner. -This work is being funded by the grant we received from NLnet foundation as a part of their PET (Privacy Enhancing Technology) fund. You can read all about this grant on our latest blog post (https://blog.cryptpad.fr/2019/05/27/Our-future-is-collaborative/). +This work is being funded by the grant we received from NLnet foundation as a part of their PET (Privacy Enhancing Technology) fund. You can read all about this grant on our latest blog post (https://blog.cryptpad.fr/2019/05/27/Our-future-is-collaborative/). ## Update notes @@ -3489,7 +3563,7 @@ This work is being funded by the grant we received from NLnet foundation as a pa ## Goals -This release coincided with a little time off for the team, so we planned to include only a few things. We recognized that the "Recent pads" view in the CryptDrive was not very useful for us because it did not include documents stored in _Shared folders_, so we decided to fix that. Otherwise, we're beginning a new project which we'll announce soon, so we've started working on some of its basic features. +This release coincided with a little time off for the team, so we planned to include only a few things. We recognized that the "Recent pads" view in the CryptDrive was not very useful for us because it did not include documents stored in _Shared folders_, so we decided to fix that. Otherwise, we're beginning a new project which we'll announce soon, so we've started working on some of its basic features. ## Update notes @@ -3512,7 +3586,7 @@ This release coincided with a little time off for the team, so we planned to inc ## Goals -For this release cycle we decided to fix some deep bugs and reduce the likelihood of regressions. This included not just errors in the code, but issues that were likely to arise from incorrect configuration. There's still some work to do, but the process of setting up a CryptPad server should be slightly easier now. +For this release cycle we decided to fix some deep bugs and reduce the likelihood of regressions. This included not just errors in the code, but issues that were likely to arise from incorrect configuration. There's still some work to do, but the process of setting up a CryptPad server should be slightly easier now. ## Update notes @@ -3557,11 +3631,11 @@ For this release cycle we decided to fix some deep bugs and reduce the likelihoo ## Goals -After all the features we've added over time, the root of the CryptPad repository had gotten to be something of a mess. We decided to spend a lot of this release period cleaning things up. We also prioritized some other features which make it easier to manage a CryptPad instance. +After all the features we've added over time, the root of the CryptPad repository had gotten to be something of a mess. We decided to spend a lot of this release period cleaning things up. We also prioritized some other features which make it easier to manage a CryptPad instance. ## Update notes -This release makes a number of serverside changes. Read the following notes carefully before updating from an earlier version of CryptPad! +This release makes a number of serverside changes. Read the following notes carefully before updating from an earlier version of CryptPad! * We realized that docker images persisted `config.js` by copying it into the `customize` volume. Since customize is exposed by the webserver, this meant that potentially private information in the configuration file would be accessible over the web. We've moved `config.js` to a `cryptpad/config/`, along with `config.example.js` and modified the docker setup so that nothing in this folder will be exposed to the web. * Consequently, you'll need to move your own `config.js` to the new location in order for your server to read it when you restart. @@ -3602,8 +3676,8 @@ This release makes a number of serverside changes. Read the following notes care ## Goals -As we're very busy wrapping up the project which has funded CryptPad's development so far, this release is very small. -We've requested assistance improving the state of our translations, and received some very helpful contributions. +As we're very busy wrapping up the project which has funded CryptPad's development so far, this release is very small. +We've requested assistance improving the state of our translations, and received some very helpful contributions. ## Update notes @@ -3627,8 +3701,8 @@ We've requested assistance improving the state of our translations, and received ## Goals -This release was developed during a busy period, so it contains fewer features than normal. -In particular we aimed to improve some aspects of our infrastructure, including finishing our deployment of _weblate_ for translations. +This release was developed during a busy period, so it contains fewer features than normal. +In particular we aimed to improve some aspects of our infrastructure, including finishing our deployment of _weblate_ for translations. ## Features @@ -3649,7 +3723,7 @@ In particular we aimed to improve some aspects of our infrastructure, including ## Goals -For this release we planned to resolve issues discovered in our beta release of encrypted spreadsheets, work towards providing an easier experience for contributors who wish to translate CryptPad, and resolve some minor usability issues that had been bothering us. +For this release we planned to resolve issues discovered in our beta release of encrypted spreadsheets, work towards providing an easier experience for contributors who wish to translate CryptPad, and resolve some minor usability issues that had been bothering us. ## Update notes @@ -3680,12 +3754,12 @@ For this release we planned to resolve issues discovered in our beta release of ## Goals -We set aside an additional week for this release in order to deploy _encrypted spreadsheets_, which we've been working toward for a long time. -This feature combines our usual focus on privacy with OnlyOffice's spreadsheet editor. +We set aside an additional week for this release in order to deploy _encrypted spreadsheets_, which we've been working toward for a long time. +This feature combines our usual focus on privacy with OnlyOffice's spreadsheet editor. -At least for this first release we're still considering this functionality to be **highly experimental**. -We've done our best to make this new application fun and easy to use, however, it will still require a lot of work before it supports all the features that you can expect from our other editors. -We welcome you to try it out and report any difficulties you encounter, though you may want to wait before you start using it for all your financial documents. +At least for this first release we're still considering this functionality to be **highly experimental**. +We've done our best to make this new application fun and easy to use, however, it will still require a lot of work before it supports all the features that you can expect from our other editors. +We welcome you to try it out and report any difficulties you encounter, though you may want to wait before you start using it for all your financial documents. ## Update notes @@ -3710,12 +3784,12 @@ We welcome you to try it out and report any difficulties you encounter, though y ## Goals -For this release we planned to improve upon last release's introduction of the display of other users' cursors in our code and slide editors by adding the same functionality to our rich text editor. +For this release we planned to improve upon last release's introduction of the display of other users' cursors in our code and slide editors by adding the same functionality to our rich text editor. -Beyond just producing software, the CryptPad team has also begun to produce peer-reviewed papers. -We have previously published [Private Document Editing with Some Trust](https://dl.acm.org/citation.cfm?doid=3209280.3209535) as a part of the 2018 proceedings of the ACM Symposium on Document Engineering. -We have recently been accepted for publication as a part of [HCI-CPT](http://2019.hci.international/hci-cpt): the first international conference on HCI (Human Computer Interaction) for cybersecurity, privacy and trust. -In preparation for this publication we've begun to collect additional usage data in order to inform the wider community of our findings regarding usability of cryptography-based collaboration systems. +Beyond just producing software, the CryptPad team has also begun to produce peer-reviewed papers. +We have previously published [Private Document Editing with Some Trust](https://dl.acm.org/citation.cfm?doid=3209280.3209535) as a part of the 2018 proceedings of the ACM Symposium on Document Engineering. +We have recently been accepted for publication as a part of [HCI-CPT](http://2019.hci.international/hci-cpt): the first international conference on HCI (Human Computer Interaction) for cybersecurity, privacy and trust. +In preparation for this publication we've begun to collect additional usage data in order to inform the wider community of our findings regarding usability of cryptography-based collaboration systems. ## Update notes @@ -3754,7 +3828,7 @@ In preparation for this publication we've begun to collect additional usage data ## Goals -For this release we chose to focus on our in-pad chat functionality and the ability to show your cursor's position to other users in the same pad. +For this release we chose to focus on our in-pad chat functionality and the ability to show your cursor's position to other users in the same pad. ## Update notes @@ -3781,8 +3855,8 @@ For this release we chose to focus on our in-pad chat functionality and the abil ## Goals -This release features long-awaited improvements to our Rich Text Pad. -This work was done over a short period, and we're releasing it now so that users can take advantage of the improvements as soon as possible. +This release features long-awaited improvements to our Rich Text Pad. +This work was done over a short period, and we're releasing it now so that users can take advantage of the improvements as soon as possible. ## Update notes @@ -3806,7 +3880,7 @@ This work was done over a short period, and we're releasing it now so that users ## Goals -For this release we aimed to address usability concerns in our Rich Text Pad, since it's our most widely used application. During this time we also received an unexpected security disclusure which we treated as being top priority. +For this release we aimed to address usability concerns in our Rich Text Pad, since it's our most widely used application. During this time we also received an unexpected security disclusure which we treated as being top priority. ## Update notes @@ -3831,7 +3905,7 @@ For this release we aimed to address usability concerns in our Rich Text Pad, si ## Goals -This release continued the work on better customization features for community instances. We also worked on usability improvements and UI issues. +This release continued the work on better customization features for community instances. We also worked on usability improvements and UI issues. ## Update notes @@ -3861,7 +3935,7 @@ This release continued the work on better customization features for community i ## Goals -This release continued to improve our _shared folder_ functionality, addressed user concerns about data portability, and implemented various features for customization for different CryptPad instances. +This release continued to improve our _shared folder_ functionality, addressed user concerns about data portability, and implemented various features for customization for different CryptPad instances. ## Update notes @@ -3872,15 +3946,15 @@ This release continued to improve our _shared folder_ functionality, addressed u * Administrators can now do more to customize their CryptPad server, most notably via the ability to override specific translations. For example, the home page now features a short message which, by default, says that the server is a community-hosted instance of the CryptPad open-source project. On CryptPad.fr, we have replaced this text to talk about our organization. You can do the same by modifying files in `cryptpad/customize/translations/`, like so: ``` -define(['/common/translations/messages.js'], function (Messages) { - // Replace the existing keys in your copied file here: - Messages.home_host = "CryptPad.fr is the official instance of the open-source CryptPad project. It is administered by XWiki SAS, the employee-owned French company which created and maintains the product."; +define(['/common/translations/messages.js'], function (Messages) { + // Replace the existing keys in your copied file here: + Messages.home_host = "CryptPad.fr is the official instance of the open-source CryptPad project. It is administered by XWiki SAS, the employee-owned French company which created and maintains the product."; - return Messages; -}); + return Messages; +}); ``` -Simply change the text assigned to `home_host` with a blurb about your own organization. We'll update the wiki soon with more info about customization. +Simply change the text assigned to `home_host` with a blurb about your own organization. We'll update the wiki soon with more info about customization. ### Features @@ -3908,11 +3982,11 @@ Simply change the text assigned to `home_host` with a blurb about your own organ ## Goals -Since last release introduced several big features, this release was allocated towards usability improvements largely related to those new features. +Since last release introduced several big features, this release was allocated towards usability improvements largely related to those new features. ## Update notes -This is a simple release. Just deploy the latest source. +This is a simple release. Just deploy the latest source. ### Features @@ -3929,8 +4003,8 @@ This is a simple release. Just deploy the latest source. ## Goals -We've been making use of some hidden features for a while, to make sure that they were safe to deploy. -This release, we worked on making _contextual chat_ and _shared folders_ available to everyone. +We've been making use of some hidden features for a while, to make sure that they were safe to deploy. +This release, we worked on making _contextual chat_ and _shared folders_ available to everyone. ## Update notes @@ -3956,12 +4030,12 @@ This release, we worked on making _contextual chat_ and _shared folders_ availab ## Goals -This release overlapped with the publication and presentation of a paper written about CryptPad's architecture. -As such, we didn't plan for any very ambitious new features, and instead focused on bug fixes and some new workflows. +This release overlapped with the publication and presentation of a paper written about CryptPad's architecture. +As such, we didn't plan for any very ambitious new features, and instead focused on bug fixes and some new workflows. ## Update notes -This is a fairly simple release. Just download the latest commits and update your cache-busting string. +This is a fairly simple release. Just download the latest commits and update your cache-busting string. ### Features @@ -3980,19 +4054,19 @@ This is a fairly simple release. Just download the latest commits and update you ## Goals -For this release we focused on deploying two very large changes in CryptPad. -For one, we'd worked on a large refactoring of the system we use to compile CSS from LESS, so as to make it more efficient. -Secondly, we reworked the architecture we use for implementing the CryptDrive functionality, so as to integrate support for shared folders. +For this release we focused on deploying two very large changes in CryptPad. +For one, we'd worked on a large refactoring of the system we use to compile CSS from LESS, so as to make it more efficient. +Secondly, we reworked the architecture we use for implementing the CryptDrive functionality, so as to integrate support for shared folders. ## Update notes -To test the _shared folders_ functionality, users can run the following command in their browser console: +To test the _shared folders_ functionality, users can run the following command in their browser console: -`localStorage.CryptPad_SF = "1";` +`localStorage.CryptPad_SF = "1";` -Alternatively, if the instance administrator would like to enable shared folders for all users, they can do so via their `/customize/application_config.js` file, by adding the following line: +Alternatively, if the instance administrator would like to enable shared folders for all users, they can do so via their `/customize/application_config.js` file, by adding the following line: -`config.disableSharedFolders = true;` +`config.disableSharedFolders = true;` ### Features @@ -4011,10 +4085,10 @@ Alternatively, if the instance administrator would like to enable shared folders ## Goals -This release took longer than usual - three weeks instead of two - due to our plans involving a complete redesign of how login and registration function. -Any time we rework a critical system within CryptPad we're very cautious about deploying it, however, this update should bring considerable value for users. -From now on, users will be able to change their passwords without losing access to their old data, however, this is very different from _password recovery_. -While we will still be unable to help you if you have forgotten your password, this update will address our inability up until this point to change your password in the event that it has been compromised in some way. +This release took longer than usual - three weeks instead of two - due to our plans involving a complete redesign of how login and registration function. +Any time we rework a critical system within CryptPad we're very cautious about deploying it, however, this update should bring considerable value for users. +From now on, users will be able to change their passwords without losing access to their old data, however, this is very different from _password recovery_. +While we will still be unable to help you if you have forgotten your password, this update will address our inability up until this point to change your password in the event that it has been compromised in some way. ## Update notes @@ -4042,7 +4116,7 @@ While we will still be unable to help you if you have forgotten your password, t ## Goals -For version 2.4.0 we chose to use our time to address difficulties that some users had, and to release some features which have been in development for some time. With the recent release of the _password-protected-pads_ feature, some users desired to be able to change the passwords that they'd already set, or to add a password to a pad retroactively. Other users wanted to recover information that had accidentally been deleted from their pads, but found that the history feature was difficult to use on networks with poor connectivity. Others still found that loading pads in general was too slow. +For version 2.4.0 we chose to use our time to address difficulties that some users had, and to release some features which have been in development for some time. With the recent release of the _password-protected-pads_ feature, some users desired to be able to change the passwords that they'd already set, or to add a password to a pad retroactively. Other users wanted to recover information that had accidentally been deleted from their pads, but found that the history feature was difficult to use on networks with poor connectivity. Others still found that loading pads in general was too slow. ## Update notes @@ -4084,7 +4158,7 @@ For version 2.4.0 we chose to use our time to address difficulties that some use ## Goals -For this release we wanted to deploy some new features related to our encrypted file functionality. +For this release we wanted to deploy some new features related to our encrypted file functionality. ## Update notes @@ -4112,7 +4186,7 @@ For this release we wanted to deploy some new features related to our encrypted ## Goals -For this release we wanted to continue our efforts towards improving CryptPad usability. We've also added a new Kanban application which was in its final stage for quite some time. +For this release we wanted to continue our efforts towards improving CryptPad usability. We've also added a new Kanban application which was in its final stage for quite some time. ## What's new @@ -4139,14 +4213,14 @@ For this release we wanted to continue our efforts towards improving CryptPad us ## Goals -This is a small release due to a surplus of holidays in France during the Month of May. -We'd been planning to implement _Password-protected Pads_ for a long time, but we had not found a good opportunity to do so within our roadmap. -After a generous donation from one of our users who considered this a critical feature, we were able to dedicate some resources towards delivering it to all of our users. +This is a small release due to a surplus of holidays in France during the Month of May. +We'd been planning to implement _Password-protected Pads_ for a long time, but we had not found a good opportunity to do so within our roadmap. +After a generous donation from one of our users who considered this a critical feature, we were able to dedicate some resources towards delivering it to all of our users. ## Update notes -This release depends on new APIs in our `chainpad-crypto` module. Additionally, we have fixed a critical bug in `chainpad-listmap`. -Admins will need to update their clientside dependencies with `bower update` when deploying. +This release depends on new APIs in our `chainpad-crypto` module. Additionally, we have fixed a critical bug in `chainpad-listmap`. +Admins will need to update their clientside dependencies with `bower update` when deploying. ## What's new @@ -4172,19 +4246,19 @@ Admins will need to update their clientside dependencies with `bower update` whe # Alpaca release (v2.0.0) -This is the first release of our 2.0 cycle. +This is the first release of our 2.0 cycle. -After careful consideration we've decided to name each release in this cycle after a cute animal, iterating through the letters of the Latin alphabet from A to Z. +After careful consideration we've decided to name each release in this cycle after a cute animal, iterating through the letters of the Latin alphabet from A to Z. ## Goals -We wanted to update CryptPad's appearance once more, adopting the colors from our logo throughout more of its interface. +We wanted to update CryptPad's appearance once more, adopting the colors from our logo throughout more of its interface. ## Update notes -This release coincides with the introduction of new APIs in ChainPad, so we recommend that adminstrators update their clientside dependencies by running `bower update`. +This release coincides with the introduction of new APIs in ChainPad, so we recommend that adminstrators update their clientside dependencies by running `bower update`. -As recent updates have updated serverside dependencies, we also recommend that you run `npm update` and _restart your server_. +As recent updates have updated serverside dependencies, we also recommend that you run `npm update` and _restart your server_. ## What's new @@ -4223,47 +4297,47 @@ As recent updates have updated serverside dependencies, we also recommend that y ## Goals -For this release we wanted to direct our effort towards improving user experience issues surrounding user accounts. +For this release we wanted to direct our effort towards improving user experience issues surrounding user accounts. ## Update notes -This release features breaking changes to some clientside dependencies. Administrators must make sure to deploy the -latest server with npm update before updating your clientside dependencies with bower update. +This release features breaking changes to some clientside dependencies. Administrators must make sure to deploy the +latest server with npm update before updating your clientside dependencies with bower update. ## What's new * newly registered users are now able to delete their accounts automatically, along with any personal - information which had been created: + information which had been created: * ToDo list data is automatically deleted, along with user profiles * all of a user's owned pads are also removed immediately in their account deletion process * users who predate account deletion will not benefit from automatic account deletion, since the server - does not have sufficient knowledge to guarantee that the information they could request to have deleted is strictly - their own. For this reason, we've started working on scripts for validating user requests, so as to enable manual - deletion by the server administrator. + does not have sufficient knowledge to guarantee that the information they could request to have deleted is strictly + their own. For this reason, we've started working on scripts for validating user requests, so as to enable manual + deletion by the server administrator. * the script can be found in cryptpad/check-account-deletion.js, and it will be a part of an ongoing - effort to improve administrator tooling for situations like this + effort to improve administrator tooling for situations like this * users who have not logged in, but wish to use their drive now see a ghost icon which they can use to create pads. - We hope this makes it easier to get started as a new user. + We hope this makes it easier to get started as a new user. * REGistered users who have saved templates in their drives can now use those templates at any time, rather than only - using them to create new pads + using them to create new pads * we've updated our file encryption code such that it does not interfere with other scripts which may be running at - the same time (synchronous blocking, for those who are interested) + the same time (synchronous blocking, for those who are interested) * we now validate message signatures clientside, except when they are coming from the history keeper because clients - trust that the server has already validated those signatures + trust that the server has already validated those signatures ## Bug fixes * we've removed some dependencies from our home page that were introduced when we updated to use bootstrap4 * we now import fontawesome as css, and not less, which saves processing time and saves room in our localStorage cache * templates which do not have a 'type' attribute set are migrated such that the pads which are created with their - content are valid + content are valid * thumbnail creation for pads is now disabled by default, due to poor performance * users can enable thumbnail creation in their settings page * we've fixed a significant bug in how our server handles checkpoints (special patches in history which contain the - entire pads content) + entire pads content) * it was possible for two users to independently create checkpoints in close proximity while the document was in a - forked state. New users joining while the session was in this state would get stuck on one side of the fork, - and could lose data if the users on the opposing fork overrode their changes + forked state. New users joining while the session was in this state would get stuck on one side of the fork, + and could lose data if the users on the opposing fork overrode their changes * we've updated our tests, which have been failing for some time because their success conditions were no longer valid * while trying to register a previously registered user, users could cancel the prompt to login as that user. - If they did so, the registration form remained locked. This has been fixed. + If they did so, the registration form remained locked. This has been fixed. diff --git a/Dockerfile b/Dockerfile index e461376580..e2be11805d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,14 +22,15 @@ 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 wget for healthcheck -RUN apt-get update && apt-get install --no-install-recommends -y wget && \ +# 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 && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -56,7 +57,7 @@ VOLUME /cryptpad/datastore ENTRYPOINT ["/bin/bash", "/cryptpad/docker-entrypoint.sh"] # Healthcheck -HEALTHCHECK --interval=1m CMD wget --no-verbose --tries=1 http://localhost:3000/ -q -O /dev/null || exit 1 +HEALTHCHECK --interval=1m CMD curl -f http://localhost:3000/ || exit 1 # Ports EXPOSE 3000 3003 diff --git a/config/config.example.js b/config/config.example.js index 7c0c9ff247..7c8184c6c2 100644 --- a/config/config.example.js +++ b/config/config.example.js @@ -2,8 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -/* globals module */ - /* DISCLAIMER: There are two recommended methods of running a CryptPad instance: @@ -89,9 +87,10 @@ module.exports = { */ //httpPort: 3000, -/* 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 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: 3001, diff --git a/customize.dist/application_config.js b/customize.dist/application_config.js index 9e2c574aa9..ab29b56fab 100644 --- a/customize.dist/application_config.js +++ b/customize.dist/application_config.js @@ -9,12 +9,14 @@ * 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 = ""; // To inform users of the support ticket panel which languages your admins speak: //AppConfig.supportLanguages = [ 'en', 'fr' ]; - + return AppConfig; }); diff --git a/customize.dist/login.js b/customize.dist/login.js index 870736a339..349e69145f 100644 --- a/customize.dist/login.js +++ b/customize.dist/login.js @@ -22,11 +22,13 @@ 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) { + Feedback, h, LocalStore, Messages, nThen, Block, Hash, ServerCommand, + ApiConfig) { var Exports = { Cred: Cred, Block: Block, @@ -218,6 +220,11 @@ 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); diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 579dcd31e0..2459a246f2 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -14,6 +14,7 @@ var map = { 'fi': 'Suomi', 'fr': 'Français', //'hi': 'हिन्दी', + 'id': 'Bahasa Indonesia', 'it': 'Italiano', 'ja': '日本語', 'nb': 'Norwegian Bokmål', @@ -45,6 +46,7 @@ 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, @@ -88,6 +90,9 @@ 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])) { @@ -129,7 +134,6 @@ define(req, function(AppConfig, Default, Language) { } }; - return Messages; }); diff --git a/customize.dist/pages/index.js b/customize.dist/pages/index.js index 1f2608792b..3b919643f0 100644 --- a/customize.dist/pages/index.js +++ b/customize.dist/pages/index.js @@ -15,7 +15,8 @@ define([ '/common/outer/local-store.js', '/customize/pages.js', '/common/pad-types.js', -], function ($, Config, h, Hash, Constants, Util, TextFit, Msg, AppConfig, LocalStore, Pages, PadTypes) { + '/common/extensions.js' +], function ($, Config, h, Hash, Constants, Util, TextFit, Msg, AppConfig, LocalStore, Pages, PadTypes, Extensions) { var urlArgs = Config.requireConf.urlArgs; var checkEarlyAccess = function (x) { @@ -164,9 +165,19 @@ 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', [ diff --git a/customize.dist/pages/install.js b/customize.dist/pages/install.js index d6e8a17137..018d221b9b 100644 --- a/customize.dist/pages/install.js +++ b/customize.dist/pages/install.js @@ -18,8 +18,6 @@ define([ return; } -Msg.install_token = "Install token"; - document.title = Msg.install_header; var frame = function (content) { @@ -27,8 +25,7 @@ Msg.install_token = "Install token"; 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.register_header)), + h('div.row.cp-page-title', h('h1', Msg.install_header)), ].concat(content)), Pages.infopageFooter(), ]), @@ -39,17 +36,12 @@ Msg.install_token = "Install token"; 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.register_notes) + Pages.setHTML(h('div.cp-register-notes'), Msg.install_notes) ]), h('div.cp-reg-form.col-md-6', [ h('div#userForm.form-group.hidden', [ h('div.cp-register-instance', [ - Msg._getKey('register_instance', [ Pages.Instance.name ]), - /*h('br'), - h('a', { - href: '/features.html' - }, Msg.register_whyRegister)*/ + Msg.install_instance, ]), h('input.form-control#installtoken', { type: 'text', @@ -75,7 +67,7 @@ Msg.install_token = "Install token"; /*h('div.checkbox-container', [ UI.createCheckbox('import-recent', Msg.register_importRecent, true) ]),*/ - h('button#register', Msg.login_register) + h('button#register', Msg.install_launch) ]) ]), ]) diff --git a/customize.dist/src/less2/include/admin.less b/customize.dist/src/less2/include/admin.less new file mode 100644 index 0000000000..f53039eb5d --- /dev/null +++ b/customize.dist/src/less2/include/admin.less @@ -0,0 +1,98 @@ +/* + * SPDX-FileCopyrightText: 2023 XWiki CryptPad Team 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; + } + } + } +} diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less index f01e69a37b..0890d80103 100644 --- a/customize.dist/src/less2/include/alertify.less +++ b/customize.dist/src/less2/include/alertify.less @@ -198,6 +198,12 @@ 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; @@ -237,6 +243,10 @@ } } } + outline: none; + &:focus { + outline: @cryptpad_color_brand solid 2px; + } } span.alertify-tabs-active { background-color: @cp_alertify-fg !important; @@ -263,6 +273,10 @@ input { .tools_placeholder-color(); + outline: none; + &:focus-visible { + outline: @cryptpad_color_brand solid 2px; + } } span.cp-password-container { diff --git a/customize.dist/src/less2/include/checkmark.less b/customize.dist/src/less2/include/checkmark.less index 723f4297a9..18e2c4ac9c 100644 --- a/customize.dist/src/less2/include/checkmark.less +++ b/customize.dist/src/less2/include/checkmark.less @@ -128,9 +128,9 @@ position: absolute; box-sizing: border-box; } + outline: none; &:focus { - box-shadow: 0px 0px 5px @cp_checkmark-back1; - outline: none; + outline: @cryptpad_color_brand solid 2px; } } @@ -216,9 +216,9 @@ height: @checkmark-dim1; height: var(--checkmark-dim1); } + outline: none; &:focus { - box-shadow: 0px 0px 5px @cp_checkmark-back1; - outline: none; + outline: @cryptpad_color_brand solid 2px; } } diff --git a/customize.dist/src/less2/include/forms.less b/customize.dist/src/less2/include/forms.less index 2f57777717..065b9db1df 100644 --- a/customize.dist/src/less2/include/forms.less +++ b/customize.dist/src/less2/include/forms.less @@ -119,10 +119,34 @@ } } + // 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; @@ -232,11 +256,9 @@ } - + outline: none; &:focus { - //border: 1px dotted @alertify-base; - box-shadow: 0px 0px 5px @cp_buttons-primary !important; - outline: none; + outline: @cryptpad_color_brand solid 2px; } &::-moz-focus-inner { border: 0; diff --git a/customize.dist/src/less2/include/notifications.less b/customize.dist/src/less2/include/notifications.less index a0dab0725e..6a69b8beb7 100644 --- a/customize.dist/src/less2/include/notifications.less +++ b/customize.dist/src/less2/include/notifications.less @@ -36,9 +36,6 @@ } .cp-reminder, .cp-avatar { cursor: pointer; - &:hover { - background-color: @cp_dropdown-bg-hover; - } } .cp-avatar { .avatar_main(30px); @@ -61,9 +58,6 @@ } &.cp-clickable { cursor: pointer; - &:hover { - background-color: @cp_dropdown-bg-hover; - } } } .cp-notification-dismiss { @@ -73,9 +67,6 @@ align-items: center; justify-content: center; cursor: pointer; - &:hover { - background-color: @cp_dropdown-bg-hover; - } } } } diff --git a/customize.dist/src/less2/include/tokenfield.less b/customize.dist/src/less2/include/tokenfield.less index caddcd29df..53adb1ffec 100644 --- a/customize.dist/src/less2/include/tokenfield.less +++ b/customize.dist/src/less2/include/tokenfield.less @@ -80,6 +80,10 @@ text-overflow: ellipsis; padding-left: 4px; vertical-align: middle; + outline: none; + &:focus { + outline: @cryptpad_color_brand solid 2px; + } } .close { opacity: 1; diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index 05111c6b07..1b43df84e3 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -77,6 +77,12 @@ &: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; } } @@ -774,7 +780,7 @@ padding: 10px; color: @toolbar-bg-color; color: var(--toolbar-bg-color); - border-radius: 5px; + border-radius: @variables_radius; span { font-size: 45px; diff --git a/customize.dist/src/less2/include/usergrid.less b/customize.dist/src/less2/include/usergrid.less index 475f9cde13..bfddc84a3c 100644 --- a/customize.dist/src/less2/include/usergrid.less +++ b/customize.dist/src/less2/include/usergrid.less @@ -115,7 +115,8 @@ } } .fa-times { - padding-left: 5px; + border-radius: @variables_radius; + margin-left: 5px; cursor: pointer; height: 100%; line-height: 25px; diff --git a/customize.dist/src/less2/pages/page-index.less b/customize.dist/src/less2/pages/page-index.less index 7873f5f551..859e376b16 100644 --- a/customize.dist/src/less2/pages/page-index.less +++ b/customize.dist/src/less2/pages/page-index.less @@ -297,6 +297,29 @@ } } + .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; diff --git a/customize.dist/translations/messages.id.js b/customize.dist/translations/messages.id.js new file mode 100644 index 0000000000..54f31c15b0 --- /dev/null +++ b/customize.dist/translations/messages.id.js @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team 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; +}); + diff --git a/docker-compose.yml b/docker-compose.yml index d1b040cbac..a2eeeb1771 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,11 +3,9 @@ # SPDX-License-Identifier: AGPL-3.0-or-later --- -version: '3.8' - services: cryptpad: - image: "cryptpad/cryptpad:version-2024.3.1" + image: "cryptpad/cryptpad:version-2024.6.0" hostname: cryptpad environment: diff --git a/docs/example-advanced.nginx.conf b/docs/example-advanced.nginx.conf index f2b32e959f..556829935b 100644 --- a/docs/example-advanced.nginx.conf +++ b/docs/example-advanced.nginx.conf @@ -9,11 +9,13 @@ # in production and require professional support please contact sales@cryptpad.fr server { - listen 443 ssl http2; - listen [::]:443 ssl http2; + listen 443 ssl; + listen [::]:443 ssl; + http2 on; # Let's Encrypt webroot include letsencrypt-webroot; + # Include mime.types to be able to support .mjs files (see "types" below) include mime.types; @@ -82,6 +84,9 @@ 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}"; @@ -229,6 +234,20 @@ 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 diff --git a/docs/example.httpd.conf b/docs/example.httpd.conf index 67127424c0..28654df76b 100644 --- a/docs/example.httpd.conf +++ b/docs/example.httpd.conf @@ -33,8 +33,14 @@ SSLStaplingCache "shmcb:logs/ssl_stapling(32768)" nokeepalive ssl-unclean-shutdown \ downgrade-1.0 force-response-1.0 Protocols h2 http/1.1 - LimitRequestBody 157286400 AddType application/javascript mjs - ProxyPass / http://localhost:3000/ upgrade=websocket - ProxyPassReverse / http://localhost:3000/ + + LimitRequestBody 157286400 + ProxyPass http://localhost:3000/ upgrade=websocket + ProxyPassReverse http://localhost:3000/ + + + ProxyPass http://localhost:3003/ upgrade=websocket + ProxyPassReverse http://localhost:3003/ + diff --git a/docs/example.nginx.conf b/docs/example.nginx.conf index 89c3cbeffc..dd61615a10 100644 --- a/docs/example.nginx.conf +++ b/docs/example.nginx.conf @@ -9,8 +9,9 @@ # in production and require professional support please contact sales@cryptpad.fr server { - listen 443 ssl http2; - listen [::]:443 ssl http2; + listen 443 ssl; + listen [::]:443 ssl; + http2 on; # Let's Encrypt webroot include letsencrypt-webroot; diff --git a/install-onlyoffice.sh b/install-onlyoffice.sh index cebb803437..7be0058959 100755 --- a/install-onlyoffice.sh +++ b/install-onlyoffice.sh @@ -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,85 +14,89 @@ PROPS_FILE="$CONF_DIR"/onlyoffice.properties declare -A PROPS -main () { - mkdir -p "$CONF_DIR" +main() { + mkdir -p "$CONF_DIR" - load_props + load_props - parse_arguments "$@" + parse_arguments "$@" - ask_for_license + ask_for_license - # Remeber the 1st version that is installed. This will help us install only - # needed OnlyOffice versions in a later version of this script. - set_prop oldest_needed_version v1 + # Remeber the 1st version that is installed. This will help us install only + # needed OnlyOffice versions in a later version of this script. + set_prop oldest_needed_version v1 - mkdir -p "$OO_DIR" - install_version v1 4f370beb - install_version v2b d9da72fd - install_version v4 6ebc6938 - install_version v5 88a356f0 - install_version v6 abd8a309 - install_version v7 ba82142f + mkdir -p "$OO_DIR" + install_version v1 4f370beb + install_version v2b d9da72fd + install_version v4 6ebc6938 + install_version v5 88a356f0 + install_version v6 abd8a309 + install_version v7 ba82142f + install_x2t v7.3+1 ab0c05b0e4c81071acea83f0c6a8e75f5870c360ec4abc4af09105dd9b52264af9711ec0b7020e87095193ac9b6e20305e446f2321a541f743626a598e5318c1 - rm -rf "$BUILDS_DIR" - if command -v rdfind &> /dev/null; then - rdfind -makehardlinks true -makeresultsfile false $OO_DIR/v* - fi + rm -rf "$BUILDS_DIR" + if command -v rdfind &>/dev/null; then + rdfind -makehardlinks true -makeresultsfile false $OO_DIR/v* + fi } -load_props () { - if [ -e "$PROPS_FILE" ]; then - while IFS='=' read -r key value; do - PROPS["$key"]="$value" - done < "$PROPS_FILE" - fi +load_props() { + if [ -e "$PROPS_FILE" ]; then + while IFS='=' read -r key value; do + PROPS["$key"]="$value" + done <"$PROPS_FILE" + fi } -set_prop () { - PROPS["$1"]="$2" +set_prop() { + PROPS["$1"]="$2" - for i in "${!PROPS[@]}"; do - echo "$i=${PROPS[$i]}" - done > "$PROPS_FILE" + for i in "${!PROPS[@]}"; do + echo "$i=${PROPS[$i]}" + done >"$PROPS_FILE" } -parse_arguments () { - while [[ $# -gt 0 ]]; do - case $1 in - -h|--help) - show_help - shift - ;; - -a|--accept-license) - ACCEPT_LICENSE="1" - shift - ;; - *) - show_help - shift - ;; - esac - done +parse_arguments() { + while [[ $# -gt 0 ]]; do + case $1 in + -h | --help) + show_help + shift + ;; + -a | --accept-license) + ACCEPT_LICENSE="1" + shift + ;; + *) + show_help + shift + ;; + esac + done } -ask_for_license () { - if [ ${ACCEPT_LICENSE+x} ] || [ "${PROPS[agree_license]:-no}" == yes ]; then - return - fi +ask_for_license() { + if [ ${ACCEPT_LICENSE+x} ] || [ "${PROPS[agree_license]:-no}" == yes ]; then + return + fi - ensure_command_available curl + 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 + set_prop "agree_license" yes } -show_help () { - cat << EOF +show_help() { + cat < "$FULL_DIR"/.commit + echo "$COMMIT" >"$FULL_DIR"/.commit - echo "$DIR updated" - else - echo "$DIR was up to date" - fi + echo "$DIR updated" + else + echo "$DIR was up to date" + fi + if [ ${CLEAR+x} ]; then + rm -f "$FULL_DIR"/.git + 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 - echo "$1 needs to be installed to run this script" - exit 1 - fi +ensure_command_available() { + if ! command -v "$1" &>/dev/null; then + echo "$1 needs to be installed to run this script" + exit 1 + fi } main "$@" diff --git a/lib/api.js b/lib/api.js index 220e954d8f..c0e5700955 100644 --- a/lib/api.js +++ b/lib/api.js @@ -8,8 +8,10 @@ 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; @@ -25,6 +27,41 @@ 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; } @@ -40,9 +77,19 @@ 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()); + Fs.writeFile(fullPath, 'PLACEHOLDER\n', w(function (err) { + if (err) { + log.error('BLOCK_PLACEHOLDER_CREATE_FAILED', err); + } + })); }).nThen(function () { // asynchronously create a historyKeeper and RPC together require('./historyKeeper.js').create(Env, function (err, historyKeeper) { @@ -61,7 +108,7 @@ nThen(function (w) { }; // spawn ws server and attach netflux event handlers - NetfluxSrv.create(new WebSocketServer({ server: Env.httpServer})) + let Server = NetfluxSrv.create(new WebSocketServer({ server: Env.httpServer})) .on('channelClose', historyKeeper.channelClose) .on('channelMessage', historyKeeper.channelMessage) .on('channelOpen', historyKeeper.channelOpen) @@ -90,6 +137,65 @@ nThen(function (w) { }); }) .register(historyKeeper.id, historyKeeper.directMessage); + // Store max active WS during the last day (reset when sending ping if enabled) + setInterval(() => { + try { + // Concurrent usage data + let oldWs = Env.maxConcurrentWs || 0; + let oldUniqueWs = Env.maxConcurrentUniqueWs || 0; + let oldChans = Env.maxActiveChannels || 0; + let oldUsers = Env.maxConcurrentRegUsers || 0; + let stats = Server.getSessionStats(); + let chans = Server.getActiveChannelCount(); + let reg = 0; + let regKeys = []; + Object.keys(Env.netfluxUsers).forEach(id => { + let keys = Env.netfluxUsers[id]; + let key = Object.keys(keys || {})[0]; + if (!key) { return; } + if (regKeys.includes(key)) { return; } + reg++; + regKeys.push(key); + }); + Env.maxConcurrentWs = Math.max(oldWs, stats.total); + Env.maxConcurrentUniqueWs = Math.max(oldUniqueWs, stats.unique); + Env.maxConcurrentRegUsers = Math.max(oldUsers, reg); + Env.maxActiveChannels = Math.max(oldChans, chans); + } catch (e) {} + }, 10000); + // Clean up active registered users and channels (possible memory leak) + setInterval(() => { + try { + let users = Env.netfluxUsers || {}; + let online = Server.getOnlineUsers() || []; + let removed = 0; + Object.keys(users).forEach(id => { + if (!online.includes(id)) { + delete users[id]; + removed++; + } + }); + if (removed) { + Env.Log.info("CLEANED_ACTIVE_USERS_MAP", {removed}); + } + } catch (e) {} + try { + let HK = require('./hk-utils'); + let chans = Env.channel_cache || {}; + let active = Server.getActiveChannels() || []; + let removed = 0; + Object.keys(chans).forEach(id => { + if (!active.includes(id)) { + HK.dropChannel(Env, id); + removed++; + } + }); + if (removed) { + Env.Log.info("CLEANED_ACTIVE_CHANNELS_MAP", {removed}); + } + } catch (e) {} + }, 30000); + }); }); diff --git a/lib/challenge-commands/totp.js b/lib/challenge-commands/totp.js index ca44e00107..a4ecfaa064 100644 --- a/lib/challenge-commands/totp.js +++ b/lib/challenge-commands/totp.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -/* globals Buffer */ const B32 = require("thirty-two"); const OTP = require("notp"); const nThen = require("nthen"); diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index 954c1c4c12..ae7dcc45f5 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -2,7 +2,6 @@ // // 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"); @@ -102,11 +101,13 @@ var shutdown = function (Env, Server, cb) { // and allow system functionality to restart the server }; -var getRegisteredUsers = 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) { @@ -115,16 +116,39 @@ var 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; Fs.readdir(dir, waitFor(function (err, list) { if (err) { return; } + // Don't count placeholders + list = list.filter(name => { + return !/\.placeholder$/.test(name); + }); 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}); }); }); }; @@ -467,6 +491,8 @@ 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, diff --git a/lib/commands/block.js b/lib/commands/block.js index 1d64364df5..c1bee1d657 100644 --- a/lib/commands/block.js +++ b/lib/commands/block.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -/* globals Buffer*/ const Block = module.exports; const Nacl = require("tweetnacl/nacl-fast"); const nThen = require("nthen"); diff --git a/lib/commands/core.js b/lib/commands/core.js index 79f3f40e81..6c92af74fb 100644 --- a/lib/commands/core.js +++ b/lib/commands/core.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -/* globals process */ const Core = module.exports; const Util = require("../common-util"); const escapeKeyCharacters = Util.escapeKeyCharacters; diff --git a/lib/commands/quota.js b/lib/commands/quota.js index 7a92dbda90..2397b76803 100644 --- a/lib/commands/quota.js +++ b/lib/commands/quota.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -/* globals Buffer*/ const Quota = module.exports; //const Util = require("../common-util"); @@ -11,6 +10,8 @@ const Https = require("https"); const Http = require("http"); const Util = require("../common-util"); const Stats = require("../stats"); +const Admin = require("./admin-rpc.js"); +const nThen = require('nthen'); var validLimitFields = ['limit', 'plan', 'note', 'users', 'origin']; @@ -112,45 +113,85 @@ var queryAccountServer = function (Env, cb) { var done = Util.once(Util.mkAsync(cb)); var rawBody = Stats.instanceData(Env); - Env.Log.info("SERVER_TELEMETRY", rawBody); - var body = JSON.stringify(rawBody); - var options = { - host: 'accounts.cryptpad.fr', - path: '/api/getauthorized', - method: 'POST', - headers: { - "Content-Type": "application/json", - "Content-Length": Buffer.byteLength(body) - } - }; + let send = () => { + Env.Log.info("SERVER_TELEMETRY", rawBody); + var body = JSON.stringify(rawBody); + + var options = { + host: 'accounts.cryptpad.fr', + path: '/api/getauthorized', + method: 'POST', + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(body) + } + }; - var req = Https.request(options, function (response) { - if (!('' + response.statusCode).match(/^2\d\d$/)) { - return void cb('SERVER ERROR ' + response.statusCode); - } - var str = ''; + var req = Https.request(options, function (response) { + if (!('' + response.statusCode).match(/^2\d\d$/)) { + return void cb('SERVER ERROR ' + response.statusCode); + } + var str = ''; - response.on('data', function (chunk) { - str += chunk; + response.on('data', function (chunk) { + str += chunk; + }); + + response.on('end', function () { + try { + var json = JSON.parse(str); + checkUpdateAvailability(Env, json); + done(void 0, json); + } catch (e) { + done(e); + } + }); }); - response.on('end', function () { - try { - var json = JSON.parse(str); - checkUpdateAvailability(Env, json); - done(void 0, json); - } catch (e) { - done(e); - } + req.on('error', function () { + done(); }); - }); - req.on('error', function () { - done(); - }); + req.end(body); + }; - req.end(body); + if (Env.provideAggregateStatistics) { + let stats = {}; + nThen(waitFor => { + Admin.getRegisteredUsers(Env, null, waitFor((err, data) => { + if (err) { return; } + 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) { + stats.maxConcurrentWs = Env.maxConcurrentWs; + Env.maxConcurrentWs = 0; + } + if (Env.maxConcurrentUniqueWs) { + stats.maxConcurrentUniqueIPs = Env.maxConcurrentUniqueWs; + Env.maxConcurrentUniqueWs = 0; + } + if (Env.maxConcurrentRegUsers) { + stats.maxConcurrentRegUsers = Env.maxConcurrentRegUsers; + Env.maxConcurrentRegUsers = 0; + } + if (Env.maxActiveChannels) { + stats.maxConcurrentChannels = Env.maxActiveChannels; + Env.maxActiveChannels = 0; + } + rawBody.statistics = stats; + send(); + }); + return; + } + send(); }; Quota.shouldContactServer = function (Env) { return !(Env.blockDailyCheck === true || diff --git a/lib/common-hash.js b/lib/common-hash.js new file mode 100644 index 0000000000..7a3beddabe --- /dev/null +++ b/lib/common-hash.js @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +module.exports = require("../www/common/common-hash"); + diff --git a/lib/decrees.js b/lib/decrees.js index 79a9783984..0124d9d903 100644 --- a/lib/decrees.js +++ b/lib/decrees.js @@ -169,6 +169,7 @@ 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])); }; @@ -211,10 +212,6 @@ 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); @@ -223,6 +220,15 @@ 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 @@ -235,7 +241,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); @@ -293,7 +299,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]; diff --git a/lib/env.js b/lib/env.js index 1a8b3917e2..d3748750f2 100644 --- a/lib/env.js +++ b/lib/env.js @@ -2,8 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -/* globals process */ - const { existsSync, readdirSync } = require('node:fs'); const Crypto = require('crypto'); diff --git a/lib/hk-util.js b/lib/hk-util.js index da0a63ab82..7080d3d4aa 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -/* global Buffer */ var HK = module.exports; const nThen = require('nthen'); diff --git a/lib/http-commands.js b/lib/http-commands.js index 54d0474ed3..04edd5ff97 100644 --- a/lib/http-commands.js +++ b/lib/http-commands.js @@ -78,15 +78,26 @@ 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; -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) {} +// 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; +*/ var randomToken = () => Nacl.util.encodeBase64(Nacl.randomBytes(24)).replace(/\//g, '-'); diff --git a/lib/http-worker.js b/lib/http-worker.js index 1937b7ad78..b93120d748 100644 --- a/lib/http-worker.js +++ b/lib/http-worker.js @@ -161,6 +161,13 @@ 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); }; @@ -506,6 +513,11 @@ 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')); @@ -583,6 +595,7 @@ 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, @@ -618,6 +631,35 @@ 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')}; @@ -711,7 +753,7 @@ app.post('/api/auth', function (req, res, next) { }); app.use(function (req, res /*, next */) { - if (/^(\/favicon\.ico\/|.*\.js\.map)$/.test(req.url)) { + if (/^(\/favicon\.ico\/|.*\.js\.map|.*\/translations\/.*\.json)/.test(req.url)) { // ignore common 404s } else { Log.info('HTTP_404', req.url); diff --git a/lib/log.js b/lib/log.js index c5b27fb85a..c0a825daf7 100644 --- a/lib/log.js +++ b/lib/log.js @@ -39,8 +39,6 @@ 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; diff --git a/lib/plugin-manager.js b/lib/plugin-manager.js index 8f51120b60..2643bbe0d0 100644 --- a/lib/plugin-manager.js +++ b/lib/plugin-manager.js @@ -4,6 +4,8 @@ const fs = require('node:fs'); const plugins = {}; +const extensions = plugins._extensions = []; +const styles = plugins._styles = []; try { let pluginsDir = fs.readdirSync(__dirname + '/plugins'); @@ -12,6 +14,18 @@ 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); } diff --git a/lib/stats.js b/lib/stats.js index d67a2ee713..c2e148977a 100644 --- a/lib/stats.js +++ b/lib/stats.js @@ -81,8 +81,8 @@ Stats.instanceData = function (Env) { if (Env.provideAggregateStatistics) { // check how many instances provide stats before we put more work into it data.providesAggregateStatistics = true; + data.statistics = {}; // Filled in lib/commands/quota.js because of async calls } - return data; }; diff --git a/lib/storage/blob.js b/lib/storage/blob.js index b029b95c51..aeb9c1f70f 100644 --- a/lib/storage/blob.js +++ b/lib/storage/blob.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -/* globals Buffer */ var Fs = require("fs"); var Fse = require("fs-extra"); var Path = require("path"); diff --git a/lib/storage/file.js b/lib/storage/file.js index d8617f300d..2c2888f7ab 100644 --- a/lib/storage/file.js +++ b/lib/storage/file.js @@ -3,7 +3,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later /*@flow*/ -/* globals Buffer */ var Fs = require("fs"); var Fse = require("fs-extra"); var Path = require("path"); diff --git a/lib/stream-file.js b/lib/stream-file.js index 3721488363..18e691373d 100644 --- a/lib/stream-file.js +++ b/lib/stream-file.js @@ -2,8 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -/* global Buffer */ - const ToPull = require('stream-to-pull-stream'); const Pull = require('pull-stream'); diff --git a/lib/workers/db-worker.js b/lib/workers/db-worker.js index 9878d2c226..63b2a84974 100644 --- a/lib/workers/db-worker.js +++ b/lib/workers/db-worker.js @@ -2,8 +2,6 @@ // // 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"); diff --git a/lib/workers/index.js b/lib/workers/index.js index 6db3f8bbd5..55487c8484 100644 --- a/lib/workers/index.js +++ b/lib/workers/index.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -/* global process */ const Util = require("../common-util"); const nThen = require('nthen'); const OS = require("os"); diff --git a/package-lock.json b/package-lock.json index 1560a01d9f..df6e37a6d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cryptpad", - "version": "2024.3.1", + "version": "2024.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cryptpad", - "version": "2024.3.1", + "version": "2024.6.0", "license": "AGPL-3.0+", "dependencies": { "@mcrowe/minibloom": "^0.2.0", @@ -17,8 +17,8 @@ "bootstrap-tokenfield": "^0.12.0", "chainpad": "^5.2.6", "chainpad-crypto": "^0.2.5", - "chainpad-listmap": "^1.0.0", - "chainpad-netflux": "^1.0.0", + "chainpad-listmap": "^1.1.0", + "chainpad-netflux": "^1.2.0", "chainpad-server": "^5.2.0", "ckeditor": "npm:ckeditor4@~4.22.1", "codemirror": "^5.19.0", @@ -41,7 +41,7 @@ "localforage": "^1.5.2", "marked": "^4.3.0", "mathjax": "3.0.5", - "netflux-websocket": "^1.0.0", + "netflux-websocket": "^1.2.0", "notp": "^2.0.3", "nthen": "0.1.8", "open-sans-fontface": "^1.4.0", @@ -60,13 +60,14 @@ "thirty-two": "^1.0.2", "tweetnacl": "~0.12.2", "ulimit": "0.0.2", - "ws": "^3.3.1", + "ws": "^8.17.1", "x2js": "^3.4.4" }, "devDependencies": { "eslint": "^8.57.0", "eslint-plugin-compat": "^4.2.0", - "lesshint": "6.3.7" + "stylelint": "^16.6.1", + "stylelint-config-standard-less": "^3.0.1" }, "funding": { "type": "opencollective", @@ -82,6 +83,188 @@ "node": ">=0.10.0" } }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.3.tgz", + "integrity": "sha512-xI/tL2zxzEbESvnSxwFgwvy5HS00oCXxL4MLs6HUiDcYfwowsoQaABKxUElp1ARITrINzBnsECOc1q0eg2GOrA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^2.3.1" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.3.1.tgz", + "integrity": "sha512-iMNHTyxLbBlWIfGtabT157LH9DUx9X8+Y3oymFEuMj8HNc+rpE3dPFGFgHjpKfjeFDjLjYIAIhXPGvS2lKxL9g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.11.tgz", + "integrity": "sha512-uox5MVhvNHqitPP+SynrB1o8oPxPMt2JLgp5ghJOWf54WGQ5OKu47efne49r1SWqs3wRP8xSWjnO9MBKxhB1dA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1" + } + }, + "node_modules/@dual-bundle/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -292,19 +475,6 @@ "integrity": "sha512-/AHFqy6OeNHS2NNZGFVRgQh+pnW8iAoV3d1fiO9b2PuQ3HzZpC30MrMrHtq1uOGy1/zcK4uPQEyI31jkM0NNAA==", "dev": true }, - "node_modules/@mrmlnc/readdir-enhanced": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", - "dev": true, - "dependencies": { - "call-me-maybe": "^1.0.1", - "glob-to-regexp": "^0.3.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@node-saml/node-saml": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@node-saml/node-saml/-/node-saml-4.0.5.tgz", @@ -369,15 +539,6 @@ "node": ">= 8" } }, - "node_modules/@nodelib/fs.stat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", - "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", @@ -438,16 +599,6 @@ "@types/send": "*" } }, - "node_modules/@types/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", - "dev": true, - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, "node_modules/@types/http-errors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", @@ -466,12 +617,6 @@ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz", "integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==" }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true - }, "node_modules/@types/ms": { "version": "0.7.33", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.33.tgz", @@ -975,24 +1120,6 @@ "node": ">=0.10.0" } }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", @@ -1034,59 +1161,11 @@ "node": ">=0.10.0" } }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, - "node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", - "dev": true, - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ast-metadata-inferer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/ast-metadata-inferer/-/ast-metadata-inferer-0.8.0.tgz", @@ -1096,51 +1175,25 @@ "@mdn/browser-compat-data": "^5.2.34" } }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } }, "node_modules/atoa": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/atoa/-/atoa-1.0.0.tgz", "integrity": "sha512-VVE1H6cc4ai+ZXo/CRWoJiHXrA1qfA31DPnx6D20+kSI547hQN5Greh51LQ1baMRMfxO5K5M4ImMtZbZt2DODQ==" }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/base64-arraybuffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", @@ -1209,11 +1262,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1272,26 +1325,6 @@ "node": ">= 0.8" } }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/call-bind": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", @@ -1305,45 +1338,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-me-maybe": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", - "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", - "dev": true - }, - "node_modules/caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", - "dev": true, - "dependencies": { - "callsites": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", - "dev": true, - "dependencies": { - "caller-callsite": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001591", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001591.tgz", @@ -1385,26 +1379,26 @@ } }, "node_modules/chainpad-listmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chainpad-listmap/-/chainpad-listmap-1.0.2.tgz", - "integrity": "sha512-8DxiK7kkVAfXaFoTriLL360MImpX8ZwuuoISoZbizOgeacwOBVYBm4JecgVmXnft1UfjYUYBcXSl7HEI6EJHEQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chainpad-listmap/-/chainpad-listmap-1.1.0.tgz", + "integrity": "sha512-1omk+bq+WX9XmcFkAjQLgKuCRRM7nWszA0dWwr2LCVXp/Y8fsSAm8id4zoX/L/5ToIXWpl53QdxDk6+4tQMH2w==", "dependencies": { - "chainpad-netflux": "^1.0.0", + "chainpad-netflux": "^1.2.0", "json.sortify": "~2.1.0" } }, "node_modules/chainpad-netflux": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chainpad-netflux/-/chainpad-netflux-1.0.1.tgz", - "integrity": "sha512-fOUiYD7790XhSFegZY2Yv0HT5YJxJV/WH3QhQYP1Qu/pu7I4XHQfDac0/cf22b8HbM6bsjFTxqcOvn4m63km9A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/chainpad-netflux/-/chainpad-netflux-1.2.0.tgz", + "integrity": "sha512-j3qzrL/tugpTNQTk1I7VMZuQJHAYRNmFaiAxLbEFre/gLIPPJqM4gHXuyTU2OFzIFSd5m5i4G3+GwZ32jLV3cA==", "dependencies": { - "netflux-websocket": "^1.0.0" + "netflux-websocket": "^1.2.0" } }, "node_modules/chainpad-server": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chainpad-server/-/chainpad-server-5.2.0.tgz", - "integrity": "sha512-WFbtzhuB636CAleuqH4e2CqmexNSOjXXE0t1Qd/4DIiHavxMy0/pd7CuOCTNr/MwD0eOd8dNm7/pFkRFL5f74A==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/chainpad-server/-/chainpad-server-5.2.1.tgz", + "integrity": "sha512-r6kNMCbMuwUMxsFIZJgyXtdGxwoXm+gFVIUBxKLOzhvbeMm0RSBYa6BJ5qgzYIwUhYgqyUxmm9tXzYecmPUHIA==", "dependencies": { "nthen": "0.1.8", "pull-stream": "^3.6.9", @@ -1456,55 +1450,15 @@ "resolved": "https://registry.npmjs.org/ckeditor4/-/ckeditor4-4.22.1.tgz", "integrity": "sha512-Yj4vTHX5YxHwc48gNqUqTm+KLkRr9tuyb4O2VIABu4oKHWRNVIdLdy6vUNe/XNx+RiTavMejfA1MVOU/MxLjqQ==" }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, + "node_modules/clone-deep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-1.0.0.tgz", + "integrity": "sha512-hmJRX8x1QOJVV+GUjOBzi6iauhPqc9hIF6xitWRBbiPZOBb6vGo/mDRIK9P74RTKSQK7AE8B0DDWY/vpRrPmQw==", "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/clone-deep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-1.0.0.tgz", - "integrity": "sha512-hmJRX8x1QOJVV+GUjOBzi6iauhPqc9hIF6xitWRBbiPZOBb6vGo/mDRIK9P74RTKSQK7AE8B0DDWY/vpRrPmQw==", - "dependencies": { - "for-own": "^1.0.0", - "is-plain-object": "^2.0.4", - "kind-of": "^5.0.0", - "shallow-clone": "^1.0.0" + "for-own": "^1.0.0", + "is-plain-object": "^2.0.4", + "kind-of": "^5.0.0", + "shallow-clone": "^1.0.0" }, "engines": { "node": ">=0.10.0" @@ -1545,10 +1499,10 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", "dev": true }, "node_modules/component-emitter": { @@ -1643,21 +1597,6 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, - "node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "dev": true, - "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/croppie": { "version": "2.6.5", "resolved": "https://registry.npmjs.org/croppie/-/croppie-2.6.5.tgz", @@ -1685,6 +1624,15 @@ "custom-event": "1.0.0" } }, + "node_modules/css-functions-list": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.2.tgz", + "integrity": "sha512-c+N0v6wbKVxTu5gOBBFkr9BEdBWaqqjQeiJ8QvSRIJOf+UxlJh930m8e6/WNeODIK0mYLFkoONrnj16i2EcvfQ==", + "dev": true, + "engines": { + "node": ">=12 || >=16" + } + }, "node_modules/css-line-break": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", @@ -1693,16 +1641,17 @@ "utrie": "^1.0.2" } }, - "node_modules/cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", "dev": true, - "bin": { - "cssesc": "bin/cssesc" + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" }, "engines": { - "node": ">=4" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, "node_modules/custom-event": { @@ -1728,15 +1677,6 @@ "node": "*" } }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1801,18 +1741,6 @@ "defined": "^1.0.0" } }, - "node_modules/dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", - "dev": true, - "dependencies": { - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -1857,6 +1785,12 @@ "integrity": "sha512-Ic85cOuXSP6h7KM0AIJ2hpJ98Bo4hyTUjc4yjMbkvD+8yTxEhfK9+8exT2KKYsSjnCn2tGsKVSZwE7ZgTORQCw==", "dev": true }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -1865,6 +1799,15 @@ "node": ">= 0.8" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2044,6 +1987,18 @@ } } }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/eslint/node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -2188,49 +2143,6 @@ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "dev": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/express": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", @@ -2283,25 +2195,6 @@ "node": ">=0.10.0" } }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2313,167 +2206,6 @@ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==" }, - "node_modules/fast-glob": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", - "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", - "dev": true, - "dependencies": { - "@mrmlnc/readdir-enhanced": "^2.2.1", - "@nodelib/fs.stat": "^1.1.2", - "glob-parent": "^3.1.0", - "is-glob": "^4.0.0", - "merge2": "^1.2.3", - "micromatch": "^3.1.10" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/fast-glob/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-glob/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-glob/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-glob/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-glob/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-glob/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-glob/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-glob/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-glob/node_modules/micromatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-glob/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -2486,6 +2218,15 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -2513,9 +2254,9 @@ "integrity": "sha512-RRTE4zhfibKpfVS233QDawwPhQIaEyxm6Zp6AcLoAy/qIx8MlEgNyQuGoTryZb+toizwSMeB/GhIzw/eWXTePw==" }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2576,13 +2317,6 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, - "node_modules/flatten": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", - "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==", - "deprecated": "flatten is deprecated in favor of utility frameworks such as lodash.", - "dev": true - }, "node_modules/follow-redirects": { "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", @@ -2629,18 +2363,6 @@ "node": ">= 0.6" } }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "dev": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -2707,15 +2429,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2748,46 +2461,74 @@ "node": ">= 6" } }, - "node_modules/glob-to-regexp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig==", - "dev": true - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", "dev": true, "dependencies": { - "type-fest": "^0.20.2" + "global-prefix": "^3.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/globby": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", - "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", "dev": true, "dependencies": { - "@types/glob": "^7.1.1", - "array-union": "^1.0.2", - "dir-glob": "^2.2.2", - "fast-glob": "^2.2.6", - "glob": "^7.1.3", - "ignore": "^4.0.3", - "pify": "^4.0.1", - "slash": "^2.0.0" + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" }, "engines": { "node": ">=6" } }, + "node_modules/global-prefix/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true + }, "node_modules/gluejs": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/gluejs/-/gluejs-2.4.0.tgz", @@ -2879,69 +2620,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/hasown": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", @@ -2953,6 +2631,18 @@ "node": ">= 0.4" } }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/html2canvas": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", @@ -3032,33 +2722,11 @@ "node": ">=0.10.0" } }, - "node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, - "node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", - "dev": true, - "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -3068,12 +2736,6 @@ "node": ">=0.8.19" } }, - "node_modules/indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA==", - "dev": true - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -3097,6 +2759,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -3150,15 +2818,6 @@ "node": ">= 0.4" } }, - "node_modules/is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -3273,31 +2932,11 @@ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/js-yaml/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "node_modules/json-buffer": { "version": "3.0.1", @@ -3305,10 +2944,10 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, "node_modules/json-schema-traverse": { @@ -3436,6 +3075,12 @@ "node": ">=0.10.0" } }, + "node_modules/known-css-properties": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.31.0.tgz", + "integrity": "sha512-sBPIUGTNF0czz0mwGGUoKKJC8Q7On1GPbCSFPfyEsfHb2DyBG0Y4QtV+EVWpINSaiGKZblDNuF5AezxSgOhesQ==", + "dev": true + }, "node_modules/koalas": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/koalas/-/koalas-1.0.2.tgz", @@ -3455,42 +3100,6 @@ "node": ">=0.10.0" } }, - "node_modules/lesshint": { - "version": "6.3.7", - "resolved": "https://registry.npmjs.org/lesshint/-/lesshint-6.3.7.tgz", - "integrity": "sha512-ontd3g1seYMgeTusLTrm5NMYllLrQs4QFPkqh6GCut2CSG2csMpFbLfd/GJTQIlJXX8ixPQ4i27NeElMuftGaQ==", - "dev": true, - "dependencies": { - "commander": "^2.8.0", - "cosmiconfig": "^5.0.1", - "globby": "^9.1.0", - "lodash.merge": "^4.0.1", - "lodash.orderby": "^4.6.0", - "postcss": "^7.0.14", - "postcss-less": "^3.1.1", - "postcss-selector-parser": "^5.0.0", - "postcss-values-parser": "^2.0.0", - "strip-json-comments": "^3.0.0" - }, - "bin": { - "lesshint": "bin/lesshint" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lesshint/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3518,6 +3127,12 @@ "immediate": "~3.0.5" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, "node_modules/localforage": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", @@ -3596,10 +3211,10 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, - "node_modules/lodash.orderby": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.orderby/-/lodash.orderby-4.6.0.tgz", - "integrity": "sha512-T0rZxKmghOOf5YPnn8EY5iLYeWCpZq8G41FfqoVHH5QDTAFaghJRmAdLiadEDq+ztgM2q5PjA+Z1fOwGrLgmtg==", + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", "dev": true }, "node_modules/log-ok": { @@ -3647,15 +3262,6 @@ "node": ">=10" } }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", @@ -3683,6 +3289,22 @@ "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-3.0.5.tgz", "integrity": "sha512-9M7VulhltkD8sIebWutK/VfAD+m+6BIFqfpjDh9Pz/etoKUtjO6UMnOhUcDmNl6iApE8C9xrUmaMyNZkZAlrMw==" }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3691,6 +3313,18 @@ "node": ">= 0.6" } }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -3719,11 +3353,11 @@ "integrity": "sha512-YtNUgIojy1brPWluzA/lE+vf21Tf4qgfs9vqbgY0tFZTsLqKkc+P/UHjf1UrLjB0RSqmPbNm3dihC/HvkqZnTg==" }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -3812,31 +3446,6 @@ "minilog": "2.x" } }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/mixin-object": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", @@ -3878,73 +3487,22 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==" }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, "node_modules/natural-compare": { @@ -3962,9 +3520,9 @@ } }, "node_modules/netflux-websocket": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/netflux-websocket/-/netflux-websocket-1.0.0.tgz", - "integrity": "sha512-xU5AXzSne9yA7eC6jXTU7UGvcG1Al0IUSeNd7IrumJBIr70DLa0nQfV3TOpZsmytBJbefi6fcfVjkgclAgWuOQ==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/netflux-websocket/-/netflux-websocket-1.2.0.tgz", + "integrity": "sha512-CNQF1wd/UOCmnfaWDATi5vTMfIpMm0Zghy1Fkvr5p7Q70wJdT85RdtnQvsqfLU5AXMUKwjeP1GgxMyAAdRUeJA==" }, "node_modules/node-releases": { "version": "2.0.14", @@ -3972,9 +3530,18 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, - "node_modules/notp": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/notp/-/notp-2.0.3.tgz", + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/notp": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/notp/-/notp-2.0.3.tgz", "integrity": "sha512-oBig/2uqkjQ5AkBuw4QJYwkEWa/q+zHxI5/I5z6IeP2NT0alpJFsP/trrfCC+9xOAgQSZXssNi962kp5KBmypQ==", "engines": { "node": "> v0.6.0" @@ -4059,18 +3626,6 @@ "node": ">=0.10.0" } }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/oidc-token-hash": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", @@ -4200,19 +3755,6 @@ "node": ">=6" } }, - "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "dev": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -4221,15 +3763,6 @@ "node": ">= 0.8" } }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4262,31 +3795,10 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, - "node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-type/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "node_modules/picomatch": { @@ -4300,15 +3812,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/pointer-symbol": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/pointer-symbol/-/pointer-symbol-1.0.0.tgz", @@ -4328,72 +3831,84 @@ "url": "https://opencollective.com/popperjs" } }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" }, "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-less": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-3.1.4.tgz", - "integrity": "sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-6.0.0.tgz", + "integrity": "sha512-FPX16mQLyEjLzEuuJtxA8X3ejDLNGGEG503d2YGZR5Ask1SpDN8KmZUMpzCvyalWRywAn1n1VOA5dcqfCLo5rg==", "dev": true, - "dependencies": { - "postcss": "^7.0.14" - }, "engines": { - "node": ">=6.14.4" - } - }, - "node_modules/postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "dev": true, - "dependencies": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "node": ">=12" }, - "engines": { - "node": ">=4" + "peerDependencies": { + "postcss": "^8.3.5" } }, - "node_modules/postcss-values-parser": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz", - "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==", + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", + "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", + "dev": true + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.0.tgz", + "integrity": "sha512-ovehqRNVCpuFzbXoTb4qLtyzK3xn3t/CUBxOs8LsnQjQrShaB4lKiHoVqY8ANaC0hBMHq5QVWk77rwGklFUDrg==", "dev": true, - "dependencies": { - "flatten": "^1.0.2", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - }, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "engines": { - "node": ">=6.14.4" + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4518,6 +4033,17 @@ "node": ">=0.10.0" } }, + "node_modules/prompt-choices/node_modules/set-value": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-3.0.3.tgz", + "integrity": "sha512-Xsn/XSatoVOGBbp5hs3UylFDs5Bi9i+ArpVJKdHPniZHoEgRniXTqHWrWrGQ0PbEClVT6WtfnBwR8CAHC9sveg==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=6.0" + } + }, "node_modules/prompt-choices/node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -4732,67 +4258,20 @@ "node": ">=0.10.0" } }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } + "node_modules/require-css": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/require-css/-/require-css-0.1.10.tgz", + "integrity": "sha512-v9XczgOfDKMkHdndAwuEDEasYNJPbL3aVDTM9tbRoAefjLm3kUZdt/Fg6Xi+0HJzliL21gGPWTXDV87XuOwCIQ==" }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/require-css": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/require-css/-/require-css-0.1.10.tgz", - "integrity": "sha512-v9XczgOfDKMkHdndAwuEDEasYNJPbL3aVDTM9tbRoAefjLm3kUZdt/Fg6Xi+0HJzliL21gGPWTXDV87XuOwCIQ==" - }, "node_modules/requirejs": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.5.tgz", @@ -4820,31 +4299,6 @@ "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz", "integrity": "sha512-UHBY3viPlJKf85YijDUcikKX6tmF4SokIDp518ZDVT92JNDcG5uKIthaT/owt3Sar0lwtOafsQuwrg22/v2Dwg==" }, - "node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "dev": true - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "engines": { - "node": ">=0.12" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -4912,15 +4366,6 @@ } ] }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "dev": true, - "dependencies": { - "ret": "~0.1.10" - } - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -5022,22 +4467,6 @@ "node": ">=0.10.0" } }, - "node_modules/set-value": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-4.0.1.tgz", - "integrity": "sha512-ayATicCYPVnlNpFmjq2/VmVwhoCQA9+13j8qWp044fmFE3IFphosPtRM+0CJ5xoIx5Uy52fCcwg3XeH2pHbbPQ==", - "funding": [ - "https://github.com/sponsors/jonschlinkert", - "https://paypal.me/jonathanschlinkert", - "https://jonschlinkert.dev/sponsor" - ], - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=11.0" - } - }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -5095,77 +4524,82 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "engines": { - "node": ">=6" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, + "node_modules/sortablejs": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.1.tgz", + "integrity": "sha512-P5Cjvb0UG1ZVNiDPj/n4V+DinttXG6K8n7vM/HQf0C25K3YKQTQY6fsr/sEGsJGpQ9exmPxluHxKBc0mLKU1lQ==" + }, + "node_modules/sortify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/sortify/-/sortify-1.0.4.tgz", + "integrity": "sha512-q+jgY7dx4fmB47wEz3Sq5ok+ibO4caQLplk/U/JXtlt0uvqDNHHZZYYfGB6oEBRbXAb345hbYjOMN5zl8yGYXA==", "dependencies": { - "kind-of": "^3.2.0" - }, + "lex": "^1.7.9" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", "dependencies": { - "is-buffer": "^1.1.5" + "define-property": "^0.2.5", + "object-copy": "^0.1.0" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/snapdragon/node_modules/define-property": { + "node_modules/static-extend/node_modules/define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, "dependencies": { "is-descriptor": "^0.1.0" }, @@ -5173,11 +4607,10 @@ "node": ">=0.10.0" } }, - "node_modules/snapdragon/node_modules/is-descriptor": { + "node_modules/static-extend/node_modules/is-descriptor": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "dev": true, "dependencies": { "is-accessor-descriptor": "^1.0.1", "is-data-descriptor": "^1.0.1" @@ -5186,212 +4619,706 @@ "node": ">= 0.4" } }, - "node_modules/snapdragon/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, - "node_modules/sortablejs": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.1.tgz", - "integrity": "sha512-P5Cjvb0UG1ZVNiDPj/n4V+DinttXG6K8n7vM/HQf0C25K3YKQTQY6fsr/sEGsJGpQ9exmPxluHxKBc0mLKU1lQ==" - }, - "node_modules/sortify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/sortify/-/sortify-1.0.4.tgz", - "integrity": "sha512-q+jgY7dx4fmB47wEz3Sq5ok+ibO4caQLplk/U/JXtlt0uvqDNHHZZYYfGB6oEBRbXAb345hbYjOMN5zl8yGYXA==", + "node_modules/stream-to-pull-stream": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/stream-to-pull-stream/-/stream-to-pull-stream-1.7.3.tgz", + "integrity": "sha512-6sNyqJpr5dIOQdgNy/xcDWwDuzAsAwVzhzrWlAPAQ7Lkjx/rv0wgvxEyKwTq6FmNd5rjTrELt/CLmaSw7crMGg==", "dependencies": { - "lex": "^1.7.9" + "looper": "^3.0.0", + "pull-stream": "^3.2.3" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-color": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/strip-color/-/strip-color-0.1.0.tgz", + "integrity": "sha512-p9LsUieSjWNNAxVCXLeilaDlmuUOrDS5/dF9znM1nZc7EGX5+zEFC0bEevsNIaldjlks+2jns5Siz6F9iK6jwA==", "engines": { "node": ">=0.10.0" } }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "node_modules/stylelint": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.6.1.tgz", + "integrity": "sha512-yNgz2PqWLkhH2hw6X9AweV9YvoafbAD5ZsFdKN9BvSDVwGvPh+AUIrn7lYwy1S7IHmtFin75LLfX1m0D2tHu8Q==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" + "@csstools/css-parser-algorithms": "^2.6.3", + "@csstools/css-tokenizer": "^2.3.1", + "@csstools/media-query-list-parser": "^2.1.11", + "@csstools/selector-specificity": "^3.1.1", + "@dual-bundle/import-meta-resolve": "^4.1.0", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^9.0.0", + "css-functions-list": "^3.2.2", + "css-tree": "^2.3.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^9.0.0", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.3.1", + "ignore": "^5.3.1", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.31.0", + "mathml-tag-names": "^2.1.3", + "meow": "^13.2.0", + "micromatch": "^4.0.7", + "normalize-path": "^3.0.0", + "picocolors": "^1.0.1", + "postcss": "^8.4.38", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^7.0.0", + "postcss-selector-parser": "^6.1.0", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^7.1.0", + "supports-hyperlinks": "^3.0.0", + "svg-tags": "^1.0.0", + "table": "^6.8.2", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "stylelint": "bin/stylelint.mjs" + }, + "engines": { + "node": ">=18.12.0" } }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "deprecated": "See https://github.com/lydell/source-map-url#deprecated", + "node_modules/stylelint-config-recommended": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.1.tgz", + "integrity": "sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": "^16.1.0" + } + }, + "node_modules/stylelint-config-recommended-less": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-less/-/stylelint-config-recommended-less-3.0.1.tgz", + "integrity": "sha512-4vp9Z+W0KwxgPL4L3WxXtlMurfen0NXKFFxPHzDYn2s6WVS26Gg7jt3FIO4ZLM1x8WalCR02nwK2h0TH/mafBg==", + "dev": true, + "dependencies": { + "postcss-less": "^6.0.0", + "stylelint-config-recommended": "^14.0.0", + "stylelint-less": "^3.0.1" + }, + "peerDependencies": { + "postcss": "^8.3.3", + "stylelint": "^16.0.2" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + } + } + }, + "node_modules/stylelint-config-standard": { + "version": "35.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-35.0.0.tgz", + "integrity": "sha512-JyQrNZk2BZwVKFauGGxW2U6RuhIfQ4XoHHo+rBzMHcAkLnwI/knpszwXjzxiMgSfcxbZBckM7Vq4LHoANTR85g==", + "dev": true, + "dependencies": { + "stylelint-config-recommended": "^14.0.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": "^16.0.0" + } + }, + "node_modules/stylelint-config-standard-less": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-standard-less/-/stylelint-config-standard-less-3.0.1.tgz", + "integrity": "sha512-l6UrXbfy37hWbeOCYrJqnKzhiy5+rkxHNGNufvyhH2K1KpEWO+lO8YrLzoOTx0VyGMatPQlpR8EXrnLRcEcvhg==", + "dev": true, + "dependencies": { + "stylelint-config-recommended-less": "^3.0.1", + "stylelint-config-standard": "^35.0.0" + }, + "peerDependencies": { + "postcss": "^8.3.3", + "stylelint": "^16.0.2" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + } + } + }, + "node_modules/stylelint-less": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/stylelint-less/-/stylelint-less-3.0.1.tgz", + "integrity": "sha512-6GkZ4jhmReXxX61IiNaniZFuyTzYTTC4HvRLNNok883d1ux/wUodM1uik+iAHZM1VSCwNASaj0Th6ZX46WZLMw==", + "dev": true, + "dependencies": { + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-value-parser": "4.2.0" + }, + "peerDependencies": { + "postcss": "^8.4.31", + "stylelint": "^16.0.2" + } + }, + "node_modules/stylelint/node_modules/@csstools/selector-specificity": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz", + "integrity": "sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.13" + } + }, + "node_modules/stylelint/node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/stylelint/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/stylelint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "node_modules/stylelint/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true + }, + "node_modules/stylelint/node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "dependencies": { - "extend-shallow": "^3.0.0" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/split-string/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "node_modules/stylelint/node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/stylelint/node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/stylelint/node_modules/file-entry-cache": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-9.0.0.tgz", + "integrity": "sha512-6MgEugi8p2tiUhqO7GnPsmbCCzj0YRCwwaTbpGRyKZesjRSzkqkAE9fPp7V2yMs5hwfgbQLgdvSSkGNg1s5Uvw==", + "dev": true, + "dependencies": { + "flat-cache": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/stylelint/node_modules/flat-cache": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-5.0.0.tgz", + "integrity": "sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==", + "dev": true, + "dependencies": { + "flatted": "^3.3.1", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/stylelint/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/stylelint/node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/split-string/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "node_modules/stylelint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/stylelint/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/stylelint/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/postcss-selector-parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", + "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { - "is-plain-object": "^2.0.4" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, + "node_modules/stylelint/node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "node_modules/stylelint/node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { - "is-descriptor": "^0.1.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "node_modules/stylelint/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "node_modules/success-symbol": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/success-symbol/-/success-symbol-0.1.0.tgz", + "integrity": "sha512-7S6uOTxPklNGxOSbDIg4KlVLBQw1UiGVyfCUYgYxrZUKRblUkmGj7r8xlfQoFudvqLv6Ap5gd76/IIFfI9JG2A==", "engines": { - "node": ">= 0.8" + "node": ">=0.10.0" } }, - "node_modules/stream-to-pull-stream": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/stream-to-pull-stream/-/stream-to-pull-stream-1.7.3.tgz", - "integrity": "sha512-6sNyqJpr5dIOQdgNy/xcDWwDuzAsAwVzhzrWlAPAQ7Lkjx/rv0wgvxEyKwTq6FmNd5rjTrELt/CLmaSw7crMGg==", + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { - "looper": "^3.0.0", - "pull-stream": "^3.2.3" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/supports-hyperlinks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", + "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", + "dev": true, "dependencies": { - "safe-buffer": "~5.1.0" + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" } }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true }, - "node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "node_modules/table": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", + "dev": true, "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=4" + "node": ">=10.0.0" } }, - "node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "node_modules/table/node_modules/ajv": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", + "dev": true, "dependencies": { - "ansi-regex": "^3.0.0" + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/strip-color": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/strip-color/-/strip-color-0.1.0.tgz", - "integrity": "sha512-p9LsUieSjWNNAxVCXLeilaDlmuUOrDS5/dF9znM1nZc7EGX5+zEFC0bEevsNIaldjlks+2jns5Siz6F9iK6jwA==", + "node_modules/table/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/success-symbol": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/success-symbol/-/success-symbol-0.1.0.tgz", - "integrity": "sha512-7S6uOTxPklNGxOSbDIg4KlVLBQw1UiGVyfCUYgYxrZUKRblUkmGj7r8xlfQoFudvqLv6Ap5gd76/IIFfI9JG2A==", + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/table/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" @@ -5472,21 +5399,6 @@ "node": ">=0.10.0" } }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5506,44 +5418,6 @@ "node": ">=0.12.0" } }, - "node_modules/to-regex/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/toggle-array": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toggle-array/-/toggle-array-1.0.1.tgz", @@ -5612,37 +5486,11 @@ "node": ">= v0.4" } }, - "node_modules/ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" - }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==", - "dev": true - }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -5659,54 +5507,6 @@ "node": ">= 0.8" } }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "dev": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "dev": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "dev": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -5737,12 +5537,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/update-browserslist-db/node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5752,22 +5546,6 @@ "punycode": "^2.1.0" } }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "dev": true - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5871,20 +5649,38 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "node_modules/ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, "dependencies": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/ws/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } }, "node_modules/x2js": { "version": "3.4.4", diff --git a/package.json b/package.json index acd163d197..cf44ccd5b7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", - "description": "realtime collaborative visual editor with zero knowledge server", - "version": "2024.3.1", + "description": "a collaborative office suite that is end-to-end encrypted and open-source", + "version": "2024.6.0", "license": "AGPL-3.0+", "repository": { "type": "git", @@ -20,8 +20,8 @@ "bootstrap-tokenfield": "^0.12.0", "chainpad": "^5.2.6", "chainpad-crypto": "^0.2.5", - "chainpad-listmap": "^1.0.0", - "chainpad-netflux": "^1.0.0", + "chainpad-listmap": "^1.1.0", + "chainpad-netflux": "^1.2.0", "chainpad-server": "^5.2.0", "ckeditor": "npm:ckeditor4@~4.22.1", "codemirror": "^5.19.0", @@ -44,7 +44,7 @@ "localforage": "^1.5.2", "marked": "^4.3.0", "mathjax": "3.0.5", - "netflux-websocket": "^1.0.0", + "netflux-websocket": "^1.2.0", "notp": "^2.0.3", "nthen": "0.1.8", "open-sans-fontface": "^1.4.0", @@ -63,19 +63,19 @@ "thirty-two": "^1.0.2", "tweetnacl": "~0.12.2", "ulimit": "0.0.2", - "ws": "^3.3.1", + "ws": "^8.17.1", "x2js": "^3.4.4" }, "devDependencies": { "eslint": "^8.57.0", "eslint-plugin-compat": "^4.2.0", - "lesshint": "6.3.7" + "stylelint": "^16.6.1", + "stylelint-config-standard-less": "^3.0.1" }, "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": { @@ -86,9 +86,9 @@ "offline": "FRESH=1 OFFLINE=1 node server.js", "offlinedev": "DEV=1 OFFLINE=1 node server.js", "package": "PACKAGE=1 node server.js", - "lint": "eslint . && ./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/", + "lint": "eslint . && stylelint \"./customize.dist/src/less2/**/*.less\"", "lint:js": "eslint .", - "lint:less": "./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/", + "lint:less": "stylelint \"./customize.dist/src/less2/**/*.less\"", "lint:translations": "node ./scripts/translations/lint-translations.js", "unused-translations": "node ./scripts/translations/unused-translations.js", "test": "node scripts/TestSelenium.js", @@ -98,5 +98,7 @@ "clear": "node scripts/clear.js", "installtoken": "node scripts/install.js" }, - "browserslist": ["> 0.5%, last 2 versions, Firefox ESR, not dead, not op_mini all"] + "browserslist": [ + "> 0.5%, last 2 versions, Firefox ESR, not dead, not op_mini all" + ] } diff --git a/readme.md b/readme.md index eb626ad390..d5766eed63 100644 --- a/readme.md +++ b/readme.md @@ -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 administators don’t 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 are encrypted, in the eventuality of a breach, attackers have no way of seeing the stored content. Moreover, if the administrators don’t alter the code, they and the service also cannot infer any piece of information about the users' content. ![Drive screenshot](screenshot.png "preview of the CryptDrive") @@ -59,11 +59,11 @@ For this reason it is best to only use instances that are running the most recen which is currently on a three-month 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 -ervers](https://cryptpad.org/instances/) that +servers](https://cryptpad.org/instances/) 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 understanding the security of CryptPad. This blog post +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. diff --git a/scripts/TestSelenium.js b/scripts/TestSelenium.js index f5930e69a5..0447671eac 100644 --- a/scripts/TestSelenium.js +++ b/scripts/TestSelenium.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -/* global process */ var WebDriver = require("selenium-webdriver"); var nThen = require('nthen'); diff --git a/scripts/build.js b/scripts/build.js index b04b830f3a..5cb8232864 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -2,8 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -/* globals process */ - var Fs = require("fs"); var Fse = require("fs-extra"); var Path = require("path"); diff --git a/scripts/check-accounts.js b/scripts/check-accounts.js index 8b15616b90..81835f5c83 100644 --- a/scripts/check-accounts.js +++ b/scripts/check-accounts.js @@ -2,7 +2,6 @@ // // 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"); diff --git a/scripts/install.js b/scripts/install.js index 86a0d0b755..4fb06a4edd 100644 --- a/scripts/install.js +++ b/scripts/install.js @@ -40,5 +40,4 @@ nThen(function (w) { console.log(token); var url = config.httpUnsafeOrigin + '/install/'; console.log(`Please visit ${url} to create your first admin user`); - }); diff --git a/scripts/issue-jwt.js b/scripts/issue-jwt.js index da36caa8e7..c7efb80f5f 100644 --- a/scripts/issue-jwt.js +++ b/scripts/issue-jwt.js @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -/* globals process */ const jwt = require("jsonwebtoken"); const Sessions = require("../lib/storage/sessions.js"); diff --git a/scripts/tests/test-lkh.js b/scripts/tests/test-lkh.js index f346ba3854..38bc140c45 100644 --- a/scripts/tests/test-lkh.js +++ b/scripts/tests/test-lkh.js @@ -2,7 +2,6 @@ // // 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"); diff --git a/scripts/tests/test-mailbox.js b/scripts/tests/test-mailbox.js index 3b88c79393..f5611a6e39 100644 --- a/scripts/tests/test-mailbox.js +++ b/scripts/tests/test-mailbox.js @@ -2,8 +2,6 @@ // // 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; diff --git a/scripts/tests/test-rpc.js b/scripts/tests/test-rpc.js index 632de44244..9963bc9973 100644 --- a/scripts/tests/test-rpc.js +++ b/scripts/tests/test-rpc.js @@ -2,8 +2,6 @@ // // 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; diff --git a/server.js b/server.js index f8e8ffd941..f240b1cd9b 100644 --- a/server.js +++ b/server.js @@ -2,9 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -/* - globals process -*/ var Express = require('express'); var Http = require('http'); var Fs = require('fs'); @@ -71,13 +68,6 @@ 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, diff --git a/www/admin/app-admin.less b/www/admin/app-admin.less index 2e49e3decc..fbaaf4b5ed 100644 --- a/www/admin/app-admin.less +++ b/www/admin/app-admin.less @@ -9,12 +9,14 @@ @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; @@ -32,6 +34,16 @@ 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; diff --git a/www/admin/inner.js b/www/admin/inner.js index cd1360d9c7..d513ab0b19 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -5,6 +5,7 @@ define([ 'jquery', '/common/toolbar.js', + '/common/pad-types.js', '/components/nthen/index.js', '/common/sframe-common.js', '/common/common-interface.js', @@ -17,11 +18,11 @@ define([ '/common/hyperscript.js', '/common/clipboard.js', 'json.sortify', - '/customize/application_config.js', '/api/config', '/api/instance', '/lib/datepicker/flatpickr.js', - '/common/hyperscript.js', + '/install/onboardscreen.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', @@ -29,6 +30,7 @@ define([ ], function( $, Toolbar, + PadTypes, nThen, SFCommon, UI, @@ -41,11 +43,12 @@ define([ h, Clipboard, Sortify, - AppConfig, ApiConfig, Instance, - Flatpickr + Flatpickr, + Onboarding, ) { + var APP = window.APP = {}; var Nacl = window.nacl; @@ -90,6 +93,12 @@ 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 : [ @@ -162,6 +171,22 @@ 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', { @@ -609,7 +634,6 @@ define([ UI.log(Messages._getKey('ui_saved', [Messages.admin_emailTitle])); }); }); - var nav = blocks.nav([button]); var form = blocks.form([ @@ -621,6 +645,35 @@ 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]); @@ -790,7 +843,7 @@ define([ var currentContainer = blocks.block([], 'cp-admin-customize-logo'); let redraw = () => { - var current = h('img', {src: '/api/logo?'+(+new Date())}); + var current = h('img', {src: '/api/logo?'+(+new Date()),alt:'Custom logo'}); // XXX $(currentContainer).empty().append(current); }; redraw(); @@ -905,7 +958,7 @@ define([ setColor(color, done); }); - let $input = $(input).on('change', () => { + let onColorPicked = () => { require(['/lib/less.min.js'], (Less) => { let color = $input.val(); let lColor = Less.color(color.slice(1)); @@ -925,7 +978,8 @@ 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', @@ -935,9 +989,18 @@ 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]); @@ -2647,9 +2710,11 @@ define([ var onRefresh = function () { sFrameChan.query('Q_ADMIN_RPC', { cmd: 'REGISTERED_USERS', - }, function (e, data) { + }, function (e, arr) { pre.innerText = ''; - pre.append(String(data)); + let data = arr[0]; + pre.append(String(data.blocks)); + pre.append(' (old value including teams: ' + String(data.users) + ')'); // XXX }); }; onRefresh(); @@ -3224,10 +3289,6 @@ 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]); } }); @@ -3835,6 +3896,32 @@ 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); }; diff --git a/www/common/common-interface.js b/www/common/common-interface.js index e34037d300..2c4d577997 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -240,11 +240,15 @@ 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); } - $(title).click(function () { + + Util.onClickEnter($(title), function (event) { + event.preventDefault(); + event.stopPropagation(); if (tab.disabled) { return; } var old = tabs[active]; if (old.onHide) { old.onHide(); } @@ -300,14 +304,17 @@ 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) ])); - $button.click(function () { + Util.onClickEnter($button, function (e) { $t.tokenfield('createToken', $input.val()); + e.stopPropagation(); }); var $container = $(h('span.cp-tokenfield-container')); @@ -325,27 +332,47 @@ 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; @@ -486,7 +513,7 @@ define([ var navs = []; buttons.forEach(function (b) { if (!b.name || !b.onClick) { return; } - var button = h('button', { tabindex: '1', 'class': b.className || '' }, [ + var button = h('button', { 'class': b.className || '' }, [ b.iconClass ? h('i' + b.iconClass) : undefined, b.name ]); @@ -509,7 +536,8 @@ define([ divClasses: 'left' }, todo); } else { - $(button).click(function () { + Util.onClickEnter($(button), function (e) { + e.stopPropagation(); todo(); }); } @@ -548,6 +576,40 @@ 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 @@ -564,6 +626,9 @@ define([ setTimeout(function () { Notifier.notify(); }); + + addTabListener(frame); + return frame; }; @@ -742,13 +807,28 @@ 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); + + frame.addEventListener('keydown', function(e) { + if (e.keyCode === 13) { + if (document.activeElement === $ok[0]) { + $ok.click(); + } else if (document.activeElement === $cancel[0]) { + $cancel.click(); + } + } else if (e.keyCode === 27) { + $cancel.click(); + } + }); + listener = listenForKeys(function () { $ok.click(); }, function () { $cancel.click(); }, frame); - document.body.appendChild(frame); setTimeout(function () { Notifier.notify(); $(frame).find('.ok').focus(); @@ -815,15 +895,19 @@ define([ }; var newCls2 = config.new ? 'new' : ''; - $(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); + $(originalBtn).addClass('cp-button-confirm-placeholder').addClass(newCls2).on('click keydown', function (e) { + if (e.type === 'click' || (e.type === 'keydown' && e.key === 'Enter')) { + 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); @@ -877,12 +961,14 @@ define([ opts = opts || {}; var attributes = merge({ type: 'password', - tabindex: '1', + tabindex: '0', 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'); + var eye = h('span.fa.fa-eye.cp-password-reveal', { + tabindex: 0 + }); var $eye = $(eye); var $input = $(input); @@ -899,7 +985,8 @@ define([ $input.focus(); }); } else { - $eye.click(function () { + Util.onClickEnter($eye, function (e) { + e.stopPropagation(); if ($eye.hasClass('fa-eye')) { $input.prop('type', 'text'); $input.focus(); @@ -925,7 +1012,8 @@ define([ title: text, href: href, target: "_blank", - 'data-tippy-placement': "right" + 'data-tippy-placement': "right", + 'aria-label': Messages.help_genericMore //TBC XXX }); return q; }; @@ -1064,6 +1152,21 @@ 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 = $('', {"class": "fa fa-file-text-o"}); UI.getIcon = function (type) { var $icon = $defaultIcon.clone(); @@ -1205,18 +1308,18 @@ define([ if (labelOpts.class) { labelOpts.class += ' cp-checkmark'; } // Mark properties - var markOpts = { tabindex: 0 }; + var markOpts = { tabindex: 0, role: 'checkbox', 'aria-checked': checked, 'aria-labelledby': inputOpts.id + '-label' }; $.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', labelTxt); + var label = h('span.cp-checkmark-label', {id: inputOpts.id + '-label'}, labelTxt); $mark.keydown(function (e) { if ($input.is(':disabled')) { return; } - if (e.which === 32) { + if (e.which === 32 || e.which === 13){ e.stopPropagation(); e.preventDefault(); $input.prop('checked', !$input.is(':checked')); @@ -1228,8 +1331,10 @@ 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'); } }); @@ -1267,7 +1372,7 @@ define([ $(mark).keydown(function (e) { if ($input.is(':disabled')) { return; } - if (e.which === 32) { + if (e.which === 13 || e.which === 32) { e.stopPropagation(); e.preventDefault(); if ($input.is(':checked')) { return; } diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 537fb89deb..5405bf3196 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -173,8 +173,12 @@ define([ var removeBtn, el; if (config.remove) { removeBtn = h('span.fa.fa-times'); - $(removeBtn).click(function () { - config.remove(el); + $(removeBtn).attr('tabindex', '0'); + $(removeBtn).on('click keydown', function(event) { + if (event.type === 'click' || (event.type === 'keydown' && event.key === 'Enter')) { + event.preventDefault(); + config.remove(el); + } }); } @@ -184,6 +188,7 @@ define([ 'data-curve': data.curvePublic || '', 'data-name': name.toLowerCase(), 'data-order': i, + 'tabindex': config.noSelect ? '-1' : '0', style: 'order:'+i+';' },[ avatar, @@ -231,6 +236,13 @@ define([ } onSelect(); }); + $div.on('keydown', '.cp-usergrid-user', function (e) { + if (e.which === 13) { + e.preventDefault(); + e.stopPropagation(); + $(this).trigger('click'); + } + }); } return { @@ -2645,7 +2657,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', logo); + var fill1 = h('div.cp-creation-fill.cp-creation-logo',{ role: 'presentation' }, logo); var fill2 = h('div.cp-creation-fill'); var $creation = $('
', { id: 'cp-creation', tabindex:1 }); $creationContainer.append([fill1, $creation, fill2]); @@ -4285,5 +4297,76 @@ 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; }); diff --git a/www/common/common-util.js b/www/common/common-util.js index fd96f12c42..5faa9171e8 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -606,6 +606,9 @@ 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; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index ed69a18dbe..4525c82878 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -2822,6 +2822,15 @@ 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; } diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 65ded1fb84..3e3cf45f15 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -1197,11 +1197,12 @@ define([ }); }; - // `app`: true (force open wiht the app), false (force open in preview), + // `app`: true (force open with 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) { - var data = manager.getFileData(el); + // In anonymous drives, `el` already contains file data + var data = el.channel ? el : manager.getFileData(el); if (data.static) { if (data.href) { @@ -2246,6 +2247,7 @@ 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) { diff --git a/www/common/extensions.js b/www/common/extensions.js new file mode 100644 index 0000000000..1a0a7d0402 --- /dev/null +++ b/www/common/extensions.js @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2024 XWiki CryptPad Team 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; +}); diff --git a/www/common/inner/access.js b/www/common/inner/access.js index de171f1168..9159aac935 100644 --- a/www/common/inner/access.js +++ b/www/common/inner/access.js @@ -367,6 +367,13 @@ 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) { @@ -459,6 +466,9 @@ 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 @@ -714,6 +724,13 @@ 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) { @@ -1025,6 +1042,13 @@ define([ }); }); }); + $(passwordOk).on('keydown', function (e) { + if (e.keyCode === 13) { + e.preventDefault(); + e.stopPropagation(); + $(passwordOk).click(); + } + }); $d.append(changePass); } if (owned) { diff --git a/www/common/inner/share.js b/www/common/inner/share.js index ed0415fd70..6340fc8b83 100644 --- a/www/common/inner/share.js +++ b/www/common/inner/share.js @@ -385,13 +385,13 @@ define([ h('label', Messages.sharedFolders_share), h('br'), ] : [ - UI.createCheckbox('cp-share-embed', Messages.share_linkEmbed, false, { mark: {tabindex:1} }), + UI.createCheckbox('cp-share-embed', Messages.share_linkEmbed, false, { mark: {tabindex:0} }), ]; if (opts.static) { linkContent = []; } linkContent.push(h('div.cp-spacer')); - linkContent.push(UI.dialog.selectableArea('', { id: 'cp-share-link-preview', tabindex: 1, rows:3})); + linkContent.push(UI.dialog.selectableArea('', { id: 'cp-share-link-preview', tabindex: 0, 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: 1, rows: 3}) + UI.dialog.selectableArea(opts.getEmbedValue(), { id: 'cp-embed-link-preview', tabindex: 0, 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:1}, + mark: {tabindex:0}, }); } var burnAfterReading = (hashes.viewHash && canBAR) ? UI.createRadio('accessRights', 'cp-share-bar', Messages.burnAfterReading_linkBurnAfterReading, false, { - mark: {tabindex:1}, + mark: {tabindex:0}, 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:1} }), + labelView, true, { mark: {tabindex:0} }), 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:1} }), + labelEdit, false, { mark: {tabindex:0} }), 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: 1, rows:2 + id: 'cp-share-link-preview', tabindex: 0, rows:2 }) ]; diff --git a/www/common/inner/sidebar-layout.js b/www/common/inner/sidebar-layout.js index f6adcb66ce..7af940cd8d 100644 --- a/www/common/inner/sidebar-layout.js +++ b/www/common/inner/sidebar-layout.js @@ -4,6 +4,7 @@ define([ 'jquery', + '/api/config', '/components/nthen/index.js', '/common/common-interface.js', '/common/common-ui-elements.js', @@ -13,6 +14,7 @@ define([ '/common/hyperscript.js', ], function( $, + ApiConfig, nThen, UI, UIElements, @@ -22,17 +24,26 @@ 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}`; @@ -109,7 +120,7 @@ define([ element ]); }; - blocks.pre = (value) => { + blocks.pre = (value) => { return h('pre', value); }; @@ -220,9 +231,7 @@ 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; @@ -246,34 +255,52 @@ 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) { return; } + if (content === false) { + delete items[key]; + return void $(div).remove(); + } options = options || {}; const title = options.noTitle ? undefined : h('label.cp-item-label', { id: `cp-${app}-${key}` - }, Messages[`${app}_${safeKey}Title`] || key); + }, options.title || Messages[`${app}_${safeKey}Title`] || key); const hint = options.noHint ? undefined : h('span.cp-sidebarlayout-description', - Messages[`${app}_${safeKey}Hint`] || 'Coming soon...'); + options.hint || Messages[`${app}_${safeKey}Hint`] || 'Coming soon...'); if (hint && options.htmlHint) { hint.innerHTML = Messages[`${app}_${safeKey}Hint`]; } - const div = h(`div.cp-sidebarlayout-element`, { - 'data-item': key, - style: 'display:none;' - }, [ - title, - hint, - content - ]); - items[key] = div; - $rightside.append(div); + $(div).append(title).append(hint).append(content); }); }; + 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); @@ -325,7 +352,7 @@ define([ 'data-category': key }, [ icon, - Messages[`${app}_cat_${key}`] || key, + category.name || Messages[`${app}_cat_${key}`] || key, ]); var $item = $(item).appendTo(container); Util.onClickEnter($item, function () { diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 1e85379936..4facd8156a 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -107,9 +107,11 @@ 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 = {}; @@ -179,15 +181,11 @@ define([ }); }; - 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; + 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 setMyId = function () { @@ -198,7 +196,7 @@ define([ myOOId = Util.createRandomInteger(); // f: function used in .some(f) but defined outside of the while var f = function (id) { - return ids[id] === myOOId; + return ids[id].ooid === myOOId; }; while (Object.keys(ids).some(f)) { myOOId = Util.createRandomInteger(); @@ -207,7 +205,7 @@ define([ var myId = getId(); ids[myId] = { ooid: myOOId, - index: getUserIndex(), + index: getNewUserIndex(), netflux: metadataMgr.getNetfluxId() }; oldIds = JSON.parse(JSON.stringify(ids)); @@ -317,7 +315,10 @@ define([ isCp: cp } }, function (err, h) { - if (!err) { evOnSync.fire(); } + if (!err) { + evOnSync.fire(); + evIntegrationSave.fire(); + } cb(err, h); }); }, @@ -912,6 +913,15 @@ define([ }); }; + const findUserByOOId = function(ooId) { + return Object.values(content.ids) + .find((user) => user.ooid === ooId); + }; + + const getMyOOIndex = function() { + return findUserByOOId(myOOId).index; + }; + var getParticipants = function () { var users = metadataMgr.getMetadata().users; var i = 1; @@ -943,19 +953,19 @@ define([ isCloseCoAuthoring:false, view: false }); - i++; - if (!myUniqueOOId) { myUniqueOOId = String(myOOId) + i; } + const myOOIndex = getMyOOIndex(); + if (!myUniqueOOId) { myUniqueOOId = String(myOOId) + myOOIndex; } p.push({ - id: myUniqueOOId, + id: String(myOOId), idOriginal: String(myOOId), username: metadataMgr.getUserData().name || Messages.anonymous, - indexUser: i, + indexUser: myOOIndex, connectionId: metadataMgr.getNetfluxId() || Hash.createChannelId(), isCloseCoAuthoring:false, view: false }); return { - index: i, + index: myOOIndex, list: p.filter(Boolean) }; }; @@ -1418,6 +1428,9 @@ define([ debug(obj, 'toOO'); chan.event('CMD', obj); + if (obj && obj.type === "saveChanges") { + evIntegrationSave.fire(); + } }; chan.on('CMD', function (obj) { @@ -1592,13 +1605,15 @@ define([ var x2tConvertData = function (data, fileName, format, cb) { var sframeChan = common.getSframeChannel(); - var e = getEditor(); - var fonts = e && e.FontLoader.fontInfos; - var files = e && e.FontLoader.fontFiles.map(function (f) { + var editor = getEditor(); + var fonts = editor && editor.FontLoader.fontInfos; + var files = editor && editor.FontLoader.fontFiles.map(function (f) { return { 'Id': f.Id, }; }); var type = common.getMetadataMgr().getPrivateData().ooType; - var images = (e && window.frames[0].AscCommon.g_oDocumentUrls.urls) || {}; + const images = editor + ? structuredClone(window.frames[0].AscCommon.g_oDocumentUrls.getUrls()) + : {}; // Fix race condition which could drop images sometimes // ==> make sure each image has a 'media/image_name.ext' entry as well @@ -1609,7 +1624,7 @@ define([ }); // Add theme images - var theme = e && window.frames[0].AscCommon.g_image_loader.map_image_index; + var theme = editor && window.frames[0].AscCommon.g_image_loader.map_image_index; if (theme) { Object.keys(theme).forEach(function (url) { if (!/^(\/|blob:|data:)/.test(url)) { @@ -1623,7 +1638,7 @@ define([ type: type, fileName: fileName, outputFormat: format, - images: (e && window.frames[0].AscCommon.g_oDocumentUrls.urls) || {}, + images: (editor && window.frames[0].AscCommon.g_oDocumentUrls.urls) || {}, fonts: fonts, fonts_files: files, mediasSources: getMediasSources(), @@ -1837,6 +1852,11 @@ define([ } delete APP.oldCursor; } + if (integrationChannel) { + APP.onDocumentUnlock = () => { + integrationChannel.event('EV_INTEGRATION_READY'); + }; + } } delete APP.startNew; @@ -2051,6 +2071,15 @@ 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); } @@ -2564,6 +2593,22 @@ 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). @@ -2668,7 +2713,6 @@ 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) { @@ -3117,6 +3161,73 @@ 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 () { diff --git a/www/common/onlyoffice/main.js b/www/common/onlyoffice/main.js index 9da5f22e83..7cc95f70e2 100644 --- a/www/common/onlyoffice/main.js +++ b/www/common/onlyoffice/main.js @@ -11,12 +11,15 @@ 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); + var obj = SFCommonO.initIframe(waitFor, true, integration.pathname); href = obj.href; hash = obj.hash; var parsed = Hash.parsePadUrl(href); @@ -24,9 +27,14 @@ define([ var opts = parsed.getOptions(); version = opts.versionHash; } + if (isIntegration) { + href = integration.href; + hash = integration.hash; + } }).nThen(function (/*waitFor*/) { var addData = function (obj) { - obj.ooType = window.location.pathname.replace(/^\//, '').replace(/\/$/, ''); + let path = (integration && integration.pathname) || window.location.pathname; + obj.ooType = path.replace(/^\//, '').replace(/\/$/, ''); obj.ooVersionHash = version; obj.ooForceVersion = localStorage.CryptPad_ooVersion || ""; }; @@ -154,18 +162,21 @@ define([ Utils.initUnsafeIframe(obj, cb); }); - - }; SFCommonO.start({ hash: hash, href: href, type: 'oo', - useCreationScreen: true, addData: addData, addRpc: addRpc, getPropChannels: getPropChannels, - messaging: true + messaging: true, + useCreationScreen: !isIntegration, + noDrive: true, + integration: isIntegration, + integrationUtils: integration.utils, + integrationConfig: integration.config || {}, + initialState: integration.initialState || undefined }); }); }); diff --git a/www/common/onlyoffice/x2t/x2t.js b/www/common/onlyoffice/x2t/x2t.js deleted file mode 100644 index 6c428ba39b..0000000000 --- a/www/common/onlyoffice/x2t/x2t.js +++ /dev/null @@ -1,5371 +0,0 @@ -// include: shell.js -// The Module object: Our interface to the outside world. We import -// and export values on it. There are various ways Module can be used: -// 1. Not defined. We create it here -// 2. A function parameter, function(Module) { ..generated code.. } -// 3. pre-run appended it, var Module = {}; ..generated code.. -// 4. External script tag defines var Module. -// We need to check if Module already exists (e.g. case 3 above). -// Substitution will be replaced with actual code on later stage of the build, -// this way Closure Compiler will not mangle it (e.g. case 4. above). -// Note that if you want to run closure, and also to use Module -// after the generated code, you will need to define var Module = {}; -// before the code. Then that object will be used in the code, and you -// can continue to use Module afterwards as well. -// if (!Module)` is crucial for Closure Compiler here as it will otherwise replace every `Module` occurrence with a string -var /** @type {{ - noImageDecoding: boolean, - noAudioDecoding: boolean, - noWasmDecoding: boolean, - canvas: HTMLCanvasElement, - ctx: Object, - dataFileDownloads: Object, - preloadResults: Object, - useWebGL: boolean, - expectedDataFileDownloads: number, -}} - */ Module; -if (!Module) /** @suppress{checkTypes}*/Module = typeof Module !== 'undefined' ? Module : {}; - -// --pre-jses are emitted after the Module integration code, so that they can -// refer to Module (if they choose; they can also define Module) -Module.noInitialRun = true; -Module.noExitRuntime = true; -(function() { - let suffix; - if (typeof document != 'undefined') { - const myScript = document.currentScript; - const mySrc = myScript.getAttribute('src'); - suffix = new URL(mySrc).search; - } else { - suffix = ''; - } - - Module.locateFile = function(path, prefix) { - return prefix + path + suffix; - }; -})(); - - -// Sometimes an existing Module object exists with properties -// meant to overwrite the default module functionality. Here -// we collect those properties and reapply _after_ we configure -// the current environment's defaults to avoid having to be so -// defensive during initialization. -var moduleOverrides = Object.assign({}, Module); - -var arguments_ = []; -var thisProgram = './this.program'; -var quit_ = (status, toThrow) => { - throw toThrow; -}; - -// Determine the runtime environment we are in. You can customize this by -// setting the ENVIRONMENT setting at compile time (see settings.js). - -// Attempt to auto-detect the environment -var ENVIRONMENT_IS_WEB = typeof window == 'object'; -var ENVIRONMENT_IS_WORKER = typeof importScripts == 'function'; -// N.b. Electron.js environment is simultaneously a NODE-environment, but -// also a web environment. -var ENVIRONMENT_IS_NODE = typeof process == 'object' && typeof process.versions == 'object' && typeof process.versions.node == 'string'; -var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER; - -// `/` should be present at the end if `scriptDirectory` is not empty -var scriptDirectory = ''; -function locateFile(path) { - if (Module['locateFile']) { - return Module['locateFile'](path, scriptDirectory); - } - return scriptDirectory + path; -} - -// Hooks that are implemented differently in different runtime environments. -var read_, - readAsync, - readBinary, - setWindowTitle; - -if (ENVIRONMENT_IS_NODE) { - - // `require()` is no-op in an ESM module, use `createRequire()` to construct - // the require()` function. This is only necessary for multi-environment - // builds, `-sENVIRONMENT=node` emits a static import declaration instead. - // TODO: Swap all `require()`'s with `import()`'s? - // These modules will usually be used on Node.js. Load them eagerly to avoid - // the complexity of lazy-loading. - var fs = require('fs'); - var nodePath = require('path'); - - if (ENVIRONMENT_IS_WORKER) { - scriptDirectory = nodePath.dirname(scriptDirectory) + '/'; - } else { - scriptDirectory = __dirname + '/'; - } - -// include: node_shell_read.js -read_ = (filename, binary) => { - // We need to re-wrap `file://` strings to URLs. Normalizing isn't - // necessary in that case, the path should already be absolute. - filename = isFileURI(filename) ? new URL(filename) : nodePath.normalize(filename); - return fs.readFileSync(filename, binary ? undefined : 'utf8'); -}; - -readBinary = (filename) => { - var ret = read_(filename, true); - if (!ret.buffer) { - ret = new Uint8Array(ret); - } - return ret; -}; - -readAsync = (filename, onload, onerror, binary = true) => { - // See the comment in the `read_` function. - filename = isFileURI(filename) ? new URL(filename) : nodePath.normalize(filename); - fs.readFile(filename, binary ? undefined : 'utf8', (err, data) => { - if (err) onerror(err); - else onload(binary ? data.buffer : data); - }); -}; -// end include: node_shell_read.js - if (!Module['thisProgram'] && process.argv.length > 1) { - thisProgram = process.argv[1].replace(/\\/g, '/'); - } - - arguments_ = process.argv.slice(2); - - if (typeof module != 'undefined') { - module['exports'] = Module; - } - - process.on('uncaughtException', (ex) => { - // suppress ExitStatus exceptions from showing an error - if (ex !== 'unwind' && !(ex instanceof ExitStatus) && !(ex.context instanceof ExitStatus)) { - throw ex; - } - }); - - quit_ = (status, toThrow) => { - process.exitCode = status; - throw toThrow; - }; - - Module['inspect'] = () => '[Emscripten Module object]'; - -} else - -// Note that this includes Node.js workers when relevant (pthreads is enabled). -// Node.js workers are detected as a combination of ENVIRONMENT_IS_WORKER and -// ENVIRONMENT_IS_NODE. -if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { - if (ENVIRONMENT_IS_WORKER) { // Check worker, not web, since window could be polyfilled - scriptDirectory = self.location.href; - } else if (typeof document != 'undefined' && document.currentScript) { // web - scriptDirectory = document.currentScript.src; - } - // blob urls look like blob:http://site.com/etc/etc and we cannot infer anything from them. - // otherwise, slice off the final part of the url to find the script directory. - // if scriptDirectory does not contain a slash, lastIndexOf will return -1, - // and scriptDirectory will correctly be replaced with an empty string. - // If scriptDirectory contains a query (starting with ?) or a fragment (starting with #), - // they are removed because they could contain a slash. - if (scriptDirectory.indexOf('blob:') !== 0) { - scriptDirectory = scriptDirectory.substr(0, scriptDirectory.replace(/[?#].*/, "").lastIndexOf('/')+1); - } else { - scriptDirectory = ''; - } - - // Differentiate the Web Worker from the Node Worker case, as reading must - // be done differently. - { -// include: web_or_worker_shell_read.js -read_ = (url) => { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, false); - xhr.send(null); - return xhr.responseText; - } - - if (ENVIRONMENT_IS_WORKER) { - readBinary = (url) => { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, false); - xhr.responseType = 'arraybuffer'; - xhr.send(null); - return new Uint8Array(/** @type{!ArrayBuffer} */(xhr.response)); - }; - } - - readAsync = (url, onload, onerror) => { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, true); - xhr.responseType = 'arraybuffer'; - xhr.onload = () => { - if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { // file URLs can return 0 - onload(xhr.response); - return; - } - onerror(); - }; - xhr.onerror = onerror; - xhr.send(null); - } - -// end include: web_or_worker_shell_read.js - } - - setWindowTitle = (title) => document.title = title; -} else -{ -} - -var out = Module['print'] || console.log.bind(console); -var err = Module['printErr'] || console.error.bind(console); - -// Merge back in the overrides -Object.assign(Module, moduleOverrides); -// Free the object hierarchy contained in the overrides, this lets the GC -// reclaim data used e.g. in memoryInitializerRequest, which is a large typed array. -moduleOverrides = null; - -// Emit code to handle expected values on the Module object. This applies Module.x -// to the proper local x. This has two benefits: first, we only emit it if it is -// expected to arrive, and second, by using a local everywhere else that can be -// minified. - -if (Module['arguments']) arguments_ = Module['arguments']; - -if (Module['thisProgram']) thisProgram = Module['thisProgram']; - -if (Module['quit']) quit_ = Module['quit']; - -// perform assertions in shell.js after we set up out() and err(), as otherwise if an assertion fails it cannot print the message - -// end include: shell.js -// include: preamble.js -// === Preamble library stuff === - -// Documentation for the public APIs defined in this file must be updated in: -// site/source/docs/api_reference/preamble.js.rst -// A prebuilt local version of the documentation is available at: -// site/build/text/docs/api_reference/preamble.js.txt -// You can also build docs locally as HTML or other formats in site/ -// An online HTML version (which may be of a different version of Emscripten) -// is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html - -var wasmBinary; -if (Module['wasmBinary']) wasmBinary = Module['wasmBinary']; -var noExitRuntime = Module['noExitRuntime'] || true; - -if (typeof WebAssembly != 'object') { - abort('no native wasm support detected'); -} - -// Wasm globals - -var wasmMemory; - -//======================================== -// Runtime essentials -//======================================== - -// whether we are quitting the application. no code should run after this. -// set in exit() and abort() -var ABORT = false; - -// set by exit() and abort(). Passed to 'onExit' handler. -// NOTE: This is also used as the process return code code in shell environments -// but only when noExitRuntime is false. -var EXITSTATUS; - -/** @type {function(*, string=)} */ -function assert(condition, text) { - if (!condition) { - // This build was created without ASSERTIONS defined. `assert()` should not - // ever be called in this configuration but in case there are callers in - // the wild leave this simple abort() implemenation here for now. - abort(text); - } -} - -// Memory management - -var HEAP, -/** @type {!Int8Array} */ - HEAP8, -/** @type {!Uint8Array} */ - HEAPU8, -/** @type {!Int16Array} */ - HEAP16, -/** @type {!Uint16Array} */ - HEAPU16, -/** @type {!Int32Array} */ - HEAP32, -/** @type {!Uint32Array} */ - HEAPU32, -/** @type {!Float32Array} */ - HEAPF32, -/** @type {!Float64Array} */ - HEAPF64; - -function updateMemoryViews() { - var b = wasmMemory.buffer; - Module['HEAP8'] = HEAP8 = new Int8Array(b); - Module['HEAP16'] = HEAP16 = new Int16Array(b); - Module['HEAPU8'] = HEAPU8 = new Uint8Array(b); - Module['HEAPU16'] = HEAPU16 = new Uint16Array(b); - Module['HEAP32'] = HEAP32 = new Int32Array(b); - Module['HEAPU32'] = HEAPU32 = new Uint32Array(b); - Module['HEAPF32'] = HEAPF32 = new Float32Array(b); - Module['HEAPF64'] = HEAPF64 = new Float64Array(b); -} - -// include: runtime_init_table.js -// In regular non-RELOCATABLE mode the table is exported -// from the wasm module and this will be assigned once -// the exports are available. -var wasmTable; -// end include: runtime_init_table.js -// include: runtime_stack_check.js -// end include: runtime_stack_check.js -// include: runtime_assertions.js -// end include: runtime_assertions.js -var __ATPRERUN__ = []; // functions called before the runtime is initialized -var __ATINIT__ = []; // functions called during startup -var __ATEXIT__ = []; // functions called during shutdown -var __ATPOSTRUN__ = []; // functions called after the main() is called - -var runtimeInitialized = false; - -var runtimeKeepaliveCounter = 0; - -function keepRuntimeAlive() { - return noExitRuntime || runtimeKeepaliveCounter > 0; -} - -function preRun() { - if (Module['preRun']) { - if (typeof Module['preRun'] == 'function') Module['preRun'] = [Module['preRun']]; - while (Module['preRun'].length) { - addOnPreRun(Module['preRun'].shift()); - } - } - callRuntimeCallbacks(__ATPRERUN__); -} - -function initRuntime() { - runtimeInitialized = true; - - -if (!Module["noFSInit"] && !FS.init.initialized) - FS.init(); -FS.ignorePermissions = false; - -TTY.init(); - callRuntimeCallbacks(__ATINIT__); -} - -function postRun() { - - if (Module['postRun']) { - if (typeof Module['postRun'] == 'function') Module['postRun'] = [Module['postRun']]; - while (Module['postRun'].length) { - addOnPostRun(Module['postRun'].shift()); - } - } - - callRuntimeCallbacks(__ATPOSTRUN__); -} - -function addOnPreRun(cb) { - __ATPRERUN__.unshift(cb); -} - -function addOnInit(cb) { - __ATINIT__.unshift(cb); -} - -function addOnExit(cb) { -} - -function addOnPostRun(cb) { - __ATPOSTRUN__.unshift(cb); -} - -// include: runtime_math.js -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32 - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc - -// end include: runtime_math.js -// A counter of dependencies for calling run(). If we need to -// do asynchronous work before running, increment this and -// decrement it. Incrementing must happen in a place like -// Module.preRun (used by emcc to add file preloading). -// Note that you can add dependencies in preRun, even though -// it happens right before run - run will be postponed until -// the dependencies are met. -var runDependencies = 0; -var runDependencyWatcher = null; -var dependenciesFulfilled = null; // overridden to take different actions when all run dependencies are fulfilled - -function getUniqueRunDependency(id) { - return id; -} - -function addRunDependency(id) { - runDependencies++; - - if (Module['monitorRunDependencies']) { - Module['monitorRunDependencies'](runDependencies); - } - -} - -function removeRunDependency(id) { - runDependencies--; - - if (Module['monitorRunDependencies']) { - Module['monitorRunDependencies'](runDependencies); - } - - if (runDependencies == 0) { - if (runDependencyWatcher !== null) { - clearInterval(runDependencyWatcher); - runDependencyWatcher = null; - } - if (dependenciesFulfilled) { - var callback = dependenciesFulfilled; - dependenciesFulfilled = null; - callback(); // can add another dependenciesFulfilled - } - } -} - -/** @param {string|number=} what */ -function abort(what) { - if (Module['onAbort']) { - Module['onAbort'](what); - } - - what = 'Aborted(' + what + ')'; - // TODO(sbc): Should we remove printing and leave it up to whoever - // catches the exception? - err(what); - - ABORT = true; - EXITSTATUS = 1; - - what += '. Build with -sASSERTIONS for more info.'; - - // Use a wasm runtime error, because a JS error might be seen as a foreign - // exception, which means we'd run destructors on it. We need the error to - // simply make the program stop. - // FIXME This approach does not work in Wasm EH because it currently does not assume - // all RuntimeErrors are from traps; it decides whether a RuntimeError is from - // a trap or not based on a hidden field within the object. So at the moment - // we don't have a way of throwing a wasm trap from JS. TODO Make a JS API that - // allows this in the wasm spec. - - // Suppress closure compiler warning here. Closure compiler's builtin extern - // defintion for WebAssembly.RuntimeError claims it takes no arguments even - // though it can. - // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure gets fixed. - /** @suppress {checkTypes} */ - var e = new WebAssembly.RuntimeError(what); - - // Throw the error whether or not MODULARIZE is set because abort is used - // in code paths apart from instantiation where an exception is expected - // to be thrown when abort is called. - throw e; -} - -// include: memoryprofiler.js -// end include: memoryprofiler.js -// include: URIUtils.js -// Prefix of data URIs emitted by SINGLE_FILE and related options. -var dataURIPrefix = 'data:application/octet-stream;base64,'; - -// Indicates whether filename is a base64 data URI. -function isDataURI(filename) { - // Prefix of data URIs emitted by SINGLE_FILE and related options. - return filename.startsWith(dataURIPrefix); -} - -// Indicates whether filename is delivered via file protocol (as opposed to http/https) -function isFileURI(filename) { - return filename.startsWith('file://'); -} -// end include: URIUtils.js -// include: runtime_exceptions.js -// end include: runtime_exceptions.js -var wasmBinaryFile; - wasmBinaryFile = 'x2t.wasm'; - if (!isDataURI(wasmBinaryFile)) { - wasmBinaryFile = locateFile(wasmBinaryFile); - } - -function getBinarySync(file) { - if (file == wasmBinaryFile && wasmBinary) { - return new Uint8Array(wasmBinary); - } - if (readBinary) { - return readBinary(file); - } - throw "both async and sync fetching of the wasm failed"; -} - -function getBinaryPromise(binaryFile) { - // If we don't have the binary yet, try to load it asynchronously. - // Fetch has some additional restrictions over XHR, like it can't be used on a file:// url. - // See https://github.com/github/fetch/pull/92#issuecomment-140665932 - // Cordova or Electron apps are typically loaded from a file:// url. - // So use fetch if it is available and the url is not a file, otherwise fall back to XHR. - if (!wasmBinary - && (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER)) { - if (typeof fetch == 'function' - && !isFileURI(binaryFile) - ) { - return fetch(binaryFile, { credentials: 'same-origin' }).then((response) => { - if (!response['ok']) { - throw "failed to load wasm binary file at '" + binaryFile + "'"; - } - return response['arrayBuffer'](); - }).catch(() => getBinarySync(binaryFile)); - } - else if (readAsync) { - // fetch is not available or url is file => try XHR (readAsync uses XHR internally) - return new Promise((resolve, reject) => { - readAsync(binaryFile, (response) => resolve(new Uint8Array(/** @type{!ArrayBuffer} */(response))), reject) - }); - } - } - - // Otherwise, getBinarySync should be able to get it synchronously - return Promise.resolve().then(() => getBinarySync(binaryFile)); -} - -function instantiateArrayBuffer(binaryFile, imports, receiver) { - return getBinaryPromise(binaryFile).then((binary) => { - return WebAssembly.instantiate(binary, imports); - }).then((instance) => { - return instance; - }).then(receiver, (reason) => { - err(`failed to asynchronously prepare wasm: ${reason}`); - - abort(reason); - }); -} - -function instantiateAsync(binary, binaryFile, imports, callback) { - if (!binary && - typeof WebAssembly.instantiateStreaming == 'function' && - !isDataURI(binaryFile) && - // Don't use streaming for file:// delivered objects in a webview, fetch them synchronously. - !isFileURI(binaryFile) && - // Avoid instantiateStreaming() on Node.js environment for now, as while - // Node.js v18.1.0 implements it, it does not have a full fetch() - // implementation yet. - // - // Reference: - // https://github.com/emscripten-core/emscripten/pull/16917 - !ENVIRONMENT_IS_NODE && - typeof fetch == 'function') { - return fetch(binaryFile, { credentials: 'same-origin' }).then((response) => { - // Suppress closure warning here since the upstream definition for - // instantiateStreaming only allows Promise rather than - // an actual Response. - // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure is fixed. - /** @suppress {checkTypes} */ - var result = WebAssembly.instantiateStreaming(response, imports); - - return result.then( - callback, - function(reason) { - // We expect the most common failure cause to be a bad MIME type for the binary, - // in which case falling back to ArrayBuffer instantiation should work. - err(`wasm streaming compile failed: ${reason}`); - err('falling back to ArrayBuffer instantiation'); - return instantiateArrayBuffer(binaryFile, imports, callback); - }); - }); - } - return instantiateArrayBuffer(binaryFile, imports, callback); -} - -// Create the wasm instance. -// Receives the wasm imports, returns the exports. -function createWasm() { - // prepare imports - var info = { - 'env': wasmImports, - 'wasi_snapshot_preview1': wasmImports, - }; - // Load the wasm module and create an instance of using native support in the JS engine. - // handle a generated wasm instance, receiving its exports and - // performing other necessary setup - /** @param {WebAssembly.Module=} module*/ - function receiveInstance(instance, module) { - var exports = instance.exports; - - wasmExports = exports; - - - wasmMemory = wasmExports['memory']; - - updateMemoryViews(); - - wasmTable = wasmExports['__indirect_function_table']; - - - addOnInit(wasmExports['__wasm_call_ctors']); - - removeRunDependency('wasm-instantiate'); - return exports; - } - // wait for the pthread pool (if any) - addRunDependency('wasm-instantiate'); - - // Prefer streaming instantiation if available. - function receiveInstantiationResult(result) { - // 'result' is a ResultObject object which has both the module and instance. - // receiveInstance() will swap in the exports (to Module.asm) so they can be called - // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line. - // When the regression is fixed, can restore the above PTHREADS-enabled path. - receiveInstance(result['instance']); - } - - // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback - // to manually instantiate the Wasm module themselves. This allows pages to - // run the instantiation parallel to any other async startup actions they are - // performing. - // Also pthreads and wasm workers initialize the wasm instance through this - // path. - if (Module['instantiateWasm']) { - - try { - return Module['instantiateWasm'](info, receiveInstance); - } catch(e) { - err(`Module.instantiateWasm callback failed with error: ${e}`); - return false; - } - } - - instantiateAsync(wasmBinary, wasmBinaryFile, info, receiveInstantiationResult); - return {}; // no exports yet; we'll fill them in later -} - -// Globals used by JS i64 conversions (see makeSetValue) -var tempDouble; -var tempI64; - -// include: runtime_debug.js -// end include: runtime_debug.js -// === Body === - -// end include: preamble.js - - /** @constructor */ - function ExitStatus(status) { - this.name = 'ExitStatus'; - this.message = `Program terminated with exit(${status})`; - this.status = status; - } - - var callRuntimeCallbacks = (callbacks) => { - while (callbacks.length > 0) { - // Pass the module as the first argument. - callbacks.shift()(Module); - } - }; - - - /** - * @param {number} ptr - * @param {string} type - */ - function getValue(ptr, type = 'i8') { - if (type.endsWith('*')) type = '*'; - switch (type) { - case 'i1': return HEAP8[((ptr)>>0)]; - case 'i8': return HEAP8[((ptr)>>0)]; - case 'i16': return HEAP16[((ptr)>>1)]; - case 'i32': return HEAP32[((ptr)>>2)]; - case 'i64': abort('to do getValue(i64) use WASM_BIGINT'); - case 'float': return HEAPF32[((ptr)>>2)]; - case 'double': return HEAPF64[((ptr)>>3)]; - case '*': return HEAPU32[((ptr)>>2)]; - default: abort(`invalid type for getValue: ${type}`); - } - } - - - /** - * @param {number} ptr - * @param {number} value - * @param {string} type - */ - function setValue(ptr, value, type = 'i8') { - if (type.endsWith('*')) type = '*'; - switch (type) { - case 'i1': HEAP8[((ptr)>>0)] = value; break; - case 'i8': HEAP8[((ptr)>>0)] = value; break; - case 'i16': HEAP16[((ptr)>>1)] = value; break; - case 'i32': HEAP32[((ptr)>>2)] = value; break; - case 'i64': abort('to do setValue(i64) use WASM_BIGINT'); - case 'float': HEAPF32[((ptr)>>2)] = value; break; - case 'double': HEAPF64[((ptr)>>3)] = value; break; - case '*': HEAPU32[((ptr)>>2)] = value; break; - default: abort(`invalid type for setValue: ${type}`); - } - } - - /** @type {function(...*):?} */ - function __ZN10CHtmlFile213OpenBatchHtmlERKNSt3__26vectorINS0_12basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEENS5_IS7_EEEERKS7_P11CHtmlParams( - ) { - abort('missing function: _ZN10CHtmlFile213OpenBatchHtmlERKNSt3__26vectorINS0_12basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEENS5_IS7_EEEERKS7_P11CHtmlParams'); - } - __ZN10CHtmlFile213OpenBatchHtmlERKNSt3__26vectorINS0_12basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEENS5_IS7_EEEERKS7_P11CHtmlParams.stub = true; - - /** @type {function(...*):?} */ - function __ZN10CHtmlFile215SetTmpDirectoryERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEE( - ) { - abort('missing function: _ZN10CHtmlFile215SetTmpDirectoryERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEE'); - } - __ZN10CHtmlFile215SetTmpDirectoryERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEE.stub = true; - - /** @type {function(...*):?} */ - function __ZN10CHtmlFile27OpenMhtERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_P11CHtmlParams( - ) { - abort('missing function: _ZN10CHtmlFile27OpenMhtERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_P11CHtmlParams'); - } - __ZN10CHtmlFile27OpenMhtERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_P11CHtmlParams.stub = true; - - /** @type {function(...*):?} */ - function __ZN10CHtmlFile28OpenHtmlERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_P11CHtmlParams( - ) { - abort('missing function: _ZN10CHtmlFile28OpenHtmlERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_P11CHtmlParams'); - } - __ZN10CHtmlFile28OpenHtmlERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_P11CHtmlParams.stub = true; - - /** @type {function(...*):?} */ - function __ZN10CHtmlFile2C1Ev( - ) { - abort('missing function: _ZN10CHtmlFile2C1Ev'); - } - __ZN10CHtmlFile2C1Ev.stub = true; - - /** @type {function(...*):?} */ - function __ZN10CHtmlFile2D1Ev( - ) { - abort('missing function: _ZN10CHtmlFile2D1Ev'); - } - __ZN10CHtmlFile2D1Ev.stub = true; - - /** @type {function(...*):?} */ - function __ZN10CVbaReader5writeEv( - ) { - abort('missing function: _ZN10CVbaReader5writeEv'); - } - __ZN10CVbaReader5writeEv.stub = true; - - /** @type {function(...*):?} */ - function __ZN10CVbaReader7convertEv( - ) { - abort('missing function: _ZN10CVbaReader7convertEv'); - } - __ZN10CVbaReader7convertEv.stub = true; - - /** @type {function(...*):?} */ - function __ZN10CVbaReaderC1ERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_( - ) { - abort('missing function: _ZN10CVbaReaderC1ERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_'); - } - __ZN10CVbaReaderC1ERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_.stub = true; - - /** @type {function(...*):?} */ - function __ZN10CVbaReaderD1Ev( - ) { - abort('missing function: _ZN10CVbaReaderD1Ev'); - } - __ZN10CVbaReaderD1Ev.stub = true; - - /** @type {function(...*):?} */ - function __ZN14NSHtmlRenderer17CASCHTMLRenderer316CreateOfficeFileENSt3__212basic_stringIwNS1_11char_traitsIwEENS1_9allocatorIwEEEERKS7_( - ) { - abort('missing function: _ZN14NSHtmlRenderer17CASCHTMLRenderer316CreateOfficeFileENSt3__212basic_stringIwNS1_11char_traitsIwEENS1_9allocatorIwEEEERKS7_'); - } - __ZN14NSHtmlRenderer17CASCHTMLRenderer316CreateOfficeFileENSt3__212basic_stringIwNS1_11char_traitsIwEENS1_9allocatorIwEEEERKS7_.stub = true; - - /** @type {function(...*):?} */ - function __ZN14NSHtmlRenderer17CASCHTMLRenderer39CloseFileEb( - ) { - abort('missing function: _ZN14NSHtmlRenderer17CASCHTMLRenderer39CloseFileEb'); - } - __ZN14NSHtmlRenderer17CASCHTMLRenderer39CloseFileEb.stub = true; - - /** @type {function(...*):?} */ - function __ZN14NSHtmlRenderer17CASCHTMLRenderer3C1Ev( - ) { - abort('missing function: _ZN14NSHtmlRenderer17CASCHTMLRenderer3C1Ev'); - } - __ZN14NSHtmlRenderer17CASCHTMLRenderer3C1Ev.stub = true; - - /** @type {function(...*):?} */ - function __ZN14NSHtmlRenderer17CASCHTMLRenderer3D1Ev( - ) { - abort('missing function: _ZN14NSHtmlRenderer17CASCHTMLRenderer3D1Ev'); - } - __ZN14NSHtmlRenderer17CASCHTMLRenderer3D1Ev.stub = true; - - /** @type {function(...*):?} */ - function __ZN22RtfConvertationManager15ConvertOOXToRtfENSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES6_( - ) { - abort('missing function: _ZN22RtfConvertationManager15ConvertOOXToRtfENSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES6_'); - } - __ZN22RtfConvertationManager15ConvertOOXToRtfENSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES6_.stub = true; - - /** @type {function(...*):?} */ - function __ZN22RtfConvertationManager15ConvertRtfToOOXENSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES6_( - ) { - abort('missing function: _ZN22RtfConvertationManager15ConvertRtfToOOXENSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES6_'); - } - __ZN22RtfConvertationManager15ConvertRtfToOOXENSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES6_.stub = true; - - /** @type {function(...*):?} */ - function __ZN22RtfConvertationManagerC1Ev( - ) { - abort('missing function: _ZN22RtfConvertationManagerC1Ev'); - } - __ZN22RtfConvertationManagerC1Ev.stub = true; - - /** @type {function(...*):?} */ - function __ZN3VBA12FormPropMaskC1Ej( - ) { - abort('missing function: _ZN3VBA12FormPropMaskC1Ej'); - } - __ZN3VBA12FormPropMaskC1Ej.stub = true; - - /** @type {function(...*):?} */ - function __ZN3VBA17TextPropsPropMaskC1Ej( - ) { - abort('missing function: _ZN3VBA17TextPropsPropMaskC1Ej'); - } - __ZN3VBA17TextPropsPropMaskC1Ej.stub = true; - - /** @type {function(...*):?} */ - function __ZN8CFb2File15SetTmpDirectoryERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEE( - ) { - abort('missing function: _ZN8CFb2File15SetTmpDirectoryERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEE'); - } - __ZN8CFb2File15SetTmpDirectoryERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEE.stub = true; - - /** @type {function(...*):?} */ - function __ZN8CFb2File4OpenERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_P10CFb2Params( - ) { - abort('missing function: _ZN8CFb2File4OpenERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_P10CFb2Params'); - } - __ZN8CFb2File4OpenERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_P10CFb2Params.stub = true; - - /** @type {function(...*):?} */ - function __ZN8CFb2File8FromHtmlERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_S8_( - ) { - abort('missing function: _ZN8CFb2File8FromHtmlERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_S8_'); - } - __ZN8CFb2File8FromHtmlERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_S8_.stub = true; - - /** @type {function(...*):?} */ - function __ZN8CFb2FileC1Ev( - ) { - abort('missing function: _ZN8CFb2FileC1Ev'); - } - __ZN8CFb2FileC1Ev.stub = true; - - /** @type {function(...*):?} */ - function __ZN8CFb2FileD1Ev( - ) { - abort('missing function: _ZN8CFb2FileD1Ev'); - } - __ZN8CFb2FileD1Ev.stub = true; - - /** @type {function(...*):?} */ - function __ZN8CXpsFileC1EPN7NSFonts17IApplicationFontsE( - ) { - abort('missing function: _ZN8CXpsFileC1EPN7NSFonts17IApplicationFontsE'); - } - __ZN8CXpsFileC1EPN7NSFonts17IApplicationFontsE.stub = true; - - /** @type {function(...*):?} */ - function __ZN9CDjVuFileC1EPN7NSFonts17IApplicationFontsE( - ) { - abort('missing function: _ZN9CDjVuFileC1EPN7NSFonts17IApplicationFontsE'); - } - __ZN9CDjVuFileC1EPN7NSFonts17IApplicationFontsE.stub = true; - - /** @type {function(...*):?} */ - function __ZN9CEpubFile16SetTempDirectoryERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEE( - ) { - abort('missing function: _ZN9CEpubFile16SetTempDirectoryERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEE'); - } - __ZN9CEpubFile16SetTempDirectoryERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEE.stub = true; - - /** @type {function(...*):?} */ - function __ZN9CEpubFile7ConvertERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_b( - ) { - abort('missing function: _ZN9CEpubFile7ConvertERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_b'); - } - __ZN9CEpubFile7ConvertERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_b.stub = true; - - /** @type {function(...*):?} */ - function __ZN9CEpubFile8FromHtmlERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_S8_( - ) { - abort('missing function: _ZN9CEpubFile8FromHtmlERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_S8_'); - } - __ZN9CEpubFile8FromHtmlERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_S8_.stub = true; - - /** @type {function(...*):?} */ - function __ZN9CEpubFileC1Ev( - ) { - abort('missing function: _ZN9CEpubFileC1Ev'); - } - __ZN9CEpubFileC1Ev.stub = true; - - /** @type {function(...*):?} */ - function __ZN9CEpubFileD1Ev( - ) { - abort('missing function: _ZN9CEpubFileD1Ev'); - } - __ZN9CEpubFileD1Ev.stub = true; - - /** @type {function(...*):?} */ - function __ZN9NSNetwork15NSFileTransport15CFileDownloader11GetFilePathEv( - ) { - abort('missing function: _ZN9NSNetwork15NSFileTransport15CFileDownloader11GetFilePathEv'); - } - __ZN9NSNetwork15NSFileTransport15CFileDownloader11GetFilePathEv.stub = true; - - /** @type {function(...*):?} */ - function __ZN9NSNetwork15NSFileTransport15CFileDownloader11SetFilePathERKNSt3__212basic_stringIwNS2_11char_traitsIwEENS2_9allocatorIwEEEE( - ) { - abort('missing function: _ZN9NSNetwork15NSFileTransport15CFileDownloader11SetFilePathERKNSt3__212basic_stringIwNS2_11char_traitsIwEENS2_9allocatorIwEEEE'); - } - __ZN9NSNetwork15NSFileTransport15CFileDownloader11SetFilePathERKNSt3__212basic_stringIwNS2_11char_traitsIwEENS2_9allocatorIwEEEE.stub = true; - - /** @type {function(...*):?} */ - function __ZN9NSNetwork15NSFileTransport15CFileDownloader12DownloadSyncEv( - ) { - abort('missing function: _ZN9NSNetwork15NSFileTransport15CFileDownloader12DownloadSyncEv'); - } - __ZN9NSNetwork15NSFileTransport15CFileDownloader12DownloadSyncEv.stub = true; - - /** @type {function(...*):?} */ - function __ZN9NSNetwork15NSFileTransport15CFileDownloaderC1ENSt3__212basic_stringIwNS2_11char_traitsIwEENS2_9allocatorIwEEEEb( - ) { - abort('missing function: _ZN9NSNetwork15NSFileTransport15CFileDownloaderC1ENSt3__212basic_stringIwNS2_11char_traitsIwEENS2_9allocatorIwEEEEb'); - } - __ZN9NSNetwork15NSFileTransport15CFileDownloaderC1ENSt3__212basic_stringIwNS2_11char_traitsIwEENS2_9allocatorIwEEEEb.stub = true; - - /** @type {function(...*):?} */ - function __ZN9NSNetwork15NSFileTransport15CFileDownloaderD1Ev( - ) { - abort('missing function: _ZN9NSNetwork15NSFileTransport15CFileDownloaderD1Ev'); - } - __ZN9NSNetwork15NSFileTransport15CFileDownloaderD1Ev.stub = true; - - var UTF8Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder('utf8') : undefined; - - /** - * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given - * array that contains uint8 values, returns a copy of that string as a - * Javascript String object. - * heapOrArray is either a regular array, or a JavaScript typed array view. - * @param {number} idx - * @param {number=} maxBytesToRead - * @return {string} - */ - var UTF8ArrayToString = (heapOrArray, idx, maxBytesToRead) => { - var endIdx = idx + maxBytesToRead; - var endPtr = idx; - // TextDecoder needs to know the byte length in advance, it doesn't stop on - // null terminator by itself. Also, use the length info to avoid running tiny - // strings through TextDecoder, since .subarray() allocates garbage. - // (As a tiny code save trick, compare endPtr against endIdx using a negation, - // so that undefined means Infinity) - while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr; - - if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { - return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); - } - var str = ''; - // If building with TextDecoder, we have already computed the string length - // above, so test loop end condition against that - while (idx < endPtr) { - // For UTF8 byte structure, see: - // http://en.wikipedia.org/wiki/UTF-8#Description - // https://www.ietf.org/rfc/rfc2279.txt - // https://tools.ietf.org/html/rfc3629 - var u0 = heapOrArray[idx++]; - if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; } - var u1 = heapOrArray[idx++] & 63; - if ((u0 & 0xE0) == 0xC0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; } - var u2 = heapOrArray[idx++] & 63; - if ((u0 & 0xF0) == 0xE0) { - u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; - } else { - u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63); - } - - if (u0 < 0x10000) { - str += String.fromCharCode(u0); - } else { - var ch = u0 - 0x10000; - str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF)); - } - } - return str; - }; - - /** - * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the - * emscripten HEAP, returns a copy of that string as a Javascript String object. - * - * @param {number} ptr - * @param {number=} maxBytesToRead - An optional length that specifies the - * maximum number of bytes to read. You can omit this parameter to scan the - * string until the first 0 byte. If maxBytesToRead is passed, and the string - * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the - * string will cut short at that byte index (i.e. maxBytesToRead will not - * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing - * frequent uses of UTF8ToString() with and without maxBytesToRead may throw - * JS JIT optimizations off, so it is worth to consider consistently using one - * @return {string} - */ - var UTF8ToString = (ptr, maxBytesToRead) => { - return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ''; - }; - var ___assert_fail = (condition, filename, line, func) => { - abort(`Assertion failed: ${UTF8ToString(condition)}, at: ` + [filename ? UTF8ToString(filename) : 'unknown filename', line, func ? UTF8ToString(func) : 'unknown function']); - }; - - var exceptionCaught = []; - - var exceptionLast = 0; - - var uncaughtExceptionCount = 0; - var ___cxa_rethrow = () => { - var info = exceptionCaught.pop(); - if (!info) { - abort('no exception to throw'); - } - var ptr = info.excPtr; - if (!info.get_rethrown()) { - // Only pop if the corresponding push was through rethrow_primary_exception - exceptionCaught.push(info); - info.set_rethrown(true); - info.set_caught(false); - uncaughtExceptionCount++; - } - exceptionLast = ptr; - throw exceptionLast; - }; - - /** @constructor */ - function ExceptionInfo(excPtr) { - this.excPtr = excPtr; - this.ptr = excPtr - 24; - - this.set_type = function(type) { - HEAPU32[(((this.ptr)+(4))>>2)] = type; - }; - - this.get_type = function() { - return HEAPU32[(((this.ptr)+(4))>>2)]; - }; - - this.set_destructor = function(destructor) { - HEAPU32[(((this.ptr)+(8))>>2)] = destructor; - }; - - this.get_destructor = function() { - return HEAPU32[(((this.ptr)+(8))>>2)]; - }; - - this.set_caught = function(caught) { - caught = caught ? 1 : 0; - HEAP8[(((this.ptr)+(12))>>0)] = caught; - }; - - this.get_caught = function() { - return HEAP8[(((this.ptr)+(12))>>0)] != 0; - }; - - this.set_rethrown = function(rethrown) { - rethrown = rethrown ? 1 : 0; - HEAP8[(((this.ptr)+(13))>>0)] = rethrown; - }; - - this.get_rethrown = function() { - return HEAP8[(((this.ptr)+(13))>>0)] != 0; - }; - - // Initialize native structure fields. Should be called once after allocated. - this.init = function(type, destructor) { - this.set_adjusted_ptr(0); - this.set_type(type); - this.set_destructor(destructor); - } - - this.set_adjusted_ptr = function(adjustedPtr) { - HEAPU32[(((this.ptr)+(16))>>2)] = adjustedPtr; - }; - - this.get_adjusted_ptr = function() { - return HEAPU32[(((this.ptr)+(16))>>2)]; - }; - - // Get pointer which is expected to be received by catch clause in C++ code. It may be adjusted - // when the pointer is casted to some of the exception object base classes (e.g. when virtual - // inheritance is used). When a pointer is thrown this method should return the thrown pointer - // itself. - this.get_exception_ptr = function() { - // Work around a fastcomp bug, this code is still included for some reason in a build without - // exceptions support. - var isPointer = ___cxa_is_pointer_type(this.get_type()); - if (isPointer) { - return HEAPU32[((this.excPtr)>>2)]; - } - var adjusted = this.get_adjusted_ptr(); - if (adjusted !== 0) return adjusted; - return this.excPtr; - }; - } - - - var ___cxa_throw = (ptr, type, destructor) => { - var info = new ExceptionInfo(ptr); - // Initialize ExceptionInfo content after it was allocated in __cxa_allocate_exception. - info.init(type, destructor); - exceptionLast = ptr; - uncaughtExceptionCount++; - throw exceptionLast; - }; - - var PATH = { - isAbs:(path) => path.charAt(0) === '/', - splitPath:(filename) => { - var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; - return splitPathRe.exec(filename).slice(1); - }, - normalizeArray:(parts, allowAboveRoot) => { - // if the path tries to go above the root, `up` ends up > 0 - var up = 0; - for (var i = parts.length - 1; i >= 0; i--) { - var last = parts[i]; - if (last === '.') { - parts.splice(i, 1); - } else if (last === '..') { - parts.splice(i, 1); - up++; - } else if (up) { - parts.splice(i, 1); - up--; - } - } - // if the path is allowed to go above the root, restore leading ..s - if (allowAboveRoot) { - for (; up; up--) { - parts.unshift('..'); - } - } - return parts; - }, - normalize:(path) => { - var isAbsolute = PATH.isAbs(path), - trailingSlash = path.substr(-1) === '/'; - // Normalize the path - path = PATH.normalizeArray(path.split('/').filter((p) => !!p), !isAbsolute).join('/'); - if (!path && !isAbsolute) { - path = '.'; - } - if (path && trailingSlash) { - path += '/'; - } - return (isAbsolute ? '/' : '') + path; - }, - dirname:(path) => { - var result = PATH.splitPath(path), - root = result[0], - dir = result[1]; - if (!root && !dir) { - // No dirname whatsoever - return '.'; - } - if (dir) { - // It has a dirname, strip trailing slash - dir = dir.substr(0, dir.length - 1); - } - return root + dir; - }, - basename:(path) => { - // EMSCRIPTEN return '/'' for '/', not an empty string - if (path === '/') return '/'; - path = PATH.normalize(path); - path = path.replace(/\/$/, ""); - var lastSlash = path.lastIndexOf('/'); - if (lastSlash === -1) return path; - return path.substr(lastSlash+1); - }, - join:function() { - var paths = Array.prototype.slice.call(arguments); - return PATH.normalize(paths.join('/')); - }, - join2:(l, r) => { - return PATH.normalize(l + '/' + r); - }, - }; - - var initRandomFill = () => { - if (typeof crypto == 'object' && typeof crypto['getRandomValues'] == 'function') { - // for modern web browsers - return (view) => crypto.getRandomValues(view); - } else - if (ENVIRONMENT_IS_NODE) { - // for nodejs with or without crypto support included - try { - var crypto_module = require('crypto'); - var randomFillSync = crypto_module['randomFillSync']; - if (randomFillSync) { - // nodejs with LTS crypto support - return (view) => crypto_module['randomFillSync'](view); - } - // very old nodejs with the original crypto API - var randomBytes = crypto_module['randomBytes']; - return (view) => ( - view.set(randomBytes(view.byteLength)), - // Return the original view to match modern native implementations. - view - ); - } catch (e) { - // nodejs doesn't have crypto support - } - } - // we couldn't find a proper implementation, as Math.random() is not suitable for /dev/random, see emscripten-core/emscripten/pull/7096 - abort("initRandomDevice"); - }; - var randomFill = (view) => { - // Lazily init on the first invocation. - return (randomFill = initRandomFill())(view); - }; - - - - var PATH_FS = { - resolve:function() { - var resolvedPath = '', - resolvedAbsolute = false; - for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { - var path = (i >= 0) ? arguments[i] : FS.cwd(); - // Skip empty and invalid entries - if (typeof path != 'string') { - throw new TypeError('Arguments to path.resolve must be strings'); - } else if (!path) { - return ''; // an invalid portion invalidates the whole thing - } - resolvedPath = path + '/' + resolvedPath; - resolvedAbsolute = PATH.isAbs(path); - } - // At this point the path should be resolved to a full absolute path, but - // handle relative paths to be safe (might happen when process.cwd() fails) - resolvedPath = PATH.normalizeArray(resolvedPath.split('/').filter((p) => !!p), !resolvedAbsolute).join('/'); - return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; - }, - relative:(from, to) => { - from = PATH_FS.resolve(from).substr(1); - to = PATH_FS.resolve(to).substr(1); - function trim(arr) { - var start = 0; - for (; start < arr.length; start++) { - if (arr[start] !== '') break; - } - var end = arr.length - 1; - for (; end >= 0; end--) { - if (arr[end] !== '') break; - } - if (start > end) return []; - return arr.slice(start, end - start + 1); - } - var fromParts = trim(from.split('/')); - var toParts = trim(to.split('/')); - var length = Math.min(fromParts.length, toParts.length); - var samePartsLength = length; - for (var i = 0; i < length; i++) { - if (fromParts[i] !== toParts[i]) { - samePartsLength = i; - break; - } - } - var outputParts = []; - for (var i = samePartsLength; i < fromParts.length; i++) { - outputParts.push('..'); - } - outputParts = outputParts.concat(toParts.slice(samePartsLength)); - return outputParts.join('/'); - }, - }; - - - - var FS_stdin_getChar_buffer = []; - - var lengthBytesUTF8 = (str) => { - var len = 0; - for (var i = 0; i < str.length; ++i) { - // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code - // unit, not a Unicode code point of the character! So decode - // UTF16->UTF32->UTF8. - // See http://unicode.org/faq/utf_bom.html#utf16-3 - var c = str.charCodeAt(i); // possibly a lead surrogate - if (c <= 0x7F) { - len++; - } else if (c <= 0x7FF) { - len += 2; - } else if (c >= 0xD800 && c <= 0xDFFF) { - len += 4; ++i; - } else { - len += 3; - } - } - return len; - }; - - var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { - // Parameter maxBytesToWrite is not optional. Negative values, 0, null, - // undefined and false each don't write out any bytes. - if (!(maxBytesToWrite > 0)) - return 0; - - var startIdx = outIdx; - var endIdx = outIdx + maxBytesToWrite - 1; // -1 for string null terminator. - for (var i = 0; i < str.length; ++i) { - // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code - // unit, not a Unicode code point of the character! So decode - // UTF16->UTF32->UTF8. - // See http://unicode.org/faq/utf_bom.html#utf16-3 - // For UTF8 byte structure, see http://en.wikipedia.org/wiki/UTF-8#Description - // and https://www.ietf.org/rfc/rfc2279.txt - // and https://tools.ietf.org/html/rfc3629 - var u = str.charCodeAt(i); // possibly a lead surrogate - if (u >= 0xD800 && u <= 0xDFFF) { - var u1 = str.charCodeAt(++i); - u = 0x10000 + ((u & 0x3FF) << 10) | (u1 & 0x3FF); - } - if (u <= 0x7F) { - if (outIdx >= endIdx) break; - heap[outIdx++] = u; - } else if (u <= 0x7FF) { - if (outIdx + 1 >= endIdx) break; - heap[outIdx++] = 0xC0 | (u >> 6); - heap[outIdx++] = 0x80 | (u & 63); - } else if (u <= 0xFFFF) { - if (outIdx + 2 >= endIdx) break; - heap[outIdx++] = 0xE0 | (u >> 12); - heap[outIdx++] = 0x80 | ((u >> 6) & 63); - heap[outIdx++] = 0x80 | (u & 63); - } else { - if (outIdx + 3 >= endIdx) break; - heap[outIdx++] = 0xF0 | (u >> 18); - heap[outIdx++] = 0x80 | ((u >> 12) & 63); - heap[outIdx++] = 0x80 | ((u >> 6) & 63); - heap[outIdx++] = 0x80 | (u & 63); - } - } - // Null-terminate the pointer to the buffer. - heap[outIdx] = 0; - return outIdx - startIdx; - }; - /** @type {function(string, boolean=, number=)} */ - function intArrayFromString(stringy, dontAddNull, length) { - var len = length > 0 ? length : lengthBytesUTF8(stringy)+1; - var u8array = new Array(len); - var numBytesWritten = stringToUTF8Array(stringy, u8array, 0, u8array.length); - if (dontAddNull) u8array.length = numBytesWritten; - return u8array; - } - var FS_stdin_getChar = () => { - if (!FS_stdin_getChar_buffer.length) { - var result = null; - if (ENVIRONMENT_IS_NODE) { - // we will read data by chunks of BUFSIZE - var BUFSIZE = 256; - var buf = Buffer.alloc(BUFSIZE); - var bytesRead = 0; - - // For some reason we must suppress a closure warning here, even though - // fd definitely exists on process.stdin, and is even the proper way to - // get the fd of stdin, - // https://github.com/nodejs/help/issues/2136#issuecomment-523649904 - // This started to happen after moving this logic out of library_tty.js, - // so it is related to the surrounding code in some unclear manner. - /** @suppress {missingProperties} */ - var fd = process.stdin.fd; - - try { - bytesRead = fs.readSync(fd, buf); - } catch(e) { - // Cross-platform differences: on Windows, reading EOF throws an exception, but on other OSes, - // reading EOF returns 0. Uniformize behavior by treating the EOF exception to return 0. - if (e.toString().includes('EOF')) bytesRead = 0; - else throw e; - } - - if (bytesRead > 0) { - result = buf.slice(0, bytesRead).toString('utf-8'); - } else { - result = null; - } - } else - if (typeof window != 'undefined' && - typeof window.prompt == 'function') { - // Browser. - result = window.prompt('Input: '); // returns null on cancel - if (result !== null) { - result += '\n'; - } - } else if (typeof readline == 'function') { - // Command line. - result = readline(); - if (result !== null) { - result += '\n'; - } - } - if (!result) { - return null; - } - FS_stdin_getChar_buffer = intArrayFromString(result, true); - } - return FS_stdin_getChar_buffer.shift(); - }; - var TTY = { - ttys:[], - init() { - // https://github.com/emscripten-core/emscripten/pull/1555 - // if (ENVIRONMENT_IS_NODE) { - // // currently, FS.init does not distinguish if process.stdin is a file or TTY - // // device, it always assumes it's a TTY device. because of this, we're forcing - // // process.stdin to UTF8 encoding to at least make stdin reading compatible - // // with text files until FS.init can be refactored. - // process.stdin.setEncoding('utf8'); - // } - }, - shutdown() { - // https://github.com/emscripten-core/emscripten/pull/1555 - // if (ENVIRONMENT_IS_NODE) { - // // inolen: any idea as to why node -e 'process.stdin.read()' wouldn't exit immediately (with process.stdin being a tty)? - // // isaacs: because now it's reading from the stream, you've expressed interest in it, so that read() kicks off a _read() which creates a ReadReq operation - // // inolen: I thought read() in that case was a synchronous operation that just grabbed some amount of buffered data if it exists? - // // isaacs: it is. but it also triggers a _read() call, which calls readStart() on the handle - // // isaacs: do process.stdin.pause() and i'd think it'd probably close the pending call - // process.stdin.pause(); - // } - }, - register(dev, ops) { - TTY.ttys[dev] = { input: [], output: [], ops: ops }; - FS.registerDevice(dev, TTY.stream_ops); - }, - stream_ops:{ - open(stream) { - var tty = TTY.ttys[stream.node.rdev]; - if (!tty) { - throw new FS.ErrnoError(43); - } - stream.tty = tty; - stream.seekable = false; - }, - close(stream) { - // flush any pending line data - stream.tty.ops.fsync(stream.tty); - }, - fsync(stream) { - stream.tty.ops.fsync(stream.tty); - }, - read(stream, buffer, offset, length, pos /* ignored */) { - if (!stream.tty || !stream.tty.ops.get_char) { - throw new FS.ErrnoError(60); - } - var bytesRead = 0; - for (var i = 0; i < length; i++) { - var result; - try { - result = stream.tty.ops.get_char(stream.tty); - } catch (e) { - throw new FS.ErrnoError(29); - } - if (result === undefined && bytesRead === 0) { - throw new FS.ErrnoError(6); - } - if (result === null || result === undefined) break; - bytesRead++; - buffer[offset+i] = result; - } - if (bytesRead) { - stream.node.timestamp = Date.now(); - } - return bytesRead; - }, - write(stream, buffer, offset, length, pos) { - if (!stream.tty || !stream.tty.ops.put_char) { - throw new FS.ErrnoError(60); - } - try { - for (var i = 0; i < length; i++) { - stream.tty.ops.put_char(stream.tty, buffer[offset+i]); - } - } catch (e) { - throw new FS.ErrnoError(29); - } - if (length) { - stream.node.timestamp = Date.now(); - } - return i; - }, - }, - default_tty_ops:{ - get_char(tty) { - return FS_stdin_getChar(); - }, - put_char(tty, val) { - if (val === null || val === 10) { - out(UTF8ArrayToString(tty.output, 0)); - tty.output = []; - } else { - if (val != 0) tty.output.push(val); // val == 0 would cut text output off in the middle. - } - }, - fsync(tty) { - if (tty.output && tty.output.length > 0) { - out(UTF8ArrayToString(tty.output, 0)); - tty.output = []; - } - }, - ioctl_tcgets(tty) { - // typical setting - return { - c_iflag: 25856, - c_oflag: 5, - c_cflag: 191, - c_lflag: 35387, - c_cc: [ - 0x03, 0x1c, 0x7f, 0x15, 0x04, 0x00, 0x01, 0x00, 0x11, 0x13, 0x1a, 0x00, - 0x12, 0x0f, 0x17, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ] - }; - }, - ioctl_tcsets(tty, optional_actions, data) { - // currently just ignore - return 0; - }, - ioctl_tiocgwinsz(tty) { - return [24, 80]; - }, - }, - default_tty1_ops:{ - put_char(tty, val) { - if (val === null || val === 10) { - err(UTF8ArrayToString(tty.output, 0)); - tty.output = []; - } else { - if (val != 0) tty.output.push(val); - } - }, - fsync(tty) { - if (tty.output && tty.output.length > 0) { - err(UTF8ArrayToString(tty.output, 0)); - tty.output = []; - } - }, - }, - }; - - - var zeroMemory = (address, size) => { - HEAPU8.fill(0, address, address + size); - return address; - }; - - var alignMemory = (size, alignment) => { - return Math.ceil(size / alignment) * alignment; - }; - var mmapAlloc = (size) => { - size = alignMemory(size, 65536); - var ptr = _emscripten_builtin_memalign(65536, size); - if (!ptr) return 0; - return zeroMemory(ptr, size); - }; - var MEMFS = { - ops_table:null, - mount(mount) { - return MEMFS.createNode(null, '/', 16384 | 511 /* 0777 */, 0); - }, - createNode(parent, name, mode, dev) { - if (FS.isBlkdev(mode) || FS.isFIFO(mode)) { - // no supported - throw new FS.ErrnoError(63); - } - if (!MEMFS.ops_table) { - MEMFS.ops_table = { - dir: { - node: { - getattr: MEMFS.node_ops.getattr, - setattr: MEMFS.node_ops.setattr, - lookup: MEMFS.node_ops.lookup, - mknod: MEMFS.node_ops.mknod, - rename: MEMFS.node_ops.rename, - unlink: MEMFS.node_ops.unlink, - rmdir: MEMFS.node_ops.rmdir, - readdir: MEMFS.node_ops.readdir, - symlink: MEMFS.node_ops.symlink - }, - stream: { - llseek: MEMFS.stream_ops.llseek - } - }, - file: { - node: { - getattr: MEMFS.node_ops.getattr, - setattr: MEMFS.node_ops.setattr - }, - stream: { - llseek: MEMFS.stream_ops.llseek, - read: MEMFS.stream_ops.read, - write: MEMFS.stream_ops.write, - allocate: MEMFS.stream_ops.allocate, - mmap: MEMFS.stream_ops.mmap, - msync: MEMFS.stream_ops.msync - } - }, - link: { - node: { - getattr: MEMFS.node_ops.getattr, - setattr: MEMFS.node_ops.setattr, - readlink: MEMFS.node_ops.readlink - }, - stream: {} - }, - chrdev: { - node: { - getattr: MEMFS.node_ops.getattr, - setattr: MEMFS.node_ops.setattr - }, - stream: FS.chrdev_stream_ops - } - }; - } - var node = FS.createNode(parent, name, mode, dev); - if (FS.isDir(node.mode)) { - node.node_ops = MEMFS.ops_table.dir.node; - node.stream_ops = MEMFS.ops_table.dir.stream; - node.contents = {}; - } else if (FS.isFile(node.mode)) { - node.node_ops = MEMFS.ops_table.file.node; - node.stream_ops = MEMFS.ops_table.file.stream; - node.usedBytes = 0; // The actual number of bytes used in the typed array, as opposed to contents.length which gives the whole capacity. - // When the byte data of the file is populated, this will point to either a typed array, or a normal JS array. Typed arrays are preferred - // for performance, and used by default. However, typed arrays are not resizable like normal JS arrays are, so there is a small disk size - // penalty involved for appending file writes that continuously grow a file similar to std::vector capacity vs used -scheme. - node.contents = null; - } else if (FS.isLink(node.mode)) { - node.node_ops = MEMFS.ops_table.link.node; - node.stream_ops = MEMFS.ops_table.link.stream; - } else if (FS.isChrdev(node.mode)) { - node.node_ops = MEMFS.ops_table.chrdev.node; - node.stream_ops = MEMFS.ops_table.chrdev.stream; - } - node.timestamp = Date.now(); - // add the new node to the parent - if (parent) { - parent.contents[name] = node; - parent.timestamp = node.timestamp; - } - return node; - }, - getFileDataAsTypedArray(node) { - if (!node.contents) return new Uint8Array(0); - if (node.contents.subarray) return node.contents.subarray(0, node.usedBytes); // Make sure to not return excess unused bytes. - return new Uint8Array(node.contents); - }, - expandFileStorage(node, newCapacity) { - var prevCapacity = node.contents ? node.contents.length : 0; - if (prevCapacity >= newCapacity) return; // No need to expand, the storage was already large enough. - // Don't expand strictly to the given requested limit if it's only a very small increase, but instead geometrically grow capacity. - // For small filesizes (<1MB), perform size*2 geometric increase, but for large sizes, do a much more conservative size*1.125 increase to - // avoid overshooting the allocation cap by a very large margin. - var CAPACITY_DOUBLING_MAX = 1024 * 1024; - newCapacity = Math.max(newCapacity, (prevCapacity * (prevCapacity < CAPACITY_DOUBLING_MAX ? 2.0 : 1.125)) >>> 0); - if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); // At minimum allocate 256b for each file when expanding. - var oldContents = node.contents; - node.contents = new Uint8Array(newCapacity); // Allocate new storage. - if (node.usedBytes > 0) node.contents.set(oldContents.subarray(0, node.usedBytes), 0); // Copy old data over to the new storage. - }, - resizeFileStorage(node, newSize) { - if (node.usedBytes == newSize) return; - if (newSize == 0) { - node.contents = null; // Fully decommit when requesting a resize to zero. - node.usedBytes = 0; - } else { - var oldContents = node.contents; - node.contents = new Uint8Array(newSize); // Allocate new storage. - if (oldContents) { - node.contents.set(oldContents.subarray(0, Math.min(newSize, node.usedBytes))); // Copy old data over to the new storage. - } - node.usedBytes = newSize; - } - }, - node_ops:{ - getattr(node) { - var attr = {}; - // device numbers reuse inode numbers. - attr.dev = FS.isChrdev(node.mode) ? node.id : 1; - attr.ino = node.id; - attr.mode = node.mode; - attr.nlink = 1; - attr.uid = 0; - attr.gid = 0; - attr.rdev = node.rdev; - if (FS.isDir(node.mode)) { - attr.size = 4096; - } else if (FS.isFile(node.mode)) { - attr.size = node.usedBytes; - } else if (FS.isLink(node.mode)) { - attr.size = node.link.length; - } else { - attr.size = 0; - } - attr.atime = new Date(node.timestamp); - attr.mtime = new Date(node.timestamp); - attr.ctime = new Date(node.timestamp); - // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize), - // but this is not required by the standard. - attr.blksize = 4096; - attr.blocks = Math.ceil(attr.size / attr.blksize); - return attr; - }, - setattr(node, attr) { - if (attr.mode !== undefined) { - node.mode = attr.mode; - } - if (attr.timestamp !== undefined) { - node.timestamp = attr.timestamp; - } - if (attr.size !== undefined) { - MEMFS.resizeFileStorage(node, attr.size); - } - }, - lookup(parent, name) { - throw FS.genericErrors[44]; - }, - mknod(parent, name, mode, dev) { - return MEMFS.createNode(parent, name, mode, dev); - }, - rename(old_node, new_dir, new_name) { - // if we're overwriting a directory at new_name, make sure it's empty. - if (FS.isDir(old_node.mode)) { - var new_node; - try { - new_node = FS.lookupNode(new_dir, new_name); - } catch (e) { - } - if (new_node) { - for (var i in new_node.contents) { - throw new FS.ErrnoError(55); - } - } - } - // do the internal rewiring - delete old_node.parent.contents[old_node.name]; - old_node.parent.timestamp = Date.now() - old_node.name = new_name; - new_dir.contents[new_name] = old_node; - new_dir.timestamp = old_node.parent.timestamp; - old_node.parent = new_dir; - }, - unlink(parent, name) { - delete parent.contents[name]; - parent.timestamp = Date.now(); - }, - rmdir(parent, name) { - var node = FS.lookupNode(parent, name); - for (var i in node.contents) { - throw new FS.ErrnoError(55); - } - delete parent.contents[name]; - parent.timestamp = Date.now(); - }, - readdir(node) { - var entries = ['.', '..']; - for (var key in node.contents) { - if (!node.contents.hasOwnProperty(key)) { - continue; - } - entries.push(key); - } - return entries; - }, - symlink(parent, newname, oldpath) { - var node = MEMFS.createNode(parent, newname, 511 /* 0777 */ | 40960, 0); - node.link = oldpath; - return node; - }, - readlink(node) { - if (!FS.isLink(node.mode)) { - throw new FS.ErrnoError(28); - } - return node.link; - }, - }, - stream_ops:{ - read(stream, buffer, offset, length, position) { - var contents = stream.node.contents; - if (position >= stream.node.usedBytes) return 0; - var size = Math.min(stream.node.usedBytes - position, length); - if (size > 8 && contents.subarray) { // non-trivial, and typed array - buffer.set(contents.subarray(position, position + size), offset); - } else { - for (var i = 0; i < size; i++) buffer[offset + i] = contents[position + i]; - } - return size; - }, - write(stream, buffer, offset, length, position, canOwn) { - // If the buffer is located in main memory (HEAP), and if - // memory can grow, we can't hold on to references of the - // memory buffer, as they may get invalidated. That means we - // need to do copy its contents. - if (buffer.buffer === HEAP8.buffer) { - canOwn = false; - } - - if (!length) return 0; - var node = stream.node; - node.timestamp = Date.now(); - - if (buffer.subarray && (!node.contents || node.contents.subarray)) { // This write is from a typed array to a typed array? - if (canOwn) { - node.contents = buffer.subarray(offset, offset + length); - node.usedBytes = length; - return length; - } else if (node.usedBytes === 0 && position === 0) { // If this is a simple first write to an empty file, do a fast set since we don't need to care about old data. - node.contents = buffer.slice(offset, offset + length); - node.usedBytes = length; - return length; - } else if (position + length <= node.usedBytes) { // Writing to an already allocated and used subrange of the file? - node.contents.set(buffer.subarray(offset, offset + length), position); - return length; - } - } - - // Appending to an existing file and we need to reallocate, or source data did not come as a typed array. - MEMFS.expandFileStorage(node, position+length); - if (node.contents.subarray && buffer.subarray) { - // Use typed array write which is available. - node.contents.set(buffer.subarray(offset, offset + length), position); - } else { - for (var i = 0; i < length; i++) { - node.contents[position + i] = buffer[offset + i]; // Or fall back to manual write if not. - } - } - node.usedBytes = Math.max(node.usedBytes, position + length); - return length; - }, - llseek(stream, offset, whence) { - var position = offset; - if (whence === 1) { - position += stream.position; - } else if (whence === 2) { - if (FS.isFile(stream.node.mode)) { - position += stream.node.usedBytes; - } - } - if (position < 0) { - throw new FS.ErrnoError(28); - } - return position; - }, - allocate(stream, offset, length) { - MEMFS.expandFileStorage(stream.node, offset + length); - stream.node.usedBytes = Math.max(stream.node.usedBytes, offset + length); - }, - mmap(stream, length, position, prot, flags) { - if (!FS.isFile(stream.node.mode)) { - throw new FS.ErrnoError(43); - } - var ptr; - var allocated; - var contents = stream.node.contents; - // Only make a new copy when MAP_PRIVATE is specified. - if (!(flags & 2) && contents.buffer === HEAP8.buffer) { - // We can't emulate MAP_SHARED when the file is not backed by the - // buffer we're mapping to (e.g. the HEAP buffer). - allocated = false; - ptr = contents.byteOffset; - } else { - // Try to avoid unnecessary slices. - if (position > 0 || position + length < contents.length) { - if (contents.subarray) { - contents = contents.subarray(position, position + length); - } else { - contents = Array.prototype.slice.call(contents, position, position + length); - } - } - allocated = true; - ptr = mmapAlloc(length); - if (!ptr) { - throw new FS.ErrnoError(48); - } - HEAP8.set(contents, ptr); - } - return { ptr, allocated }; - }, - msync(stream, buffer, offset, length, mmapFlags) { - MEMFS.stream_ops.write(stream, buffer, 0, length, offset, false); - // should we check if bytesWritten and length are the same? - return 0; - }, - }, - }; - - /** @param {boolean=} noRunDep */ - var asyncLoad = (url, onload, onerror, noRunDep) => { - var dep = !noRunDep ? getUniqueRunDependency(`al ${url}`) : ''; - readAsync(url, (arrayBuffer) => { - assert(arrayBuffer, `Loading data file "${url}" failed (no arrayBuffer).`); - onload(new Uint8Array(arrayBuffer)); - if (dep) removeRunDependency(dep); - }, (event) => { - if (onerror) { - onerror(); - } else { - throw `Loading data file "${url}" failed.`; - } - }); - if (dep) addRunDependency(dep); - }; - - - var preloadPlugins = Module['preloadPlugins'] || []; - var FS_handledByPreloadPlugin = (byteArray, fullname, finish, onerror) => { - // Ensure plugins are ready. - if (typeof Browser != 'undefined') Browser.init(); - - var handled = false; - preloadPlugins.forEach((plugin) => { - if (handled) return; - if (plugin['canHandle'](fullname)) { - plugin['handle'](byteArray, fullname, finish, onerror); - handled = true; - } - }); - return handled; - }; - var FS_createPreloadedFile = (parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile, canOwn, preFinish) => { - // TODO we should allow people to just pass in a complete filename instead - // of parent and name being that we just join them anyways - var fullname = name ? PATH_FS.resolve(PATH.join2(parent, name)) : parent; - var dep = getUniqueRunDependency(`cp ${fullname}`); // might have several active requests for the same fullname - function processData(byteArray) { - function finish(byteArray) { - if (preFinish) preFinish(); - if (!dontCreateFile) { - FS.createDataFile(parent, name, byteArray, canRead, canWrite, canOwn); - } - if (onload) onload(); - removeRunDependency(dep); - } - if (FS_handledByPreloadPlugin(byteArray, fullname, finish, () => { - if (onerror) onerror(); - removeRunDependency(dep); - })) { - return; - } - finish(byteArray); - } - addRunDependency(dep); - if (typeof url == 'string') { - asyncLoad(url, (byteArray) => processData(byteArray), onerror); - } else { - processData(url); - } - }; - - var FS_modeStringToFlags = (str) => { - var flagModes = { - 'r': 0, - 'r+': 2, - 'w': 512 | 64 | 1, - 'w+': 512 | 64 | 2, - 'a': 1024 | 64 | 1, - 'a+': 1024 | 64 | 2, - }; - var flags = flagModes[str]; - if (typeof flags == 'undefined') { - throw new Error(`Unknown file open mode: ${str}`); - } - return flags; - }; - - var FS_getMode = (canRead, canWrite) => { - var mode = 0; - if (canRead) mode |= 292 | 73; - if (canWrite) mode |= 146; - return mode; - }; - - - - var FS = { - root:null, - mounts:[], - devices:{ - }, - streams:[], - nextInode:1, - nameTable:null, - currentPath:"/", - initialized:false, - ignorePermissions:true, - ErrnoError:null, - genericErrors:{ - }, - filesystems:null, - syncFSRequests:0, - lookupPath(path, opts = {}) { - path = PATH_FS.resolve(path); - - if (!path) return { path: '', node: null }; - - var defaults = { - follow_mount: true, - recurse_count: 0 - }; - opts = Object.assign(defaults, opts) - - if (opts.recurse_count > 8) { // max recursive lookup of 8 - throw new FS.ErrnoError(32); - } - - // split the absolute path - var parts = path.split('/').filter((p) => !!p); - - // start at the root - var current = FS.root; - var current_path = '/'; - - for (var i = 0; i < parts.length; i++) { - var islast = (i === parts.length-1); - if (islast && opts.parent) { - // stop resolving - break; - } - - current = FS.lookupNode(current, parts[i]); - current_path = PATH.join2(current_path, parts[i]); - - // jump to the mount's root node if this is a mountpoint - if (FS.isMountpoint(current)) { - if (!islast || (islast && opts.follow_mount)) { - current = current.mounted.root; - } - } - - // by default, lookupPath will not follow a symlink if it is the final path component. - // setting opts.follow = true will override this behavior. - if (!islast || opts.follow) { - var count = 0; - while (FS.isLink(current.mode)) { - var link = FS.readlink(current_path); - current_path = PATH_FS.resolve(PATH.dirname(current_path), link); - - var lookup = FS.lookupPath(current_path, { recurse_count: opts.recurse_count + 1 }); - current = lookup.node; - - if (count++ > 40) { // limit max consecutive symlinks to 40 (SYMLOOP_MAX). - throw new FS.ErrnoError(32); - } - } - } - } - - return { path: current_path, node: current }; - }, - getPath(node) { - var path; - while (true) { - if (FS.isRoot(node)) { - var mount = node.mount.mountpoint; - if (!path) return mount; - return mount[mount.length-1] !== '/' ? `${mount}/${path}` : mount + path; - } - path = path ? `${node.name}/${path}` : node.name; - node = node.parent; - } - }, - hashName(parentid, name) { - var hash = 0; - - for (var i = 0; i < name.length; i++) { - hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0; - } - return ((parentid + hash) >>> 0) % FS.nameTable.length; - }, - hashAddNode(node) { - var hash = FS.hashName(node.parent.id, node.name); - node.name_next = FS.nameTable[hash]; - FS.nameTable[hash] = node; - }, - hashRemoveNode(node) { - var hash = FS.hashName(node.parent.id, node.name); - if (FS.nameTable[hash] === node) { - FS.nameTable[hash] = node.name_next; - } else { - var current = FS.nameTable[hash]; - while (current) { - if (current.name_next === node) { - current.name_next = node.name_next; - break; - } - current = current.name_next; - } - } - }, - lookupNode(parent, name) { - var errCode = FS.mayLookup(parent); - if (errCode) { - throw new FS.ErrnoError(errCode, parent); - } - var hash = FS.hashName(parent.id, name); - for (var node = FS.nameTable[hash]; node; node = node.name_next) { - var nodeName = node.name; - if (node.parent.id === parent.id && nodeName === name) { - return node; - } - } - // if we failed to find it in the cache, call into the VFS - return FS.lookup(parent, name); - }, - createNode(parent, name, mode, rdev) { - var node = new FS.FSNode(parent, name, mode, rdev); - - FS.hashAddNode(node); - - return node; - }, - destroyNode(node) { - FS.hashRemoveNode(node); - }, - isRoot(node) { - return node === node.parent; - }, - isMountpoint(node) { - return !!node.mounted; - }, - isFile(mode) { - return (mode & 61440) === 32768; - }, - isDir(mode) { - return (mode & 61440) === 16384; - }, - isLink(mode) { - return (mode & 61440) === 40960; - }, - isChrdev(mode) { - return (mode & 61440) === 8192; - }, - isBlkdev(mode) { - return (mode & 61440) === 24576; - }, - isFIFO(mode) { - return (mode & 61440) === 4096; - }, - isSocket(mode) { - return (mode & 49152) === 49152; - }, - flagsToPermissionString(flag) { - var perms = ['r', 'w', 'rw'][flag & 3]; - if ((flag & 512)) { - perms += 'w'; - } - return perms; - }, - nodePermissions(node, perms) { - if (FS.ignorePermissions) { - return 0; - } - // return 0 if any user, group or owner bits are set. - if (perms.includes('r') && !(node.mode & 292)) { - return 2; - } else if (perms.includes('w') && !(node.mode & 146)) { - return 2; - } else if (perms.includes('x') && !(node.mode & 73)) { - return 2; - } - return 0; - }, - mayLookup(dir) { - var errCode = FS.nodePermissions(dir, 'x'); - if (errCode) return errCode; - if (!dir.node_ops.lookup) return 2; - return 0; - }, - mayCreate(dir, name) { - try { - var node = FS.lookupNode(dir, name); - return 20; - } catch (e) { - } - return FS.nodePermissions(dir, 'wx'); - }, - mayDelete(dir, name, isdir) { - var node; - try { - node = FS.lookupNode(dir, name); - } catch (e) { - return e.errno; - } - var errCode = FS.nodePermissions(dir, 'wx'); - if (errCode) { - return errCode; - } - if (isdir) { - if (!FS.isDir(node.mode)) { - return 54; - } - if (FS.isRoot(node) || FS.getPath(node) === FS.cwd()) { - return 10; - } - } else { - if (FS.isDir(node.mode)) { - return 31; - } - } - return 0; - }, - mayOpen(node, flags) { - if (!node) { - return 44; - } - if (FS.isLink(node.mode)) { - return 32; - } else if (FS.isDir(node.mode)) { - if (FS.flagsToPermissionString(flags) !== 'r' || // opening for write - (flags & 512)) { // TODO: check for O_SEARCH? (== search for dir only) - return 31; - } - } - return FS.nodePermissions(node, FS.flagsToPermissionString(flags)); - }, - MAX_OPEN_FDS:4096, - nextfd() { - for (var fd = 0; fd <= FS.MAX_OPEN_FDS; fd++) { - if (!FS.streams[fd]) { - return fd; - } - } - throw new FS.ErrnoError(33); - }, - getStreamChecked(fd) { - var stream = FS.getStream(fd); - if (!stream) { - throw new FS.ErrnoError(8); - } - return stream; - }, - getStream:(fd) => FS.streams[fd], - createStream(stream, fd = -1) { - if (!FS.FSStream) { - FS.FSStream = /** @constructor */ function() { - this.shared = { }; - }; - FS.FSStream.prototype = {}; - Object.defineProperties(FS.FSStream.prototype, { - object: { - /** @this {FS.FSStream} */ - get() { return this.node; }, - /** @this {FS.FSStream} */ - set(val) { this.node = val; } - }, - isRead: { - /** @this {FS.FSStream} */ - get() { return (this.flags & 2097155) !== 1; } - }, - isWrite: { - /** @this {FS.FSStream} */ - get() { return (this.flags & 2097155) !== 0; } - }, - isAppend: { - /** @this {FS.FSStream} */ - get() { return (this.flags & 1024); } - }, - flags: { - /** @this {FS.FSStream} */ - get() { return this.shared.flags; }, - /** @this {FS.FSStream} */ - set(val) { this.shared.flags = val; }, - }, - position : { - /** @this {FS.FSStream} */ - get() { return this.shared.position; }, - /** @this {FS.FSStream} */ - set(val) { this.shared.position = val; }, - }, - }); - } - // clone it, so we can return an instance of FSStream - stream = Object.assign(new FS.FSStream(), stream); - if (fd == -1) { - fd = FS.nextfd(); - } - stream.fd = fd; - FS.streams[fd] = stream; - return stream; - }, - closeStream(fd) { - FS.streams[fd] = null; - }, - chrdev_stream_ops:{ - open(stream) { - var device = FS.getDevice(stream.node.rdev); - // override node's stream ops with the device's - stream.stream_ops = device.stream_ops; - // forward the open call - if (stream.stream_ops.open) { - stream.stream_ops.open(stream); - } - }, - llseek() { - throw new FS.ErrnoError(70); - }, - }, - major:(dev) => ((dev) >> 8), - minor:(dev) => ((dev) & 0xff), - makedev:(ma, mi) => ((ma) << 8 | (mi)), - registerDevice(dev, ops) { - FS.devices[dev] = { stream_ops: ops }; - }, - getDevice:(dev) => FS.devices[dev], - getMounts(mount) { - var mounts = []; - var check = [mount]; - - while (check.length) { - var m = check.pop(); - - mounts.push(m); - - check.push.apply(check, m.mounts); - } - - return mounts; - }, - syncfs(populate, callback) { - if (typeof populate == 'function') { - callback = populate; - populate = false; - } - - FS.syncFSRequests++; - - if (FS.syncFSRequests > 1) { - err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`); - } - - var mounts = FS.getMounts(FS.root.mount); - var completed = 0; - - function doCallback(errCode) { - FS.syncFSRequests--; - return callback(errCode); - } - - function done(errCode) { - if (errCode) { - if (!done.errored) { - done.errored = true; - return doCallback(errCode); - } - return; - } - if (++completed >= mounts.length) { - doCallback(null); - } - }; - - // sync all mounts - mounts.forEach((mount) => { - if (!mount.type.syncfs) { - return done(null); - } - mount.type.syncfs(mount, populate, done); - }); - }, - mount(type, opts, mountpoint) { - var root = mountpoint === '/'; - var pseudo = !mountpoint; - var node; - - if (root && FS.root) { - throw new FS.ErrnoError(10); - } else if (!root && !pseudo) { - var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); - - mountpoint = lookup.path; // use the absolute path - node = lookup.node; - - if (FS.isMountpoint(node)) { - throw new FS.ErrnoError(10); - } - - if (!FS.isDir(node.mode)) { - throw new FS.ErrnoError(54); - } - } - - var mount = { - type, - opts, - mountpoint, - mounts: [] - }; - - // create a root node for the fs - var mountRoot = type.mount(mount); - mountRoot.mount = mount; - mount.root = mountRoot; - - if (root) { - FS.root = mountRoot; - } else if (node) { - // set as a mountpoint - node.mounted = mount; - - // add the new mount to the current mount's children - if (node.mount) { - node.mount.mounts.push(mount); - } - } - - return mountRoot; - }, - unmount(mountpoint) { - var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); - - if (!FS.isMountpoint(lookup.node)) { - throw new FS.ErrnoError(28); - } - - // destroy the nodes for this mount, and all its child mounts - var node = lookup.node; - var mount = node.mounted; - var mounts = FS.getMounts(mount); - - Object.keys(FS.nameTable).forEach((hash) => { - var current = FS.nameTable[hash]; - - while (current) { - var next = current.name_next; - - if (mounts.includes(current.mount)) { - FS.destroyNode(current); - } - - current = next; - } - }); - - // no longer a mountpoint - node.mounted = null; - - // remove this mount from the child mounts - var idx = node.mount.mounts.indexOf(mount); - node.mount.mounts.splice(idx, 1); - }, - lookup(parent, name) { - return parent.node_ops.lookup(parent, name); - }, - mknod(path, mode, dev) { - var lookup = FS.lookupPath(path, { parent: true }); - var parent = lookup.node; - var name = PATH.basename(path); - if (!name || name === '.' || name === '..') { - throw new FS.ErrnoError(28); - } - var errCode = FS.mayCreate(parent, name); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - if (!parent.node_ops.mknod) { - throw new FS.ErrnoError(63); - } - return parent.node_ops.mknod(parent, name, mode, dev); - }, - create(path, mode) { - mode = mode !== undefined ? mode : 438 /* 0666 */; - mode &= 4095; - mode |= 32768; - return FS.mknod(path, mode, 0); - }, - mkdir(path, mode) { - mode = mode !== undefined ? mode : 511 /* 0777 */; - mode &= 511 | 512; - mode |= 16384; - return FS.mknod(path, mode, 0); - }, - mkdirTree(path, mode) { - var dirs = path.split('/'); - var d = ''; - for (var i = 0; i < dirs.length; ++i) { - if (!dirs[i]) continue; - d += '/' + dirs[i]; - try { - FS.mkdir(d, mode); - } catch(e) { - if (e.errno != 20) throw e; - } - } - }, - mkdev(path, mode, dev) { - if (typeof dev == 'undefined') { - dev = mode; - mode = 438 /* 0666 */; - } - mode |= 8192; - return FS.mknod(path, mode, dev); - }, - symlink(oldpath, newpath) { - if (!PATH_FS.resolve(oldpath)) { - throw new FS.ErrnoError(44); - } - var lookup = FS.lookupPath(newpath, { parent: true }); - var parent = lookup.node; - if (!parent) { - throw new FS.ErrnoError(44); - } - var newname = PATH.basename(newpath); - var errCode = FS.mayCreate(parent, newname); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - if (!parent.node_ops.symlink) { - throw new FS.ErrnoError(63); - } - return parent.node_ops.symlink(parent, newname, oldpath); - }, - rename(old_path, new_path) { - var old_dirname = PATH.dirname(old_path); - var new_dirname = PATH.dirname(new_path); - var old_name = PATH.basename(old_path); - var new_name = PATH.basename(new_path); - // parents must exist - var lookup, old_dir, new_dir; - - // let the errors from non existant directories percolate up - lookup = FS.lookupPath(old_path, { parent: true }); - old_dir = lookup.node; - lookup = FS.lookupPath(new_path, { parent: true }); - new_dir = lookup.node; - - if (!old_dir || !new_dir) throw new FS.ErrnoError(44); - // need to be part of the same mount - if (old_dir.mount !== new_dir.mount) { - throw new FS.ErrnoError(75); - } - // source must exist - var old_node = FS.lookupNode(old_dir, old_name); - // old path should not be an ancestor of the new path - var relative = PATH_FS.relative(old_path, new_dirname); - if (relative.charAt(0) !== '.') { - throw new FS.ErrnoError(28); - } - // new path should not be an ancestor of the old path - relative = PATH_FS.relative(new_path, old_dirname); - if (relative.charAt(0) !== '.') { - throw new FS.ErrnoError(55); - } - // see if the new path already exists - var new_node; - try { - new_node = FS.lookupNode(new_dir, new_name); - } catch (e) { - // not fatal - } - // early out if nothing needs to change - if (old_node === new_node) { - return; - } - // we'll need to delete the old entry - var isdir = FS.isDir(old_node.mode); - var errCode = FS.mayDelete(old_dir, old_name, isdir); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - // need delete permissions if we'll be overwriting. - // need create permissions if new doesn't already exist. - errCode = new_node ? - FS.mayDelete(new_dir, new_name, isdir) : - FS.mayCreate(new_dir, new_name); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - if (!old_dir.node_ops.rename) { - throw new FS.ErrnoError(63); - } - if (FS.isMountpoint(old_node) || (new_node && FS.isMountpoint(new_node))) { - throw new FS.ErrnoError(10); - } - // if we are going to change the parent, check write permissions - if (new_dir !== old_dir) { - errCode = FS.nodePermissions(old_dir, 'w'); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - } - // remove the node from the lookup hash - FS.hashRemoveNode(old_node); - // do the underlying fs rename - try { - old_dir.node_ops.rename(old_node, new_dir, new_name); - } catch (e) { - throw e; - } finally { - // add the node back to the hash (in case node_ops.rename - // changed its name) - FS.hashAddNode(old_node); - } - }, - rmdir(path) { - var lookup = FS.lookupPath(path, { parent: true }); - var parent = lookup.node; - var name = PATH.basename(path); - var node = FS.lookupNode(parent, name); - var errCode = FS.mayDelete(parent, name, true); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - if (!parent.node_ops.rmdir) { - throw new FS.ErrnoError(63); - } - if (FS.isMountpoint(node)) { - throw new FS.ErrnoError(10); - } - parent.node_ops.rmdir(parent, name); - FS.destroyNode(node); - }, - readdir(path) { - var lookup = FS.lookupPath(path, { follow: true }); - var node = lookup.node; - if (!node.node_ops.readdir) { - throw new FS.ErrnoError(54); - } - return node.node_ops.readdir(node); - }, - unlink(path) { - var lookup = FS.lookupPath(path, { parent: true }); - var parent = lookup.node; - if (!parent) { - throw new FS.ErrnoError(44); - } - var name = PATH.basename(path); - var node = FS.lookupNode(parent, name); - var errCode = FS.mayDelete(parent, name, false); - if (errCode) { - // According to POSIX, we should map EISDIR to EPERM, but - // we instead do what Linux does (and we must, as we use - // the musl linux libc). - throw new FS.ErrnoError(errCode); - } - if (!parent.node_ops.unlink) { - throw new FS.ErrnoError(63); - } - if (FS.isMountpoint(node)) { - throw new FS.ErrnoError(10); - } - parent.node_ops.unlink(parent, name); - FS.destroyNode(node); - }, - readlink(path) { - var lookup = FS.lookupPath(path); - var link = lookup.node; - if (!link) { - throw new FS.ErrnoError(44); - } - if (!link.node_ops.readlink) { - throw new FS.ErrnoError(28); - } - return PATH_FS.resolve(FS.getPath(link.parent), link.node_ops.readlink(link)); - }, - stat(path, dontFollow) { - var lookup = FS.lookupPath(path, { follow: !dontFollow }); - var node = lookup.node; - if (!node) { - throw new FS.ErrnoError(44); - } - if (!node.node_ops.getattr) { - throw new FS.ErrnoError(63); - } - return node.node_ops.getattr(node); - }, - lstat(path) { - return FS.stat(path, true); - }, - chmod(path, mode, dontFollow) { - var node; - if (typeof path == 'string') { - var lookup = FS.lookupPath(path, { follow: !dontFollow }); - node = lookup.node; - } else { - node = path; - } - if (!node.node_ops.setattr) { - throw new FS.ErrnoError(63); - } - node.node_ops.setattr(node, { - mode: (mode & 4095) | (node.mode & ~4095), - timestamp: Date.now() - }); - }, - lchmod(path, mode) { - FS.chmod(path, mode, true); - }, - fchmod(fd, mode) { - var stream = FS.getStreamChecked(fd); - FS.chmod(stream.node, mode); - }, - chown(path, uid, gid, dontFollow) { - var node; - if (typeof path == 'string') { - var lookup = FS.lookupPath(path, { follow: !dontFollow }); - node = lookup.node; - } else { - node = path; - } - if (!node.node_ops.setattr) { - throw new FS.ErrnoError(63); - } - node.node_ops.setattr(node, { - timestamp: Date.now() - // we ignore the uid / gid for now - }); - }, - lchown(path, uid, gid) { - FS.chown(path, uid, gid, true); - }, - fchown(fd, uid, gid) { - var stream = FS.getStreamChecked(fd); - FS.chown(stream.node, uid, gid); - }, - truncate(path, len) { - if (len < 0) { - throw new FS.ErrnoError(28); - } - var node; - if (typeof path == 'string') { - var lookup = FS.lookupPath(path, { follow: true }); - node = lookup.node; - } else { - node = path; - } - if (!node.node_ops.setattr) { - throw new FS.ErrnoError(63); - } - if (FS.isDir(node.mode)) { - throw new FS.ErrnoError(31); - } - if (!FS.isFile(node.mode)) { - throw new FS.ErrnoError(28); - } - var errCode = FS.nodePermissions(node, 'w'); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - node.node_ops.setattr(node, { - size: len, - timestamp: Date.now() - }); - }, - ftruncate(fd, len) { - var stream = FS.getStreamChecked(fd); - if ((stream.flags & 2097155) === 0) { - throw new FS.ErrnoError(28); - } - FS.truncate(stream.node, len); - }, - utime(path, atime, mtime) { - var lookup = FS.lookupPath(path, { follow: true }); - var node = lookup.node; - node.node_ops.setattr(node, { - timestamp: Math.max(atime, mtime) - }); - }, - open(path, flags, mode) { - if (path === "") { - throw new FS.ErrnoError(44); - } - flags = typeof flags == 'string' ? FS_modeStringToFlags(flags) : flags; - mode = typeof mode == 'undefined' ? 438 /* 0666 */ : mode; - if ((flags & 64)) { - mode = (mode & 4095) | 32768; - } else { - mode = 0; - } - var node; - if (typeof path == 'object') { - node = path; - } else { - path = PATH.normalize(path); - try { - var lookup = FS.lookupPath(path, { - follow: !(flags & 131072) - }); - node = lookup.node; - } catch (e) { - // ignore - } - } - // perhaps we need to create the node - var created = false; - if ((flags & 64)) { - if (node) { - // if O_CREAT and O_EXCL are set, error out if the node already exists - if ((flags & 128)) { - throw new FS.ErrnoError(20); - } - } else { - // node doesn't exist, try to create it - node = FS.mknod(path, mode, 0); - created = true; - } - } - if (!node) { - throw new FS.ErrnoError(44); - } - // can't truncate a device - if (FS.isChrdev(node.mode)) { - flags &= ~512; - } - // if asked only for a directory, then this must be one - if ((flags & 65536) && !FS.isDir(node.mode)) { - throw new FS.ErrnoError(54); - } - // check permissions, if this is not a file we just created now (it is ok to - // create and write to a file with read-only permissions; it is read-only - // for later use) - if (!created) { - var errCode = FS.mayOpen(node, flags); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - } - // do truncation if necessary - if ((flags & 512) && !created) { - FS.truncate(node, 0); - } - // we've already handled these, don't pass down to the underlying vfs - flags &= ~(128 | 512 | 131072); - - // register the stream with the filesystem - var stream = FS.createStream({ - node, - path: FS.getPath(node), // we want the absolute path to the node - flags, - seekable: true, - position: 0, - stream_ops: node.stream_ops, - // used by the file family libc calls (fopen, fwrite, ferror, etc.) - ungotten: [], - error: false - }); - // call the new stream's open function - if (stream.stream_ops.open) { - stream.stream_ops.open(stream); - } - if (Module['logReadFiles'] && !(flags & 1)) { - if (!FS.readFiles) FS.readFiles = {}; - if (!(path in FS.readFiles)) { - FS.readFiles[path] = 1; - } - } - return stream; - }, - close(stream) { - if (FS.isClosed(stream)) { - throw new FS.ErrnoError(8); - } - if (stream.getdents) stream.getdents = null; // free readdir state - try { - if (stream.stream_ops.close) { - stream.stream_ops.close(stream); - } - } catch (e) { - throw e; - } finally { - FS.closeStream(stream.fd); - } - stream.fd = null; - }, - isClosed(stream) { - return stream.fd === null; - }, - llseek(stream, offset, whence) { - if (FS.isClosed(stream)) { - throw new FS.ErrnoError(8); - } - if (!stream.seekable || !stream.stream_ops.llseek) { - throw new FS.ErrnoError(70); - } - if (whence != 0 && whence != 1 && whence != 2) { - throw new FS.ErrnoError(28); - } - stream.position = stream.stream_ops.llseek(stream, offset, whence); - stream.ungotten = []; - return stream.position; - }, - read(stream, buffer, offset, length, position) { - if (length < 0 || position < 0) { - throw new FS.ErrnoError(28); - } - if (FS.isClosed(stream)) { - throw new FS.ErrnoError(8); - } - if ((stream.flags & 2097155) === 1) { - throw new FS.ErrnoError(8); - } - if (FS.isDir(stream.node.mode)) { - throw new FS.ErrnoError(31); - } - if (!stream.stream_ops.read) { - throw new FS.ErrnoError(28); - } - var seeking = typeof position != 'undefined'; - if (!seeking) { - position = stream.position; - } else if (!stream.seekable) { - throw new FS.ErrnoError(70); - } - var bytesRead = stream.stream_ops.read(stream, buffer, offset, length, position); - if (!seeking) stream.position += bytesRead; - return bytesRead; - }, - write(stream, buffer, offset, length, position, canOwn) { - if (length < 0 || position < 0) { - throw new FS.ErrnoError(28); - } - if (FS.isClosed(stream)) { - throw new FS.ErrnoError(8); - } - if ((stream.flags & 2097155) === 0) { - throw new FS.ErrnoError(8); - } - if (FS.isDir(stream.node.mode)) { - throw new FS.ErrnoError(31); - } - if (!stream.stream_ops.write) { - throw new FS.ErrnoError(28); - } - if (stream.seekable && stream.flags & 1024) { - // seek to the end before writing in append mode - FS.llseek(stream, 0, 2); - } - var seeking = typeof position != 'undefined'; - if (!seeking) { - position = stream.position; - } else if (!stream.seekable) { - throw new FS.ErrnoError(70); - } - var bytesWritten = stream.stream_ops.write(stream, buffer, offset, length, position, canOwn); - if (!seeking) stream.position += bytesWritten; - return bytesWritten; - }, - allocate(stream, offset, length) { - if (FS.isClosed(stream)) { - throw new FS.ErrnoError(8); - } - if (offset < 0 || length <= 0) { - throw new FS.ErrnoError(28); - } - if ((stream.flags & 2097155) === 0) { - throw new FS.ErrnoError(8); - } - if (!FS.isFile(stream.node.mode) && !FS.isDir(stream.node.mode)) { - throw new FS.ErrnoError(43); - } - if (!stream.stream_ops.allocate) { - throw new FS.ErrnoError(138); - } - stream.stream_ops.allocate(stream, offset, length); - }, - mmap(stream, length, position, prot, flags) { - // User requests writing to file (prot & PROT_WRITE != 0). - // Checking if we have permissions to write to the file unless - // MAP_PRIVATE flag is set. According to POSIX spec it is possible - // to write to file opened in read-only mode with MAP_PRIVATE flag, - // as all modifications will be visible only in the memory of - // the current process. - if ((prot & 2) !== 0 - && (flags & 2) === 0 - && (stream.flags & 2097155) !== 2) { - throw new FS.ErrnoError(2); - } - if ((stream.flags & 2097155) === 1) { - throw new FS.ErrnoError(2); - } - if (!stream.stream_ops.mmap) { - throw new FS.ErrnoError(43); - } - return stream.stream_ops.mmap(stream, length, position, prot, flags); - }, - msync(stream, buffer, offset, length, mmapFlags) { - if (!stream.stream_ops.msync) { - return 0; - } - return stream.stream_ops.msync(stream, buffer, offset, length, mmapFlags); - }, - munmap:(stream) => 0, - ioctl(stream, cmd, arg) { - if (!stream.stream_ops.ioctl) { - throw new FS.ErrnoError(59); - } - return stream.stream_ops.ioctl(stream, cmd, arg); - }, - readFile(path, opts = {}) { - opts.flags = opts.flags || 0; - opts.encoding = opts.encoding || 'binary'; - if (opts.encoding !== 'utf8' && opts.encoding !== 'binary') { - throw new Error(`Invalid encoding type "${opts.encoding}"`); - } - var ret; - var stream = FS.open(path, opts.flags); - var stat = FS.stat(path); - var length = stat.size; - var buf = new Uint8Array(length); - FS.read(stream, buf, 0, length, 0); - if (opts.encoding === 'utf8') { - ret = UTF8ArrayToString(buf, 0); - } else if (opts.encoding === 'binary') { - ret = buf; - } - FS.close(stream); - return ret; - }, - writeFile(path, data, opts = {}) { - opts.flags = opts.flags || 577; - var stream = FS.open(path, opts.flags, opts.mode); - if (typeof data == 'string') { - var buf = new Uint8Array(lengthBytesUTF8(data)+1); - var actualNumBytes = stringToUTF8Array(data, buf, 0, buf.length); - FS.write(stream, buf, 0, actualNumBytes, undefined, opts.canOwn); - } else if (ArrayBuffer.isView(data)) { - FS.write(stream, data, 0, data.byteLength, undefined, opts.canOwn); - } else { - throw new Error('Unsupported data type'); - } - FS.close(stream); - }, - cwd:() => FS.currentPath, - chdir(path) { - var lookup = FS.lookupPath(path, { follow: true }); - if (lookup.node === null) { - throw new FS.ErrnoError(44); - } - if (!FS.isDir(lookup.node.mode)) { - throw new FS.ErrnoError(54); - } - var errCode = FS.nodePermissions(lookup.node, 'x'); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - FS.currentPath = lookup.path; - }, - createDefaultDirectories() { - FS.mkdir('/tmp'); - FS.mkdir('/home'); - FS.mkdir('/home/web_user'); - }, - createDefaultDevices() { - // create /dev - FS.mkdir('/dev'); - // setup /dev/null - FS.registerDevice(FS.makedev(1, 3), { - read: () => 0, - write: (stream, buffer, offset, length, pos) => length, - }); - FS.mkdev('/dev/null', FS.makedev(1, 3)); - // setup /dev/tty and /dev/tty1 - // stderr needs to print output using err() rather than out() - // so we register a second tty just for it. - TTY.register(FS.makedev(5, 0), TTY.default_tty_ops); - TTY.register(FS.makedev(6, 0), TTY.default_tty1_ops); - FS.mkdev('/dev/tty', FS.makedev(5, 0)); - FS.mkdev('/dev/tty1', FS.makedev(6, 0)); - // setup /dev/[u]random - // use a buffer to avoid overhead of individual crypto calls per byte - var randomBuffer = new Uint8Array(1024), randomLeft = 0; - var randomByte = () => { - if (randomLeft === 0) { - randomLeft = randomFill(randomBuffer).byteLength; - } - return randomBuffer[--randomLeft]; - }; - FS.createDevice('/dev', 'random', randomByte); - FS.createDevice('/dev', 'urandom', randomByte); - // we're not going to emulate the actual shm device, - // just create the tmp dirs that reside in it commonly - FS.mkdir('/dev/shm'); - FS.mkdir('/dev/shm/tmp'); - }, - createSpecialDirectories() { - // create /proc/self/fd which allows /proc/self/fd/6 => readlink gives the - // name of the stream for fd 6 (see test_unistd_ttyname) - FS.mkdir('/proc'); - var proc_self = FS.mkdir('/proc/self'); - FS.mkdir('/proc/self/fd'); - FS.mount({ - mount() { - var node = FS.createNode(proc_self, 'fd', 16384 | 511 /* 0777 */, 73); - node.node_ops = { - lookup(parent, name) { - var fd = +name; - var stream = FS.getStreamChecked(fd); - var ret = { - parent: null, - mount: { mountpoint: 'fake' }, - node_ops: { readlink: () => stream.path }, - }; - ret.parent = ret; // make it look like a simple root node - return ret; - } - }; - return node; - } - }, {}, '/proc/self/fd'); - }, - createStandardStreams() { - // TODO deprecate the old functionality of a single - // input / output callback and that utilizes FS.createDevice - // and instead require a unique set of stream ops - - // by default, we symlink the standard streams to the - // default tty devices. however, if the standard streams - // have been overwritten we create a unique device for - // them instead. - if (Module['stdin']) { - FS.createDevice('/dev', 'stdin', Module['stdin']); - } else { - FS.symlink('/dev/tty', '/dev/stdin'); - } - if (Module['stdout']) { - FS.createDevice('/dev', 'stdout', null, Module['stdout']); - } else { - FS.symlink('/dev/tty', '/dev/stdout'); - } - if (Module['stderr']) { - FS.createDevice('/dev', 'stderr', null, Module['stderr']); - } else { - FS.symlink('/dev/tty1', '/dev/stderr'); - } - - // open default streams for the stdin, stdout and stderr devices - var stdin = FS.open('/dev/stdin', 0); - var stdout = FS.open('/dev/stdout', 1); - var stderr = FS.open('/dev/stderr', 1); - }, - ensureErrnoError() { - if (FS.ErrnoError) return; - FS.ErrnoError = /** @this{Object} */ function ErrnoError(errno, node) { - // We set the `name` property to be able to identify `FS.ErrnoError` - // - the `name` is a standard ECMA-262 property of error objects. Kind of good to have it anyway. - // - when using PROXYFS, an error can come from an underlying FS - // as different FS objects have their own FS.ErrnoError each, - // the test `err instanceof FS.ErrnoError` won't detect an error coming from another filesystem, causing bugs. - // we'll use the reliable test `err.name == "ErrnoError"` instead - this.name = 'ErrnoError'; - this.node = node; - this.setErrno = /** @this{Object} */ function(errno) { - this.errno = errno; - }; - this.setErrno(errno); - this.message = 'FS error'; - - }; - FS.ErrnoError.prototype = new Error(); - FS.ErrnoError.prototype.constructor = FS.ErrnoError; - // Some errors may happen quite a bit, to avoid overhead we reuse them (and suffer a lack of stack info) - [44].forEach((code) => { - FS.genericErrors[code] = new FS.ErrnoError(code); - FS.genericErrors[code].stack = ''; - }); - }, - staticInit() { - FS.ensureErrnoError(); - - FS.nameTable = new Array(4096); - - FS.mount(MEMFS, {}, '/'); - - FS.createDefaultDirectories(); - FS.createDefaultDevices(); - FS.createSpecialDirectories(); - - FS.filesystems = { - 'MEMFS': MEMFS, - }; - }, - init(input, output, error) { - FS.init.initialized = true; - - FS.ensureErrnoError(); - - // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here - Module['stdin'] = input || Module['stdin']; - Module['stdout'] = output || Module['stdout']; - Module['stderr'] = error || Module['stderr']; - - FS.createStandardStreams(); - }, - quit() { - FS.init.initialized = false; - // force-flush all streams, so we get musl std streams printed out - // close all of our streams - for (var i = 0; i < FS.streams.length; i++) { - var stream = FS.streams[i]; - if (!stream) { - continue; - } - FS.close(stream); - } - }, - findObject(path, dontResolveLastLink) { - var ret = FS.analyzePath(path, dontResolveLastLink); - if (!ret.exists) { - return null; - } - return ret.object; - }, - analyzePath(path, dontResolveLastLink) { - // operate from within the context of the symlink's target - try { - var lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); - path = lookup.path; - } catch (e) { - } - var ret = { - isRoot: false, exists: false, error: 0, name: null, path: null, object: null, - parentExists: false, parentPath: null, parentObject: null - }; - try { - var lookup = FS.lookupPath(path, { parent: true }); - ret.parentExists = true; - ret.parentPath = lookup.path; - ret.parentObject = lookup.node; - ret.name = PATH.basename(path); - lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); - ret.exists = true; - ret.path = lookup.path; - ret.object = lookup.node; - ret.name = lookup.node.name; - ret.isRoot = lookup.path === '/'; - } catch (e) { - ret.error = e.errno; - }; - return ret; - }, - createPath(parent, path, canRead, canWrite) { - parent = typeof parent == 'string' ? parent : FS.getPath(parent); - var parts = path.split('/').reverse(); - while (parts.length) { - var part = parts.pop(); - if (!part) continue; - var current = PATH.join2(parent, part); - try { - FS.mkdir(current); - } catch (e) { - // ignore EEXIST - } - parent = current; - } - return current; - }, - createFile(parent, name, properties, canRead, canWrite) { - var path = PATH.join2(typeof parent == 'string' ? parent : FS.getPath(parent), name); - var mode = FS_getMode(canRead, canWrite); - return FS.create(path, mode); - }, - createDataFile(parent, name, data, canRead, canWrite, canOwn) { - var path = name; - if (parent) { - parent = typeof parent == 'string' ? parent : FS.getPath(parent); - path = name ? PATH.join2(parent, name) : parent; - } - var mode = FS_getMode(canRead, canWrite); - var node = FS.create(path, mode); - if (data) { - if (typeof data == 'string') { - var arr = new Array(data.length); - for (var i = 0, len = data.length; i < len; ++i) arr[i] = data.charCodeAt(i); - data = arr; - } - // make sure we can write to the file - FS.chmod(node, mode | 146); - var stream = FS.open(node, 577); - FS.write(stream, data, 0, data.length, 0, canOwn); - FS.close(stream); - FS.chmod(node, mode); - } - return node; - }, - createDevice(parent, name, input, output) { - var path = PATH.join2(typeof parent == 'string' ? parent : FS.getPath(parent), name); - var mode = FS_getMode(!!input, !!output); - if (!FS.createDevice.major) FS.createDevice.major = 64; - var dev = FS.makedev(FS.createDevice.major++, 0); - // Create a fake device that a set of stream ops to emulate - // the old behavior. - FS.registerDevice(dev, { - open(stream) { - stream.seekable = false; - }, - close(stream) { - // flush any pending line data - if (output && output.buffer && output.buffer.length) { - output(10); - } - }, - read(stream, buffer, offset, length, pos /* ignored */) { - var bytesRead = 0; - for (var i = 0; i < length; i++) { - var result; - try { - result = input(); - } catch (e) { - throw new FS.ErrnoError(29); - } - if (result === undefined && bytesRead === 0) { - throw new FS.ErrnoError(6); - } - if (result === null || result === undefined) break; - bytesRead++; - buffer[offset+i] = result; - } - if (bytesRead) { - stream.node.timestamp = Date.now(); - } - return bytesRead; - }, - write(stream, buffer, offset, length, pos) { - for (var i = 0; i < length; i++) { - try { - output(buffer[offset+i]); - } catch (e) { - throw new FS.ErrnoError(29); - } - } - if (length) { - stream.node.timestamp = Date.now(); - } - return i; - } - }); - return FS.mkdev(path, mode, dev); - }, - forceLoadFile(obj) { - if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true; - if (typeof XMLHttpRequest != 'undefined') { - throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread."); - } else if (read_) { - // Command-line. - try { - // WARNING: Can't read binary files in V8's d8 or tracemonkey's js, as - // read() will try to parse UTF8. - obj.contents = intArrayFromString(read_(obj.url), true); - obj.usedBytes = obj.contents.length; - } catch (e) { - throw new FS.ErrnoError(29); - } - } else { - throw new Error('Cannot load without read() or XMLHttpRequest.'); - } - }, - createLazyFile(parent, name, url, canRead, canWrite) { - // Lazy chunked Uint8Array (implements get and length from Uint8Array). Actual getting is abstracted away for eventual reuse. - /** @constructor */ - function LazyUint8Array() { - this.lengthKnown = false; - this.chunks = []; // Loaded chunks. Index is the chunk number - } - LazyUint8Array.prototype.get = /** @this{Object} */ function LazyUint8Array_get(idx) { - if (idx > this.length-1 || idx < 0) { - return undefined; - } - var chunkOffset = idx % this.chunkSize; - var chunkNum = (idx / this.chunkSize)|0; - return this.getter(chunkNum)[chunkOffset]; - }; - LazyUint8Array.prototype.setDataGetter = function LazyUint8Array_setDataGetter(getter) { - this.getter = getter; - }; - LazyUint8Array.prototype.cacheLength = function LazyUint8Array_cacheLength() { - // Find length - var xhr = new XMLHttpRequest(); - xhr.open('HEAD', url, false); - xhr.send(null); - if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); - var datalength = Number(xhr.getResponseHeader("Content-length")); - var header; - var hasByteServing = (header = xhr.getResponseHeader("Accept-Ranges")) && header === "bytes"; - var usesGzip = (header = xhr.getResponseHeader("Content-Encoding")) && header === "gzip"; - - var chunkSize = 1024*1024; // Chunk size in bytes - - if (!hasByteServing) chunkSize = datalength; - - // Function to get a range from the remote URL. - var doXHR = (from, to) => { - if (from > to) throw new Error("invalid range (" + from + ", " + to + ") or no bytes requested!"); - if (to > datalength-1) throw new Error("only " + datalength + " bytes available! programmer error!"); - - // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available. - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, false); - if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to); - - // Some hints to the browser that we want binary data. - xhr.responseType = 'arraybuffer'; - if (xhr.overrideMimeType) { - xhr.overrideMimeType('text/plain; charset=x-user-defined'); - } - - xhr.send(null); - if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); - if (xhr.response !== undefined) { - return new Uint8Array(/** @type{Array} */(xhr.response || [])); - } - return intArrayFromString(xhr.responseText || '', true); - }; - var lazyArray = this; - lazyArray.setDataGetter((chunkNum) => { - var start = chunkNum * chunkSize; - var end = (chunkNum+1) * chunkSize - 1; // including this byte - end = Math.min(end, datalength-1); // if datalength-1 is selected, this is the last block - if (typeof lazyArray.chunks[chunkNum] == 'undefined') { - lazyArray.chunks[chunkNum] = doXHR(start, end); - } - if (typeof lazyArray.chunks[chunkNum] == 'undefined') throw new Error('doXHR failed!'); - return lazyArray.chunks[chunkNum]; - }); - - if (usesGzip || !datalength) { - // if the server uses gzip or doesn't supply the length, we have to download the whole file to get the (uncompressed) length - chunkSize = datalength = 1; // this will force getter(0)/doXHR do download the whole file - datalength = this.getter(0).length; - chunkSize = datalength; - out("LazyFiles on gzip forces download of the whole file when length is accessed"); - } - - this._length = datalength; - this._chunkSize = chunkSize; - this.lengthKnown = true; - }; - if (typeof XMLHttpRequest != 'undefined') { - if (!ENVIRONMENT_IS_WORKER) throw 'Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc'; - var lazyArray = new LazyUint8Array(); - Object.defineProperties(lazyArray, { - length: { - get: /** @this{Object} */ function() { - if (!this.lengthKnown) { - this.cacheLength(); - } - return this._length; - } - }, - chunkSize: { - get: /** @this{Object} */ function() { - if (!this.lengthKnown) { - this.cacheLength(); - } - return this._chunkSize; - } - } - }); - - var properties = { isDevice: false, contents: lazyArray }; - } else { - var properties = { isDevice: false, url: url }; - } - - var node = FS.createFile(parent, name, properties, canRead, canWrite); - // This is a total hack, but I want to get this lazy file code out of the - // core of MEMFS. If we want to keep this lazy file concept I feel it should - // be its own thin LAZYFS proxying calls to MEMFS. - if (properties.contents) { - node.contents = properties.contents; - } else if (properties.url) { - node.contents = null; - node.url = properties.url; - } - // Add a function that defers querying the file size until it is asked the first time. - Object.defineProperties(node, { - usedBytes: { - get: /** @this {FSNode} */ function() { return this.contents.length; } - } - }); - // override each stream op with one that tries to force load the lazy file first - var stream_ops = {}; - var keys = Object.keys(node.stream_ops); - keys.forEach((key) => { - var fn = node.stream_ops[key]; - stream_ops[key] = function forceLoadLazyFile() { - FS.forceLoadFile(node); - return fn.apply(null, arguments); - }; - }); - function writeChunks(stream, buffer, offset, length, position) { - var contents = stream.node.contents; - if (position >= contents.length) - return 0; - var size = Math.min(contents.length - position, length); - if (contents.slice) { // normal array - for (var i = 0; i < size; i++) { - buffer[offset + i] = contents[position + i]; - } - } else { - for (var i = 0; i < size; i++) { // LazyUint8Array from sync binary XHR - buffer[offset + i] = contents.get(position + i); - } - } - return size; - } - // use a custom read function - stream_ops.read = (stream, buffer, offset, length, position) => { - FS.forceLoadFile(node); - return writeChunks(stream, buffer, offset, length, position) - }; - // use a custom mmap function - stream_ops.mmap = (stream, length, position, prot, flags) => { - FS.forceLoadFile(node); - var ptr = mmapAlloc(length); - if (!ptr) { - throw new FS.ErrnoError(48); - } - writeChunks(stream, HEAP8, ptr, length, position); - return { ptr, allocated: true }; - }; - node.stream_ops = stream_ops; - return node; - }, - }; - - var SYSCALLS = { - DEFAULT_POLLMASK:5, - calculateAt(dirfd, path, allowEmpty) { - if (PATH.isAbs(path)) { - return path; - } - // relative path - var dir; - if (dirfd === -100) { - dir = FS.cwd(); - } else { - var dirstream = SYSCALLS.getStreamFromFD(dirfd); - dir = dirstream.path; - } - if (path.length == 0) { - if (!allowEmpty) { - throw new FS.ErrnoError(44);; - } - return dir; - } - return PATH.join2(dir, path); - }, - doStat(func, path, buf) { - try { - var stat = func(path); - } catch (e) { - if (e && e.node && PATH.normalize(path) !== PATH.normalize(FS.getPath(e.node))) { - // an error occurred while trying to look up the path; we should just report ENOTDIR - return -54; - } - throw e; - } - HEAP32[((buf)>>2)] = stat.dev; - HEAP32[(((buf)+(4))>>2)] = stat.mode; - HEAPU32[(((buf)+(8))>>2)] = stat.nlink; - HEAP32[(((buf)+(12))>>2)] = stat.uid; - HEAP32[(((buf)+(16))>>2)] = stat.gid; - HEAP32[(((buf)+(20))>>2)] = stat.rdev; - (tempI64 = [stat.size>>>0,(tempDouble=stat.size,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? (+(Math.floor((tempDouble)/4294967296.0)))>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)], HEAP32[(((buf)+(24))>>2)] = tempI64[0],HEAP32[(((buf)+(28))>>2)] = tempI64[1]); - HEAP32[(((buf)+(32))>>2)] = 4096; - HEAP32[(((buf)+(36))>>2)] = stat.blocks; - var atime = stat.atime.getTime(); - var mtime = stat.mtime.getTime(); - var ctime = stat.ctime.getTime(); - (tempI64 = [Math.floor(atime / 1000)>>>0,(tempDouble=Math.floor(atime / 1000),(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? (+(Math.floor((tempDouble)/4294967296.0)))>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)], HEAP32[(((buf)+(40))>>2)] = tempI64[0],HEAP32[(((buf)+(44))>>2)] = tempI64[1]); - HEAPU32[(((buf)+(48))>>2)] = (atime % 1000) * 1000; - (tempI64 = [Math.floor(mtime / 1000)>>>0,(tempDouble=Math.floor(mtime / 1000),(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? (+(Math.floor((tempDouble)/4294967296.0)))>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)], HEAP32[(((buf)+(56))>>2)] = tempI64[0],HEAP32[(((buf)+(60))>>2)] = tempI64[1]); - HEAPU32[(((buf)+(64))>>2)] = (mtime % 1000) * 1000; - (tempI64 = [Math.floor(ctime / 1000)>>>0,(tempDouble=Math.floor(ctime / 1000),(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? (+(Math.floor((tempDouble)/4294967296.0)))>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)], HEAP32[(((buf)+(72))>>2)] = tempI64[0],HEAP32[(((buf)+(76))>>2)] = tempI64[1]); - HEAPU32[(((buf)+(80))>>2)] = (ctime % 1000) * 1000; - (tempI64 = [stat.ino>>>0,(tempDouble=stat.ino,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? (+(Math.floor((tempDouble)/4294967296.0)))>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)], HEAP32[(((buf)+(88))>>2)] = tempI64[0],HEAP32[(((buf)+(92))>>2)] = tempI64[1]); - return 0; - }, - doMsync(addr, stream, len, flags, offset) { - if (!FS.isFile(stream.node.mode)) { - throw new FS.ErrnoError(43); - } - if (flags & 2) { - // MAP_PRIVATE calls need not to be synced back to underlying fs - return 0; - } - var buffer = HEAPU8.slice(addr, addr + len); - FS.msync(stream, buffer, offset, len, flags); - }, - varargs:undefined, - get() { - var ret = HEAP32[((SYSCALLS.varargs)>>2)]; - SYSCALLS.varargs += 4; - return ret; - }, - getp() { return SYSCALLS.get() }, - getStr(ptr) { - var ret = UTF8ToString(ptr); - return ret; - }, - getStreamFromFD(fd) { - var stream = FS.getStreamChecked(fd); - return stream; - }, - }; - function ___syscall_chdir(path) { - try { - - path = SYSCALLS.getStr(path); - FS.chdir(path); - return 0; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return -e.errno; - } - } - - var setErrNo = (value) => { - HEAP32[((___errno_location())>>2)] = value; - return value; - }; - - function ___syscall_fcntl64(fd, cmd, varargs) { - SYSCALLS.varargs = varargs; - try { - - var stream = SYSCALLS.getStreamFromFD(fd); - switch (cmd) { - case 0: { - var arg = SYSCALLS.get(); - if (arg < 0) { - return -28; - } - while (FS.streams[arg]) { - arg++; - } - var newStream; - newStream = FS.createStream(stream, arg); - return newStream.fd; - } - case 1: - case 2: - return 0; // FD_CLOEXEC makes no sense for a single process. - case 3: - return stream.flags; - case 4: { - var arg = SYSCALLS.get(); - stream.flags |= arg; - return 0; - } - case 5: { - var arg = SYSCALLS.getp(); - var offset = 0; - // We're always unlocked. - HEAP16[(((arg)+(offset))>>1)] = 2; - return 0; - } - case 6: - case 7: - return 0; // Pretend that the locking is successful. - case 16: - case 8: - return -28; // These are for sockets. We don't have them fully implemented yet. - case 9: - // musl trusts getown return values, due to a bug where they must be, as they overlap with errors. just return -1 here, so fcntl() returns that, and we set errno ourselves. - setErrNo(28); - return -1; - default: { - return -28; - } - } - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return -e.errno; - } - } - - function ___syscall_fstat64(fd, buf) { - try { - - var stream = SYSCALLS.getStreamFromFD(fd); - return SYSCALLS.doStat(FS.stat, stream.path, buf); - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return -e.errno; - } - } - - - var stringToUTF8 = (str, outPtr, maxBytesToWrite) => { - return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite); - }; - - function ___syscall_getcwd(buf, size) { - try { - - if (size === 0) return -28; - var cwd = FS.cwd(); - var cwdLengthInBytes = lengthBytesUTF8(cwd) + 1; - if (size < cwdLengthInBytes) return -68; - stringToUTF8(cwd, buf, size); - return cwdLengthInBytes; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return -e.errno; - } - } - - - function ___syscall_getdents64(fd, dirp, count) { - try { - - var stream = SYSCALLS.getStreamFromFD(fd) - if (!stream.getdents) { - stream.getdents = FS.readdir(stream.path); - } - - var struct_size = 280; - var pos = 0; - var off = FS.llseek(stream, 0, 1); - - var idx = Math.floor(off / struct_size); - - while (idx < stream.getdents.length && pos + struct_size <= count) { - var id; - var type; - var name = stream.getdents[idx]; - if (name === '.') { - id = stream.node.id; - type = 4; // DT_DIR - } - else if (name === '..') { - var lookup = FS.lookupPath(stream.path, { parent: true }); - id = lookup.node.id; - type = 4; // DT_DIR - } - else { - var child = FS.lookupNode(stream.node, name); - id = child.id; - type = FS.isChrdev(child.mode) ? 2 : // DT_CHR, character device. - FS.isDir(child.mode) ? 4 : // DT_DIR, directory. - FS.isLink(child.mode) ? 10 : // DT_LNK, symbolic link. - 8; // DT_REG, regular file. - } - (tempI64 = [id>>>0,(tempDouble=id,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? (+(Math.floor((tempDouble)/4294967296.0)))>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)], HEAP32[((dirp + pos)>>2)] = tempI64[0],HEAP32[(((dirp + pos)+(4))>>2)] = tempI64[1]); - (tempI64 = [(idx + 1) * struct_size>>>0,(tempDouble=(idx + 1) * struct_size,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? (+(Math.floor((tempDouble)/4294967296.0)))>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)], HEAP32[(((dirp + pos)+(8))>>2)] = tempI64[0],HEAP32[(((dirp + pos)+(12))>>2)] = tempI64[1]); - HEAP16[(((dirp + pos)+(16))>>1)] = 280; - HEAP8[(((dirp + pos)+(18))>>0)] = type; - stringToUTF8(name, dirp + pos + 19, 256); - pos += struct_size; - idx += 1; - } - FS.llseek(stream, idx * struct_size, 0); - return pos; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return -e.errno; - } - } - - function ___syscall_ioctl(fd, op, varargs) { - SYSCALLS.varargs = varargs; - try { - - var stream = SYSCALLS.getStreamFromFD(fd); - switch (op) { - case 21509: { - if (!stream.tty) return -59; - return 0; - } - case 21505: { - if (!stream.tty) return -59; - if (stream.tty.ops.ioctl_tcgets) { - var termios = stream.tty.ops.ioctl_tcgets(stream); - var argp = SYSCALLS.getp(); - HEAP32[((argp)>>2)] = termios.c_iflag || 0; - HEAP32[(((argp)+(4))>>2)] = termios.c_oflag || 0; - HEAP32[(((argp)+(8))>>2)] = termios.c_cflag || 0; - HEAP32[(((argp)+(12))>>2)] = termios.c_lflag || 0; - for (var i = 0; i < 32; i++) { - HEAP8[(((argp + i)+(17))>>0)] = termios.c_cc[i] || 0; - } - return 0; - } - return 0; - } - case 21510: - case 21511: - case 21512: { - if (!stream.tty) return -59; - return 0; // no-op, not actually adjusting terminal settings - } - case 21506: - case 21507: - case 21508: { - if (!stream.tty) return -59; - if (stream.tty.ops.ioctl_tcsets) { - var argp = SYSCALLS.getp(); - var c_iflag = HEAP32[((argp)>>2)]; - var c_oflag = HEAP32[(((argp)+(4))>>2)]; - var c_cflag = HEAP32[(((argp)+(8))>>2)]; - var c_lflag = HEAP32[(((argp)+(12))>>2)]; - var c_cc = [] - for (var i = 0; i < 32; i++) { - c_cc.push(HEAP8[(((argp + i)+(17))>>0)]); - } - return stream.tty.ops.ioctl_tcsets(stream.tty, op, { c_iflag, c_oflag, c_cflag, c_lflag, c_cc }); - } - return 0; // no-op, not actually adjusting terminal settings - } - case 21519: { - if (!stream.tty) return -59; - var argp = SYSCALLS.getp(); - HEAP32[((argp)>>2)] = 0; - return 0; - } - case 21520: { - if (!stream.tty) return -59; - return -28; // not supported - } - case 21531: { - var argp = SYSCALLS.getp(); - return FS.ioctl(stream, op, argp); - } - case 21523: { - // TODO: in theory we should write to the winsize struct that gets - // passed in, but for now musl doesn't read anything on it - if (!stream.tty) return -59; - if (stream.tty.ops.ioctl_tiocgwinsz) { - var winsize = stream.tty.ops.ioctl_tiocgwinsz(stream.tty); - var argp = SYSCALLS.getp(); - HEAP16[((argp)>>1)] = winsize[0]; - HEAP16[(((argp)+(2))>>1)] = winsize[1]; - } - return 0; - } - case 21524: { - // TODO: technically, this ioctl call should change the window size. - // but, since emscripten doesn't have any concept of a terminal window - // yet, we'll just silently throw it away as we do TIOCGWINSZ - if (!stream.tty) return -59; - return 0; - } - case 21515: { - if (!stream.tty) return -59; - return 0; - } - default: return -28; // not supported - } - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return -e.errno; - } - } - - function ___syscall_lstat64(path, buf) { - try { - - path = SYSCALLS.getStr(path); - return SYSCALLS.doStat(FS.lstat, path, buf); - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return -e.errno; - } - } - - function ___syscall_mkdirat(dirfd, path, mode) { - try { - - path = SYSCALLS.getStr(path); - path = SYSCALLS.calculateAt(dirfd, path); - // remove a trailing slash, if one - /a/b/ has basename of '', but - // we want to create b in the context of this function - path = PATH.normalize(path); - if (path[path.length-1] === '/') path = path.substr(0, path.length-1); - FS.mkdir(path, mode, 0); - return 0; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return -e.errno; - } - } - - function ___syscall_newfstatat(dirfd, path, buf, flags) { - try { - - path = SYSCALLS.getStr(path); - var nofollow = flags & 256; - var allowEmpty = flags & 4096; - flags = flags & (~6400); - path = SYSCALLS.calculateAt(dirfd, path, allowEmpty); - return SYSCALLS.doStat(nofollow ? FS.lstat : FS.stat, path, buf); - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return -e.errno; - } - } - - function ___syscall_openat(dirfd, path, flags, varargs) { - SYSCALLS.varargs = varargs; - try { - - path = SYSCALLS.getStr(path); - path = SYSCALLS.calculateAt(dirfd, path); - var mode = varargs ? SYSCALLS.get() : 0; - return FS.open(path, flags, mode).fd; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return -e.errno; - } - } - - - - function ___syscall_readlinkat(dirfd, path, buf, bufsize) { - try { - - path = SYSCALLS.getStr(path); - path = SYSCALLS.calculateAt(dirfd, path); - if (bufsize <= 0) return -28; - var ret = FS.readlink(path); - - var len = Math.min(bufsize, lengthBytesUTF8(ret)); - var endChar = HEAP8[buf+len]; - stringToUTF8(ret, buf, bufsize+1); - // readlink is one of the rare functions that write out a C string, but does never append a null to the output buffer(!) - // stringToUTF8() always appends a null byte, so restore the character under the null byte after the write. - HEAP8[buf+len] = endChar; - return len; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return -e.errno; - } - } - - function ___syscall_rmdir(path) { - try { - - path = SYSCALLS.getStr(path); - FS.rmdir(path); - return 0; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return -e.errno; - } - } - - function ___syscall_stat64(path, buf) { - try { - - path = SYSCALLS.getStr(path); - return SYSCALLS.doStat(FS.stat, path, buf); - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return -e.errno; - } - } - - function ___syscall_unlinkat(dirfd, path, flags) { - try { - - path = SYSCALLS.getStr(path); - path = SYSCALLS.calculateAt(dirfd, path); - if (flags === 0) { - FS.unlink(path); - } else if (flags === 512) { - FS.rmdir(path); - } else { - abort('Invalid flags passed to unlinkat'); - } - return 0; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return -e.errno; - } - } - - var nowIsMonotonic = true;; - var __emscripten_get_now_is_monotonic = () => nowIsMonotonic; - - var __emscripten_throw_longjmp = () => { - throw Infinity; - }; - - var convertI32PairToI53Checked = (lo, hi) => { - return ((hi + 0x200000) >>> 0 < 0x400001 - !!lo) ? (lo >>> 0) + hi * 4294967296 : NaN; - }; - function __gmtime_js(time_low, time_high,tmPtr) { - var time = convertI32PairToI53Checked(time_low, time_high);; - - - var date = new Date(time * 1000); - HEAP32[((tmPtr)>>2)] = date.getUTCSeconds(); - HEAP32[(((tmPtr)+(4))>>2)] = date.getUTCMinutes(); - HEAP32[(((tmPtr)+(8))>>2)] = date.getUTCHours(); - HEAP32[(((tmPtr)+(12))>>2)] = date.getUTCDate(); - HEAP32[(((tmPtr)+(16))>>2)] = date.getUTCMonth(); - HEAP32[(((tmPtr)+(20))>>2)] = date.getUTCFullYear()-1900; - HEAP32[(((tmPtr)+(24))>>2)] = date.getUTCDay(); - var start = Date.UTC(date.getUTCFullYear(), 0, 1, 0, 0, 0, 0); - var yday = ((date.getTime() - start) / (1000 * 60 * 60 * 24))|0; - HEAP32[(((tmPtr)+(28))>>2)] = yday; - ; - } - - var isLeapYear = (year) => { - return year%4 === 0 && (year%100 !== 0 || year%400 === 0); - }; - - var MONTH_DAYS_LEAP_CUMULATIVE = [0,31,60,91,121,152,182,213,244,274,305,335]; - - var MONTH_DAYS_REGULAR_CUMULATIVE = [0,31,59,90,120,151,181,212,243,273,304,334]; - var ydayFromDate = (date) => { - var leap = isLeapYear(date.getFullYear()); - var monthDaysCumulative = (leap ? MONTH_DAYS_LEAP_CUMULATIVE : MONTH_DAYS_REGULAR_CUMULATIVE); - var yday = monthDaysCumulative[date.getMonth()] + date.getDate() - 1; // -1 since it's days since Jan 1 - - return yday; - }; - - - var __mktime_js = function(tmPtr) { - - var ret = (() => { - var date = new Date(HEAP32[(((tmPtr)+(20))>>2)] + 1900, - HEAP32[(((tmPtr)+(16))>>2)], - HEAP32[(((tmPtr)+(12))>>2)], - HEAP32[(((tmPtr)+(8))>>2)], - HEAP32[(((tmPtr)+(4))>>2)], - HEAP32[((tmPtr)>>2)], - 0); - - // There's an ambiguous hour when the time goes back; the tm_isdst field is - // used to disambiguate it. Date() basically guesses, so we fix it up if it - // guessed wrong, or fill in tm_isdst with the guess if it's -1. - var dst = HEAP32[(((tmPtr)+(32))>>2)]; - var guessedOffset = date.getTimezoneOffset(); - var start = new Date(date.getFullYear(), 0, 1); - var summerOffset = new Date(date.getFullYear(), 6, 1).getTimezoneOffset(); - var winterOffset = start.getTimezoneOffset(); - var dstOffset = Math.min(winterOffset, summerOffset); // DST is in December in South - if (dst < 0) { - // Attention: some regions don't have DST at all. - HEAP32[(((tmPtr)+(32))>>2)] = Number(summerOffset != winterOffset && dstOffset == guessedOffset); - } else if ((dst > 0) != (dstOffset == guessedOffset)) { - var nonDstOffset = Math.max(winterOffset, summerOffset); - var trueOffset = dst > 0 ? dstOffset : nonDstOffset; - // Don't try setMinutes(date.getMinutes() + ...) -- it's messed up. - date.setTime(date.getTime() + (trueOffset - guessedOffset)*60000); - } - - HEAP32[(((tmPtr)+(24))>>2)] = date.getDay(); - var yday = ydayFromDate(date)|0; - HEAP32[(((tmPtr)+(28))>>2)] = yday; - // To match expected behavior, update fields from date - HEAP32[((tmPtr)>>2)] = date.getSeconds(); - HEAP32[(((tmPtr)+(4))>>2)] = date.getMinutes(); - HEAP32[(((tmPtr)+(8))>>2)] = date.getHours(); - HEAP32[(((tmPtr)+(12))>>2)] = date.getDate(); - HEAP32[(((tmPtr)+(16))>>2)] = date.getMonth(); - HEAP32[(((tmPtr)+(20))>>2)] = date.getYear(); - - return date.getTime() / 1000; - })(); - return (setTempRet0((tempDouble=ret,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? (+(Math.floor((tempDouble)/4294967296.0)))>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)), ret>>>0); - }; - - - - - - - function __mmap_js(len,prot,flags,fd,offset_low, offset_high,allocated,addr) { - var offset = convertI32PairToI53Checked(offset_low, offset_high);; - - - try { - - if (isNaN(offset)) return 61; - var stream = SYSCALLS.getStreamFromFD(fd); - var res = FS.mmap(stream, len, offset, prot, flags); - var ptr = res.ptr; - HEAP32[((allocated)>>2)] = res.allocated; - HEAPU32[((addr)>>2)] = ptr; - return 0; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return -e.errno; - } - ; - } - - - - - function __munmap_js(addr,len,prot,flags,fd,offset_low, offset_high) { - var offset = convertI32PairToI53Checked(offset_low, offset_high);; - - - try { - - if (isNaN(offset)) return 61; - var stream = SYSCALLS.getStreamFromFD(fd); - if (prot & 2) { - SYSCALLS.doMsync(addr, stream, len, flags, offset); - } - FS.munmap(stream); - // implicitly return 0 - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return -e.errno; - } - ; - } - - - - var stringToNewUTF8 = (str) => { - var size = lengthBytesUTF8(str) + 1; - var ret = _malloc(size); - if (ret) stringToUTF8(str, ret, size); - return ret; - }; - var __tzset_js = (timezone, daylight, tzname) => { - // TODO: Use (malleable) environment variables instead of system settings. - var currentYear = new Date().getFullYear(); - var winter = new Date(currentYear, 0, 1); - var summer = new Date(currentYear, 6, 1); - var winterOffset = winter.getTimezoneOffset(); - var summerOffset = summer.getTimezoneOffset(); - - // Local standard timezone offset. Local standard time is not adjusted for daylight savings. - // This code uses the fact that getTimezoneOffset returns a greater value during Standard Time versus Daylight Saving Time (DST). - // Thus it determines the expected output during Standard Time, and it compares whether the output of the given date the same (Standard) or less (DST). - var stdTimezoneOffset = Math.max(winterOffset, summerOffset); - - // timezone is specified as seconds west of UTC ("The external variable - // `timezone` shall be set to the difference, in seconds, between - // Coordinated Universal Time (UTC) and local standard time."), the same - // as returned by stdTimezoneOffset. - // See http://pubs.opengroup.org/onlinepubs/009695399/functions/tzset.html - HEAPU32[((timezone)>>2)] = stdTimezoneOffset * 60; - - HEAP32[((daylight)>>2)] = Number(winterOffset != summerOffset); - - function extractZone(date) { - var match = date.toTimeString().match(/\(([A-Za-z ]+)\)$/); - return match ? match[1] : "GMT"; - }; - var winterName = extractZone(winter); - var summerName = extractZone(summer); - var winterNamePtr = stringToNewUTF8(winterName); - var summerNamePtr = stringToNewUTF8(summerName); - if (summerOffset < winterOffset) { - // Northern hemisphere - HEAPU32[((tzname)>>2)] = winterNamePtr; - HEAPU32[(((tzname)+(4))>>2)] = summerNamePtr; - } else { - HEAPU32[((tzname)>>2)] = summerNamePtr; - HEAPU32[(((tzname)+(4))>>2)] = winterNamePtr; - } - }; - - var _abort = () => { - abort(''); - }; - - var _emscripten_date_now = () => Date.now(); - - var getHeapMax = () => - // Stay one Wasm page short of 4GB: while e.g. Chrome is able to allocate - // full 4GB Wasm memories, the size will wrap back to 0 bytes in Wasm side - // for any code that deals with heap sizes, which would require special - // casing all heap size related code to treat 0 specially. - 2147483648; - var _emscripten_get_heap_max = () => getHeapMax(); - - var _emscripten_get_now; - // Modern environment where performance.now() is supported: - // N.B. a shorter form "_emscripten_get_now = performance.now;" is - // unfortunately not allowed even in current browsers (e.g. FF Nightly 75). - _emscripten_get_now = () => performance.now(); - ; - - var _emscripten_memcpy_big = (dest, src, num) => HEAPU8.copyWithin(dest, src, src + num); - - - var growMemory = (size) => { - var b = wasmMemory.buffer; - var pages = (size - b.byteLength + 65535) / 65536; - try { - // round size grow request up to wasm page size (fixed 64KB per spec) - wasmMemory.grow(pages); // .grow() takes a delta compared to the previous size - updateMemoryViews(); - return 1 /*success*/; - } catch(e) { - } - // implicit 0 return to save code size (caller will cast "undefined" into 0 - // anyhow) - }; - var _emscripten_resize_heap = (requestedSize) => { - var oldSize = HEAPU8.length; - // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned. - requestedSize >>>= 0; - // With multithreaded builds, races can happen (another thread might increase the size - // in between), so return a failure, and let the caller retry. - - // Memory resize rules: - // 1. Always increase heap size to at least the requested size, rounded up - // to next page multiple. - // 2a. If MEMORY_GROWTH_LINEAR_STEP == -1, excessively resize the heap - // geometrically: increase the heap size according to - // MEMORY_GROWTH_GEOMETRIC_STEP factor (default +20%), At most - // overreserve by MEMORY_GROWTH_GEOMETRIC_CAP bytes (default 96MB). - // 2b. If MEMORY_GROWTH_LINEAR_STEP != -1, excessively resize the heap - // linearly: increase the heap size by at least - // MEMORY_GROWTH_LINEAR_STEP bytes. - // 3. Max size for the heap is capped at 2048MB-WASM_PAGE_SIZE, or by - // MAXIMUM_MEMORY, or by ASAN limit, depending on which is smallest - // 4. If we were unable to allocate as much memory, it may be due to - // over-eager decision to excessively reserve due to (3) above. - // Hence if an allocation fails, cut down on the amount of excess - // growth, in an attempt to succeed to perform a smaller allocation. - - // A limit is set for how much we can grow. We should not exceed that - // (the wasm binary specifies it, so if we tried, we'd fail anyhow). - var maxHeapSize = getHeapMax(); - if (requestedSize > maxHeapSize) { - return false; - } - - var alignUp = (x, multiple) => x + (multiple - x % multiple) % multiple; - - // Loop through potential heap size increases. If we attempt a too eager - // reservation that fails, cut down on the attempted size and reserve a - // smaller bump instead. (max 3 times, chosen somewhat arbitrarily) - for (var cutDown = 1; cutDown <= 4; cutDown *= 2) { - var overGrownHeapSize = oldSize * (1 + 0.2 / cutDown); // ensure geometric growth - // but limit overreserving (default to capping at +96MB overgrowth at most) - overGrownHeapSize = Math.min(overGrownHeapSize, requestedSize + 100663296 ); - - var newSize = Math.min(maxHeapSize, alignUp(Math.max(requestedSize, overGrownHeapSize), 65536)); - - var replacement = growMemory(newSize); - if (replacement) { - - return true; - } - } - return false; - }; - - var ENV = { - }; - - var getExecutableName = () => { - return thisProgram || './this.program'; - }; - var getEnvStrings = () => { - if (!getEnvStrings.strings) { - // Default values. - // Browser language detection #8751 - var lang = ((typeof navigator == 'object' && navigator.languages && navigator.languages[0]) || 'C').replace('-', '_') + '.UTF-8'; - var env = { - 'USER': 'web_user', - 'LOGNAME': 'web_user', - 'PATH': '/', - 'PWD': '/', - 'HOME': '/home/web_user', - 'LANG': lang, - '_': getExecutableName() - }; - // Apply the user-provided values, if any. - for (var x in ENV) { - // x is a key in ENV; if ENV[x] is undefined, that means it was - // explicitly set to be so. We allow user code to do that to - // force variables with default values to remain unset. - if (ENV[x] === undefined) delete env[x]; - else env[x] = ENV[x]; - } - var strings = []; - for (var x in env) { - strings.push(`${x}=${env[x]}`); - } - getEnvStrings.strings = strings; - } - return getEnvStrings.strings; - }; - - var stringToAscii = (str, buffer) => { - for (var i = 0; i < str.length; ++i) { - HEAP8[((buffer++)>>0)] = str.charCodeAt(i); - } - // Null-terminate the string - HEAP8[((buffer)>>0)] = 0; - }; - - var _environ_get = (__environ, environ_buf) => { - var bufSize = 0; - getEnvStrings().forEach((string, i) => { - var ptr = environ_buf + bufSize; - HEAPU32[(((__environ)+(i*4))>>2)] = ptr; - stringToAscii(string, ptr); - bufSize += string.length + 1; - }); - return 0; - }; - - - var _environ_sizes_get = (penviron_count, penviron_buf_size) => { - var strings = getEnvStrings(); - HEAPU32[((penviron_count)>>2)] = strings.length; - var bufSize = 0; - strings.forEach((string) => bufSize += string.length + 1); - HEAPU32[((penviron_buf_size)>>2)] = bufSize; - return 0; - }; - - - var _proc_exit = (code) => { - EXITSTATUS = code; - if (!keepRuntimeAlive()) { - if (Module['onExit']) Module['onExit'](code); - ABORT = true; - } - quit_(code, new ExitStatus(code)); - }; - /** @suppress {duplicate } */ - /** @param {boolean|number=} implicit */ - var exitJS = (status, implicit) => { - EXITSTATUS = status; - - _proc_exit(status); - }; - var _exit = exitJS; - - function _fd_close(fd) { - try { - - var stream = SYSCALLS.getStreamFromFD(fd); - FS.close(stream); - return 0; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return e.errno; - } - } - - /** @param {number=} offset */ - var doReadv = (stream, iov, iovcnt, offset) => { - var ret = 0; - for (var i = 0; i < iovcnt; i++) { - var ptr = HEAPU32[((iov)>>2)]; - var len = HEAPU32[(((iov)+(4))>>2)]; - iov += 8; - var curr = FS.read(stream, HEAP8, ptr, len, offset); - if (curr < 0) return -1; - ret += curr; - if (curr < len) break; // nothing more to read - if (typeof offset !== 'undefined') { - offset += curr; - } - } - return ret; - }; - - function _fd_read(fd, iov, iovcnt, pnum) { - try { - - var stream = SYSCALLS.getStreamFromFD(fd); - var num = doReadv(stream, iov, iovcnt); - HEAPU32[((pnum)>>2)] = num; - return 0; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return e.errno; - } - } - - - function _fd_seek(fd,offset_low, offset_high,whence,newOffset) { - var offset = convertI32PairToI53Checked(offset_low, offset_high);; - - - try { - - if (isNaN(offset)) return 61; - var stream = SYSCALLS.getStreamFromFD(fd); - FS.llseek(stream, offset, whence); - (tempI64 = [stream.position>>>0,(tempDouble=stream.position,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? (+(Math.floor((tempDouble)/4294967296.0)))>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)], HEAP32[((newOffset)>>2)] = tempI64[0],HEAP32[(((newOffset)+(4))>>2)] = tempI64[1]); - if (stream.getdents && offset === 0 && whence === 0) stream.getdents = null; // reset readdir state - return 0; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return e.errno; - } - ; - } - - /** @param {number=} offset */ - var doWritev = (stream, iov, iovcnt, offset) => { - var ret = 0; - for (var i = 0; i < iovcnt; i++) { - var ptr = HEAPU32[((iov)>>2)]; - var len = HEAPU32[(((iov)+(4))>>2)]; - iov += 8; - var curr = FS.write(stream, HEAP8, ptr, len, offset); - if (curr < 0) return -1; - ret += curr; - if (typeof offset !== 'undefined') { - offset += curr; - } - } - return ret; - }; - - function _fd_write(fd, iov, iovcnt, pnum) { - try { - - var stream = SYSCALLS.getStreamFromFD(fd); - var num = doWritev(stream, iov, iovcnt); - HEAPU32[((pnum)>>2)] = num; - return 0; - } catch (e) { - if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; - return e.errno; - } - } - - var _getentropy = (buffer, size) => { - randomFill(HEAPU8.subarray(buffer, buffer + size)); - return 0; - }; - - - var arraySum = (array, index) => { - var sum = 0; - for (var i = 0; i <= index; sum += array[i++]) { - // no-op - } - return sum; - }; - - - var MONTH_DAYS_LEAP = [31,29,31,30,31,30,31,31,30,31,30,31]; - - var MONTH_DAYS_REGULAR = [31,28,31,30,31,30,31,31,30,31,30,31]; - var addDays = (date, days) => { - var newDate = new Date(date.getTime()); - while (days > 0) { - var leap = isLeapYear(newDate.getFullYear()); - var currentMonth = newDate.getMonth(); - var daysInCurrentMonth = (leap ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR)[currentMonth]; - - if (days > daysInCurrentMonth-newDate.getDate()) { - // we spill over to next month - days -= (daysInCurrentMonth-newDate.getDate()+1); - newDate.setDate(1); - if (currentMonth < 11) { - newDate.setMonth(currentMonth+1) - } else { - newDate.setMonth(0); - newDate.setFullYear(newDate.getFullYear()+1); - } - } else { - // we stay in current month - newDate.setDate(newDate.getDate()+days); - return newDate; - } - } - - return newDate; - }; - - - - - var writeArrayToMemory = (array, buffer) => { - HEAP8.set(array, buffer); - }; - - var _strftime = (s, maxsize, format, tm) => { - // size_t strftime(char *restrict s, size_t maxsize, const char *restrict format, const struct tm *restrict timeptr); - // http://pubs.opengroup.org/onlinepubs/009695399/functions/strftime.html - - var tm_zone = HEAPU32[(((tm)+(40))>>2)]; - - var date = { - tm_sec: HEAP32[((tm)>>2)], - tm_min: HEAP32[(((tm)+(4))>>2)], - tm_hour: HEAP32[(((tm)+(8))>>2)], - tm_mday: HEAP32[(((tm)+(12))>>2)], - tm_mon: HEAP32[(((tm)+(16))>>2)], - tm_year: HEAP32[(((tm)+(20))>>2)], - tm_wday: HEAP32[(((tm)+(24))>>2)], - tm_yday: HEAP32[(((tm)+(28))>>2)], - tm_isdst: HEAP32[(((tm)+(32))>>2)], - tm_gmtoff: HEAP32[(((tm)+(36))>>2)], - tm_zone: tm_zone ? UTF8ToString(tm_zone) : '' - }; - - var pattern = UTF8ToString(format); - - // expand format - var EXPANSION_RULES_1 = { - '%c': '%a %b %d %H:%M:%S %Y', // Replaced by the locale's appropriate date and time representation - e.g., Mon Aug 3 14:02:01 2013 - '%D': '%m/%d/%y', // Equivalent to %m / %d / %y - '%F': '%Y-%m-%d', // Equivalent to %Y - %m - %d - '%h': '%b', // Equivalent to %b - '%r': '%I:%M:%S %p', // Replaced by the time in a.m. and p.m. notation - '%R': '%H:%M', // Replaced by the time in 24-hour notation - '%T': '%H:%M:%S', // Replaced by the time - '%x': '%m/%d/%y', // Replaced by the locale's appropriate date representation - '%X': '%H:%M:%S', // Replaced by the locale's appropriate time representation - // Modified Conversion Specifiers - '%Ec': '%c', // Replaced by the locale's alternative appropriate date and time representation. - '%EC': '%C', // Replaced by the name of the base year (period) in the locale's alternative representation. - '%Ex': '%m/%d/%y', // Replaced by the locale's alternative date representation. - '%EX': '%H:%M:%S', // Replaced by the locale's alternative time representation. - '%Ey': '%y', // Replaced by the offset from %EC (year only) in the locale's alternative representation. - '%EY': '%Y', // Replaced by the full alternative year representation. - '%Od': '%d', // Replaced by the day of the month, using the locale's alternative numeric symbols, filled as needed with leading zeros if there is any alternative symbol for zero; otherwise, with leading characters. - '%Oe': '%e', // Replaced by the day of the month, using the locale's alternative numeric symbols, filled as needed with leading characters. - '%OH': '%H', // Replaced by the hour (24-hour clock) using the locale's alternative numeric symbols. - '%OI': '%I', // Replaced by the hour (12-hour clock) using the locale's alternative numeric symbols. - '%Om': '%m', // Replaced by the month using the locale's alternative numeric symbols. - '%OM': '%M', // Replaced by the minutes using the locale's alternative numeric symbols. - '%OS': '%S', // Replaced by the seconds using the locale's alternative numeric symbols. - '%Ou': '%u', // Replaced by the weekday as a number in the locale's alternative representation (Monday=1). - '%OU': '%U', // Replaced by the week number of the year (Sunday as the first day of the week, rules corresponding to %U ) using the locale's alternative numeric symbols. - '%OV': '%V', // Replaced by the week number of the year (Monday as the first day of the week, rules corresponding to %V ) using the locale's alternative numeric symbols. - '%Ow': '%w', // Replaced by the number of the weekday (Sunday=0) using the locale's alternative numeric symbols. - '%OW': '%W', // Replaced by the week number of the year (Monday as the first day of the week) using the locale's alternative numeric symbols. - '%Oy': '%y', // Replaced by the year (offset from %C ) using the locale's alternative numeric symbols. - }; - for (var rule in EXPANSION_RULES_1) { - pattern = pattern.replace(new RegExp(rule, 'g'), EXPANSION_RULES_1[rule]); - } - - var WEEKDAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; - var MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; - - function leadingSomething(value, digits, character) { - var str = typeof value == 'number' ? value.toString() : (value || ''); - while (str.length < digits) { - str = character[0]+str; - } - return str; - } - - function leadingNulls(value, digits) { - return leadingSomething(value, digits, '0'); - } - - function compareByDay(date1, date2) { - function sgn(value) { - return value < 0 ? -1 : (value > 0 ? 1 : 0); - } - - var compare; - if ((compare = sgn(date1.getFullYear()-date2.getFullYear())) === 0) { - if ((compare = sgn(date1.getMonth()-date2.getMonth())) === 0) { - compare = sgn(date1.getDate()-date2.getDate()); - } - } - return compare; - } - - function getFirstWeekStartDate(janFourth) { - switch (janFourth.getDay()) { - case 0: // Sunday - return new Date(janFourth.getFullYear()-1, 11, 29); - case 1: // Monday - return janFourth; - case 2: // Tuesday - return new Date(janFourth.getFullYear(), 0, 3); - case 3: // Wednesday - return new Date(janFourth.getFullYear(), 0, 2); - case 4: // Thursday - return new Date(janFourth.getFullYear(), 0, 1); - case 5: // Friday - return new Date(janFourth.getFullYear()-1, 11, 31); - case 6: // Saturday - return new Date(janFourth.getFullYear()-1, 11, 30); - } - } - - function getWeekBasedYear(date) { - var thisDate = addDays(new Date(date.tm_year+1900, 0, 1), date.tm_yday); - - var janFourthThisYear = new Date(thisDate.getFullYear(), 0, 4); - var janFourthNextYear = new Date(thisDate.getFullYear()+1, 0, 4); - - var firstWeekStartThisYear = getFirstWeekStartDate(janFourthThisYear); - var firstWeekStartNextYear = getFirstWeekStartDate(janFourthNextYear); - - if (compareByDay(firstWeekStartThisYear, thisDate) <= 0) { - // this date is after the start of the first week of this year - if (compareByDay(firstWeekStartNextYear, thisDate) <= 0) { - return thisDate.getFullYear()+1; - } - return thisDate.getFullYear(); - } - return thisDate.getFullYear()-1; - } - - var EXPANSION_RULES_2 = { - '%a': (date) => WEEKDAYS[date.tm_wday].substring(0,3) , - '%A': (date) => WEEKDAYS[date.tm_wday], - '%b': (date) => MONTHS[date.tm_mon].substring(0,3), - '%B': (date) => MONTHS[date.tm_mon], - '%C': (date) => { - var year = date.tm_year+1900; - return leadingNulls((year/100)|0,2); - }, - '%d': (date) => leadingNulls(date.tm_mday, 2), - '%e': (date) => leadingSomething(date.tm_mday, 2, ' '), - '%g': (date) => { - // %g, %G, and %V give values according to the ISO 8601:2000 standard week-based year. - // In this system, weeks begin on a Monday and week 1 of the year is the week that includes - // January 4th, which is also the week that includes the first Thursday of the year, and - // is also the first week that contains at least four days in the year. - // If the first Monday of January is the 2nd, 3rd, or 4th, the preceding days are part of - // the last week of the preceding year; thus, for Saturday 2nd January 1999, - // %G is replaced by 1998 and %V is replaced by 53. If December 29th, 30th, - // or 31st is a Monday, it and any following days are part of week 1 of the following year. - // Thus, for Tuesday 30th December 1997, %G is replaced by 1998 and %V is replaced by 01. - - return getWeekBasedYear(date).toString().substring(2); - }, - '%G': (date) => getWeekBasedYear(date), - '%H': (date) => leadingNulls(date.tm_hour, 2), - '%I': (date) => { - var twelveHour = date.tm_hour; - if (twelveHour == 0) twelveHour = 12; - else if (twelveHour > 12) twelveHour -= 12; - return leadingNulls(twelveHour, 2); - }, - '%j': (date) => { - // Day of the year (001-366) - return leadingNulls(date.tm_mday + arraySum(isLeapYear(date.tm_year+1900) ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR, date.tm_mon-1), 3); - }, - '%m': (date) => leadingNulls(date.tm_mon+1, 2), - '%M': (date) => leadingNulls(date.tm_min, 2), - '%n': () => '\n', - '%p': (date) => { - if (date.tm_hour >= 0 && date.tm_hour < 12) { - return 'AM'; - } - return 'PM'; - }, - '%S': (date) => leadingNulls(date.tm_sec, 2), - '%t': () => '\t', - '%u': (date) => date.tm_wday || 7, - '%U': (date) => { - var days = date.tm_yday + 7 - date.tm_wday; - return leadingNulls(Math.floor(days / 7), 2); - }, - '%V': (date) => { - // Replaced by the week number of the year (Monday as the first day of the week) - // as a decimal number [01,53]. If the week containing 1 January has four - // or more days in the new year, then it is considered week 1. - // Otherwise, it is the last week of the previous year, and the next week is week 1. - // Both January 4th and the first Thursday of January are always in week 1. [ tm_year, tm_wday, tm_yday] - var val = Math.floor((date.tm_yday + 7 - (date.tm_wday + 6) % 7 ) / 7); - // If 1 Jan is just 1-3 days past Monday, the previous week - // is also in this year. - if ((date.tm_wday + 371 - date.tm_yday - 2) % 7 <= 2) { - val++; - } - if (!val) { - val = 52; - // If 31 December of prev year a Thursday, or Friday of a - // leap year, then the prev year has 53 weeks. - var dec31 = (date.tm_wday + 7 - date.tm_yday - 1) % 7; - if (dec31 == 4 || (dec31 == 5 && isLeapYear(date.tm_year%400-1))) { - val++; - } - } else if (val == 53) { - // If 1 January is not a Thursday, and not a Wednesday of a - // leap year, then this year has only 52 weeks. - var jan1 = (date.tm_wday + 371 - date.tm_yday) % 7; - if (jan1 != 4 && (jan1 != 3 || !isLeapYear(date.tm_year))) - val = 1; - } - return leadingNulls(val, 2); - }, - '%w': (date) => date.tm_wday, - '%W': (date) => { - var days = date.tm_yday + 7 - ((date.tm_wday + 6) % 7); - return leadingNulls(Math.floor(days / 7), 2); - }, - '%y': (date) => { - // Replaced by the last two digits of the year as a decimal number [00,99]. [ tm_year] - return (date.tm_year+1900).toString().substring(2); - }, - // Replaced by the year as a decimal number (for example, 1997). [ tm_year] - '%Y': (date) => date.tm_year+1900, - '%z': (date) => { - // Replaced by the offset from UTC in the ISO 8601:2000 standard format ( +hhmm or -hhmm ). - // For example, "-0430" means 4 hours 30 minutes behind UTC (west of Greenwich). - var off = date.tm_gmtoff; - var ahead = off >= 0; - off = Math.abs(off) / 60; - // convert from minutes into hhmm format (which means 60 minutes = 100 units) - off = (off / 60)*100 + (off % 60); - return (ahead ? '+' : '-') + String("0000" + off).slice(-4); - }, - '%Z': (date) => date.tm_zone, - '%%': () => '%' - }; - - // Replace %% with a pair of NULLs (which cannot occur in a C string), then - // re-inject them after processing. - pattern = pattern.replace(/%%/g, '\0\0') - for (var rule in EXPANSION_RULES_2) { - if (pattern.includes(rule)) { - pattern = pattern.replace(new RegExp(rule, 'g'), EXPANSION_RULES_2[rule](date)); - } - } - pattern = pattern.replace(/\0\0/g, '%') - - var bytes = intArrayFromString(pattern, false); - if (bytes.length > maxsize) { - return 0; - } - - writeArrayToMemory(bytes, s); - return bytes.length-1; - }; - var _strftime_l = (s, maxsize, format, tm, loc) => { - return _strftime(s, maxsize, format, tm); // no locale support yet - }; - - - var _system = (command) => { - if (ENVIRONMENT_IS_NODE) { - if (!command) return 1; // shell is available - - var cmdstr = UTF8ToString(command); - if (!cmdstr.length) return 0; // this is what glibc seems to do (shell works test?) - - var cp = require('child_process'); - var ret = cp.spawnSync(cmdstr, [], {shell:true, stdio:'inherit'}); - - var _W_EXITCODE = (ret, sig) => ((ret) << 8 | (sig)); - - // this really only can happen if process is killed by signal - if (ret.status === null) { - // sadly node doesn't expose such function - var signalToNumber = (sig) => { - // implement only the most common ones, and fallback to SIGINT - switch (sig) { - case 'SIGHUP': return 1; - case 'SIGINT': return 2; - case 'SIGQUIT': return 3; - case 'SIGFPE': return 8; - case 'SIGKILL': return 9; - case 'SIGALRM': return 14; - case 'SIGTERM': return 15; - } - return 2; // SIGINT - } - return _W_EXITCODE(0, signalToNumber(ret.signal)); - } - - return _W_EXITCODE(ret.status, 0); - } - // int system(const char *command); - // http://pubs.opengroup.org/onlinepubs/000095399/functions/system.html - // Can't call external programs. - if (!command) return 0; // no shell available - setErrNo(52); - return -1; - }; - - var wasmTableMirror = []; - var getWasmTableEntry = (funcPtr) => { - var func = wasmTableMirror[funcPtr]; - if (!func) { - if (funcPtr >= wasmTableMirror.length) wasmTableMirror.length = funcPtr + 1; - wasmTableMirror[funcPtr] = func = wasmTable.get(funcPtr); - } - return func; - }; - - var getCFunc = (ident) => { - var func = Module['_' + ident]; // closure exported function - return func; - }; - - - - var stringToUTF8OnStack = (str) => { - var size = lengthBytesUTF8(str) + 1; - var ret = stackAlloc(size); - stringToUTF8(str, ret, size); - return ret; - }; - - - /** - * @param {string|null=} returnType - * @param {Array=} argTypes - * @param {Arguments|Array=} args - * @param {Object=} opts - */ - var ccall = (ident, returnType, argTypes, args, opts) => { - // For fast lookup of conversion functions - var toC = { - 'string': (str) => { - var ret = 0; - if (str !== null && str !== undefined && str !== 0) { // null string - // at most 4 bytes per UTF-8 code point, +1 for the trailing '\0' - ret = stringToUTF8OnStack(str); - } - return ret; - }, - 'array': (arr) => { - var ret = stackAlloc(arr.length); - writeArrayToMemory(arr, ret); - return ret; - } - }; - - function convertReturnValue(ret) { - if (returnType === 'string') { - - return UTF8ToString(ret); - } - if (returnType === 'boolean') return Boolean(ret); - return ret; - } - - var func = getCFunc(ident); - var cArgs = []; - var stack = 0; - if (args) { - for (var i = 0; i < args.length; i++) { - var converter = toC[argTypes[i]]; - if (converter) { - if (stack === 0) stack = stackSave(); - cArgs[i] = converter(args[i]); - } else { - cArgs[i] = args[i]; - } - } - } - var ret = func.apply(null, cArgs); - function onDone(ret) { - if (stack !== 0) stackRestore(stack); - return convertReturnValue(ret); - } - - ret = onDone(ret); - return ret; - }; - - - var FSNode = /** @constructor */ function(parent, name, mode, rdev) { - if (!parent) { - parent = this; // root node sets parent to itself - } - this.parent = parent; - this.mount = parent.mount; - this.mounted = null; - this.id = FS.nextInode++; - this.name = name; - this.mode = mode; - this.node_ops = {}; - this.stream_ops = {}; - this.rdev = rdev; - }; - var readMode = 292/*292*/ | 73/*73*/; - var writeMode = 146/*146*/; - Object.defineProperties(FSNode.prototype, { - read: { - get: /** @this{FSNode} */function() { - return (this.mode & readMode) === readMode; - }, - set: /** @this{FSNode} */function(val) { - val ? this.mode |= readMode : this.mode &= ~readMode; - } - }, - write: { - get: /** @this{FSNode} */function() { - return (this.mode & writeMode) === writeMode; - }, - set: /** @this{FSNode} */function(val) { - val ? this.mode |= writeMode : this.mode &= ~writeMode; - } - }, - isFolder: { - get: /** @this{FSNode} */function() { - return FS.isDir(this.mode); - } - }, - isDevice: { - get: /** @this{FSNode} */function() { - return FS.isChrdev(this.mode); - } - } - }); - FS.FSNode = FSNode; - FS.createPreloadedFile = FS_createPreloadedFile; - FS.staticInit();; -var wasmImports = { - /** @export */ - _ZN10CHtmlFile213OpenBatchHtmlERKNSt3__26vectorINS0_12basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEENS5_IS7_EEEERKS7_P11CHtmlParams: __ZN10CHtmlFile213OpenBatchHtmlERKNSt3__26vectorINS0_12basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEENS5_IS7_EEEERKS7_P11CHtmlParams, - /** @export */ - _ZN10CHtmlFile215SetTmpDirectoryERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEE: __ZN10CHtmlFile215SetTmpDirectoryERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEE, - /** @export */ - _ZN10CHtmlFile27OpenMhtERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_P11CHtmlParams: __ZN10CHtmlFile27OpenMhtERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_P11CHtmlParams, - /** @export */ - _ZN10CHtmlFile28OpenHtmlERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_P11CHtmlParams: __ZN10CHtmlFile28OpenHtmlERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_P11CHtmlParams, - /** @export */ - _ZN10CHtmlFile2C1Ev: __ZN10CHtmlFile2C1Ev, - /** @export */ - _ZN10CHtmlFile2D1Ev: __ZN10CHtmlFile2D1Ev, - /** @export */ - _ZN10CVbaReader5writeEv: __ZN10CVbaReader5writeEv, - /** @export */ - _ZN10CVbaReader7convertEv: __ZN10CVbaReader7convertEv, - /** @export */ - _ZN10CVbaReaderC1ERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_: __ZN10CVbaReaderC1ERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_, - /** @export */ - _ZN10CVbaReaderD1Ev: __ZN10CVbaReaderD1Ev, - /** @export */ - _ZN14NSHtmlRenderer17CASCHTMLRenderer316CreateOfficeFileENSt3__212basic_stringIwNS1_11char_traitsIwEENS1_9allocatorIwEEEERKS7_: __ZN14NSHtmlRenderer17CASCHTMLRenderer316CreateOfficeFileENSt3__212basic_stringIwNS1_11char_traitsIwEENS1_9allocatorIwEEEERKS7_, - /** @export */ - _ZN14NSHtmlRenderer17CASCHTMLRenderer39CloseFileEb: __ZN14NSHtmlRenderer17CASCHTMLRenderer39CloseFileEb, - /** @export */ - _ZN14NSHtmlRenderer17CASCHTMLRenderer3C1Ev: __ZN14NSHtmlRenderer17CASCHTMLRenderer3C1Ev, - /** @export */ - _ZN14NSHtmlRenderer17CASCHTMLRenderer3D1Ev: __ZN14NSHtmlRenderer17CASCHTMLRenderer3D1Ev, - /** @export */ - _ZN22RtfConvertationManager15ConvertOOXToRtfENSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES6_: __ZN22RtfConvertationManager15ConvertOOXToRtfENSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES6_, - /** @export */ - _ZN22RtfConvertationManager15ConvertRtfToOOXENSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES6_: __ZN22RtfConvertationManager15ConvertRtfToOOXENSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES6_, - /** @export */ - _ZN22RtfConvertationManagerC1Ev: __ZN22RtfConvertationManagerC1Ev, - /** @export */ - _ZN3VBA12FormPropMaskC1Ej: __ZN3VBA12FormPropMaskC1Ej, - /** @export */ - _ZN3VBA17TextPropsPropMaskC1Ej: __ZN3VBA17TextPropsPropMaskC1Ej, - /** @export */ - _ZN8CFb2File15SetTmpDirectoryERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEE: __ZN8CFb2File15SetTmpDirectoryERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEE, - /** @export */ - _ZN8CFb2File4OpenERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_P10CFb2Params: __ZN8CFb2File4OpenERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_P10CFb2Params, - /** @export */ - _ZN8CFb2File8FromHtmlERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_S8_: __ZN8CFb2File8FromHtmlERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_S8_, - /** @export */ - _ZN8CFb2FileC1Ev: __ZN8CFb2FileC1Ev, - /** @export */ - _ZN8CFb2FileD1Ev: __ZN8CFb2FileD1Ev, - /** @export */ - _ZN8CXpsFileC1EPN7NSFonts17IApplicationFontsE: __ZN8CXpsFileC1EPN7NSFonts17IApplicationFontsE, - /** @export */ - _ZN9CDjVuFileC1EPN7NSFonts17IApplicationFontsE: __ZN9CDjVuFileC1EPN7NSFonts17IApplicationFontsE, - /** @export */ - _ZN9CEpubFile16SetTempDirectoryERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEE: __ZN9CEpubFile16SetTempDirectoryERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEE, - /** @export */ - _ZN9CEpubFile7ConvertERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_b: __ZN9CEpubFile7ConvertERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_b, - /** @export */ - _ZN9CEpubFile8FromHtmlERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_S8_: __ZN9CEpubFile8FromHtmlERKNSt3__212basic_stringIwNS0_11char_traitsIwEENS0_9allocatorIwEEEES8_S8_, - /** @export */ - _ZN9CEpubFileC1Ev: __ZN9CEpubFileC1Ev, - /** @export */ - _ZN9CEpubFileD1Ev: __ZN9CEpubFileD1Ev, - /** @export */ - _ZN9NSNetwork15NSFileTransport15CFileDownloader11GetFilePathEv: __ZN9NSNetwork15NSFileTransport15CFileDownloader11GetFilePathEv, - /** @export */ - _ZN9NSNetwork15NSFileTransport15CFileDownloader11SetFilePathERKNSt3__212basic_stringIwNS2_11char_traitsIwEENS2_9allocatorIwEEEE: __ZN9NSNetwork15NSFileTransport15CFileDownloader11SetFilePathERKNSt3__212basic_stringIwNS2_11char_traitsIwEENS2_9allocatorIwEEEE, - /** @export */ - _ZN9NSNetwork15NSFileTransport15CFileDownloader12DownloadSyncEv: __ZN9NSNetwork15NSFileTransport15CFileDownloader12DownloadSyncEv, - /** @export */ - _ZN9NSNetwork15NSFileTransport15CFileDownloaderC1ENSt3__212basic_stringIwNS2_11char_traitsIwEENS2_9allocatorIwEEEEb: __ZN9NSNetwork15NSFileTransport15CFileDownloaderC1ENSt3__212basic_stringIwNS2_11char_traitsIwEENS2_9allocatorIwEEEEb, - /** @export */ - _ZN9NSNetwork15NSFileTransport15CFileDownloaderD1Ev: __ZN9NSNetwork15NSFileTransport15CFileDownloaderD1Ev, - /** @export */ - __assert_fail: ___assert_fail, - /** @export */ - __cxa_rethrow: ___cxa_rethrow, - /** @export */ - __cxa_throw: ___cxa_throw, - /** @export */ - __syscall_chdir: ___syscall_chdir, - /** @export */ - __syscall_fcntl64: ___syscall_fcntl64, - /** @export */ - __syscall_fstat64: ___syscall_fstat64, - /** @export */ - __syscall_getcwd: ___syscall_getcwd, - /** @export */ - __syscall_getdents64: ___syscall_getdents64, - /** @export */ - __syscall_ioctl: ___syscall_ioctl, - /** @export */ - __syscall_lstat64: ___syscall_lstat64, - /** @export */ - __syscall_mkdirat: ___syscall_mkdirat, - /** @export */ - __syscall_newfstatat: ___syscall_newfstatat, - /** @export */ - __syscall_openat: ___syscall_openat, - /** @export */ - __syscall_readlinkat: ___syscall_readlinkat, - /** @export */ - __syscall_rmdir: ___syscall_rmdir, - /** @export */ - __syscall_stat64: ___syscall_stat64, - /** @export */ - __syscall_unlinkat: ___syscall_unlinkat, - /** @export */ - _emscripten_get_now_is_monotonic: __emscripten_get_now_is_monotonic, - /** @export */ - _emscripten_throw_longjmp: __emscripten_throw_longjmp, - /** @export */ - _gmtime_js: __gmtime_js, - /** @export */ - _mktime_js: __mktime_js, - /** @export */ - _mmap_js: __mmap_js, - /** @export */ - _munmap_js: __munmap_js, - /** @export */ - _tzset_js: __tzset_js, - /** @export */ - abort: _abort, - /** @export */ - emscripten_date_now: _emscripten_date_now, - /** @export */ - emscripten_get_heap_max: _emscripten_get_heap_max, - /** @export */ - emscripten_get_now: _emscripten_get_now, - /** @export */ - emscripten_memcpy_big: _emscripten_memcpy_big, - /** @export */ - emscripten_resize_heap: _emscripten_resize_heap, - /** @export */ - environ_get: _environ_get, - /** @export */ - environ_sizes_get: _environ_sizes_get, - /** @export */ - exit: _exit, - /** @export */ - fd_close: _fd_close, - /** @export */ - fd_read: _fd_read, - /** @export */ - fd_seek: _fd_seek, - /** @export */ - fd_write: _fd_write, - /** @export */ - getentropy: _getentropy, - /** @export */ - invoke_ii: invoke_ii, - /** @export */ - invoke_iii: invoke_iii, - /** @export */ - invoke_iiii: invoke_iiii, - /** @export */ - invoke_iiiii: invoke_iiiii, - /** @export */ - invoke_iiiiii: invoke_iiiiii, - /** @export */ - invoke_v: invoke_v, - /** @export */ - invoke_vi: invoke_vi, - /** @export */ - invoke_vii: invoke_vii, - /** @export */ - invoke_viii: invoke_viii, - /** @export */ - invoke_viiii: invoke_viiii, - /** @export */ - invoke_viiiii: invoke_viiiii, - /** @export */ - invoke_viiiiii: invoke_viiiiii, - /** @export */ - invoke_viiiiiiiii: invoke_viiiiiiiii, - /** @export */ - strftime_l: _strftime_l, - /** @export */ - system: _system -}; -var wasmExports = createWasm(); -var ___wasm_call_ctors = () => (___wasm_call_ctors = wasmExports['__wasm_call_ctors'])(); -var _main1 = Module['_main1'] = (a0) => (_main1 = Module['_main1'] = wasmExports['main1'])(a0); -var _malloc = (a0) => (_malloc = wasmExports['malloc'])(a0); -var ___errno_location = () => (___errno_location = wasmExports['__errno_location'])(); -var setTempRet0 = (a0) => (setTempRet0 = wasmExports['setTempRet0'])(a0); -var _emscripten_builtin_memalign = (a0, a1) => (_emscripten_builtin_memalign = wasmExports['emscripten_builtin_memalign'])(a0, a1); -var _setThrew = (a0, a1) => (_setThrew = wasmExports['setThrew'])(a0, a1); -var stackSave = () => (stackSave = wasmExports['stackSave'])(); -var stackRestore = (a0) => (stackRestore = wasmExports['stackRestore'])(a0); -var stackAlloc = (a0) => (stackAlloc = wasmExports['stackAlloc'])(a0); -var ___cxa_is_pointer_type = (a0) => (___cxa_is_pointer_type = wasmExports['__cxa_is_pointer_type'])(a0); -var dynCall_viijii = Module['dynCall_viijii'] = (a0, a1, a2, a3, a4, a5, a6) => (dynCall_viijii = Module['dynCall_viijii'] = wasmExports['dynCall_viijii'])(a0, a1, a2, a3, a4, a5, a6); -var dynCall_ji = Module['dynCall_ji'] = (a0, a1) => (dynCall_ji = Module['dynCall_ji'] = wasmExports['dynCall_ji'])(a0, a1); -var dynCall_jij = Module['dynCall_jij'] = (a0, a1, a2, a3) => (dynCall_jij = Module['dynCall_jij'] = wasmExports['dynCall_jij'])(a0, a1, a2, a3); -var dynCall_iiiijii = Module['dynCall_iiiijii'] = (a0, a1, a2, a3, a4, a5, a6, a7) => (dynCall_iiiijii = Module['dynCall_iiiijii'] = wasmExports['dynCall_iiiijii'])(a0, a1, a2, a3, a4, a5, a6, a7); -var dynCall_viiij = Module['dynCall_viiij'] = (a0, a1, a2, a3, a4, a5) => (dynCall_viiij = Module['dynCall_viiij'] = wasmExports['dynCall_viiij'])(a0, a1, a2, a3, a4, a5); -var dynCall_vij = Module['dynCall_vij'] = (a0, a1, a2, a3) => (dynCall_vij = Module['dynCall_vij'] = wasmExports['dynCall_vij'])(a0, a1, a2, a3); -var dynCall_jiji = Module['dynCall_jiji'] = (a0, a1, a2, a3, a4) => (dynCall_jiji = Module['dynCall_jiji'] = wasmExports['dynCall_jiji'])(a0, a1, a2, a3, a4); -var dynCall_jiij = Module['dynCall_jiij'] = (a0, a1, a2, a3, a4) => (dynCall_jiij = Module['dynCall_jiij'] = wasmExports['dynCall_jiij'])(a0, a1, a2, a3, a4); -var dynCall_viij = Module['dynCall_viij'] = (a0, a1, a2, a3, a4) => (dynCall_viij = Module['dynCall_viij'] = wasmExports['dynCall_viij'])(a0, a1, a2, a3, a4); -var dynCall_iiiji = Module['dynCall_iiiji'] = (a0, a1, a2, a3, a4, a5) => (dynCall_iiiji = Module['dynCall_iiiji'] = wasmExports['dynCall_iiiji'])(a0, a1, a2, a3, a4, a5); -var dynCall_jii = Module['dynCall_jii'] = (a0, a1, a2) => (dynCall_jii = Module['dynCall_jii'] = wasmExports['dynCall_jii'])(a0, a1, a2); -var dynCall_iji = Module['dynCall_iji'] = (a0, a1, a2, a3) => (dynCall_iji = Module['dynCall_iji'] = wasmExports['dynCall_iji'])(a0, a1, a2, a3); -var dynCall_jji = Module['dynCall_jji'] = (a0, a1, a2, a3) => (dynCall_jji = Module['dynCall_jji'] = wasmExports['dynCall_jji'])(a0, a1, a2, a3); -var dynCall_iiji = Module['dynCall_iiji'] = (a0, a1, a2, a3, a4) => (dynCall_iiji = Module['dynCall_iiji'] = wasmExports['dynCall_iiji'])(a0, a1, a2, a3, a4); -var dynCall_iiiiij = Module['dynCall_iiiiij'] = (a0, a1, a2, a3, a4, a5, a6) => (dynCall_iiiiij = Module['dynCall_iiiiij'] = wasmExports['dynCall_iiiiij'])(a0, a1, a2, a3, a4, a5, a6); -var dynCall_iiiiijj = Module['dynCall_iiiiijj'] = (a0, a1, a2, a3, a4, a5, a6, a7, a8) => (dynCall_iiiiijj = Module['dynCall_iiiiijj'] = wasmExports['dynCall_iiiiijj'])(a0, a1, a2, a3, a4, a5, a6, a7, a8); -var dynCall_iiiiiijj = Module['dynCall_iiiiiijj'] = (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) => (dynCall_iiiiiijj = Module['dynCall_iiiiiijj'] = wasmExports['dynCall_iiiiiijj'])(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); - -function invoke_iiiii(index,a1,a2,a3,a4) { - var sp = stackSave(); - try { - return getWasmTableEntry(index)(a1,a2,a3,a4); - } catch(e) { - stackRestore(sp); - if (e !== e+0) throw e; - _setThrew(1, 0); - } -} - -function invoke_vi(index,a1) { - var sp = stackSave(); - try { - getWasmTableEntry(index)(a1); - } catch(e) { - stackRestore(sp); - if (e !== e+0) throw e; - _setThrew(1, 0); - } -} - -function invoke_vii(index,a1,a2) { - var sp = stackSave(); - try { - getWasmTableEntry(index)(a1,a2); - } catch(e) { - stackRestore(sp); - if (e !== e+0) throw e; - _setThrew(1, 0); - } -} - -function invoke_ii(index,a1) { - var sp = stackSave(); - try { - return getWasmTableEntry(index)(a1); - } catch(e) { - stackRestore(sp); - if (e !== e+0) throw e; - _setThrew(1, 0); - } -} - -function invoke_viii(index,a1,a2,a3) { - var sp = stackSave(); - try { - getWasmTableEntry(index)(a1,a2,a3); - } catch(e) { - stackRestore(sp); - if (e !== e+0) throw e; - _setThrew(1, 0); - } -} - -function invoke_iii(index,a1,a2) { - var sp = stackSave(); - try { - return getWasmTableEntry(index)(a1,a2); - } catch(e) { - stackRestore(sp); - if (e !== e+0) throw e; - _setThrew(1, 0); - } -} - -function invoke_iiii(index,a1,a2,a3) { - var sp = stackSave(); - try { - return getWasmTableEntry(index)(a1,a2,a3); - } catch(e) { - stackRestore(sp); - if (e !== e+0) throw e; - _setThrew(1, 0); - } -} - -function invoke_v(index) { - var sp = stackSave(); - try { - getWasmTableEntry(index)(); - } catch(e) { - stackRestore(sp); - if (e !== e+0) throw e; - _setThrew(1, 0); - } -} - -function invoke_viiii(index,a1,a2,a3,a4) { - var sp = stackSave(); - try { - getWasmTableEntry(index)(a1,a2,a3,a4); - } catch(e) { - stackRestore(sp); - if (e !== e+0) throw e; - _setThrew(1, 0); - } -} - -function invoke_iiiiii(index,a1,a2,a3,a4,a5) { - var sp = stackSave(); - try { - return getWasmTableEntry(index)(a1,a2,a3,a4,a5); - } catch(e) { - stackRestore(sp); - if (e !== e+0) throw e; - _setThrew(1, 0); - } -} - -function invoke_viiiii(index,a1,a2,a3,a4,a5) { - var sp = stackSave(); - try { - getWasmTableEntry(index)(a1,a2,a3,a4,a5); - } catch(e) { - stackRestore(sp); - if (e !== e+0) throw e; - _setThrew(1, 0); - } -} - -function invoke_viiiiii(index,a1,a2,a3,a4,a5,a6) { - var sp = stackSave(); - try { - getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6); - } catch(e) { - stackRestore(sp); - if (e !== e+0) throw e; - _setThrew(1, 0); - } -} - -function invoke_viiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9) { - var sp = stackSave(); - try { - getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9); - } catch(e) { - stackRestore(sp); - if (e !== e+0) throw e; - _setThrew(1, 0); - } -} - - -// include: postamble.js -// === Auto-generated postamble setup entry stuff === - -Module['ccall'] = ccall; -Module['FS'] = FS; - - -var calledRun; - -dependenciesFulfilled = function runCaller() { - // If run has never been called, and we should call run (INVOKE_RUN is true, and Module.noInitialRun is not false) - if (!calledRun) run(); - if (!calledRun) dependenciesFulfilled = runCaller; // try this again later, after new deps are fulfilled -}; - -function run() { - - if (runDependencies > 0) { - return; - } - - preRun(); - - // a preRun added a dependency, run will be called later - if (runDependencies > 0) { - return; - } - - function doRun() { - // run may have just been called through dependencies being fulfilled just in this very frame, - // or while the async setStatus time below was happening - if (calledRun) return; - calledRun = true; - Module['calledRun'] = true; - - if (ABORT) return; - - initRuntime(); - - if (Module['onRuntimeInitialized']) Module['onRuntimeInitialized'](); - - postRun(); - } - - if (Module['setStatus']) { - Module['setStatus']('Running...'); - setTimeout(function() { - setTimeout(function() { - Module['setStatus'](''); - }, 1); - doRun(); - }, 1); - } else - { - doRun(); - } -} - -if (Module['preInit']) { - if (typeof Module['preInit'] == 'function') Module['preInit'] = [Module['preInit']]; - while (Module['preInit'].length > 0) { - Module['preInit'].pop()(); - } -} - -run(); - - -// end include: postamble.js diff --git a/www/common/onlyoffice/x2t/x2t.wasm b/www/common/onlyoffice/x2t/x2t.wasm deleted file mode 100755 index e851c70a40..0000000000 Binary files a/www/common/onlyoffice/x2t/x2t.wasm and /dev/null differ diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index e5b19ca80e..029a15f748 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -3014,6 +3014,8 @@ 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(); @@ -3047,6 +3049,8 @@ 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 @@ -3156,6 +3160,7 @@ define([ loadUniversal(Cursor, 'cursor', function () {}); loadUniversal(Integration, 'integration', function () {}); loadUniversal(Messenger, 'messenger', function () {}); + loadOnlyOffice(); store.messenger = store.modules['messenger']; // And now we're ready diff --git a/www/common/outer/serviceworker.js b/www/common/outer/serviceworker.js index 106e4d8486..5bd343980c 100644 --- a/www/common/outer/serviceworker.js +++ b/www/common/outer/serviceworker.js @@ -6,8 +6,8 @@ importScripts('/components/requirejs/require.js'); -window = self; -localStorage = { +window = self; // eslint-disable-line no-global-assign +localStorage = { // eslint-disable-line no-global-assign setItem: function (k, v) { localStorage[k] = v; }, getItem: function (k) { return localStorage[k]; } }; diff --git a/www/common/outer/sharedworker.js b/www/common/outer/sharedworker.js index d7743dbf61..59c818ab83 100644 --- a/www/common/outer/sharedworker.js +++ b/www/common/outer/sharedworker.js @@ -6,8 +6,8 @@ importScripts('/components/requirejs/require.js'); -window = self; -localStorage = { +window = self; // eslint-disable-line no-global-assign +localStorage = { // eslint-disable-line no-global-assign setItem: function (k, v) { localStorage[k] = v; }, getItem: function (k) { return localStorage[k]; } }; diff --git a/www/common/outer/webworker.js b/www/common/outer/webworker.js index 96d1748487..d5cc0b7d99 100644 --- a/www/common/outer/webworker.js +++ b/www/common/outer/webworker.js @@ -6,8 +6,8 @@ importScripts('/components/requirejs/require.js'); -window = self; -localStorage = { +window = self; // eslint-disable-line no-global-assign +localStorage = { // eslint-disable-line no-global-assign setItem: function (k, v) { localStorage[k] = v; }, getItem: function (k) { return localStorage[k]; } }; diff --git a/www/common/outer/x2t.js b/www/common/outer/x2t.js index e2b575e0ce..a814302c0f 100644 --- a/www/common/outer/x2t.js +++ b/www/common/outer/x2t.js @@ -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/x2t/x2t.js'], function() { // FIXME why does this fail without an access-control-allow-origin header? + require(['/common/onlyoffice/dist/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"); diff --git a/www/common/pad-types.js b/www/common/pad-types.js index 86390e885f..78adb56dfb 100644 --- a/www/common/pad-types.js +++ b/www/common/pad-types.js @@ -12,12 +12,22 @@ define([ OOCurrentVersion.currentVersion, ); - let availableTypes = AppConfig.availablePadTypes.filter( - (t) => ooEnabled || !OO_APPS.includes(t), + let availablePadTypes = 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); diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 81cd6ccfc2..3f886725ce 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -648,8 +648,8 @@ define([ const integrationHasUnsavedChanges = function(unsavedChanges, cb) { integrationChannel.query('Q_INTEGRATION_HAS_UNSAVED_CHANGES', unsavedChanges, cb); }; - var inte = common.createIntegration(onLocal, cpNfInner.chainpad, - integrationSave, integrationHasUnsavedChanges); + var inte = common.createIntegration(integrationSave, + integrationHasUnsavedChanges); if (inte) { integration = true; evIntegrationSave.reg(function () { diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js index 1d59432556..aa1bde788f 100644 --- a/www/common/sframe-common-codemirror.js +++ b/www/common/sframe-common-codemirror.js @@ -176,6 +176,10 @@ define([ else { editor.execCommand("insertTab"); } } }, + //remove focus from editor + "Esc": function () { + editor.display.input.blur(); + }, "Shift-Tab": function () { editor.execCommand("indentLess"); }, diff --git a/www/common/sframe-common-integration.js b/www/common/sframe-common-integration.js index 46af8fa2c2..8b1ab5374f 100644 --- a/www/common/sframe-common-integration.js +++ b/www/common/sframe-common-integration.js @@ -9,8 +9,6 @@ define([ module.create = function ( Common, - onLocal, - chainpad, saveHandler, unsavedChangesHandler) { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 1387fb2ac1..6c46ced048 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -78,6 +78,7 @@ 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 @@ -2076,6 +2077,16 @@ 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); @@ -2089,6 +2100,15 @@ 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) { diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 625a0dfe63..c297b5273f 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -34,6 +34,7 @@ define([ '/common/common-constants.js', '/components/localforage/dist/localforage.min.js', '/common/hyperscript.js', + '/common/extensions.js' ], function ( $, ApiConfig, @@ -64,7 +65,8 @@ define([ Language, Constants, localForage, - h + h, + Ext ) { // Chainpad Netflux Inner var funcs = {}; @@ -775,6 +777,8 @@ define([ return Util.checkRestrictedApp(app, AppConfig, ea, priv.plan, priv.loggedIn); }; + funcs.getExtensions = Ext.getExtensions; + funcs.mailbox = {}; Object.freeze(funcs); diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 1f2271e189..2b0d2f6481 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -1195,6 +1195,7 @@ 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(); diff --git a/www/common/translations/messages.bg.json b/www/common/translations/messages.bg.json index 5216cce935..d83656818a 100644 --- a/www/common/translations/messages.bg.json +++ b/www/common/translations/messages.bg.json @@ -407,5 +407,24 @@ "settings_userFeedback": "Активиране на обратната връзка с потребителя", "settings_deleteTitle": "Изтриване на акаунта", "settings_deleteHint": "Изтриването на акаунта е окончателно. Вашият CryptDrive и вашият списък с документи ще бъдат изтрити от сървъра. Останалите ви документи ще бъдат изтрити след 90 дни, ако никой друг не ги е съхранил в своя CryptDrive.", - "settings_deleteButton": "Изтриване на акаунта си" + "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, докато щраквате с десния бутон, за да видите опциите." } diff --git a/www/common/translations/messages.ca.json b/www/common/translations/messages.ca.json index 1854164ef8..ff1e88d021 100644 --- a/www/common/translations/messages.ca.json +++ b/www/common/translations/messages.ca.json @@ -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": "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_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_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 de suport integrat", + "features_f_support_note": "Resposta prioritària de l'equip d'administració per correu electrònic i sistema de tiquets 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'abús", + "support_cat_abuse": "Informa d'un 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": "Suport", + "supportPage": "Assistència", "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 Configuració.", "autostore_store": "Desa", "autostore_hide": "No ho desis", - "crowdfunding_button": "Doneu suport a CryptPad", + "crowdfunding_button": "Doneu suport al 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 el suport.", + "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.", "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 de recompensa:", + "admin_support_premium": "Tiquets prèmium:", "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": "Suport", + "admin_cat_support": "Assistència", "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 {1}. 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 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.", + "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.", "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": "

Necessitem la vostra ajuda!

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 Full de ruta i Objectius de finançament.", - "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.", + "crowdfunding_popup_text": "

Necessitem la vostra ajuda!

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 Full de ruta i Objectius de finançament.", + "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.", "admin_supportAddError": "Clau privada no vàlida", - "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.", + "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.", "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 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_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_close": "Tanca el tiquet", - "support_remove": "Elimina el tiquet", + "support_remove": "Suprimeix el tiquet", "support_showData": "Mostra/oculta les dades de l'usuari", "support_from": "De: {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 de suport", + "support_notification": "Un administrador ha respost al vostre tiquet d'assistència", "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 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_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_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": "{0} ha començat", "reminder_minutes": "{0} començarà en {1} minut(s)", "reminder_time": "{0} avui a {1}", - "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_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_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": "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_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_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 premium ajuden a millorar la usabilitat de CryptPad i es beneficien de respostes prioritzades als seus bitllets de suport.", + "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_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 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 migrar les vostres dades a un compte nou", - "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 ", - "support_warning_document": "Especifiqueu quin tipus de document està causant el problema i proporcioneu un identificador de document o un enllaç", + "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 migrar les vostres dades a un compte nou", + "support_warning_drives": "Tingueu en compte que els administradors no poden identificar carpetes i documents pel seu nom. Per a carpetes compartides, proporcioneu un identificador de document ", + "support_warning_document": "Especifiqueu quin tipus de document està causant el problema i proporcioneu un identificador de document 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 condicions del servei. 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 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_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_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 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.", + "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.", "support_formHint": "Utilitzeu aquest formulari per contactar amb seguretat amb els administradors sobre problemes i preguntes.
Tingueu en compte que alguns problemes/preguntes ja es poden abordar a la Guia d'usuari de CryptPad. 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 {0}. Voleu concedir l'accés?", "owner_removedPending": "{0} ha cancel·lat la vostra oferta de propietat de {1}", @@ -1666,5 +1666,56 @@ "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 {0} a la unitat de l'equip {1}" + "dph_sf_destroyed_team": "Un propietari ha destruït la carpeta compartida {0} a la unitat de l'equip {1}", + "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" } diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json index a06aa64cb2..247d98bba1 100644 --- a/www/common/translations/messages.de.json +++ b/www/common/translations/messages.de.json @@ -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": "Erlaube keine neuen Benutzerregistrierungen", + "admin_registrationHint": "Besucher der Instanz können keine Accounts erstellen. Einladungen können von Administratoren erstellt werden.", "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 {0} 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.", + "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.", "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,5 +1763,22 @@ "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}" + "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": "
  • Erstelle auf dieser Seite deinen ersten Administrator-Account. Administratoren verwalten die Einstellungen der Instanz einschließlich der Speicherplatzkontingente und haben Zugriff auf die Moderationswerkzeuge.
  • Dein Passwort ist der geheime Schlüssel, der alle deine Dokumente und Administratorrechte auf dieser Instanz verschlüsselt. Wenn du es verlierst, gibt es keine Möglichkeit, deine Daten wiederherzustellen.
  • Wenn du einen gemeinsam genutzten Computer verwendest, denke daran, dich abzumelden, wenn du fertig bist. Wird das Browserfenster einfach geschlossen, bleibt dein Account zugänglich.
", + "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.
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)" } diff --git a/www/common/translations/messages.eu.json b/www/common/translations/messages.eu.json index a1d0b0af14..aa1660557d 100644 --- a/www/common/translations/messages.eu.json +++ b/www/common/translations/messages.eu.json @@ -1733,5 +1733,35 @@ "support_closed_tag": "Itxita", "support_privacyTitle": "Erantzun anonimoki", "moderationPage": "Laguntza mahaia", - "admin_supportInit": "Hasieratu laguntza mahaia instantzia honetan" + "admin_supportInit": "Hasieratu laguntza mahaia instantzia honetan", + "support_openTicketTitle": "Ireki txartel bat erabiltzaile batekin", + "support_privacyHint": "Markatu aukera hau zure erabiltzaile-izenaren ordez \"Laguntza-taldea\" gisa erantzuteko", + "support_notificationsHint": "Markatu aukera hau txartel eta erantzun berrien jakinarazpenak desgaitzeko", + "support_notificationsTitle": "Desgaitu jakinarazpenak", + "support_recordedEmpty": "Kode-zatirik ez", + "support_userChannel": "Erabiltzailearen jakinarazpenen kanalaren IDa", + "support_openTicketHint": "Kopiatu hartzailearen erabiltzailearen datuak bere profil-orritik edo lehendik dagoen laguntza-txartel batetik. Mezu honi buruzko CryptPad jakinarazpena jasoko dute.", + "support_recordedId": "Kode-zatiaren ID (esklusiboa)", + "support_userKey": "Erabiltzailearen gako publikoa", + "support_invalChan": "Jakinarazpen-kanal baliogabea", + "support_recordedTitle": "Kode-zatiak", + "admin_supportTeamTitle": "Kudeatu laguntza taldea", + "support_pasteUserData": "Itsatsi erabiltzailearen datuak hemen", + "support_legacyButton": "Lortu txartel aktiboak", + "support_recordedHint": "Gorde testu arrunta klik bakarreko lasterbide gisa laguntza-mezuetan txertatzeko.", + "support_recordedContent": "Edukia", + "support_legacyTitle": "Ikusi laguntza-datu zaharrak", + "support_searchLabel": "Bilatu (titulua edo txartelaren ID)", + "support_legacyHint": "Ikusi laguntza sistema zaharraren txartelak eta berrerabili berrian.", + "support_legacyDump": "Esportatu guztiak", + "support_insertRecorded": "Txertatu kode-zatia", + "support_legacyClear": "Ezabatu kontu honetarako", + "support_moveActive": "Eraman aktibora", + "support_team": "Laguntza taldea", + "support_answerAs": "{0} gisa erantzuten", + "support_movePending": "Eraman artxibora", + "admin_supportOpen": "Ireki laguntza tresnak", + "support_copyUserData": "Kopiatu erabiltzailearen datuak", + "support_userNotification": "Laguntza-txartel edo erantzun berria: {0}", + "admin_supportTeamHint": "Gehitu eta kendu jendea instantziako laguntza-taldetik" } diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json index 3e36950f8a..9457c52fd4 100644 --- a/www/common/translations/messages.fr.json +++ b/www/common/translations/messages.fr.json @@ -1018,7 +1018,7 @@ "admin_defaultlimitTitle": "Limite de stockage (Mo)", "admin_defaultlimitHint": "Limite de stockage maximum pour le CryptDrive (utilisateur·ices et équipes) si aucune règle spéciale n'est appliquée", "admin_registrationTitle": "Fermer l'enregistrement", - "admin_registrationHint": "Ne pas autoriser l'enregistrement de nouvelles·aux utilisateur·ices", + "admin_registrationHint": "Les visiteur·ices de l'instance ne sont pas autorisé·es à créer un compte. Des invitations peuvent être créées par les administrateur·ices.", "snapshots_cantMake": "La capture n'a pas pu être effectuée. Vous êtes déconnecté.", "snapshots_notFound": "Cette capture n'existe plus car l'historique du document a été supprimé.", "admin_limitUser": "Clé publique de l'utilisateur·ice", @@ -1680,7 +1680,7 @@ "kanban_showTags": "Voir tous les mots-clés", "kanban_hideTags": "Voir moins de mots-clés", "admin_forcemfaTitle": "Authentification à deux facteurs obligatoire", - "admin_forcemfaHint": "Tous les utilisateurs de cette instance seront obligés d'activer l'authentification à deux facteurs pour se connecter à leur compte.", + "admin_forcemfaHint": "Tou·tes les utilisateur·ices de l'instance devront activer l'authentification a deux facteurs (2FA) pour pouvoir se connecter à leur compte. Notez que les utilisateur·ices existant·es ne pourront pas continuer à utiliser leur compte sans mettre en place une application TOTP.", "loading_mfa_required": "L'authentification à deux facteurs est requise pour cette instance. Veuillez mettre à jour votre compte en utilisant une application d'authentification et le formulaire ci-dessous.", "admin_cat_users": "Registre utilisateur·ices", "admin_invitationCreate": "Créer un lien d'invitation", @@ -1706,7 +1706,7 @@ "admin_logoTitle": "Logo personnalisé", "admin_supportAdd": "Ajouter un contact à l'équipe support", "admin_logoHint": "SVG, PNG ou JPG, taille maximum 200 kB", - "admin_logoRemoveButton": "Logo par défaut", + "admin_logoRemoveButton": "Réinitialiser", "admin_colorCurrent": "Couleur d'accent actuelle", "admin_colorChange": "Changer la couleur", "admin_colorPick": "Choisir une couleur", @@ -1763,5 +1763,22 @@ "support_openTicketHint": "Copiez les données de l'utilisateur·ices destinataire depuis leur page de profil ou depuis un ticket de support existant. Iels recevront une notification CryptPad à propos de ce message.", "support_invalChan": "Canal de notifications invalide", "support_legacyHint": "Voir les tickets de l'ancien système de support et les recréer dans le nouveau.", - "support_userNotification": "Nouveau ticket de support ou réponse : {0}" + "support_userNotification": "Nouveau ticket de support ou réponse : {0}", + "install_token": "Jeton d'installation", + "install_header": "Installation", + "admin_appSelection": "Configuration des applications", + "install_instance": "Créer le premier compte administrateur·ice, puis continuer pour personnaliser l'instance", + "install_launch": "Paramétrage de l'instance", + "admin_appsTitle": "Applications de l'instance", + "admin_appsHint": "Choisissez quelles applications sont activées sur cette instance.", + "admin_cat_apps": "Applications", + "onboarding_upload": "Sélectionner un logo", + "onboarding_save_error": "Certaines options n'ont pas pu être sauvegardées correctement. Veuillez visiter l'espace d'administration pour vérifier les valeurs indiquées.", + "admin_onboardingNameHint": "Veuillez choisir un titre, une description, une couleur de thème et un logo (tous sont optionnels)", + "admin_onboardingNameTitle": "Bienvenu sur votre instance CryptPad", + "admin_onboardingOptionsTitle": "Réglages de l'instance", + "admin_onboardingNamePlaceholder": "Nom de l'instance", + "admin_onboardingOptionsHint": "Veuillez sélectionner les options appropriées pour votre instance.
Ces paramètres peuvent être changés plus tard depuis l'espace d'administration.", + "admin_onboardingDescPlaceholder": "Texte de description de l'instance", + "install_notes": "
  • Créez votre premier compte administrateur·ice sur cette page. Les administrateur·ices peuvent paramétrer l'instance, ceci incluant les quotas de stockage, et ont accès aux outils de modération.
  • Votre mot de passe est la clé secrète qui chiffre l'ensemble de vos documents et vos privilèges d'administration sur l'instance. Si vous le perdez il n'est pas possible de récupérer vos données.
  • Si vous utilisez un ordinateur partagé, n'oubliez pas de vous déconnecter quand vous aurez terminé. Simplement fermer la fenêtre du navigateur web laisse votre compte exposé à des risques de sécurité.
" } diff --git a/www/common/translations/messages.id.json b/www/common/translations/messages.id.json index df0015fcad..2477397e8d 100644 --- a/www/common/translations/messages.id.json +++ b/www/common/translations/messages.id.json @@ -682,7 +682,7 @@ "crowdfunding_popup_text": "

Kami butuh bantuan Anda

Untuk memastikan CryptPad dikembangkan secara aktif, pertimbangkan mendukung proyek ini melalui laman OpenCollective, di mana Anda dapat melihat Peta jalan dan Sasaran pendanaan.", "admin_activeSessionsHint": "Jumlah koneksi WebSocket aktif (dan alamat IP unik yang terhubung)", "admin_updateLimitHint": "Memperbarui pembaruan batasan penyimpanan pengguna secara paksa dapat dilakukan kapan pun, tetapi hanya perlu jika ada kesalahan", - "admin_flushCacheHint": "Paksa pengguna untuk mengunduh aset sisi klien terbaru (hanya jika server Anda dalam mode segar)", + "admin_flushCacheHint": "Paksa semuapengguna untuk mengunduh aset terkini setelah perubahan kustomisasi atau konfigurasi. Menghindari memulai ulang server tetapi membuat semua pengguna mengatur ulang koneksinya, silakan gunakan sesuai kebutuhan.", "contact_devHint": "Untuk permintaan fitur, peningkatan penggunaan, atau untuk berterima kasih.", "timeoutError": "Sebuah kesalahan telah memutuskan koneksi Anda ke server.
Tekan Esc untuk memuat ulang laman.", "profile_info": "Pengguna lain dapat menemukan profil Anda melalui avatar Anda di daftar pengguna dokumen.", @@ -1412,7 +1412,7 @@ "team_inviteLinkError": "Terjadi kesalahan saat membuat tautan.", "info_imprintFlavour": "Informasi legal tentang administrator server ini", "snapshot_error_exists": "Sudah ada snapshot dari versi ini", - "admin_registrationHint": "Jangan perbolehkan pengguna baru untuk mendaftar", + "admin_registrationHint": "Tamu server tidak dapat membuat akun. Undangan dapat dibuat oleh administrator.", "admin_unarchiveHint": "Pulihkan dokumen yang sebelumnya diarsip", "broadcast_newSurvey": "Sebuah survei baru telah tersedia. Klik untuk membuka.", "mfa_status_off": "2FA belum aktif di akun ini", @@ -1667,7 +1667,7 @@ "admin_broadcastHint": "Kirimkan pesan ke semua pengguna di server ini. Semua pengguna yang sudah ada dan pengguna baru akan menerimanya sebagai notifikasi. Tampilkan pesan sebelum dikirimkan dengan \"Tampilkan notifikasi\". Notifikasi yang ditampilkan memiliki ikon merah dan hanya tersedia untuk Anda.", "support_warning_abuse": "Mohon laporkan konten yang melanggar Ketentuan Layanan. Mohon sediakan tautan ke dokumen atau profil pengguna yang melanggar dan jelaskan bagaimana mereka melanggar ketentuannya. Informasi tambahan dalam konteks yang Anda mengetahui konten atau perilaku dapat membantu administrator mencegah pelanggaran di masa depan", "safeLinks_error": "Tautan ini disalin dari bilah alamat peramban dan tidak menyediakan akses ke dokumen. Silakan gunakan menu Bagikan untuk membagikan ke kontak secara langsung atau salin tautannya. Baca lebih lanjut tentang fitur Tautan Aman.", - "admin_cat_users": "Direktori Pengguna", + "admin_cat_users": "Direktori pengguna", "admin_invitationCreate": "Buat tautan undangan", "admin_registrationSsoTitle": "Tutup pendaftaran SSO", "admin_invitationTitle": "Tautan undangan", @@ -1680,7 +1680,7 @@ "admin_usersRemove": "Hapus", "admin_invitationEmail": "Surel pengguna", "admin_storeInvitedLabel": "Simpan pengguna yang diundang secara otomatis", - "admin_forcemfaHint": "Semua pengguna di server ini akan diminta untuk menyiapkan autentikasi dua faktor untuk masuk ke akun mereka.", + "admin_forcemfaHint": "Semua pengguna di server ini akan diminta untuk menyiapkan autentikasi dua faktor untuk masuk ke akun mereka. Pengguna yang sudah ada tidak akan dapat tetap menggunakan akunnya tanpa menyiapkan aplikasi TOTP.", "admin_invitationHint": "Setiap tautan undangan membuat satu akun, bahkan jika pendaftaran ditutup. Nama pengguna dan surel hanya untuk keperluan identifikasi Anda. CryptPad tidak akan mengirim surel berisi tautan undangan (atau yang lain), mohon salin tautan dan kirimkan melalui saluran aman pilihan Anda.", "calendar_rec_change_first": "Memindahkan pengulangan pertama ke kalender lain. Semua kejadian yang berulang juga akan dipindahkan.", "admin_invitationCopy": "Salin tautan", @@ -1735,5 +1735,50 @@ "support_notificationsTitle": "Nonaktifkan notifikasi", "moderationPage": "Ruang bantuan", "admin_supportInit": "Mulai ruang bantuan di server ini", - "admin_supportRotateNotify": "Peringatan: kunci baru telah dibuat tetapi ada kesalahan yang tidak terduga yang mencegah sistem untuk mengirimnya kepada para moderator. Silakan hapus dan tambahkan lagi semua anggota tim bantuan" + "admin_supportRotateNotify": "Peringatan: kunci baru telah dibuat tetapi ada kesalahan yang tidak terduga yang mencegah sistem untuk mengirimnya kepada para moderator. Silakan hapus dan tambahkan lagi semua anggota tim bantuan", + "support_openTicketTitle": "Buka tiket dengan pengguna", + "support_notificationsHint": "Centang opsi ini untuk menonaktifkan notifikasi untuk tiket dan balasan baru", + "support_userChannel": "ID saluran notifikasi pengguna", + "support_openTicketHint": "Salin data pengguna penerima dari laman profilnya atau tiket dukungan yang sudah ada. Mereka akan menerima notifikasi CryptPad tentang pesan ini.", + "support_recordedId": "ID potongan (unik)", + "support_userKey": "Kunci publik pengguna", + "support_recordedTitle": "Potongan", + "support_invalChan": "Saluran notifikasi tidak valid", + "admin_supportTeamTitle": "Kelola tim dukungan", + "support_pasteUserData": "Tempelkan data pengguna di sini", + "admin_supportTeamHint": "Tambahkan atau keluarkan orang dari tim dukungan server", + "support_legacyButton": "Dapatkan tiket aktif", + "support_recordedHint": "Simpan teks umum sebagai pintasan sekali klik untuk disisipkan dalam pesan dukungan.", + "support_recordedContent": "Konten", + "support_recordedEmpty": "Tidak ada potongan", + "support_legacyTitle": "Tampilkan data dukungan lama", + "support_searchLabel": "Cari (judul atau ID tiket)", + "support_legacyHint": "Lihat tiket dari sistem dukungan lawas dan buat ulang dalam yang baru.", + "support_legacyDump": "Ekspor semua", + "support_insertRecorded": "Sisipkan potongan", + "support_legacyClear": "Hapus untuk akun ini", + "support_team": "Tim Dukungan", + "support_answerAs": "Menjawab sebagai {0}", + "support_movePending": "Pindahkan ke arsip", + "admin_supportOpen": "Buka pusat bantuan", + "support_moveActive": "Pindahkan ke aktif", + "support_copyUserData": "Salin data pengguna", + "support_userNotification": "Tiket atau respons dukungan baru: {0}", + "install_header": "Pemasangan", + "install_token": "Token pemasangan", + "admin_appSelection": "Konfigurasi aplikasi", + "install_instance": "Buat akun admin pertama, terus lanjut mengubah server", + "install_launch": "Penyiapan server", + "admin_appsTitle": "Aplikasi server", + "admin_appsHint": "Pilih aplikasi apa saja untuk diaktifkan di server ini.", + "admin_cat_apps": "Aplikasi", + "onboarding_upload": "Pilih logo", + "onboarding_save_error": "Beberapa opsi tidak dapat disimpan dengan benar. Silakan kunjungi panel administrasi untuk memeriksa nilai.", + "admin_onboardingNameHint": "Silakan pilih nama, deskripsi, warna aksen, dan logo (semua bersifat opsional)", + "admin_onboardingNameTitle": "Selamat datang di server CryptPad Anda", + "admin_onboardingOptionsTitle": "Opsi server", + "admin_onboardingNamePlaceholder": "Judul server", + "admin_onboardingDescPlaceholder": "Teks deskripsi server", + "admin_onboardingOptionsHint": "Silakan pilih opsi sesuai untuk server Anda.
Pengaturan ini dapat diubah lagi dalam panel admin.", + "install_notes": "
  • Buat akun administrator Anda yang pertama di laman ini. Administrator mengelola pengaturan server termasuk kuota penyimpanan dan memiliki akses ke peralatan moderasi.
  • Kata sandi Anda adalah kunci rahasia yang mengenkripsi semua dokumen dan hak administrator Anda di server ini.Jika Anda kehilangan kata sandi tersebut, kami tidak dapat memulihkan data Anda.
  • Jika Anda menggunakan komputer terbagi, jangan lupa untuk keluar dari akun setelah Anda selesai. Hanya menutup jendela peramban membuat akun Anda rentan.
" } diff --git a/www/common/translations/messages.it.json b/www/common/translations/messages.it.json index 895e05b531..4a91325dc0 100644 --- a/www/common/translations/messages.it.json +++ b/www/common/translations/messages.it.json @@ -13,7 +13,7 @@ "todo": "Promemoria", "contacts": "Contatti", "sheet": "Foglio", - "teams": "Team", + "teams": "Gruppi", "presentation": "Presentazione", "doc": "Documento", "form": "Modulo", @@ -253,8 +253,8 @@ "fm_folder": "Cartella", "fm_sharedFolder": "Cartella condivisa", "fm_folderName": "Nome della cartella", - "fm_numberOfFolders": "Numero di cartelle", - "fm_numberOfFiles": "Numero di file", + "fm_numberOfFolders": "N° di cartelle", + "fm_numberOfFiles": "N° di file", "fm_fileName": "Nome del file", "fm_type": "Tipo", "fm_lastAccess": "Ultimo accesso", @@ -469,7 +469,7 @@ "features_f_subscribe": "Abbonati", "features_f_supporter_note": "Aiuta CryptPad a diventare finanziariamente sostenibile e a dimostrare che i software che proteggono la privacy, finanziati volontariamente dagli utenti e dalle utenti, devono essere la norma", "features_f_supporter": "Sostieni la privacy", - "features_f_support_note": "Risposta prioritaria da parte del team di amministrazione via email e sistema per le richieste di assistenza integrato", + "features_f_support_note": "Risposta prioritaria da parte della squadra di amministrazione via email e sistema per le richieste di assistenza integrato", "features_f_storage2_note": "Da 5GB a 50GB in funzione del piano selezionato, limite aumentato di {0}MB per il caricamento di file", "features_f_storage2": "Spazio di archiviazione aggiuntivo", "features_f_reg_note": "Con ulteriori vantaggi", @@ -515,7 +515,7 @@ "upload_modal_owner": "File di tua proprietà", "upload_modal_filename": "Nome del file (estensione {0} aggiunta automaticamente)", "settings_cursorShowHint": "Puoi decidere di visualizzare la posizione del cursore degli altri utenti e delle altre utenti nei documenti collaborativi.", - "settings_cursorShareHint": "Puoi decidere di mostrare la posizione del tuo cursore agli altri utenti e alle altri utenti nei documenti collaborativi.", + "settings_cursorShareHint": "Puoi decidere di mostrare la posizione del tuo cursore agli altri utenti e alle altre utenti nei documenti collaborativi.", "settings_cursorColorHint": "Cambia il colore associato al tuo account nei documenti collaborativi.", "settings_changePasswordPending": "La tua password è in fase di modifica. Non chiudere o ricaricare questa pagina fino al completamento del processo.", "settings_changePasswordError": "Si è verificato un errore. Se non riesci ad accedere o cambiare la tua password contatta il tuo amministratore o la tua amministratrice di CryptPad.", @@ -549,7 +549,7 @@ "kanban_color": "Colore", "kanban_body": "Contenuto", "kanban_title": "Titolo", - "teams": "Team", + "teams": "Gruppi", "contacts": "Contatti", "restrictedError": "Non sei autorizzato/a ad accedere a questo documento", "access_allow": "Lista", @@ -563,9 +563,9 @@ "team_inviteInvalidLinkError": "Il link d'invito non è valido.", "team_links": "Link d'invito", "team_cat_link": "Link d'invito", - "team_inviteJoin": "Unisciti al team", + "team_inviteJoin": "Entra nel gruppo", "team_invitePleaseLogin": "Accedi o registrati per accettare questo invito.", - "team_inviteFromMsg": "{0} ti ha invitato ad unirti al team {1}", + "team_inviteFromMsg": "{0} ti ha invitato ad entrare nel gruppo {1}", "team_inviteFrom": "Da:", "team_inviteLinkCopy": "Copia link", "team_inviteLinkCreate": "Crea link", @@ -573,30 +573,30 @@ "team_inviteLinkNote": "Aggiungi un messaggio personalizzato", "pad_wordCount": "Parole: {0}", "teams_table_role": "Ruolo", - "teams_table_owners": "Gestisci team", - "teams_table_admins": "Gestisci membri", + "teams_table_owners": "Gestisci il gruppo", + "teams_table_admins": "Gestisci i membri", "teams_table_specific": "Eccezioni", "teams_table_generic": "Ruoli e permessi", "teams_table": "Ruoli", "properties_passwordSuccessFile": "La password è stata cambiata con successo.", "drive_sfPasswordError": "Password errata", - "team_title": "Team: {0}", + "team_title": "Gruppo: {0}", "team_pendingOwner": "(in attesa)", "team_deleteButton": "Elimina", "team_pending": "Invitato/a", "sent": "Messaggio inviato", - "team_listTitle": "I tuoi team", + "team_listTitle": "I tuoi gruppi", "team_avatarHint": "Dimensione massima 500 kB (png, jpg, jpeg, gif)", - "team_avatarTitle": "Immagine del team", - "team_nameHint": "Imposta il nome del team", - "team_nameTitle": "Nome del team", + "team_avatarTitle": "Immagine del gruppo", + "team_nameHint": "Imposta il nome del gruppo", + "team_nameTitle": "Nome del gruppo", "team_members": "Membri", - "team_leaveButton": "Lascia questo team", - "team_createName": "Nome del team", - "team_createLabel": "Crea un nuovo team", + "team_leaveButton": "Esci da questo gruppo", + "team_createName": "Nome del gruppo", + "team_createLabel": "Crea un nuovo gruppo", "team_cat_chat": "Chat", "team_cat_members": "Membri", - "team_cat_list": "Team", + "team_cat_list": "Gruppi", "team_inviteModalButton": "Invita", "owner_unknownUser": "sconosciuto/a", "owner_removePendingText": "In attesa", @@ -645,7 +645,7 @@ "drive_active1Day": "Ultime 24 ore", "contact_email": "Email", "contact_chat": "Chat", - "contact_dev": "Contatta il team di sviluppo", + "contact_dev": "Contatta la quadra di sviluppo", "contact_admin": "Contatta gli amministratori e le amministratrici per: {0}", "footer_donate": "Dona", "admin_registeredTitle": "Utenti registrati", @@ -700,7 +700,7 @@ "notifications_cat_friends": "Richieste di contatto", "notifications_dismissAll": "Ignora tutte", "team_cat_create": "Nuovo", - "team_cat_back": "Torna ai team", + "team_cat_back": "Torna ai gruppi", "contacts_unmute": "Riattiva", "contacts_mutedUsers": "Account silenziati", "creation_noOwner": "Nessun/a proprietario/a", @@ -797,14 +797,14 @@ "burnAfterReading_warningAccess": "Questo documento è impostato per l'autodistruzione. Cliccando sul pulsante qui sotto vedrai il contenuto una sola volta prima che sia eliminato in modo permanente. Una volta visto il contenuto chiudendo questa finestra perderai l'accesso al documento. Se non sei sicuro/a di voler continuare puoi chiudere questa finestra adesso e riaprirla più tardi.", "burnAfterReading_warningLink": "Il documento è impostato per l'autodistruzione. Quando il destinatario o la destinataria visiterà questo link, potrà vedere il documento una sola volta prima che questo sia eliminato in modo permanente.", "team_inviteLinkErrorName": "Aggiungi un nome per la persona che stai invitando. Potrà essere cambiarlo in seguito. ", - "team_inviteLinkNoteMsg": "Questo messaggio verrà mostrato prima che il destinatario o la destinataria decida di unirsi a questo team.", + "team_inviteLinkNoteMsg": "Questo messaggio verrà mostrato prima che il destinatario o la destinataria decida di entrare nel gruppo.", "share_noContactsNotLoggedIn": "Accedi o registrati per vedere i tuoi contatti esistenti e aggiungerne di nuovi.", "share_noContactsLoggedIn": "Non hai ancora aggiunto nessuno/a ai tuoi contatti su CryptPad. Condividi il link al tuo profilo per permettere ad altre persone di inviarti richieste di contatto.", "share_embedPasswordAlert": "Questo elemento è protetto da una password. Quando incorpori questo documento ai visualizzatori ed alle visualizzatrici sarà richiesta la password.", "share_linkPasswordAlert": "Questo elemento è protetto da una password. Quando spedirai il link il destinatario o la destinataria dovrà inserire la password.", "share_linkWarning": "Questo link contiene la chiave per il tuo documento. Le persone destinatarie avranno un accesso non revocabile al suo contenuto.", "teams_table_specificHint": "In queste cartelle condivise (che sono di una versione precedente) i visualizzatori e le visualizzatrici possono modificare i documenti esistenti. I documenti creati o copiati in queste cartelle avranno i permessi standard.", - "teams_table_generic_own": "Gestione del team: modifica il nome e l'immagine del team, aggiungi o rimuovi i suoi proprietari o le sue proprietarie, oppure elimina il team.", + "teams_table_generic_own": "Gestione del gruppo: modifica il nome e l'immagine del gruppo, aggiungi o rimuovi i suoi proprietari o le sue proprietarie, oppure elimina il gruppo.", "teams_table_generic_edit": "Modifica: crea, modifica ed elimina cartelle e documenti.", "teams_table_generic_view": "Visualizzazione: accedi a cartelle e documenti (sola lettura).", "driveOfflineError": "La tua connessione a CryptPad è stata persa. Le modifiche apportate a questo documento non saranno salvate nel tuo CryptDrive. Chiudi tutte le tue schede di CryptPad e riprova in una nuova finestra. ", @@ -815,27 +815,27 @@ "settings_codeBrackets": "Chiudi automaticamente le parentesi", "team_demoteMeConfirm": "Stai per rinunciare ai tuoi diritti. Questa azione non può essere annullata. Vuoi proseguire?", "team_pendingOwnerTitle": "Questo amministratore o questa amministratrice non ha ancora accettato la proposta di diventare proprietario/a.", - "team_deleteConfirm": "Stai per eliminare i dati di un intero team. Questo può influire sull'accesso degli altri membri del team ai propri dati. L'eliminazione è irreversibile. Sei sicuro/a di voler continuare?", - "team_kickConfirm": "{0} saprà che l'hai eliminato/a dal team. Sei sicuro/a?", - "team_ownerConfirm": "I co-proprietari e le co-proprietarie possono modificare o eliminare il team e rimuoverti come proprietario/a. Vuoi continuare?", - "owner_team_add": "{0} propone tu sia proprietario/a del team {1}. Accetti?", - "team_maxTeams": "Ogni utente può essere membro di {0} team al massimo.", - "team_listSlot": "Posizione disponibile per un nuovo team", + "team_deleteConfirm": "Stai per eliminare i dati di un intero gruppo. Questo può influire sull'accesso degli altri membri del gruppo ai propri dati. L'eliminazione è irreversibile. Sei sicuro/a di voler continuare?", + "team_kickConfirm": "{0} saprà che l'hai rimosso/a dal gruppo. Sei sicuro/a?", + "team_ownerConfirm": "I co-proprietari e le co-proprietarie possono modificare o eliminare il gruppo e rimuoverti come proprietario/a. Vuoi continuare?", + "owner_team_add": "{0} propone tu sia proprietario/a del gruppo {1}. Accetti?", + "team_maxTeams": "Ogni utente può essere membro di {0} gruppi al massimo.", + "team_listSlot": "Posizione disponibile per un nuovo gruppo", "team_rosterPromoteOwner": "Proponi di diventare proprietario/a", - "team_deleteTitle": "Eliminazione del team", - "team_deleteHint": "Elimina il team e tutti i documenti di sua esclusiva proprietà.", - "team_quota": "Il limite dello spazio di archiviazione del tuo team", + "team_deleteTitle": "Elimina il gruppo", + "team_deleteHint": "Elimina il gruppo e tutti i documenti di sua esclusiva proprietà.", + "team_quota": "Il limite dello spazio di archiviazione del tuo gruppo", "properties_passwordWarningFile": "La password è stata modificata con successo ma non è stato possibile aggiornare il tuo CryptDrive con i nuovi dati. Potrebbe essere necessario rimuovere la vecchia versione del file manualmente.", "teams_table_generic_admin": "Gestione dei membri: invita e rimuovi membri, modifica il ruolo dei membri fino a quello di amministratore/trice.", "share_contactPasswordAlert": "Questo elemento è protetto da una password. Siccome lo stai condividendo con un contatto CryptPad, il destinatario o la destinataria non dovrà inserire la password.", "share_copyProfileLink": "Copia il link del profilo", - "team_inviteLinkTitle": "Crea un invito personalizzato per questo team", + "team_inviteLinkTitle": "Crea un invito personalizzato per questo gruppo", "team_inviteLinkSetPassword": "Proteggi il link con una password (consigliato)", "team_inviteLinkTempName": "Nome temporaneo (visibile nella lista degli inviti in attesa)", - "team_inviteLinkWarning": "Le persone che accedono a questo link potranno unirsi al team e vedere i suoi contenuti. Condividilo con prudenza.", + "team_inviteLinkWarning": "Le persone che accedono a questo link potranno entrare nel gruppo e vedere i suoi contenuti. Condividilo con prudenza.", "team_invitePasswordLoading": "Decriptazione dell'invito in corso", - "team_inviteGetData": "Ricezione di dati del team in corso", - "team_inviteTitle": "Invito al team", + "team_inviteGetData": "Ricezione di dati del gruppo in corso", + "team_inviteTitle": "Invito a un gruppo", "burnAfterReading_linkBurnAfterReading": "Visualizza una sola volta, poi autodistruggi", "oo_sheetMigration_loading": "È in corso l'aggiornamento del documento all'ultima versione, ci vorrà un minuto.", "settings_cat_security": "Sicurezza e privacy", @@ -856,25 +856,25 @@ "admin_activePadsHint": "Numero di documenti attualmente aperti (visualizzato o in fase di modifica)", "team_admins": "Amministratori/trici", "team_cat_admin": "Amministrazione", - "team_kickedFromTeam": "{0} ti ha espulso/a dal team: {1}", - "team_infoContent": "Ogni team dispone di un proprio CryptDrive, spazio di archiviazione, chat ed elenco membri. I proprietari e le proprietarie hanno la facoltà di eliminare il team, gli amministratori e le amministratrici possono invitare o espellere i membri, mentre i membri possono decidere di lasciare il team.", + "team_kickedFromTeam": "{0} ti ha rimosso/a dal gruppo: {1}", + "team_infoContent": "Ogni gruppo dispone di un proprio CryptDrive (e relativo spazio di archiviazione), di una propria chat e del proprio elenco membri. I proprietari e le proprietarie hanno la facoltà di eliminare il gruppo, gli amministratori e le amministratrici possono invitare nuovi membri o rimuovere quelli esistenti, mentre i membri possono decidere di uscire dal gruppo.", "team_owner": "Proprietari/e", - "team_leaveConfirm": "Se lasci questo team perderai l'accesso al suo CryptDrive, alla cronologia della chat ed agli altri contenuti. Sei sicuro/a?", + "team_leaveConfirm": "Se esci da questo gruppo perderai l'accesso al suo CryptDrive, alla cronologia della chat ed agli altri contenuti. Sei sicuro/a?", "team_cat_drive": "Drive", - "team_declineInvitation": "{0} ha rifiutato la tua proposta di unirsi al team: {1}", - "team_invitedToTeam": "{0} ti ha invitato a unirsi suo team: {1}", - "team_pcsSelectHelp": "La creazione di un documento di tua proprietà nel drive del team ne attribuisce la proprietà al team stesso.", - "team_pickFriends": "Scegli i contatti da invitare a questo team", + "team_declineInvitation": "{0} ha rifiutato la tua proposta di entrare nel gruppo: {1}", + "team_invitedToTeam": "{0} ti ha invitato ad entrare nel suo gruppo: {1}", + "team_pcsSelectHelp": "La creazione di un documento di tua proprietà nel drive del gruppo ne attribuisce la proprietà al gruppo stesso.", + "team_pickFriends": "Scegli i contatti da invitare in questo gruppo", "owner_removed": "{0} ha rimosso i tuoi diritti di proprietà per {1}", "owner_request_declined": "{0} ha rifiutato la tua proposta di diventare proprietario/a di {1}", "owner_add": "{0} propone tu sia proprietario/a del documento {1} Accetti?", "owner_request_accepted": "{0} ha accettato la tua proposta di diventare proprietario/a di {1}", - "share_linkTeam": "Aggiungi al drive del team", + "share_linkTeam": "Aggiungi al drive del gruppo", "owner_removedPending": "{0} ha rifiutato la tua proposta di essere co-proprietario/a di {1}", "team_pcsSelectLabel": "Salva in", - "team_acceptInvitation": "{0} ha accettato la tua proposta di unirsi al team: {1}", - "team_infoLabel": "Informazioni sui team", - "team_rosterKick": "Espelli dal team", + "team_acceptInvitation": "{0} ha accettato la tua proposta di entrare nel gruppo: {1}", + "team_infoLabel": "Informazioni sui gruppi", + "team_rosterKick": "Rimuovi dal gruppo", "team_rosterPromote": "Promuovi", "team_inviteButton": "Invita membri", "owner_request": "{0} propone tu sia proprietario/a di {1}", @@ -943,7 +943,7 @@ "support_cat_other": "Altro", "support_cat_account": "Account", "user_about": "Informazioni su CryptPad", - "support_languagesPreamble": "Il team di supporto parla le seguenti lingue:", + "support_languagesPreamble": "La squadra di assistenza parla le seguenti lingue:", "slide_textCol": "Colore del testo", "slide_backCol": "Colore di sfondo", "toolbar_file": "File", @@ -961,9 +961,9 @@ "fm_restricted": "Non hai accesso", "fm_emptyTrashOwned": "Il cestino contiene documenti di tua proprietà. Puoi rimuoverli solo dal tuo drive oppure distruggerli per tutti gli utenti e tutte le utenti.", "oo_refresh": "Aggiorna", - "notification_folderSharedTeam": "{0} ha condiviso una cartella con il team {2}: {1}", - "notification_fileSharedTeam": "{0} ha condiviso un file con il team {2}: {1}", - "notification_padSharedTeam": "{0} ha condiviso un documento con il team {2}: {1}", + "notification_folderSharedTeam": "{0} ha condiviso una cartella con il gruppo {2}: {1}", + "notification_fileSharedTeam": "{0} ha condiviso un file con il gruppo {2}: {1}", + "notification_padSharedTeam": "{0} ha condiviso un documento con il gruppo {2}: {1}", "support_cat_all": "Tutto", "support_cat_bug": "Segnalazione di errori", "support_cat_data": "Perdita di dati", @@ -1026,18 +1026,18 @@ "unableToDisplay": "Impossibile visualizzare il documento. Premi Esc per ricaricare la pagina. Se il problema persiste, contatta l'assistenza.", "documentID": "Codice identificativo del documento", "error_unhelpfulScriptError": "Errore di script: vedi la console del browser per i dettagli", - "loading_state_5": "Ricostruisci documento", - "loading_state_4": "Carica i team", - "loading_state_3": "Carica cartelle condivise", - "loading_state_2": "Aggiorna contenuto", - "loading_state_1": "Carica drive", - "loading_state_0": "Crea interfaccia", + "loading_state_5": "Ricostruzione del documento", + "loading_state_4": "Caricamento dei gruppi", + "loading_state_3": "Caricamento delle cartelle condivise", + "loading_state_2": "Aggiornamento del contenuto", + "loading_state_1": "Caricamento del drive", + "loading_state_0": "Creazione dell'interfaccia", "fm_shareFolderPassword": "Proteggi questa cartella con una password (facoltativo)", "access_destroyPad": "Distruggi questo documento o questa cartella in modo permanente", "fm_deletedFolder": "Cartella eliminata", "team_exportButton": "Scarica", - "team_exportHint": "Scarica tutti i documenti nel drive del tuo team. Ove possibile i documenti saranno scaricati in formati leggibili da altre applicazioni. Quando questo non sarà possibile, i documenti verranno scaricati in formati leggibili da CryptPad.", - "team_exportTitle": "Scarica il drive del team", + "team_exportHint": "Scarica tutti i documenti presenti nel drive del tuo gruppo. Ove possibile i documenti saranno scaricati in formati leggibili da altre applicazioni. Quando questo non sarà possibile, i documenti verranno scaricati in formati leggibili da CryptPad.", + "team_exportTitle": "Scarica il drive del gruppo", "admin_cat_quota": "Spazio di archiviazione per gli/le utenti", "admin_invalLimit": "Limite non valido", "admin_setlimitTitle": "Applica un limite personalizzato", @@ -1049,9 +1049,9 @@ "admin_limit": "Limite attuale: {0}", "admin_setlimitButton": "Imposta limite", "admin_defaultlimitTitle": "Limite dello spazio di archiviazione (MB)", - "admin_defaultlimitHint": "Limite massimo dello spazio di archiviazione dei CryptDrive (utenti e team) se non vengono applicate regole personalizzate", + "admin_defaultlimitHint": "Limite massimo dello spazio di archiviazione dei CryptDrive (per utenti e gruppi) se non vengono applicate regole personalizzate", "admin_registrationTitle": "Chiudi le registrazioni", - "admin_registrationHint": "Non consentire la registrazione di nuovi/e utenti", + "admin_registrationHint": "Chi visita l'istanza non ha l'autorizzazione per creare un account. Gli inviti possono essere creati dagli amministratori o dalle amministratrici.", "snapshots_cantMake": "Impossibile creare l'istantanea. Sei disconnesso/a.", "snapshots_notFound": "Questa istantanea non esiste più perché la cronologia del documento è stata eliminata.", "snapshot_error_exists": "È già presente un'istantanea di questa versione", @@ -1158,7 +1158,7 @@ "settings_deleteSubscription": "Gestisci il mio abbonamento", "calendar_weekNumber": "Settimana {0}", "calendar_day": "Giorno", - "calendar_deleteTeamConfirm": "Sicuro/a di voler eliminare questo calendario dal team?", + "calendar_deleteTeamConfirm": "Sicuro/a di voler eliminare questo calendario dal gruppo?", "calendar_deleteOwned": "Rimarrà visibile agli altri ed alle altre utenti con cui è stato condiviso.", "calendar_loc": "Posizione", "calendar_allDay": "Tutto il giorno", @@ -1329,19 +1329,19 @@ "calendar_settings": "Impostazioni del calendario", "date": "Data", "context_menu": "Azioni della cartella", - "team_nameTooLong": "Il nome del team è troppo lungo (massimo 50 caratteri)", - "team_nameAlreadySet": "Il nome del team è già {0}", + "team_nameTooLong": "Il nome del gruppo è troppo lungo (massimo 50 caratteri)", + "team_nameAlreadySet": "Il nome del gruppo è già {0}", "dph_reason": "Motivo: {0}", "dph_sf_destroyed": "La cartella condivisa {0} è stata distrutta da un proprietario o una proprietaria", - "dph_sf_destroyed_team": "La cartella condivisa {0} nel team drive {1} è stata distrutta da un proprietario o una proprietaria", + "dph_sf_destroyed_team": "La cartella condivisa {0} nel drive del gruppo {1} è stata distrutta da un proprietario o una proprietaria", "dph_default": "Questo contenuto non è più disponibile", - "dph_account_moderated": "Questo account è stato sospeso dal team di moderazione", + "dph_account_moderated": "Questo account è stato sospeso dalla squadra di moderazione", "dph_account_pw": "La password di questo account è stata modificata", "dph_account_inactive": "Questo account è stato eliminato per inattività", "dph_pad_inactive": "Questo documento è stato eliminato per inattività", - "dph_pad_moderated": "Questo documento è stato eliminato dal team di moderazione", + "dph_pad_moderated": "Questo documento è stato eliminato dalla squadra di moderazione", "dph_tmp_destroyed": "Questo modello è stato distrutto da un proprietario o una proprietaria", - "dph_tmp_moderated": "Questo modello è stato eliminato dal team di moderazione", + "dph_tmp_moderated": "Questo modello è stato eliminato dalla squadra di moderazione", "dph_tmp_moderated_account": "Questo modello è stato eliminato perché appartenente ad un account sospeso", "access_passwordUsed": "Questa password è già stata usata per questo documento. Non può essere riutilizzata.", "status": "Pagina di stato", @@ -1360,7 +1360,7 @@ "support_warning_bug": "Specifica in quale browser si verifica il problema e se sono installate estensioni. Fornisci il maggior numero possibile di dettagli sul problema e sui passaggi necessari per riprodurlo", "ui_openDirectly": "Questa funzionalità non è disponibile quando CryptPad è incorporato in un altro sito. Vuoi aprire questo documento in una nuova scheda?", "fivehundred_internalServerError": "Errore interno del server", - "support_cat_drives": "Drive o team", + "support_cat_drives": "Drive o gruppi", "support_cat_document": "Documento", "support_cat_abuse": "Segnalazione di abuso", "error_embeddingDisabledSpecific": "L'incorporamento è disabilitato per questa applicazione di CryptPad.", @@ -1531,7 +1531,7 @@ "form_condition_v": "Scegli una risposta", "support_warning_account": "Si noti che gli amministratori e le amministratrici non sono in grado di reimpostare le password. Se hai perso le credenziali del tuo account e sei ancora connesso/a, puoi migrare i tuoi dati su un nuovo account", "ui_ms": "millisecondi", - "og_teamDrive": "Drive del team", + "og_teamDrive": "Drive del gruppo", "og_default": "CryptPad: suite di collaborazione criptata end-to-end", "calendar_rec_stop": "Interrompi la ripetizione", "calendar_rec_freq_weekly": "settimane", @@ -1543,7 +1543,7 @@ "mfa_revoke_code": "Inserisci il codice di verifica", "settings_otp_tuto": "Scansiona questo codice QR con la tua app di autenticazione e inserisci il codice di verifica per confermare.", "oo_cantMigrate": "Questo foglio supera la dimensione massima di caricamento ed è troppo grande per essere migrato.", - "team_leaveOwner": "Prima uscire dal team devi abbandonare il ruolo di proprietario/a. Dato che i team devono avere almeno un proprietario o una proprietaria, se sei attualmente l'unico/a dovrai aggiungere un proprietario o una proprietaria prima di proseguire.", + "team_leaveOwner": "Prima uscire dal gruppo devi abbandonare il ruolo di proprietario/a. Dato che i gruppi devono avere almeno un proprietario o una proprietaria, se sei attualmente l'unico/a dovrai aggiungere un proprietario o una proprietaria prima di proseguire.", "form_condorcetExtendedDisplay": "Numero di partite vinte da ogni candidato/a: ", "recovery_mfa_error": "Errore sconosciuto. Ricarica e prova di nuovo.", "mfa_revoke_button": "Conferma la disattivazione dell'autenticazione a due fattori", @@ -1593,7 +1593,7 @@ "admin_emailHint": "Imposta l'email di contatto per la tua istanza", "admin_totpRecoveryHint": "Gli/le utenti possono copiare i dati di recupero nella pagina di ripristino dell'autenticazione a due fattori /recovery/ e inviarli via email agli amministratori e alle amministratrici dell'istanza. Incolla i dati di recupero qui sotto per disabilitare l'autenticazione a due fattori per un account", "admin_totpRecoveryMethod": "Metodo di recupero dell'autenticazione a due fattori", - "admin_consentToContactHint": "La telemetria del server include l'email di contatto dell'amministratore/trice, in modo che il team di sviluppo ti possa informare di eventuali problemi gravi con il software o con la tua configurazione. L'email non sarà mai condivisa, venduta o utilizzata per scopi di marketing. Acconsenti ad essere contattato/a se desideri essere informato/a di problemi critici con il tuo server.", + "admin_consentToContactHint": "La telemetria del server include l'email di contatto dell'amministratore/trice, in modo che la squadra di sviluppo ti possa informare di eventuali problemi gravi con il software o con la tua configurazione. L'email non sarà mai condivisa, venduta o utilizzata per scopi di marketing. Acconsenti ad essere contattato/a se desideri essere informato/a di problemi critici con il tuo server.", "admin_invitationCreate": "Crea link d'invito", "admin_registrationSsoTitle": "Chiudi le registrazioni con SSO", "admin_invitationTitle": "Link d'invito", @@ -1613,14 +1613,14 @@ "admin_supportInitGenerate": "Genera le chiavi dell'assistenza", "admin_supportPrivButton": "Mostra chiave", "admin_checkupButton": "Esegui la diagnostica", - "admin_removeDonateButtonHint": "Lo sviluppo di CryptPad è in parte finanziato da sovvenzioni e donazioni pubbliche. Pubblicizzare la nostra campagna di raccolta fondi sulla tua istanza aiuta il team di sviluppo a continuare il proprio lavoro. Se li ritieni inopportuni, puoi disattivare questi avvisi.", + "admin_removeDonateButtonHint": "Lo sviluppo di CryptPad è in parte finanziato da sovvenzioni e donazioni pubbliche. Pubblicizzare la nostra campagna di raccolta fondi sulla tua istanza aiuta la squadra di sviluppo a continuare il proprio lavoro. Se li ritieni inopportuni, puoi disattivare questi avvisi.", "admin_removeDonateButtonLabel": "Non pubblicizzare le campagne di raccolta fondi", "admin_blockDailyCheckTitle": "Telemetria del server", - "admin_blockDailyCheckHint": "Le istanze di CryptPad inviano un messaggio al server del team di sviluppo quando vengono installate e successivamente una volta al giorno. Ciò consente al team di sapere quali versioni del software sono in circolazione. Puoi disattivare la raccolta di questi dati qui sotto. Il contenuto dei messaggi può essere esaminato nel registro del server dell'applicazione.", + "admin_blockDailyCheckHint": "Le istanze di CryptPad inviano un messaggio al server della squadra di sviluppo quando vengono installate e successivamente una volta al giorno. Ciò consente alla squadra di sviluppo di sapere quali versioni del software sono in circolazione. Puoi disattivare la raccolta di questi dati qui sotto. Il contenuto dei messaggi può essere esaminato nel registro del server dell'applicazione.", "admin_consentToContactLabel": "Acconsento", "admin_blockDailyCheckLabel": "Disattiva la telemetria del server", "admin_provideAggregateStatisticsTitle": "Aggregazione statistica", - "admin_provideAggregateStatisticsHint": "Puoi scegliere di fornire al team di sviluppo ulteriori metriche di utilizzo, come i numeri approssimativi di utenti registrati/e e di utenti giornalieri/e della tua istanza.", + "admin_provideAggregateStatisticsHint": "Puoi scegliere di fornire alla squadra di sviluppo ulteriori metriche di utilizzo, come i numeri approssimativi di utenti registrati/e e di utenti giornalieri/e della tua istanza.", "admin_provideAggregateStatisticsLabel": "Fornisci statistiche aggregate", "admin_instancePurposeTitle": "Obiettivo dell'istanza", "admin_purpose_noanswer": "Preferisco non rispondere", @@ -1679,12 +1679,12 @@ "calendar_rec_warn_delall": "Questo evento non si ripeterà più. Il primo evento del {0} sarà mantenuto, tutti gli altri saranno rimossi.", "calendar_rec_warn_updateall": "La regola per la ripetizione di questo evento è stata modificata. Il primo evento del {0} sarà mantenuto, tutti gli altri saranno sostituiti.", "admin_forcemfaTitle": "Autenticazione a due fattori obbligatoria", - "admin_forcemfaHint": "A tutti gli utenti e tutte le utenti di questa istanza verrà chiesto di impostare l'autenticazione a due fattori per accedere al proprio account.", + "admin_forcemfaHint": "A tutti gli utenti e tutte le utenti di questa istanza verrà chiesto di impostare l'autenticazione a due fattori per accedere al proprio account. Gli/le utenti esistenti non potranno continuare a utilizzare il proprio account senza prima impostare un'applicazione TOTP.", "admin_cat_security": "Sicurezza", "admin_cat_customize": "Aspetto", "admin_colorTitle": "Colore principale", "admin_logoTitle": "Logo personalizzato", - "admin_supportAdd": "Aggiungi un contatto al team di assistenza", + "admin_supportAdd": "Aggiungi un contatto alla squadra di assistenza", "admin_logoHint": "SVG, PNG o JPG, dimensione massima 200KB", "admin_colorCurrent": "Colore principale attuale", "admin_logoButton": "Carica nuovo logo", @@ -1697,18 +1697,18 @@ "admin_supportEnabled": "Il sistema di assistenza è abilitato.", "support_cat_settings": "Impostazioni", "admin_supportDisabled": "Il sistema di assistenza è disabilitato.", - "admin_supportMembers": "Team di assistenza", + "admin_supportMembers": "Squadra di assistenza", "support_cat_search": "Cerca", "support_privacyTitle": "Rispondi in modo anonimo", "support_notificationsTitle": "Disattiva le notifiche", "support_userKey": "Chiave pubblica dell'utente", "support_invalChan": "Canale di notifica non valido", - "admin_supportTeamTitle": "Gestisci il team di assistenza", - "admin_supportTeamHint": "Aggiungi e rimuovi persone dal team di assistenza dell'istanza", + "admin_supportTeamTitle": "Gestisci la squadra di assistenza", + "admin_supportTeamHint": "Aggiungi e rimuovi persone dalla squadra di assistenza dell'istanza", "support_pasteUserData": "Incolla qui i dati dell'utente", "support_recordedContent": "Contenuto", "support_answerAs": "Stai rispondendo come {0}", - "support_team": "Il team di assistenza", + "support_team": "La squadra di assistenza", "support_movePending": "Sposta nell'archivio", "admin_supportOpen": "Apri il servizio d'assistenza", "moderationPage": "Servizio d'assistenza", @@ -1717,10 +1717,10 @@ "admin_cat_users": "Elenco utenti", "admin_invitationCopy": "Copia link", "admin_usersRemove": "Rimuovi", - "support_privacyHint": "Seleziona questa opzione per rispondere come \"Il team di assistenza\" invece che con il tuo nome utente", + "support_privacyHint": "Seleziona questa opzione per rispondere come \"La squadra di assistenza\" invece che con il tuo nome utente", "earlyAccessBlocked": "Questa applicazione non è ancora disponibile su questa istanza", "admin_invitationHint": "I link d'invito permettono di creare un account ciascuno, anche se le registrazioni sono chiuse. Il nome utente e l'email sono solo a scopo di identificazione. CryptPad non invierà il link d'invito (o qualsiasi altra comunicazione) via email. Copia il link qui sotto e invialo utilizzando un canale sicuro di tua scelta.", - "admin_supportRotateNotify": "Attenzione: le nuove chiavi sono state generate ma un errore imprevisto ha impedito al sistema di inviarle ai moderatori ed alle moderatrici. Si prega di rimuovere e aggiungere nuovamente tutti i membri del team di assistenza", + "admin_supportRotateNotify": "Attenzione: le nuove chiavi sono state generate ma un errore imprevisto ha impedito al sistema di inviarle ai moderatori ed alle moderatrici. Si prega di rimuovere e aggiungere nuovamente tutti i membri della squadra di assistenza", "pad_tocHide": "Indice", "admin_consentToContactTitle": "Acconsenti ad essere contattato/a", "resources_learnWhy": "Per saperne di più sulle immagini bloccate", @@ -1734,5 +1734,12 @@ "support_recordedEmpty": "Nessuna scorciatoia", "support_legacyTitle": "Visualizza i vecchi dati dell'assistenza", "support_legacyDump": "Esporta tutte", - "support_insertRecorded": "Inserisci scorciatoia" + "support_insertRecorded": "Inserisci scorciatoia", + "pad_goToAnchor": "Vai all'ancora", + "onboarding_upload": "Seleziona un logo", + "onboarding_save_error": "Non è stato possibile salvare correttamente alcune opzioni. Visita il pannello di amministrazione per verificare i valori indicati.", + "admin_onboardingNameHint": "Scegli un titolo, una descrizione, un colore principale ed un logo (sono tutti facoltativi)", + "admin_onboardingNameTitle": "Benvenut* nella tua istanza di CryptPad", + "admin_onboardingNamePlaceholder": "Titolo dell'istanza", + "admin_onboardingDescPlaceholder": "Descrizione dell'istanza" } diff --git a/www/common/translations/messages.ja.json b/www/common/translations/messages.ja.json index 13d5192316..3d97926a6c 100644 --- a/www/common/translations/messages.ja.json +++ b/www/common/translations/messages.ja.json @@ -1138,7 +1138,7 @@ "notification_fileSharedTeam": "{0}がチーム({2})とファイル({1})を共有しました", "notification_padSharedTeam": "{0}がチーム({2})とドキュメント({1})を共有しました", "requestEdit_accepted": "{1}があなたにドキュメント({0})の編集権を付与しました", - "admin_registrationHint": "ユーザーの新規登録を許可しない", + "admin_registrationHint": "このインスタンスの訪問者にはアカウントの作成が許可されていません。管理人は招待を行うことができます。", "oo_deletedVersion": "このバージョンは履歴に存在しません。", "team_pickFriends": "チームに招待する連絡先を選択", "notification_fileShared": "{0}があなたとファイルを共有しました: {1}", @@ -1692,7 +1692,7 @@ "calendar_rec_change": "繰り返しのイベントを異なるカレンダーに移動。この変更は、このイベントまたは全ての繰り返しのイベントに適用できます。", "dph_pad_pw": "このドキュメントはパスワードにより保護されています", "access_passwordUsed": "このパスワードは既にこのドキュメントに使用されています。再度利用することはできません。", - "admin_forcemfaHint": "このインスタンスの全てのユーザーに、ログイン用の二要素認証を設定するように要求します。", + "admin_forcemfaHint": "このインスタンスの全てのユーザーに、ログイン用の二要素認証を設定するように要求します。なお、既存のユーザーが使用を継続するには、二要素認証のアプリケーションを設定する必要があります。", "admin_invitationHint": "招待リンクは、登録が締め切られている場合でもアカウントを1つ作成します。ユーザー名とメールアドレスは、ユーザーの特定用にのみ使用します。CryptPadは、招待リンクその他を電子メールで送信することはありません。リンクをコピーし、何か安全な方法でリンクを送信してください。", "admin_usersHint": "このインスタンスの既知のアカウントの一覧。以下を選択してアカウントを自動的に追加するか、フォームから手動で情報を入力してください。", "admin_diskUsageWarning": "注意して使用してください!このインスタンスに保存されているデータのサイズ次第では、このレポートを作成すると、サーバーで利用可能な全てのメモリー量を使用し、クラッシュする可能性があります。", @@ -1754,5 +1754,22 @@ "admin_supportInit": "このインスタンスのヘルプデスクを初期化", "admin_supportRotateNotify": "警告:新しい鍵が作成されましたが、不明なエラーのため、モデレーターに送信できませんでした。サポートチームの全メンバーを削除し、改めて追加してください", "admin_supportTeamHint": "インスタンスのサポートチームにメンバーを追加したり、サポートチームからメンバーを削除したりできます", - "admin_colorHint": "あなたのCryptPadインスタンスのアクセントの色を変更できます。ライトテーマとダークテーマの両方で、テキストとボタンが十分なコントラストを備えており、可読性があることを確認してください。" + "admin_colorHint": "あなたのCryptPadインスタンスのアクセントの色を変更できます。ライトテーマとダークテーマの両方で、テキストとボタンが十分なコントラストを備えており、可読性があることを確認してください。", + "support_recordedHint": "共通のテキストをショートカットとして保存すると、クリックでサポート用メッセージに挿入することができます。", + "install_token": "インストール用のトークン", + "install_header": "インストール", + "admin_appSelection": "アプリケーションの設定", + "install_launch": "インスタンスのセットアップ", + "admin_appsTitle": "インスタンスのアプリケーション", + "admin_appsHint": "このインスタンスで有効にするアプリケーションを選択してください。", + "admin_cat_apps": "アプリケーション", + "onboarding_upload": "ロゴを選択してください", + "onboarding_save_error": "いくつかの設定を適切に保存できませんでした。管理用パネルから値を確認してください。", + "admin_onboardingNameHint": "タイトル、説明文、アクセントの色、ロゴを選択してください(全て任意です)", + "admin_onboardingNameTitle": "あなたのCryptPadのインスタンスにようこそ", + "support_legacyHint": "旧式のサポートシステムのチケットを表示した後、新しいサポートシステムで改めてチケットを作成します。", + "admin_onboardingOptionsTitle": "インスタンスのオプション", + "admin_onboardingOptionsHint": "インスタンスに適切な設定を選択してください。
これらの設定は、後から管理者パネルで変更できます。", + "admin_onboardingNamePlaceholder": "インスタンスのタイトル", + "admin_onboardingDescPlaceholder": "インスタントの説明文" } diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json index 900de6aed3..a36fced883 100644 --- a/www/common/translations/messages.json +++ b/www/common/translations/messages.json @@ -1004,7 +1004,7 @@ "snapshot_error_exists": "There is already a snapshot of this version", "snapshots_notFound": "This snapshot no longer exists because the history of the document has been deleted.", "snapshots_cantMake": "The snapshot could not be created. You are disconnected.", - "admin_registrationHint": "Do not allow any new users to register", + "admin_registrationHint": "Visitors to the instance are not able to create accounts. Invitations can be created by administrators.", "admin_registrationTitle": "Close registration", "admin_defaultlimitHint": "Maximum storage limit for CryptDrives (users and teams) when no custom rule is applied", "admin_defaultlimitTitle": "Storage limit (MB)", @@ -1680,7 +1680,7 @@ "kanban_showTags": "See all tags", "kanban_hideTags": "See less tags", "admin_forcemfaTitle": "Mandatory Two-Factor Authentication", - "admin_forcemfaHint": "All users on this instance will be asked to set up two-factor authentication to log in to their account.", + "admin_forcemfaHint": "All users on this instance will be asked to set up two-factor authentication to log in to their account. Note that existing users will not be able to keep using their account without setting up a TOTP application.", "loading_mfa_required": "Two-factor authentication is required on this instance. Please update your account using an authenticator app and the form below.", "admin_cat_users": "User directory", "admin_registrationSsoTitle": "Close SSO registration", @@ -1763,5 +1763,22 @@ "support_copyUserData": "Copy user data", "support_insertRecorded": "Insert snippet", "admin_supportOpen": "Open help-desk", - "support_userNotification": "New support ticket or response: {0}" + "support_userNotification": "New support ticket or response: {0}", + "install_instance": "Create the first admin account, then proceed to customize this instance", + "install_token": "Installation token", + "install_header": "Installation", + "admin_appSelection": "App configuration", + "install_launch": "Instance setup", + "install_notes": "
  • Create your first administrator account on this page. Administrators manage instance settings including storage quotas, and have access to moderation tools.
  • Your password is the secret key that encrypts all of your documents and administrator privileges on this instance. If you lose it there is no way we can recover your data.
  • If you are using a shared computer, remember to log out when you are done. Only closing the browser window leaves your account exposed.
", + "admin_appsTitle": "Instance applications", + "onboarding_save_error": "Some options could not be saved properly. Please visit the administration panel to check the values.", + "admin_appsHint": "Choose which apps to enable on this instance.", + "admin_cat_apps": "Applications", + "onboarding_upload": "Select logo", + "admin_onboardingNameTitle": "Welcome to your CryptPad instance", + "admin_onboardingNameHint": "Please choose a title, description, accent color and logo (all are optional)", + "admin_onboardingOptionsTitle": "Instance options", + "admin_onboardingOptionsHint": "Please select the appropriate option for your instance.
These settings can be changed later in the admin panel.", + "admin_onboardingNamePlaceholder": "Instance title", + "admin_onboardingDescPlaceholder": "Instance description text" } diff --git a/www/common/translations/messages.ko.json b/www/common/translations/messages.ko.json index e95b0b9e68..110cd1e3c8 100644 --- a/www/common/translations/messages.ko.json +++ b/www/common/translations/messages.ko.json @@ -1,5 +1,116 @@ { "type": { - "drive": "CryptDrive" - } + "drive": "CryptDrive", + "slide": "마크다운(MD) 슬라이드", + "file": "파일", + "media": "미디어", + "todo": "할 일", + "form": "양식", + "teams": "협업", + "doc": "문서", + "presentation": "발표자료", + "diagram": "다이어그램", + "kanban": "칸반", + "poll": "투표", + "whiteboard": "화이트보드", + "contacts": "연락처", + "sheet": "시트" + }, + "formattedMB": "{0} MB", + "propertiesButtonTitle": "문서 속성 받기", + "onLogout": "로그아웃 하셨습니다, {0}여기를 누르세요{1} 로그인 하기
아니면 'ESC' 버튼을 눌러 읽기전용 모드로 문서를 여십시오.", + "padNotPinnedVariable": "해당문서는 {4}일 후 비활성 파기됩니다, {0}로그인{1} or {2}등록{3} 하여 권한을 얻기.", + "expiredError": "해당문서는 파기기한이 도래하여 더 이상 사용이 불가능 합니다.", + "deletedError": "해당 문서는 이미 삭제되어 활용이 불가능 합니다.", + "inactiveError": "해당 문서는 비활성 파기되었습니다. 'ESC' 버튼을 눌러 새 문서를 작성하세요.", + "invalidHashError": "요청한 문서의 URL이 유효하지 않습니다.", + "errorCopy": " 'ESC'버튼을 누르면 읽기전용모드로 계속 사용 가능합니다.", + "errorRedirectToHome": "'ESC'버튼을 눌러 당신의 CryptDrive로 되돌아가십시오.", + "loading": "불러오는 중...", + "error": "오류", + "saved": "저장 됨", + "deleted": "삭제됨", + "deletedFromServer": "문서 파괴됨", + "mustLogin": "해당 페이지에 접근하려면 로그인 하셔야 합니다", + "realtime_unrecoverableError": "회생불가의 오류 발생. 'OK'를 눌러 다시 불러오세요.", + "disconnected": "연결 해제", + "synchronizing": "동기화 중", + "reconnecting": "재연결 중", + "typing": "편집", + "initializing": "초기화 중...", + "forgotten": "쓰레기통으로 이동", + "errorState": "치명적 오류: {0}", + "readonly": "읽기 전용", + "anonymous": "손님", + "users": "사용자", + "viewer": "관찰자", + "viewers": "관찰자들", + "editor": "편집기", + "userlist_offline": "현재 오프라인상태라 사용자목록을 이용 불가능 합니다.", + "language": "언어", + "upgrade": "승급", + "upgradeAccount": "계정 승급", + "GB": "GB", + "KB": "kB", + "formattedGB": "{0} GB", + "pinLimitReached": "저장소 용량이 가득 찼습니다", + "pinLimitReachedAlertNoAccounts": "저장소 용량이 가득찼습니다", + "pinLimitNotPinned": "저장소 용량이 가득 찼습니다.
CryptDrive에 새로운 문서를 저장 할 수 없습니다.", + "pinLimitDrive": "저장소 용량이 가득 찼습니다. CryptDrive에 새로운 문서를 생성 할 수 없습니다.", + "importButton": "불러오기", + "importButtonTitle": "로컬파일을 불러오기", + "exportButton": "내보내기", + "exportButtonTitle": "로컬 파일로 내보내기", + "exportPrompt": "선호하는 파일명은?", + "user_rename": "표시명 변경", + "user_displayName": "표시명", + "user_accountName": "계정명", + "saveTitle": "제목저장 (enter)", + "forgetButton": "삭제", + "movedToTrash": "파일을 쓰레기통에 있습니다.
내 저장소에 접속하세요", + "shareButton": "공유하기", + "shareSuccess": "링크를 클립보드에 저장", + "userListButton": "사용자 목록", + "chatButton": "말하기", + "userAccountButton": "사용자 메뉴", + "newButton": "새", + "newButtonTitle": "새 문서 생성", + "uploadButton": "파일들 올리기", + "uploadFolderButton": "폴더를 올리기", + "saveTemplateButton": "양식으로 저장", + "saveTemplatePrompt": "양식의 제목 고르기", + "templateSaved": "양식 저장됨!", + "selectTemplate": "양식으로 저장하거나 ESC 누르기", + "useTemplate": "양식으로 시작하겠습니까?", + "useTemplateCancel": "새로운 시작 (ESC)", + "template_import": "양식 불러오기", + "template_empty": "가용 양식이 없어", + "previewButtonTitle": "마크다운(MD) 미리보기 표시 or 숨기기", + "presentButtonTitle": "발표모드 진입", + "colorButtonTitle": "발표모드 글 색상 변경", + "propertiesButton": "속성들", + "printText": "출력", + "printButton": "출력 (enter)", + "printButtonTitle2": "문서 출력 하거나 PDF파일로 변환하기", + "printOptions": "층 설정", + "printSlideNumber": "판 번호 표시", + "printDate": "날짜 표시", + "printTitle": "문서 제목 표시", + "formattedKB": "{0}kB", + "common_connectionLost": "서버 연결 실패
연결이 재개되기 전까지 읽기전용 모드로 사용됩니다.", + "typeError": "해당 어플리캐이션에서 이 문서가 호환되지 않습니다", + "padNotPinned": "해당문서는 3개월 이상 비활성되어 파기되었습니다, {0}로그인{1} or {2}등록{3} 하여 권한을 얻기.", + "anonymousStoreDisabled": "CryptPad의 관리자가 '게스트'에게 저장공간 사용을 금지하였습니다. 로그인하여 당신의 CryptDrive에 접속하세요.", + "chainpadError": "중요한 판올림 오류 발생. 해당 페이지는 읽기전용 모드로 전환됩니다.
'ESC'버튼을 눌러 계속 보거나, 편집을 다시 시도하십시오.", + "newVersionError": "Crytpad의 새로운 버전이 사용 가능합니다.
새버전으로 다시 불러오기, 또는 'ESC'를 눌러 오프라인 모드로 접근하기.", + "disabledApp": "해당 어플리캐이션은 비활성화 되었습니다. 이 CryptPad의 관리자에게 연락하여 추가 정보를 얻으세요.", + "storageStatus": "Storage:
{0} 사용 중 {1}", + "MB": "MB", + "pinLimitReachedAlert": "저장소 용량이 가득 찼습니다. CryptDrive에 새로운 문서를 저장 할 수 없습니다.
CryptDrive의 문서를 지우거나 프리미엄을 구독하여 당신의 가용용량을 늘리세요.", + "clickToEdit": "눌러서 수정하기", + "forgetPrompt": "OK를 누르면 삭제됩니다. 확실한가요?", + "uploadButtonTitle": "당신의 CryptDrive에 새로운 파일을 올리기", + "useTemplateOK": "양식을 고르세요 (Enter)", + "backgroundButtonTitle": "발표 배경판 색상 변경", + "printCSS": "Custom style rules (CSS):" } diff --git a/www/common/translations/messages.ru.json b/www/common/translations/messages.ru.json index 909a3c5c88..15cd6f5da5 100644 --- a/www/common/translations/messages.ru.json +++ b/www/common/translations/messages.ru.json @@ -1130,7 +1130,7 @@ "admin_getlimitsTitle": "Индивидуальные лимиты", "admin_getlimitsHint": "Список все индивидуальных лимитов хранилища, применяемых в Вашем экземпляре.", "admin_defaultlimitHint": "Максимальный лимит хранилища для CryptDrives (пользователи и команды), когда не задано индивидуального правила", - "admin_registrationHint": "Не позволять новым пользователям регистрироваться", + "admin_registrationHint": "Посетители экземпляра не могут создавать учетные записи. Администраторы могут создавать приглашения на регистрацию.", "oo_version_latest": "Последняя", "history_restoreDrivePrompt": "Вы уверены, что хотите заменить текущую версию CryptDrive на отображаемую версию?", "history_restoreDriveTitle": "Восстановить выбранную версию CryptDrive", @@ -1622,7 +1622,7 @@ "ssoauth_form_hint_register": "Добавьте пароль CryptPad для дополнительной безопасности или оставьте пустым и продолжайте. Если вы не добавите пароль, ключи, защищающие ваши данные, будут доступны администраторам экземпляра.", "settings_removeOwnedHint": "Все документы, в которых Вы являетесь единственным владельцем, будут безвозвратно уничтожены", "admin_channelPlaceholder": "Заглушка на месте уничтоженного документа", - "admin_forcemfaHint": "Всем пользователям этого экземпляра будет предложено настроить двухфакторную аутентификацию для входа в свою учетную запись.", + "admin_forcemfaHint": "Всем пользователям этого экземпляра будет предложено настроить двухфакторную аутентификацию для входа в свою учетную запись. Обратите внимание, что существующие пользователи не смогут продолжать использовать свою учетную запись без настройки приложения TOTP.", "admin_totpDisable": "Отключение 2FA для этой учетной записи", "loading_enter_otp": "Эта учетная запись защищена двухфакторной аутентификацией. Пожалуйста, введите свой проверочный код", "calendar_rec_change": "Перенос повторяющегося события в другой календарь. Вы можете применить это изменение только к этому событию или ко всем повторам этого события.", @@ -1763,5 +1763,22 @@ "admin_supportSetupHint": "Создайте или обновите ключи поддержки.", "admin_supportMembers": "Команда Поддержки", "support_privacyHint": "Установите этот флажок, чтобы отвечать как «Команда Поддержки», а не от своего имени пользователя", - "support_openTicketHint": "Копирует данные пользователя-получателя со страницы его профиля или существующего запроса в службу поддержки. Они получат уведомление CryptPad об этом сообщении." + "support_openTicketHint": "Копирует данные пользователя-получателя со страницы его профиля или существующего запроса в службу поддержки. Они получат уведомление CryptPad об этом сообщении.", + "install_token": "Токен установки", + "install_header": "Установка", + "admin_appSelection": "Конфигурация приложений", + "install_launch": "Настройка экземпляра", + "admin_appsTitle": "Приложения на данном экземпляре", + "admin_appsHint": "Выберите, какие приложения включить на этом экземпляре.", + "admin_cat_apps": "Приложения", + "onboarding_upload": "Выберите логотип", + "admin_onboardingNameTitle": "Добро пожаловать в Ваш экземпляр CryptPad", + "install_notes": "
  • Создайте свою первую учетную запись администратора на этой странице. Администраторы управляют настройками экземпляра, включая квоты хранилища, и имеют доступ к инструментам модерации.
  • Ваш пароль — это секретный ключ, который шифрует все Ваши документы и права администратора в этом экземпляре. Если Вы потеряете его, мы не сможем восстановить Ваши данные.
  • Если Вы используете общий компьютер, не забывайте выходить из учётной записи Cryptpad , когда заканчиваете работу. Только лишь закрывая окно браузера Вы оставляете Вашу учетную запись открытой.
", + "onboarding_save_error": "Не удалось сохранить некоторые параметры должным образом. Пожалуйста, посетите панель администрирования, чтобы проверить значения.", + "admin_onboardingOptionsTitle": "Параметры экземпляра", + "admin_onboardingNameHint": "Пожалуйста, выберите название, описание, цветовой акцент и логотип (всё по желанию)", + "admin_onboardingOptionsHint": "Пожалуйста, выберите подходящий вариант для Вашего экземпляра.
Эти настройки можно изменить позже в панели администратора.", + "admin_onboardingNamePlaceholder": "Заголовок экземпляра", + "install_instance": "Создайте первую учетную запись администратора, а затем приступайте к настройке этого экземпляра", + "admin_onboardingDescPlaceholder": "Текст описания экземпляра" } diff --git a/www/common/translations/messages.tr.json b/www/common/translations/messages.tr.json index 5606fe98c5..62b85d2e5a 100644 --- a/www/common/translations/messages.tr.json +++ b/www/common/translations/messages.tr.json @@ -8,12 +8,16 @@ "file": "Dosya", "whiteboard": "Beyaz Tahta", "drive": "CryptSürücü", - "slide": "Sunum", + "slide": "Markdown Sunumları", "poll": "Anket", "code": "Kod", "pad": "Zengin metin biçimi", "doc": "Doküman", - "presentation": "Sunum" + "presentation": "Sunum", + "diagram": "Diyagram", + "sheet": "Sheet", + "kanban": "Kanban", + "form": "Form" }, "main_catch_phrase": "Ekip iş birliği sistemi,
şifreli ve açık kaynak", "main_title": "CryptPad: Tamamen Gizli, İşbirliğine Dayalı Gerçek Zamanlı Düzenleme", diff --git a/www/common/translations/messages.uk.json b/www/common/translations/messages.uk.json index 914fe77e58..dcd1cac5e2 100644 --- a/www/common/translations/messages.uk.json +++ b/www/common/translations/messages.uk.json @@ -104,7 +104,7 @@ "contacts_confirmRemove": "Точно вилучити {0} із контактів?", "contacts_remove": "Вилучити контакт", "contacts_send": "Надіслати", - "contacts_request": "{0} пропонує вам контакт. Прийняти?", + "contacts_request": "{0} хоче додати вас до списку контактів. Схвалити?", "contacts_rejected": "Запрошення на контакт відхилено", "contacts_added": "Запрошення на контакт прийнято.", "contacts_title": "Контакти", @@ -308,7 +308,7 @@ "settings_autostoreNo": "Запитувати", "settings_autostoreYes": "Автоматично", "settings_autostoreHint": "Автоматично Всі відкриті документи зберігатимуться до вашого CryptDrive-каталогу.
Запитувати Якщо ваш CryptDrive-каталог ще не містить документа, вам буде запропоновано його туди зберегти.
Вручну Жодних документів не буде автоматично збережено до CryptDrive-каталогу. Пропозицію їх зберегти буде сховано.", - "settings_autostoreTitle": "Зберігати до CryptDrive-каталогу", + "settings_autostoreTitle": "Зберігання документів у CryptDrive", "settings_importDone": "Імпорт завершено", "settings_importConfirm": "Точно імпортувати нещодавні документи цього переглядача до CryptDrive-каталогу вашого облікового запису?", "settings_import": "Імпорт", @@ -376,7 +376,7 @@ "login_unhandledError": "Виникла неочікувана помилка :(", "login_invalPass": "Пароль обов'язковий", "login_invalUser": "Псевдонім обов'язковий", - "login_noSuchUser": "Хибний псевдонім чи пароль. Повторіть спробу чи зареєструйтесь", + "login_noSuchUser": "Хибний псевдонім чи пароль", "login_hashing": "Триває хешування паролю, просимо зачекати.", "deleted": "Видалено", "saved": "Збережено", @@ -388,7 +388,7 @@ "invalidHashError": "Ви спробували переглянути документ за хибною URL-адресою.", "chainpadError": "При оновленні вашого файлу виникла критична помилка. Сторінку переведено до режиму читання, щоб ви не втратили своєї роботи.
Натисніть Esc, щоб переглянути документ, або перезавантажте, щоб продовжити редагування.", "inactiveError": "Документ видалено через бездіяльність. Натисніть Esc, щоб створити новий.", - "expiredError": "Документ стерто через бездіяльність.", + "expiredError": "Цей документ досягнув терміну знищення і більше не є доступним.", "deletedError": "Документ видалено.", "anonymousStoreDisabled": "Адміністрація цього сервера CryptPad вимкнула гостьовий доступ до файлів. Увійдіть, щоб доступитися свого CryptDrive-каталогу.", "padNotPinnedVariable": "Документ буде стерто через {4} дні бездіяльності. {0}Ввійдіть{1} або {2}зареєструйтесь{3}, щоб зберігати його довше.", @@ -412,7 +412,8 @@ "kanban": "Канбан", "poll": "Опитування", "code": "Код", - "pad": "Гіпертекст" + "pad": "Гіпертекст", + "diagram": "Діаграма" }, "main_title": "CryptPad: спільне редагування наживо — це просто", "upload_modal_owner": "Власний файл", @@ -518,7 +519,7 @@ "upload_cancelled": "Скасовано", "upload_pending": "Очікування", "upload_choose": "Обрати файл", - "upload_tooLargeBrief": "Файл перевищує межу: {0}МБ", + "upload_tooLargeBrief": "Файл перевищує обмеження {0}MB для цього диска", "upload_tooLarge": "Файл важить більше, ніж дозволено вивантажувати вашим обліковим записом.", "upload_notEnoughSpaceBrief": "Бракує простору", "upload_notEnoughSpace": "Простору в CryptDrive-каталозі недостатньо для цього файлу.", @@ -758,7 +759,7 @@ "settings_colorthemeTitle": "Палітра", "settings_cat_style": "Оформлення", "settings_cat_kanban": "Канбан", - "settings_cat_security": "Конфіденційність", + "settings_cat_security": "Безпека та конфіденційність", "slide_textCol": "Колір тексту", "slide_backCol": "Колір тла", "infobar_versionHash": "Ви переглядаєте стару версію цього документа ({0}).", @@ -777,7 +778,7 @@ "docs_link": "Документація", "creation_helperText": "Відкрити документацію", "creation_new": "Створити {0}", - "creation_expiresIn": "Чинно ще", + "creation_expiresIn": "Знищити в", "register_registrationIsClosed": "Реєстрацію закрито.", "admin_supportInitHelp": "Для вашого сервера ще не налаштовано скриньки підтримки. Якщо бажаєте отримувати на скриньку підтримки листи від користувачок і користувачів, попрохайте адміністрацію сервера запустити скрипт «./scripts/generate-admin-keys.js», додати відкритий ключ у файл «config.js» і надіслати вам закритий ключ.", "admin_cat_support": "Підтримка", @@ -820,11 +821,11 @@ "contact_devHint": "Щодо бажаних функцій, удосконалення інтерфейсу чи просто подяки.", "contact_dev": "Сконтактуйте з командою розробки", "contact_adminHint": "Щодо будь-яких проблем з обліковим записом, просторовим обмеженням чи доступністю служби.\n", - "contact_admin": "Зв'язатися з адміністрацією: {0}", + "contact_admin": "Зверніться до адміністраторів для: {0}", "footer_donate": "Пожертвувати", "admin_flushCacheDone": "Кеш успішно стерто", "admin_flushCacheButton": "Стерти кеш", - "admin_flushCacheHint": "Примусити користувачок і користувачів завантажити найновіші клієнтські ресурси (лише якщо ваш сервер — у режимі «fresh»)", + "admin_flushCacheHint": "Змушує всіх користувачів завантажувати найновіші ресурси після зміни налаштувань або конфігурації. Уникає перезавантаження сервера, але змушує кожного активного користувача скинути з'єднання, будь ласка, використовуйте економно.", "admin_flushCacheTitle": "Стерти HTTP-кеш", "admin_updateLimitDone": "Оновлення успішно завершено", "admin_updateLimitButton": "Оновити квоти", @@ -845,7 +846,7 @@ "survey": "Опитування CryptPad", "crowdfunding_popup_no": "Поки ні", "crowdfunding_popup_text": "

Нам потрібна ваша допомога!

Щоб активна розробка CryptPad тривала, підтримайте проєкт через сторінку OpenCollective, де ви можете переглянути план і фінансові цілі.", - "crowdfunding_button2": "Допомогти CryptPad", + "crowdfunding_button2": "Пожертвувати", "crowdfunding_button": "Підтримати CryptPad", "autostore_notAvailable": "Збережіть цей документ до свого CryptDrive-каталогу, щоб змогти використати цю функцію.", "autostore_forceSave": "Зберегти файл до CryptDrive-каталогу", @@ -891,7 +892,7 @@ "password_info": "Документ, який ви намагаєтесь відкрити, було стерто чи захищено новим паролем. Введіть чинний пароль, щоб доступитися вмісту.", "creation_newPadModalDescription": "Натисніть застосунок, яким слід створити документ. Або оберіть його клавішею Tab і підтвердьте клавішею Enter.", "creation_passwordValue": "Пароль", - "creation_expiration": "Дійсний до", + "creation_expiration": "Дата знищення", "creation_noOwner": "Нічий", "creation_owners": "Власність", "creation_create": "Створити", @@ -977,7 +978,7 @@ "team_cat_back": "Назад до команд", "team_cat_drive": "Каталог", "team_cat_chat": "Бесіда", - "team_avatarHint": "Максимум 500 кБ (png, jpeg, gif)", + "team_avatarHint": "Максимальний розмір 500 кБ (png, jpg, jpeg, gif)", "team_avatarTitle": "Аватар команди", "team_cat_admin": "Адміністрація", "team_deleteButton": "Видалити", @@ -1007,8 +1008,8 @@ "notifications_cat_all": "Всі", "notificationsPage": "Сповіщення", "openNotificationsApp": "Відкрити панель сповіщень", - "team_demoteMeConfirm": "Точно позбутися своїх повноважень? Цього неможливо буде скасувати.", - "owner_removeMeConfirm": "Точно позбутися свого права власності? Цього неможливо буде скасувати.", + "team_demoteMeConfirm": "Відмовитися від своїх повноважень. Ви не зможете скасувати цю дію. Впевнені?", + "owner_removeMeConfirm": "Відмовитися від своїх прав власності. Ви не зможете скасувати цю дію. Впевнені?", "owner_removePendingText": "Очікування", "owner_removeText": "Власність", "access_offline": "Зараз ви поза мережею. Керування доступом недоступне.", @@ -1157,5 +1158,381 @@ "admin_registrationTitle": "Закрити реєстрацію", "ui_restore": "Відновити", "ui_archive": "Архівувати", - "admin_documentType": "Тип" + "admin_documentType": "Тип", + "mdToolbar_tutorial": "https://www.markdowntutorial.com/", + "admin_supportAddError": "Неправильний приватний ключ", + "support_disabledTitle": "Підтримку не ввімкнено", + "support_disabledHint": "Цей інстанс CryptPad ще не налаштований на використання форми підтримки.", + "support_cat_new": "Новий тікет", + "support_formTitle": "Новий тікет", + "support_formButton": "Надіслати", + "support_formTitleError": "Помилка: заголовок порожній", + "support_formContentError": "Помилка: вміст порожній", + "support_formMessage": "Введіть своє повідомлення…", + "admin_supportAddKey": "Додати приватний ключ", + "admin_supportListHint": "Ось список тікетів, надісланих користувачами на поштову скриньку підтримки. Всі адміністратори можуть бачити повідомлення та відповіді на них. Закритий тікет не може бути відкритий повторно. Ви можете лише видалити (приховати) закриті тікети, при цьому видалені тікети залишаються видимими для інших адміністраторів.", + "support_formHint": "Використовуйте цю форму для безпечного зв'язку з адміністраторами щодо проблем і питань.
Зверніть увагу, що деякі проблеми/питання вже можуть бути висвітлені вCryptPad User Guide. Будь ласка, не створюйте новий тикет, якщо у вас уже є відкритий тикет щодо тієї самої проблеми. Натомість, відповідайте на своє оригінальне повідомлення з будь-якою додатковою інформацією.", + "support_cat_tickets": "Наявні квитки", + "support_listTitle": "Тікети підтримки", + "support_listHint": "Ось список тикетів, надісланих адміністраторам, та їхні відповіді. Закритий тікет не може бути відкритий повторно, але ви можете створити новий. Ви можете приховати закриті тікети.", + "support_answer": "Відповісти", + "support_close": "Закрити тікет", + "support_remove": "Видалити тікет", + "support_showData": "Показати/приховати користувацьку інформацію", + "support_from": "Від: {0}", + "support_closed": "Цей тікет закрито", + "fc_noAction": "Дії недоступні", + "requestEdit_viewPad": "Відкрити документ у новій вкладці", + "later": "Вирішити пізніше", + "requestEdit_request": "{1} хоче відредагувати документ {0}", + "requestEdit_accepted": "{1} надав вам права на редагування документа{0}", + "requestEdit_sent": "Запит надіслано", + "properties_unknownUser": "{0} невідомий(і) користувач(и)", + "owner_unknownUser": "невідомо", + "owner_addConfirm": "Співвласники зможуть змінювати контент і видалити вас як власника. Ви впевнені?", + "owner_add": "{0} хоче, щоб ви стали власником документа {1}. Ви згодні?", + "owner_request_accepted": "{0} прийняв вашу пропозицію стати власником {1}", + "owner_removed": "{0} позбавив вас права власності на {1}", + "owner_removedPending": "{0} скасував вашу пропозицію щодо володіння для {1}", + "team_pcsSelectLabel": "Зберігати в", + "team_invitedToTeam": "{0}запросив вас приєднатись у його команду: {1}", + "team_kickedFromTeam": "{0} вигнав вас з команди: {1}", + "team_acceptInvitation": "{0} прийняв вашу пропозицію приєднатися до команди: {1}", + "team_rosterDemote": "Розжалувати", + "team_rosterKick": "Вигнати з команди", + "team_inviteButton": "Запросити учасників", + "team_nameTitle": "Назва команди", + "team_nameHint": "Встановити імʼя команді", + "team_maxTeams": "Кожен обліковий запис користувача може бути членом лише {0} команд.", + "owner_team_add": "{0} хоче, щоб ви стали власником команди {1}. Ви згодні?", + "team_rosterPromoteOwner": "Запропонувати право власності", + "team_kickConfirm": "{0} дізнаються, що ви видалили їх з команди. Ви впевнені?", + "sent": "Повідомлення надіслано", + "team_pending": "Запрошено", + "team_deleteTitle": "Видалення команди", + "team_pendingOwner": "(на розгляді)", + "team_pendingOwnerTitle": "Цей адміністратор ще не прийняв пропозицію власності.", + "drive_sfPasswordError": "Хибний пароль", + "password_error_seed": "Документ не знайдено!
Цю помилку може спричинити дві причини: або було додано/змінено пароль, або документ було видалено з сервера.", + "properties_confirmNewFile": "Ви впевнені? Додавання пароля змінить URL-адресу цього файлу. Користувачі без пароля втратять доступ до цього файлу.", + "properties_passwordSuccessFile": "Пароль успішно змінено.", + "teams_table": "Ролі", + "teams_table_generic": "Ролі та права", + "teams_table_generic_view": "Перегляд: доступ до папок і документів (тільки для читання).", + "teams_table_generic_edit": "Редагувати: створювати, змінювати та видаляти папки і документи.", + "teams_table_generic_admin": "Керувати учасниками: запрошувати та відкликати учасників, змінювати ролі учасників аж до Адміністратора.", + "teams_table_specific": "Виняток", + "teams_table_admins": "Керувати учасниками", + "teams_table_owners": "Керувати командою", + "teams_table_role": "Роль", + "pad_wordCount": "Слів: {0}", + "share_contactPasswordAlert": "Цей елемент захищено паролем. Оскільки ви ділитеся ним з контактом CryptPad, одержувачу не потрібно буде вводити пароль.", + "share_embedPasswordAlert": "Цей елемент захищено паролем. Коли ви вбудуєте цей документ, глядачам буде запропоновано ввести пароль.", + "passwordFaqLink": "Прочитати більше щодо паролів", + "contacts_mute": "Приглушити", + "contacts_unmute": "Увімкнути", + "team_inviteLinkTitle": "Створити персоналізоване запрошення для цієї команди", + "team_inviteLinkTempName": "Тимчасове ім'я (видно у списку очікуваних запрошень)", + "team_inviteLinkNote": "Додати персональне повідомлення", + "team_inviteLinkNoteMsg": "Це повідомлення буде показано до того, як одержувач вирішить, чи приєднатися до цієї команди.", + "team_inviteLinkLoading": "Створення вашого посилання", + "team_inviteLinkErrorName": "Будь ласка, додайте ім'я людини, яку ви запрошуєте. Вони можуть змінити його пізніше. ", + "team_inviteLinkCreate": "Створити посилання", + "team_inviteLinkCopy": "Скопіювати посилання", + "team_inviteFrom": "Від:", + "team_invitePleaseLogin": "Будь ласка увійдіть або зареєструйтеся, щоб схвалити запрошення.", + "team_inviteEnterPassword": "Будь ласка, введіть пароль-запрошення, щоб продовжити.", + "team_inviteJoin": "Приєднатися до команди", + "team_inviteTitle": "Запрошення для команди", + "team_inviteGetData": "Отримання даних про команду", + "burnAfterReading_generateLink": "Натисніть на кнопку нижче, щоб згенерувати посилання.", + "burnAfterReading_proceed": "переглянути та видалити", + "burnAfterReading_warningDeleted": "Цей документ було видалено назавжди, після закриття цього вікна ви не зможете отримати до нього доступ.", + "oo_invalidFormat": "Цей файл не можна імпортувати", + "oo_importInProgress": "Імпортування в процесі", + "oo_exportInProgress": "Експортування в процесі", + "oo_sheetMigration_loading": "Оновлення документа до останньої версії. Зачекайте приблизно 1 хвилину.", + "oo_sheetMigration_anonymousEditor": "Доступна оновлена версія, натисніть OK для перезавантаження.", + "profile_login": "Щоб додати цього користувача до своїх контактів, вам потрібно увійти в систему", + "historyTrim_historySize": "Історія: {0}", + "trimHistory_getSizeError": "Помилка під час обчислення розміру історії диска", + "trimHistory_error": "Помилка під час видалення історії", + "trimHistory_needMigration": "Будь ласка оновіть свій CryptDrive щоб увімкнути цю функцію.", + "trimHistory_currentSize": "Поточний розмір історії: {0}", + "owner_request": "{0} хоче, щоб ви стали власником {1}", + "team_leaveButton": "Вийти з команди", + "team_deleteHint": "Видалити команду та всі документи, що належать виключно команді.", + "drive_sfPassword": "Ваша спільна тека {0} більше не доступна. Її або видалено власником, або захищено новим паролем. Ви можете видалити цю папку зі свого CryptDrive або відновити доступ до неї за допомогою нового пароля.", + "requestEdit_confirm": "{1} попросив надати йому можливість редагувати документ {0}. Ви хочете надати йому доступ?", + "owner_removeConfirm": "Ви впевнені, що хочете видалити право власності для вибраних користувачів? Вони отримають сповіщення про цю дію.", + "team_declineInvitation": "{0} відхилив вашу пропозицію приєднатися до команди: {1}", + "team_rosterPromote": "Просувати", + "teams_table_generic_own": "Керування командою: зміна назви та аватарки команди, додавання або видалення Власників, зміна підписки команди, видалення команди.", + "contacts_manageMuted": "Керування приглушеням", + "contacts_muteInfo": "Ви не отримуватимете жодних сповіщень або повідомлень від приглушених користувачів.
Вони не знатимуть, що ви їх приглушили. ", + "safeLinks_error": "Це посилання скопійоване з адресного рядка браузера і не надає доступу до документа. Будь ласка використовуйте Поділитись щоб поділитися безпосередньо з контактами або скопіювати посилання. Дізнайтеся більше про функцію безпечних посилань.", + "support_notification": "Адміністратор відповів на ваш запит у службу підтримки", + "requestEdit_button": "Запросити права на редагування", + "owner_request_declined": "{0} відхилив вашу пропозицію стати власником {1}", + "team_pcsSelectHelp": "Створення власного документа на диску вашої команди надасть команді право власності на нього.", + "team_leaveConfirm": "Якщо ви покинете цю команду, ви втратите доступ до її CryptDrive, історії чату та іншого вмісту. Ви впевнені?", + "team_ownerConfirm": "Співвласники можуть змінити або видалити команду і видалити вас як власника. Ви впевнені?", + "team_viewers": "Глядачі", + "properties_passwordWarningFile": "Пароль було успішно змінено, але ми не змогли оновити ваш CryptDrive новими даними. Можливо, вам доведеться видалити стару версію файлу вручну.", + "team_inviteLinkWarning": "Люди, які перейдуть за цим посиланням, зможуть приєднатися до цієї групи і переглянути її вміст. Діліться ним обережно.", + "oo_sheetMigration_complete": "Доступна оновлена версія, натисніть OK для перезавантаження.", + "properties_confirmChangeFile": "Ви впевнені? Користувачі без нового пароля втратять доступ до цього файлу.", + "teams_table_specificHint": "Це старі теки зі спільним доступом, де користувачі все ще мають право редагувати наявні документи. Документи, створені або скопійовані у ці теки, матимуть стандартні права доступу.", + "contacts_mutedUsers": "Приглушені акаунти", + "team_inviteFromMsg": "{0} запросив вас до команди {1}", + "team_invitePasswordLoading": "Розшифровка запрошення", + "burnAfterReading_warningLink": "Ви налаштували цей документ на самознищення. Після того, як одержувач перейде за цим посиланням, він зможе переглянути документ один раз, перш ніж його буде остаточно видалено.", + "burnAfterReading_warningAccess": "Цей документ самознищиться. Коли ви натиснете кнопку нижче, ви побачите його вміст один раз, перш ніж він буде видалений назавжди. Коли ви закриєте це вікно, ви більше не зможете отримати до нього доступ. Якщо ви не готові продовжувати, ви можете закрити це вікно і повернутися пізніше.", + "driveOfflineError": "Ваше з'єднання з CryptPad було втрачено. Зміни в цьому документі не будуть збережені на вашому CryptDrive. Будь ласка, закрийте всі вкладки CryptPad і повторіть спробу в новому вікні. ", + "team_inviteLinkSetPassword": "Захистити посилання паролем (рекомендується)", + "historyTrim_contentsSize": "Контакти: {0}", + "access_noContact": "Немає інших контактів для додавання", + "kanban_conflicts": "Наразі редагується:", + "cba_writtenBy": "Надруковано: {0}", + "cba_properties": "Авторські кольори (експериментальні)", + "cba_hint": "Цей параметр буде запам'ятовуватися при створенні наступного документа.", + "cba_enable": "Увімкнути", + "cba_disable": "Очистити та вимкнути", + "cba_show": "Показати кольори автора", + "cba_hide": "Приховати кольори автора", + "oo_login": "Будь ласка, увійдіть або зареєструйтесь, щоб покращити продуктивність електронних таблиць.", + "cba_title": "Авторські кольори", + "comments_notification": "Відповіді на ваш коментар \"{0}\" у {1}", + "history_fastPrev": "Попередній сеанс редагування", + "history_fastNext": "Наступний сеанс редагування", + "snapshots_new": "Новий знімок", + "snapshots_placeholder": "Заголовок знімку", + "snapshot_error_exists": "Вже є знімок цієї версії", + "snapshots_cantMake": "Знімок не вдалося створити. Ви від'єднані від мережі.", + "admin_limitPlan": "План: {0}", + "admin_limitNote": "Примітка: {0}", + "oo_cantMigrate": "Цей аркуш перевищує максимальний розмір завантаження і є занадто великим для перенесення.", + "admin_supportPrivTitle": "Підтримка приватного ключа поштової скриньки", + "support_premiumLink": "Переглянути варіанти підписки", + "snaphot_title": "Знімок", + "profile_defaultAlt": "Зображення профілю за замовчуванням", + "snapshots_ooPickVersion": "Перед створенням знімка необхідно вибрати версію", + "team_leaveOwner": "Будь ласка, понизьте себе в ролі власника перед тим, як покинути команду. Зверніть увагу, що команда повинна мати принаймні одного власника, будь ласка, додайте ще одного перед тим, як продовжити, якщо ви наразі єдиний власник.", + "support_premiumPriority": "Преміум користувачі допомагають покращувати зручність використання CryptPad і отримують пріоритетні відповіді на свої запити в службу підтримки.", + "unknownPad": "Невідомий документ", + "mentions_notification": "{0} згадали вас у {1}", + "comments_deleted": "Коментар видалено автором", + "comments_edited": "Змінено", + "comments_submit": "Надіслати", + "comments_reply": "Відповісти", + "comments_resolve": "Розвʼязати", + "comments_comment": "Коментар", + "comments_error": "Не вдалося додати коментар", + "fm_sort": "Впорядкувати", + "code_editorTheme": "Вигляд редактора", + "support_cat_data": "Втрата вмісту", + "support_cat_bug": "Звіт про ваду", + "support_cat_other": "Інше", + "support_cat_all": "Усе", + "support_attachments": "Вкладення", + "support_addAttachment": "Додати вкладення", + "notification_fileSharedTeam": "{0} поділилися файлом із командою {2}: {1}", + "notification_folderSharedTeam": "{0} поділилися текою з командою {2}: {1}", + "support_category": "Оберіть тип", + "support_formCategoryError": "Помилка: тип порожній", + "fm_restricted": "Ви не маєте доступу", + "fm_noResult": "Нічого не знайдено", + "history_cantRestore": "Відновити не вдалося. Ви відʼєднані.", + "history_userPrev": "Попередній автор", + "history_userNext": "Наступний автор", + "history_restoreDriveTitle": "Відновити обрану версію CryptDrive", + "history_restoreDrivePrompt": "Упевнені, що хочете замінити поточну версію CryptDrive показаною версією?", + "snapshots_open": "Відкрити", + "snapshots_restore": "Відновити", + "snapshots_close": "Закрити", + "snapshots_delete": "Видалити", + "team_exportTitle": "Завантажити сховище команди", + "team_exportButton": "Завантажити", + "fm_deletedFolder": "Видалити теку", + "fm_shareFolderPassword": "Захистити цю теку паролем (не обовʼязково)", + "error_unhelpfulScriptError": "Помилка скрипту: Дивіться консоль браузера для подробиць", + "fileTableHeader": "Завантаження й вивантаження", + "download_zip_file": "Файл {0}/{1}", + "admin_support_premium": "Преміум квитки:", + "admin_support_normal": "Квитки без відповіді:", + "admin_support_answered": "Квитки з відповіддю:", + "admin_support_closed": "Закриті квитки:", + "admin_support_open": "Показувати", + "admin_support_collapse": "Згорнути", + "admin_support_first": "Створено: ", + "admin_support_last": "Оновлено: ", + "addOptionalPassword": "Додати пароль (не обовʼязково)", + "fm_cantUploadHere": "Не вдалося завантажити файл сюди", + "settings_deleteContinue": "Видалити мою обліковку", + "settings_deleteSubscription": "Керувати підпискою", + "calendar_dateTimeRange": "{0} {1} - {2}", + "calendar_noNotification": "Порожньо", + "oo_conversionSupport": "Ваш оглядач не підтримує перетворення офісного формату. Ми радимо користати останню версію Firefox або Chrome.", + "admin_supportPrivButton": "Показати ключ", + "calendar_more": "{0} більше", + "mdToolbar_embed": "Вмостити файл", + "admin_updateAvailableTitle": "Нова версія", + "admin_updateAvailableHint": "Доступна нова версія CryptPad", + "admin_updateAvailableButton": "Огляньте примітки випуску", + "admin_provideAggregateStatisticsLabel": "Надавати збірну статистику", + "resources_imageBlocked": "CryptPad заблокував віддалене зображення", + "resources_openInNewTab": "Відкрити у новій вкладці", + "resources_learnWhy": "Дізнатися, чому це заблоковано", + "notification_openLink": "Ви одержали посилання {0} від {1}:", + "notification_linkShared": "{0} поділились посиланням із вами: {1}", + "offlineError": "Не вдалося синхронізувати недавні дані, зараз неможливо показати цю сторінку. Завантаження продовжиться, коли ваше зʼєднання з сервісом буде відновлено.", + "support_languagesPreamble": "Підтримка розуміє такі мови:", + "fm_emptyTrashOwned": "Ваш смітник містить документи у вашій власности. Ви можете усунути їх лише з вашого сховища, або знищити їх для усіх користувачів.", + "team_exportHint": "Завантажити усі документи у сховищі цієї команди. Документи будуть завантажені у форматі, придатному до читання іншими застосунками, коли такий формат є доступним. В іншому випадку, документи будуть завантажені у форматі, придатний до читання CryptPad.", + "download_zip": "Збирання файлу ZIP…", + "oo_lostEdits": "На жаль, не вдалося відновити ваші недавньо збережені зміни після надходження нового вмісту.", + "toolbar_degraded": "Понад {0} правників наразі присутні в документі. Перелік користувачів та чат вимкнено для підвищення продуктивності.", + "notification_padSharedTeam": "{0} поділилися документом із командою {2}: {1}", + "settings_deleteWarning": "Увага: наразі ви є передплатником(-цею) преміум-плану (оплачено або дано іншим користувачем). Будь ласка, скасуйте ваш план перед видаленням обліковки, бо після видалення це неможливо буде зробити без звернення до підтримки.", + "oo_importBin": "Натисніть «Гаразд», щоби імпортувати внутрішній формат .bin.", + "restrictedLoginPrompt": "Ви не можете переглянути цей документ. Увійдіть, якщо вважаєте, що ваша обліковка має до цього доступ.", + "admin_provideAggregateStatisticsTitle": "Збір статистики", + "share_versionHash": "Ви збираєтесь поділитися обраною версією документу в режимі лише для читання. Це також надасть право на перегляд для всіх версій документу.", + "unableToDisplay": "Не вдалося показати документ. Натисніть Esc, щоби перезавантажити сторінку. Якщо проблема не мине, звʼяжіться з підтримкою.", + "history_trimPrompt": "Цей документ має історію, довжиною в {0}, що може спричинити повільне завантаження. Очистіть історію, якщо вона не потрібна.", + "admin_provideAggregateStatisticsHint": "Ви можете погодитись надавати додаткові дані використання розробникам, як-от приблизну кількість зареєстрованих та щоденних користувачів на вашому примірнику.", + "fc_open_formro": "Відкрити (як учасник)", + "upload_modal_alt": "Альтернативний текст", + "upload_addOptionalAlt": "Додайте описовий текст (необов'язково)", + "admin_supportPrivHint": "Відображати закритий ключ, який знадобиться іншим адміністраторам для перегляду запитів служби підтримки. На панелі адміністратора відобразиться форма для введення цього ключа.", + "fc_openIn": "Відкрити в {0}", + "download_step3": "Перетворення...", + "earlyAccessBlocked": "Ця програма поки що недоступна на цьому екземплярі", + "admin_archiveNote": "Примітка", + "support_warning_account": "Зверніть увагу, що адміністратори не можуть скинути паролі. Якщо ви втратили облікові дані свого облікового запису, але все ще маєте активний сеанс, ви можете перенести свої дані в новий обліковий запис", + "support_warning_document": "Укажіть тип документа, який викликає проблему, і надайте ідентифікатор документа або посилання", + "premiumOnly": "Створення нових документів у цьому додатку наразі обмежено для підписників на {0}. Це експериментальний додаток з раннім доступом для тестування. Незабаром він стане доступним для всіх в {0}.", + "bounce_confirm": "Ви збираєтеся залишити: {0}\n\nВи впевнені, що бажаєте відвідати \"{1}\"?", + "support_warning_drives": "Зауважте, що адміністратори не можуть ідентифікувати теки та документи за назвою. Для спільних тек надайте ідентифікатор документа", + "premiumAccess": "Як підписник на {0}, ви можете створювати нові документи в цьому додатку з раннім доступом. Будь ласка, зауважте, що він є експериментальним і поки що не варто довіряти йому важливі дані.", + "support_warning_prompt": "Будь ласка, виберіть найбільш відповідну категорію для вашої проблеми. Це допомагає адміністраторам сортувати та надає додаткові пропозиції щодо інформації, яку потрібно надати", + "support_warning_bug": "Будь ласка, вкажіть, у якому браузері виникає проблема та чи встановлено якісь розширення. Надайте якомога більше деталей про проблему та кроки, необхідні для її відтворення", + "bounce_danger": "Посилання, яке ви натиснули, веде не на веб-сторінку, а на певний код або дані, які можуть бути шкідливими.\n\n(\"{0}\")\n\nCryptPad блокує їх з міркувань безпеки. Натиснувши OK, ви закриєте цю вкладку.", + "support_warning_abuse": "Повідомте про вміст, який порушує Загальні положення та умови. Надайте посилання на образливі документи або профілі користувачів і опишіть, як вони порушують умови. Будь-яка додаткова інформація про контекст, у якому ви виявили вміст або поведінку, може допомогти адміністраторам запобігти майбутнім порушенням", + "support_cat_document": "Документ", + "support_warning_other": "Який характер вашого запиту? Надайте якомога більше відповідної інформації, щоб нам було легше швидко вирішити вашу проблему", + "support_cat_abuse": "Повідомити про зловживання", + "ui_openDirectly": "Ця функціональність недоступна, якщо CryptPad вбудовано в інший сайт. Відкрити цей документ у новій вкладці?", + "support_cat_debugging": "Дані налагодження", + "support_debuggingDataTitle": "Інформація про налагодження облікового запису", + "fivehundred_internalServerError": "Внутрішня помилка сервера", + "home_morestorage": "Щоб отримати більше місця для зберігання:", + "login_instance": "Підключіться до свого облікового запису на {0}", + "footer_website": "Вебсторінка проєкту", + "register_instance": "Створити новий обліковий запис на {0}", + "admin_uptimeTitle": "Час запуску", + "admin_noticeTitle": "Примітки домашнього екрана", + "admin_generatedAt": "Час звіту", + "ui_false": "брехня", + "ui_undefined": "невідомо", + "ui_none": "немає", + "ui_success": "Успіх", + "ui_fetch": "Отримати", + "ui_confirm": "Підтвердити", + "admin_restoreReason": "Будь ласка, вкажіть причину відновлення та підтвердьте, що ви бажаєте продовжити", + "admin_accountMetadataTitle": "Інформація про обліковий запис", + "admin_documentMetadataTitle": "Інформація про документ", + "admin_documentMetadataHint": "Запитувати документ або файл за його ідентифікатором або URL-адресою", + "admin_documentSize": "Розмір документа", + "admin_documentMetadata": "Поточні метадані", + "admin_documentCreationTime": "Створено", + "admin_currentlyOpen": "Наразі відкрито", + "admin_channelAvailable": "Доступно", + "admin_channelArchived": "Архівовано", + "admin_documentModifiedTime": "Остання зміна", + "admin_documentMetadataPlaceholder": "Посилання на документ або його ідентифікатор", + "home_location": "Зашифровані дані зберігаються в {0}", + "ui_true": "правда", + "admin_archiveReason": "Будь ласка, вкажіть причину архівування та підтвердьте, що ви бажаєте продовжити", + "admin_accountMetadataHint": "Введіть відкритий ключ користувача, щоб отримати дані про його обліковий запис.", + "admin_noticeHint": "Додаткове повідомлення для відображення на головній сторінці", + "admin_cat_database": "База даних", + "support_debuggingDataHint": "Наступна інформація міститься в заявках у службу підтримки, які ви надсилаєте. Жоден із них не дозволяє адміністраторам отримати доступ або розшифрувати ваші документи. Ця інформація зашифрована таким чином, що її можуть прочитати лише адміністратори.", + "admin_uptimeHint": "Дата і час запуску сервера", + "ui_generateReport": "Згенерувати звіт", + "admin_lastPinTime": "Час останньої активності шпильки", + "admin_blockMetadataHint": "Блок входу — це те, що дозволяє обліковому запису входити в CryptPad за допомогою комбінації імені користувача та пароля", + "admin_blockMetadataTitle": "Інформація про блок входу", + "admin_blockMetadataPlaceholder": "Абсолютний або відносний блок URL", + "admin_accountMetadataPlaceholder": "Ідентифікатор користувача (відкритий ключ підпису)", + "admin_currentlyOnline": "Зараз онлайн", + "admin_planName": "Назва плану", + "admin_note": "Примітка плану", + "admin_storageUsage": "Дані збережені", + "admin_channelCount": "Кількість документів", + "admin_fileCount": "Кількість файлів", + "admin_blockKey": "Заблокувати відкритий ключ", + "admin_firstPinTime": "Час першої активності шпильки", + "admin_blockAvailable": "Блок доступний", + "admin_blockArchived": "Блок заархівовано", + "admin_archiveBlock": "Заархівувати блок", + "admin_restoreBlock": "Відновити заархівований блок", + "admin_archiveDocument": "Заархівувати документ", + "admin_restoreDocument": "Відновити документ", + "admin_planlimit": "Обмеження сховища", + "admin_reportContent": "Повідомити про вміст", + "admin_getRawMetadata": "Історія метаданих", + "og_register": "Створити обліковий запис на {0}", + "og_encryptedAppType": "Зашифровано {0}", + "admin_documentConflict": "Заархівувати/відновити", + "fm_rmFilter": "Вилучити фільтр", + "fm_filterBy": "Фільтр", + "calendar_rec": "Повторити", + "calendar_rec_no": "Одноразово", + "calendar_rec_updated": "Роль оновлено на {0}", + "calendar_rec_daily": "Щодня", + "calendar_rec_stop": "Зупинити повторення", + "calendar_rec_weekly": "Щотижня в {0}", + "calendar_rec_edit_all": "Редагувати всі події", + "calendar_rec_monthly": "Щомісяця, день {1}", + "calendar_rec_edit_from": "Редагувати майбутні події", + "calendar_rec_yearly": "Щорічно в {2}", + "calendar_rec_edit_one": "Редагувати тільки цю подію", + "calendar_rec_weekend": "Щодня у вихідні дні", + "calendar_rec_edit": "Це повторювана подія", + "calendar_str_filter_day": "Дні: {0}", + "calendar_rec_weekdays": "Щодня по буднях", + "calendar_str_filter_monthday": "Дні місяця: {0}", + "calendar_rec_custom": "Користувача", + "calendar_str_filter_yearday": "Дні року: {0}", + "calendar_str_filter_month": "Місяці: {0}", + "calendar_str_filter": "Фільтри:", + "calendar_rec_freq_daily": "дні", + "calendar_rec_freq_weekly": "тижні", + "calendar_rec_freq_monthly": "місяці", + "calendar_str_for": "протягом {0} разів", + "calendar_rec_freq_yearly": "роки", + "calendar_rec_until_no": "Ніколи", + "calendar_str_day": "на {0}", + "calendar_str_daily": "{0} день(ів)", + "calendar_rec_until_count": "Після", + "calendar_str_weekly": "{0} тиждень(ів)", + "calendar_str_yearly": "{0} рік(ів)", + "og_login": "Увійти до {0}", + "og_contact": "{0} Контакт", + "ui_jsRequired": "Для виконання шифрування у вашому браузері має бути ввімкнено JavaScript", + "admin_conflictExplanation": "Існує дві версії цього документа. Відновлення архівної версії замінить актуальну версію. Архівування поточної версії замінить архівовану версію. Жодна з цих дій не може бути скасована.", + "calendar_str_filter_weekno": "Тижні: {0}", + "calendar_rec_until": "Зупинити повторення", + "calendar_str_monthly": "{0} місяць(ів)", + "calendar_rec_until_count2": "рази", + "calendar_rec_monthly_pick": "По днях", + "calendar_nth_1": "перше", + "calendar_list_end": "{0} чи {1}", + "calendar_nth_2": "друге", + "calendar_list": "{0}, {1}", + "calendar_nth_3": "третє", + "calendar_nth_4": "четверте", + "calendar_rec_every_date": "Кожні {0}", + "calendar_month_last": "останній день" } diff --git a/www/cryptpad-api.js b/www/cryptpad-api.js index 7833539173..e73ef40bdd 100644 --- a/www/cryptpad-api.js +++ b/www/cryptpad-api.js @@ -14,6 +14,13 @@ var getTxid = function () { return Math.random().toString(16).replace('0.', ''); }; + var getInstanceURL = () => { + var scripts = document.getElementsByTagName('script'); + for (var i = scripts.length - 1; i >= 0; i--) { + var match = scripts[i].src.match(/(.*)web-apps\/apps\/api\/documents\/api.js/i); + if (match) { return match[1]; } + } + }; var makeChan = function (iframe, iOrigin) { var handlers = {}; @@ -75,6 +82,7 @@ var makeIframe = function () {}; // placeholder + let onDocumentReady = []; var start = function (config, chan) { return new Promise(function (resolve, reject) { setTimeout(function () { @@ -83,7 +91,8 @@ var getBlob = function (cb) { var xhr = new XMLHttpRequest(); - xhr.open('GET', config.document.url, true); + let url = config.document.url; + xhr.open('GET', url, true); xhr.responseType = 'blob'; xhr.onload = function () { if (this.status === 200) { @@ -131,9 +140,18 @@ var getSession = function (cb) { chan.send('GET_SESSION', { - key: key + key: key, + keepOld: !config.events.onNewKey }, function (obj) { if (obj && obj.error) { reject(obj.error); return console.error(obj.error); } + + // OnlyOffice + if (!config.events.onNewKey) { + key = obj.key; + console.error(key, obj); + return void cb(); + } + if (obj.key !== key) { // The outside app may reject our new key if the "old" one is deprecated. // This will happen if multiple users try to update the key at the same @@ -153,21 +171,52 @@ }; getSession(onKeyValidated); + chan.on('DOCUMENT_READY', function () { + if (config.events.onAppReady) { + config.events.onAppReady(); + } + if (config.events.onReady) { + config.events.onReady(); + } + if (config.events.onDocumentReady) { + config.events.onDocumentReady(); + } + onDocumentReady.forEach(f => { + try { f(); } catch (e) { console.error(e); } + }); + }); + + chan.on('ON_DOWNLOADAS', blob => { + let url = URL.createObjectURL(blob); + config.events.onDownloadAs({ + data: { + fileType: config.document && config.document.fileType, + url + } + }); + }); + chan.on('SAVE', function (data, cb) { blob = data; config.events.onSave(data, cb); }); chan.on('RELOAD', function () { config.document.blob = blob; - document.getElementById('cryptpad-editor').remove(); + if (!config.editorConfig) { // Not OnlyOffice shim + document.getElementById('cryptpad-editor').remove(); + } makeIframe(config); }); chan.on('HAS_UNSAVED_CHANGES', function(unsavedChanges, cb) { - config.events.onHasUnsavedChanges(unsavedChanges); + if (config.events.onHasUnsavedChanges) { + config.events.onHasUnsavedChanges(unsavedChanges); + } cb(); }); chan.on('ON_INSERT_IMAGE', function(data, cb) { - config.events.onInsertImage(data, cb); + if (config.events.onIntertImage) { + config.events.onInsertImage(data, cb); + } else { cb(); } }); }); @@ -192,7 +241,28 @@ * @return {promise} */ var init = function (cryptpadURL, containerId, config) { - return new Promise(function (resolve, reject) { + // OnlyOffice shim: don't provide a URL + if (!config && typeof(containerId) === "object") { + config = containerId; + containerId = cryptpadURL; + cryptpadURL = getInstanceURL(); + } + + // OnlyOffice shim + let url = config.document.url; + if (/^http:\/\/localhost\/cache\/files\//.test(url)) { + url = url.replace(/(http:\/\/localhost\/cache\/files\/)/, getInstanceURL() + 'ooapi/'); + } + config.document.url = url; + if (config.documentType === "spreadsheet") { + config.documentType = "sheet"; + } + if (config.documentType === "text") { + config.documentType = "doc"; + } + + let chan; + let ret = new Promise(function (resolve, reject) { setTimeout(function () { if (!cryptpadURL || typeof(cryptpadURL) !== "string") { @@ -209,8 +279,8 @@ if (!config) { return reject('Missing args: no data provided'); } if(['document.url', 'document.fileType', 'documentType', - 'events.onSave', 'events.onHasUnsavedChanges', - 'events.onNewKey', 'events.onInsertImage'].some(function (k) { + /*'events.onSave', 'events.onHasUnsavedChanges', + 'events.onNewKey', 'events.onInsertImage'*/].some(function (k) { var s = k.split('.'); var c = config; return s.some(function (key) { @@ -235,14 +305,23 @@ makeIframe = function (config) { var iframe = document.createElement('iframe'); iframe.setAttribute('id', 'cryptpad-editor'); + iframe.setAttribute('name', 'frameEditor'); + iframe.setAttribute('align', 'top'); iframe.setAttribute("src", url); - container.appendChild(iframe); + iframe.setAttribute("width", config.width); + iframe.setAttribute("height", config.height); + if (config.editorConfig) { // OnlyOffice + container.replaceWith(iframe); + container = iframe; + } else { + container.appendChild(iframe); + } var onMsg = function (msg) { var data = typeof(msg.data) === "string" ? JSON.parse(msg.data) : msg.data; if (!data || data.q !== 'INTEGRATION_READY') { return; } window.removeEventListener('message', onMsg); - var chan = makeChan(iframe, parsed.origin); + chan = makeChan(iframe, parsed.origin); start(config, chan).then(resolve).catch(reject); }; window.addEventListener('message', onMsg); @@ -250,8 +329,24 @@ makeIframe(config); }); }); + + ret.downloadAs = (arg) => { + if (!chan) { + return void onDocumentReady.push(() => { + ret.downloadAs(arg); + }); + } + + chan.send('DOWNLOAD_AS', arg); + }; + + return ret; }; + init.version = () => { return '7.3.0'; }; + init.DocEditor = init; // OnlyOffice shim + + window.DocsAPI = init; return init; }; diff --git a/www/install/main.js b/www/install/main.js index fffb7343d2..92d6ddcbc0 100644 --- a/www/install/main.js +++ b/www/install/main.js @@ -16,13 +16,16 @@ define([ '/common/hyperscript.js', '/customize/pages.js', '/common/rpc.js', + 'onboardscreen.js', + 'less!/install/onboarding.less', 'css!/components/components-font-awesome/css/font-awesome.min.css', -], function ($, Login, Cryptpad, /*Test,*/ Cred, UI, Util, Realtime, Constants, Feedback, LocalStore, h, Pages, Rpc) { +], function ($, Login, Cryptpad, /*Test,*/ Cred, UI, Util, Realtime, Constants, Feedback, LocalStore, h, Pages, Rpc, OnboardScreen) { if (window.top !== window) { return; } var Messages = Cryptpad.Messages; $(function () { - if (LocalStore.isLoggedIn()) { + + if (LocalStore.isLoggedIn() && !localStorage.CP_dev) { // already logged in, redirect to drive document.location.href = '/drive/'; return; @@ -55,10 +58,14 @@ define([ } } + var showTitleScreen = function (sendAdminDecree, sendAdminRpc) { + OnboardScreen.create(sendAdminDecree, sendAdminRpc); + }; + var registerClick = function () { var uname = $uname.val().trim(); - // trim whitespace surrounding the username since it is otherwise included in key derivation - // most people won't realize that its presence is significant + // trim whitespace surrounding the username since it is otherwise included in key derivation + // most people won't realize that its presence is significant $uname.val(uname); var passwd = $passwd.val(); @@ -112,6 +119,28 @@ define([ return void UI.alert(Messages.register_mustAcceptTerms); } + let startOnboarding = function (network, proxy) { + Rpc.create(network, proxy.edPrivate, proxy.edPublic, function (e, rpc) { + if (e) { + // TODO: handle error + return; + } + + let sendAdminDecree = function (command, data, callback) { + var params = ['ADMIN_DECREE', [command, data]]; + rpc.send('ADMIN', params, callback); + }; + + let sendAdminRpc = function (command, data, callback) { + var params = [command, data]; + rpc.send('ADMIN', params, callback); + }; + + showTitleScreen(sendAdminDecree, sendAdminRpc); + UI.removeLoadingScreen(); + }); + }; + setTimeout(function () { var span = h('span', [ h('h2', [ @@ -145,7 +174,8 @@ define([ edPublic: proxy.edPublic }, function (e) { if (e) { UI.alert(Messages.error); return console.error(e); } - window.location.href = '/drive/'; + localStorage.CP_admin = "1"; + startOnboarding(data.network, proxy); }); }); diff --git a/www/install/onboarding.less b/www/install/onboarding.less new file mode 100644 index 0000000000..4820c6681e --- /dev/null +++ b/www/install/onboarding.less @@ -0,0 +1,125 @@ +/* + * SPDX-FileCopyrightText: 2024 XWiki CryptPad Team and contributors + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +@import (reference) "../../customize/src/less2/include/browser.less"; +@import (reference) "../../customize/src/less2/include/framework.less"; +@import (reference) "../../customize/src/less2/include/tools.less"; +@import (reference) "../../customize/src/less2/include/markdown.less"; +@import (reference) "../../customize/src/less2/include/avatar.less"; +@import (reference) '../../customize/src/less2/include/admin.less'; +@import (reference) '../../customize/src/less2/include/icon-colors.less'; + +&.cp-page-install { + .admin_main(); + div.cp-palette-container { + max-width: 400px; + } + #cp-onboarding { + position: fixed; + z-index: 1000; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + background-color: @cp_loading-bg; + color: @cp_loading-fg; + display: flex; + flex-flow: column; + justify-content: center; + align-items: center; + .cp-onboarding-box { + width: 850px; + background-color: @cp_loading-progress-bg; + border-radius: @variables_radius_L; + box-shadow: 0px 0px 10px 0px @cp_shadow-color; + padding: 30px 110px 20px 110px; + } + .cp-onboarding-logo-input { + display: none; + } + .cp-onboardscreen-logo { + min-width: 200px; + max-width: 200px; + nav { + display: flex; + margin-top: 0.5rem; + justify-content: center; + } + } + } +} + +.configscreen { + //width: 70%; + //height: 75%; + background-color: @cp_loading-progress-bg; + border-radius: @variables_radius_L; + box-shadow: 0px 0px 10px 0px @cp_shadow-color; + padding: 30px 110px 20px 110px; +} + +.cp-onboardscreen-screentitle { + text-align: center; + font-size: 1rem; + margin-bottom: 1rem; + h1.cp-onboardscreen-title { + font-family: "IBM Plex Mono"; + color: @cryptpad_color_link; + font-size: 1.5rem; + font-weight: 500; + } +} + +.cp-instance-form { + display: flex; + flex-direction: row; + gap: 1rem; + input, textarea { + border: 0px transparent !important; + } + .cp-onboardscreen-logo { + .cp-sidebar-form { + background-color: @cp_forms-bg; + border-radius: @variables_radius; + padding: 0.5rem; + .cp-admin-customize-logo { + img { + max-width: 100%; + } + } + } + } + .cp-instance-text-form { + width: 100%; + display: flex; + flex-direction: column; + gap: 1rem; + .cp-onboardscreen-name { + input { + font-family: "IBM Plex Mono"; + font-size: 1.2em; + } + } + .cp-onboardscreen-colorpick { + div.cp-palette-container { + display: flex; + justify-content: flex-start; + .cp-palette-color { + margin-right: 1rem; + } + } + } + } +} + + +nav.cp-onboardscreen-nav { + width: 100%; + display: flex; + align-items: flex-end; + justify-content: space-between; + margin-top: 1rem; +} \ No newline at end of file diff --git a/www/install/onboardscreen.js b/www/install/onboardscreen.js new file mode 100644 index 0000000000..2fcbb179e0 --- /dev/null +++ b/www/install/onboardscreen.js @@ -0,0 +1,500 @@ +// SPDX-FileCopyrightText: 2024 XWiki CryptPad Team and contributors +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +define([ + 'jquery', + '/common/inner/sidebar-layout.js', + '/customize/messages.js', + '/customize/application_config.js', + '/common/hyperscript.js', + '/common/common-interface.js', + '/common/common-util.js', + '/common/common-ui-elements.js', + '/common/pad-types.js', + '/components/nthen/index.js', + + 'css!/components/bootstrap/dist/css/bootstrap.min.css', + 'css!/components/components-font-awesome/css/font-awesome.min.css', + +], function( + $, + Sidebar, + Messages, + AppConfig, + h, + UI, + Util, + UIElements, + PadTypes, + nThen +) { + + let pages = []; + const gotoPage = function (Env, page) { + if (typeof(page) !== "number" || !page) { page = 0; } + if (page > (pages.length - 1)) { page = pages.length - 1; } + + var nextPageFunction = pages[page]; + var nextPageForm = nextPageFunction(Env); + let frame = h('div.cp-onboarding-box', nextPageForm); + Env.overlay.empty().append(frame); + }; + const blocks = Sidebar.blocks('admin'); + + var flushCache = (Env, cb) => { + const { sendAdminRpc } = Env; + sendAdminRpc('FLUSH_CACHE', {}, function (e, response) { + if (e || response.error) { + console.error(e || response.error); + } + cb(); + }); + }; + + var selections = { + title: '', + description: '', + logoURL: '', + color: '', + appsToDisable: [], + mfa: false, + closeRegistration: false + }; + + const saveAndRedirect = (Env) => { + const { sendAdminDecree, sendAdminRpc } = Env; + + let hasError = false; + let error = () => { + hasError = true; + }; + nThen(waitFor => { + var name = selections.title; + if (!name) { return; } + sendAdminDecree('SET_INSTANCE_NAME', [name], waitFor((e, response) => { + if (e || response.error) { + console.error(e || response.error); + return void error(); + } + })); + }).nThen(waitFor => { + var description = selections.description; + if (!description) { return; } + sendAdminDecree('SET_INSTANCE_DESCRIPTION', [ + description + ], waitFor((e, response) => { + if (e || response.error) { + console.error(e || response.error); + return void error(); + } + + })); + }).nThen(waitFor => { + var dataURL = selections.logoURL; + if (!dataURL) { return; } + sendAdminRpc('UPLOAD_LOGO', {dataURL}, waitFor(function (e, response) { + if (e || response.error) { + console.error(e || response.error); + return void error(); + } + })); + }).nThen(waitFor => { + var color = selections.color; + if (!color) { return; } + sendAdminRpc('CHANGE_COLOR', {color}, waitFor(function (e, response) { + if (e || response.error) { + console.error(e || response.error); + return void error(); + } + })); + }).nThen(waitFor => { + var apps = selections.appsToDisable; + if (!apps.length) { return; } + sendAdminDecree('DISABLE_APPS', apps, waitFor(function (e, response) { + if (e || response.error) { + UI.warn(Messages.error); + return; + } + })); + }).nThen(waitFor => { + if (!selections.mfa) { return; } + sendAdminDecree('ENFORCE_MFA', [selections.mfa], waitFor((e, response) => { + if (e || response.error) { + UI.warn(Messages.error); + return; + } + })); + }).nThen(waitFor => { + if (!selections.closeRegistration) { return; } + sendAdminDecree('RESTRICT_REGISTRATION', [ + selections.closeRegistration + ], waitFor(function (e, response) { + if (e || response.error) { + UI.warn(Messages.error); + return; + } + })); + }).nThen(() => { + flushCache(Env, function () { + if (hasError) { + UI.alert(Messages.onboarding_save_error, function () { + document.location.href = '/drive/'; + }); + return; + } + document.location.href = '/drive/'; + }); + }); + }; + + const titleConfig = function (Env) { + let titleInput = blocks.input({ + type: 'text', + value: selections.title, + placeholder: Messages.admin_onboardingNamePlaceholder, + 'aria-labelledby': 'cp-admin-name' + }); + let description = blocks.textarea({ + placeholder: Messages.admin_onboardingDescPlaceholder, + 'aria-labelledby': 'cp-admin-description' + }, selections.description); + $(description).addClass('cp-onboardscreen-desc'); + + let dataURL = selections.logoURL; + var getLogoBlock = function() { + let inputLogo = blocks.input({ + type: 'file', + accept: 'image/*', + 'aria-labelledby': 'cp-admin-logo' + }); + + var currentContainer = blocks.block([], 'cp-admin-customize-logo'); + + var upload = blocks.button('secondary', '', Messages.onboarding_upload); + + let formLogo = blocks.form([ + currentContainer, + blocks.block(inputLogo, 'cp-onboarding-logo-input'), + blocks.nav([upload]) + ]); + + let $button = $(upload); + + let state = false; + let redraw = () => { + var current = h('img', {src: dataURL || '/api/logo?'+(+new Date())}); + $(currentContainer).empty().append(current); + $button.removeAttr('disabled'); + state = !!dataURL; + if (dataURL) { + $button.text(Messages.admin_logoRemoveButton); + $button.removeClass('btn-secondary').addClass('btn-danger-alt'); + } else { + $button.text(Messages.onboarding_upload); + $button.removeClass('btn-danger-alt').addClass('btn-secondary'); + } + }; + redraw(); + + let $input = $(inputLogo); + $input.on('change', function () { + let files = inputLogo.files; + if (files.length !== 1) { + UI.warn(Messages.error); + return; + } + let reader = new FileReader(); + reader.onloadend = function () { + dataURL = this.result; + redraw(dataURL); + }; + reader.readAsDataURL(files[0]); + }); + Util.onClickEnter($button, function () { + $button.attr('disabled', 'disabled'); + if (!state) { + return void $input.click(); + } + dataURL = ''; + redraw(); + }); + + return formLogo; + }; + + var getColorBlock = function () { + + // Number of accent color presets + var colors = UIElements.makePalette(4, (color, $color) => { + let rgb = $color.css('background-color'); + let hex = Util.rgbToHex(rgb); + if (hex) { + selections.color = color ? hex : ''; + selections.colorId = color; + } + }); + if (selections.colorId) { + colors.setValue(selections.colorId); + } + var content = h('div.cp-onboardscreen-colorpick', [ + h('label', {for:'cp-install-color'}, Messages.kanban_color), + colors + ]); + + return content; + }; + + var button = blocks.activeButton('primary', '', Messages.continue, function () { + selections.title = $(titleInput).val() || ''; + selections.description = $(description).val() || ''; + if (dataURL) { + selections.logoURL = dataURL; + } + + gotoPage(Env, 1); + }); + + var titleBlock = h('div.cp-onboardscreen-name', titleInput); + var descriptionBlock = h('div', description); + var logoBlock = h('div.cp-onboardscreen-logo', getLogoBlock()); + var colorBlock = h('div.cp-onboardscreen-color', getColorBlock()); + + var screenTitle = h('div.cp-onboardscreen-screentitle'); + $(screenTitle).append(('div.cp-onboardscreen-maintitle', [ + h('h1.cp-onboardscreen-title', Messages.admin_onboardingNameTitle), + h('span', Messages.admin_onboardingNameHint) + ])); + var nav = blocks.nav([h('span'), button]); + + $(button).addClass('cp-onboardscreen-save'); + $(nav).addClass('cp-onboardscreen-nav'); + + var textForm = h('div.cp-instance-text-form', [ + titleBlock, + descriptionBlock, + colorBlock, + ]); + + var instanceForm = h('div.cp-instance-form', [logoBlock, textForm]); + + var form = blocks.form([ + screenTitle, + instanceForm + ], nav); + + $(form).addClass('cp-onboardscreen-form'); + + return form; + + }; + + const createAppsGrid = appsToDisable => { + const grid = blocks.block([], 'cp-admin-customize-apps-grid'); + const $grid = $(grid); + const allApps = PadTypes.appsToSelect; + + let select = function (app, $app) { + if (appsToDisable.indexOf(app) === -1) { + appsToDisable.push(app); + $app.toggleClass('cp-inactive-app', true); + $app.toggleClass('cp-active-app', false); + } else { + appsToDisable.splice(appsToDisable.indexOf(app), 1); + $app.toggleClass('cp-inactive-app', false); + $app.toggleClass('cp-active-app', true); + } + }; + + allApps.forEach(app => { + let name = Messages.type[app] || app; + let icon = UI.getNewIcon(app); + let appBlock = h('div.cp-appblock', + {tabindex:0, role:"button"}, + [ + icon, + h('span.cp-app-name', name), + h('i.fa.fa-check.cp-on-enabled') + ]); + let $app = $(appBlock).appendTo($grid); + if (appsToDisable.includes(app)) { + $app.addClass('cp-inactive-app'); + } else { + $app.addClass('cp-active-app'); + } + $app.on('click', () => select(app, $app)); + }); + + return grid; + }; + + const appConfig = function (Env) { + const appsToDisable = selections.appsToDisable; + const grid = createAppsGrid(appsToDisable); + + + var save = blocks.activeButton('primary', '', Messages.continue, function () { + gotoPage(Env, 2); + }); + + var prev = blocks.activeButton('secondary', '', Messages.form_backButton, function () { + gotoPage(Env, 0); + }); + + var screenTitle = h('div.cp-onboardscreen-screentitle'); + $(screenTitle).append(h('div.cp-onboardscreen-maintitle', h('h1.cp-onboardscreen-title', Messages.admin_appsTitle), h('span', Messages.admin_appsHint))); + $(save).addClass('cp-onboardscreen-save'); + $(prev).addClass('cp-onboardscreen-prev'); + var nav = blocks.nav([prev, save]); + $(nav).addClass('cp-onboardscreen-nav'); + let form = blocks.form([ + screenTitle, + grid + ], nav); + + $(form).addClass('cp-onboardscreen-form'); + + return form; + }; + + const mfaRegistrationScreen = function (Env) { + var restrict = blocks.activeCheckbox({ + key: 'registration', + getState: function () { + return selections.closeRegistration; + }, + label: 'registration', + query: function (val, setState) { + selections.closeRegistration = val; + setState(val); + }, + }); + + var forceMFA = blocks.activeCheckbox({ + key: 'forcemfa', + getState: function () { + return selections.mfa; + }, + label: 'forcemfa', + query: function (val, setState) { + selections.mfa = val; + setState(val); + }, + }); + + + let mfaOption = h('div.cp-optionblock', [ + forceMFA, + h('span.cp-option-hint', Messages.admin_forcemfaHint) + ]); + let registrationOption = h('div.cp-optionblock', [ + restrict, + h('span.cp-option-hint', Messages.admin_registrationHint) + ]); + const grid = blocks.block([ + mfaOption, + registrationOption + ], 'cp-admin-customize-options-grid'); + + var save = blocks.activeButton('primary', '', Messages.settings_save, function () { + saveAndRedirect(Env); + }); + + var prev = blocks.activeButton('secondary', '', Messages.form_backButton, function () { + gotoPage(Env, 1); + }); + + var screenTitle = h('div.cp-onboardscreen-screentitle'); + $(screenTitle).append(h('div.cp-onboardscreen-maintitle', h('h1.cp-onboardscreen-title', Messages.admin_onboardingOptionsTitle), UI.setHTML(h('span'), Messages.admin_onboardingOptionsHint))); + $(save).addClass('cp-onboardscreen-save'); + $(prev).addClass('cp-onboardscreen-prev'); + var nav = blocks.nav([prev, save]); + $(nav).addClass('cp-onboardscreen-nav'); + + var form = blocks.form([ + screenTitle, + grid], nav + ); + + $(form).addClass('cp-onboardscreen-form'); + + return form; + }; + + pages = [ + titleConfig, + appConfig, + mfaRegistrationScreen + ]; + const create = (sendAdminDecree, sendAdminRpc) => { + let Env = { + sendAdminDecree, + sendAdminRpc + }; + Env.overlay = $(h('div#cp-onboarding')); + gotoPage(Env, 0); + $('body').append(Env.overlay); + }; + + // XXX test functions to remove + window.CP_onboarding_test = () => { + create(() => {}, () => {}); + }; + window.CP_onboarding_test_full = () => { + require([ + '/common/cryptpad-common.js', + '/common/outer/local-store.js', + '/common/outer/login-block.js', + '/common/rpc.js', + '/common/common-constants.js', + '/common/cryptget.js', + ], function (CryptPad, LocalStore, Block, Rpc, Constants, CryptGet) { + var blockHash = LocalStore.getBlockHash(); + if (!blockHash) { return void console.error('NOT LOGGED IN'); } + var parsed = Block.parseBlockHash(blockHash); + var sessionToken = LocalStore.getSessionToken() || undefined; + let userHash; + nThen(w => { + // Get user keys + Util.getBlock(parsed.href, { + bearer: sessionToken + }, w((err, response) => { + if (err) { + w.abort(); + return void console.error('Please login in and try again'); + } + response.arrayBuffer().then(w(arraybuffer => { + arraybuffer = new Uint8Array(arraybuffer); + var block_info = Block.decrypt(arraybuffer, parsed.keys); + userHash = block_info[Constants.userHashKey]; + })); + })); + }).nThen(() => { + // Make RPC + if (!userHash) { return void console.error('AUTH FAILED'); } + CryptPad.makeNetwork((err, network) => { + if (err) { return void console.error(err); } + CryptGet.get(userHash, (err, val) => { + let p = Util.tryParse(val); + Rpc.create(network, p.edPrivate, p.edPublic, function (e, rpc) { + let sendAdminDecree = function (command, data, callback) { + var params = ['ADMIN_DECREE', [command, data]]; + rpc.send('ADMIN', params, callback); + }; + + let sendAdminRpc = function (command, data, callback) { + var params = [command, data]; + rpc.send('ADMIN', params, callback); + }; + + create(sendAdminDecree, sendAdminRpc); + }); + }, {network: network}); + }); + }); + }); + }; + return { create, createAppsGrid }; + +}); + diff --git a/www/integration/main.js b/www/integration/main.js index f0876d2757..fe3ffd0101 100644 --- a/www/integration/main.js +++ b/www/integration/main.js @@ -96,6 +96,13 @@ define([ http.send(); }; chan.on('GET_SESSION', function (data, cb) { + if (data.keepOld) { + var key = data.key + "000000000000000000000000000000000"; + console.warn('KEY', key); + return void cb({ + key: `/2/integration/edit/${key.slice(0,24)}/` + }); + } var getHash = function () { //isNew = true; return Hash.createRandomHash('integration'); @@ -126,6 +133,24 @@ define([ var onInsertImage = function (data, cb) { chan.send('ON_INSERT_IMAGE', data, cb); }; + var onReady = function () { + chan.send('DOCUMENT_READY', {}); + }; + + let downloadAs; + chan.on('DOWNLOAD_AS', function (format) { + if (typeof(downloadAs) !== "function") { + console.error('UNSUPPORTED COMMAND', 'downloadAs'); + return; + } + downloadAs(format); + }); + let setDownloadAs = f => { + downloadAs = f; + }; + let onDownloadAs = function (blob) { // DownloadAs callback + chan.send('ON_DOWNLOADAS', blob); + }; chan.on('START', function (data) { console.warn('INNER START', data); @@ -141,13 +166,20 @@ define([ autosave: data.autosave }, utils: { + onReady: onReady, + onDownloadAs, + setDownloadAs, save: save, reload: reload, onHasUnsavedChanges: onHasUnsavedChanges, onInsertImage: onInsertImage } }; - require(['/common/sframe-app-outer.js'], function () { + let path = "/common/sframe-app-outer.js"; + if (['sheet', 'doc', 'presentation'].includes(data.application)) { + path = '/common/onlyoffice/main.js'; + } + require([path], function () { console.warn('SAO REQUIRED'); delete window.CP_integration_outer; }); diff --git a/www/kanban/app-kanban.less b/www/kanban/app-kanban.less index 8d50f1e910..ffe0a0da8f 100644 --- a/www/kanban/app-kanban.less +++ b/www/kanban/app-kanban.less @@ -63,11 +63,13 @@ .kanban-colors(@kanban-colors; (@index - 1)); @color: extract(@kanban-colors, @index); // make a numbered class selector for each color - .cp-kanban-palette-color@{index}{ - background-color: @color !important; + .cp-palette-color@{index}{ &.cp-kanban-palette-board { background-color: @color !important; } + } + .cp-kanban-palette-color@{index}{ + background-color: @color !important; &.kanban-board-inner { background-color: fade(@color, 50%) !important; } @@ -81,10 +83,12 @@ // .cp-kanban-card-color@{index}{ // background-color: @color !important; // } - .cp-kanban-palette-color@{index}{ + .cp-palette-color@{index}{ &.cp-kanban-palette-card { background-color: @color !important; } + } + .cp-kanban-palette-color@{index}{ &.kanban-item { background-color: @color !important; } @@ -140,23 +144,7 @@ } margin-bottom: 15px; } - #cp-kanban-edit-colors { - display: flex; - justify-content: space-between; - .cp-kanban-palette { - 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; - } - } - } + #cp-kanban-edit-tags { .tokenfield { margin: 0; @@ -187,6 +175,11 @@ border: 0; background: transparent; align-self: flex-start; + outline-style: none; + border-radius: @variables_radius; + &:focus { + outline: @cryptpad_color_brand solid 2px; + } @media (hover: none) { margin-right: 20px; } @@ -420,6 +413,9 @@ .fa { margin-right: 5px; } + &:focus{ + outline: @cryptpad_color_brand solid 2px; + } } } } @@ -655,6 +651,9 @@ &:hover { background-color: @cp_kanban-add-hover; } + &:focus { + outline: @cryptpad_color_brand solid 2px; + } } .kanban-header-yellow { diff --git a/www/kanban/inner.js b/www/kanban/inner.js index e3579e9520..c8f270f6d8 100644 --- a/www/kanban/inner.js +++ b/www/kanban/inner.js @@ -185,7 +185,12 @@ define([ update(); }; - var conflicts, conflictContainer, titleInput, tagsDiv, colors, text; + var colors = UIElements.makePalette(8, color => { + dataObject.color = color; + commit(); + }); + + var conflicts, conflictContainer, titleInput, tagsDiv, text; var content = h('div', [ conflictContainer = h('div#cp-kanban-edit-conflicts', [ h('div', Messages.kanban_conflicts), @@ -200,7 +205,7 @@ define([ h('label', {for:'cp-kanban-edit-tags'}, Messages.fm_tagsName), tagsDiv = h('div#cp-kanban-edit-tags'), h('label', {for:'cp-kanban-edit-color'}, Messages.kanban_color), - colors = h('div#cp-kanban-edit-colors'), + colors, ]); var $tags = $(tagsDiv); @@ -362,11 +367,8 @@ define([ // Colors var $colors = $(colors); - var palette = ['']; - for (var i=1; i<=8; i++) { palette.push('color'+i); } - var selectedColor = ''; var resetThemeClass = function () { - $colors.find('.cp-kanban-palette').each(function (i, el) { + $colors.find('.cp-palette-color').each(function (i, el) { var $c = $(el); $c.removeClass('cp-kanban-palette-card'); $c.removeClass('cp-kanban-palette-board'); @@ -377,31 +379,13 @@ define([ } }); }; - palette.forEach(function (color) { - var $color = $(h('span.cp-kanban-palette.fa')); - $color.addClass('cp-kanban-palette-'+(color || 'nocolor')); - $color.click(function () { - if (offline) { return; } - if (color === selectedColor) { return; } - selectedColor = color; - $colors.find('.cp-kanban-palette').removeClass('fa-check'); - var $col = $colors.find('.cp-kanban-palette-'+(color || 'nocolor')); - $col.addClass('fa-check'); - - dataObject.color = color; - commit(); - }).appendTo($colors); - }); var color = { getValue: function () { - return selectedColor; + return colors.getValue(); }, setValue: function (color) { resetThemeClass(); - $colors.find('.cp-kanban-palette').removeClass('fa-check'); - var $col = $colors.find('.cp-kanban-palette-'+(color || 'nocolor')); - $col.addClass('fa-check'); - selectedColor = color; + colors.setValue(color); } }; @@ -453,6 +437,7 @@ define([ $modal.find('nav button.danger').prop('disabled', unlocked ? '' : 'disabled'); offline = !unlocked; + colors.disable(offline); }); @@ -899,8 +884,9 @@ define([ if (migrated) { framework.localChange(); } var addBoardDefault = document.getElementById('kanban-addboard'); + let $addBoard = $(addBoardDefault).attr('tabindex', 0); $(addBoardDefault).attr('title', Messages.kanban_addBoard); - addBoardDefault.addEventListener('click', function () { + Util.onClickEnter($addBoard, function () { if (framework.isReadOnly() || framework.isLocked()) { return; } /*var counter = 1; diff --git a/www/kanban/jkanban_cp.js b/www/kanban/jkanban_cp.js index 2edd1bc54b..1b3a9967b8 100644 --- a/www/kanban/jkanban_cp.js +++ b/www/kanban/jkanban_cp.js @@ -112,6 +112,9 @@ define([ var boardContainer = document.createElement('div'); boardContainer.classList.add('kanban-container'); boardContainerOuter.appendChild(boardContainer); + self.container = boardContainer; + //add boards + self.addBoards(); var addBoard = document.createElement('div'); addBoard.id = 'kanban-addboard'; addBoard.innerHTML = ''; @@ -126,9 +129,6 @@ define([ trash.appendChild(trashBg); self.boardContainer.push(trash); - self.container = boardContainer; - //add boards - self.addBoards(); //appends to container self.element.appendChild(boardContainerOuter); self.element.appendChild(trash); @@ -717,12 +717,14 @@ define([ //add button var addTopBoardItem = document.createElement('span'); addTopBoardItem.classList.add('kanban-title-button'); + $(addTopBoardItem).attr('tabindex', '0'); addTopBoardItem.setAttribute('data-top', "1"); addTopBoardItem.innerHTML = ''; footerBoard.appendChild(addTopBoardItem); __onAddItemClickHandler(addTopBoardItem); var addBoardItem = document.createElement('span'); addBoardItem.classList.add('kanban-title-button'); + $(addBoardItem).attr('tabindex', '0'); addBoardItem.innerHTML = ''; footerBoard.appendChild(addBoardItem); __onAddItemClickHandler(addBoardItem); diff --git a/www/lib/optional/optional.js b/www/lib/optional/optional.js index 00767379f8..c42ec28353 100644 --- a/www/lib/optional/optional.js +++ b/www/lib/optional/optional.js @@ -13,7 +13,7 @@ define("optional", [], { var onLoadFailure = function(err){ // optional module failed to load. var failedId = err.requireModules && err.requireModules[0]; - console.warn("Could not load optional module: " + failedId); + //console.warn("Could not load optional module: " + failedId); // Undefine the module to cleanup internal stuff in requireJS requirejs.undef(failedId); diff --git a/www/mediatag/index.html b/www/mediatag/index.html deleted file mode 100644 index bdae6a8174..0000000000 --- a/www/mediatag/index.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - Test media-tag - - - - - Media-tag: -
-
-
- diff --git a/www/mediatag/main.js b/www/mediatag/main.js deleted file mode 100644 index f8f0512dd2..0000000000 --- a/www/mediatag/main.js +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -require([ - 'jquery', - '/mediatag/media-tag.js', - '/components/tweetnacl/nacl-fast.min.js' -], function ($, MediaTag) { - console.log(MediaTag); - console.log($('media-tag')); - if (typeof MediaTag === "function") { - MediaTag.PdfPlugin.viewer = '/lib/pdfjs/web/viewer.html'; - - var config = { - allowed: ['download'], - download: { - text: 'Download' - } - }; - MediaTag($('media-tag'), config) - .on('progress', function (data) { - console.log(data.progress); - }) - .on('complete', function (data) { - console.log(data); - }) - .on('error', function (data) { - console.error(data); - }); - MediaTag($('media-tag')[1]) - .on('progress', function (data) { - console.log(data.progress); - }) - .on('complete', function (data) { - console.log(data); - }) - .on('error', function (data) { - console.error(data); - }); - MediaTag($('media-tag')[2]) - .on('progress', function (data) { - console.log(data.progress); - }) - .on('complete', function (data) { - console.log(data); - }) - .on('error', function (data) { - console.error(data); - }); - MediaTag($('media-tag')[3]) - .on('progress', function (data) { - console.log(data.progress); - }) - .on('complete', function (data) { - console.log(data); - }) - .on('error', function (data) { - console.error(data); - }); - } -}); diff --git a/www/mediatag/media-tag.js b/www/mediatag/media-tag.js deleted file mode 100644 index 55b5f822ea..0000000000 --- a/www/mediatag/media-tag.js +++ /dev/null @@ -1,432 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -(function(name, definition) { - if (typeof module !== 'undefined') { module.exports = definition(); } - else if (typeof define === 'function' && typeof define.amd === 'object') { define(definition); } - else { this[name] = definition(); } -}('MediaTag', function() { - var cache; - var cypherChunkLength = 131088; - - // Save a blob on the file system - var saveFile = function (blob, url, fileName) { - if (window.navigator && window.navigator.msSaveOrOpenBlob) { - window.navigator.msSaveOrOpenBlob(blob, fileName); - } else { - // We want to be able to download the file with a name, so we need an "a" tag with - // a download attribute - var a = document.createElement("a"); - a.href = url; - a.download = fileName; - // It's not in the DOM, so we can't use a.click(); - var event = new MouseEvent("click"); - a.dispatchEvent(event); - } - }; - - var fixHTML = function (str) { - if (!str) { return ''; } - return str.replace(/[<>&"']/g, function (x) { - return ({ "<": "<", ">": ">", "&": "&", '"': """, "'": "'" })[x]; - }); - }; - - - // Default config, can be overriden per media-tag call - var config = { - allowed: [ - 'image/png', - 'image/jpeg', - 'image/jpg', - 'image/gif', - 'audio/mp3', - 'audio/ogg', - 'audio/wav', - 'audio/webm', - 'video/mp4', - 'video/ogg', - 'video/webm', - 'application/pdf', - //'application/dash+xml', // FIXME? - 'download' - ], - pdf: {}, - download: { - text: "Download" - }, - Plugins: { - image: function (metadata, url, content, cfg, cb) { - var img = document.createElement('img'); - img.setAttribute('src', url); - img.blob = content; - cb(void 0, img); - }, - video: function (metadata, url, content, cfg, cb) { - var video = document.createElement('video'); - video.setAttribute('src', url); - video.setAttribute('controls', true); - cb(void 0, video); - }, - audio: function (metadata, url, content, cfg, cb) { - var audio = document.createElement('audio'); - audio.setAttribute('src', url); - audio.setAttribute('controls', true); - cb(void 0, audio); - }, - pdf: function (metadata, url, content, cfg, cb) { - var iframe = document.createElement('iframe'); - if (cfg.pdf.viewer) { // PDFJS - var viewerUrl = cfg.pdf.viewer + '?file=' + url; - iframe.src = viewerUrl + '#' + window.encodeURIComponent(metadata.name); - return void cb (void 0, iframe); - } - iframe.src = url + '#' + window.encodeURIComponent(metadata.name); - return void cb (void 0, iframe); - }, - download: function (metadata, url, content, cfg, cb) { - var btn = document.createElement('button'); - btn.innerHTML = cfg.download.text + '
' + - metadata.name ? '' + fixHTML(metadata.name) + '' : ''; - btn.addEventListener('click', function () { - saveFile(content, url, metadata.name); - }); - cb(void 0, btn); - } - } - }; - - - // Download a blob from href - var download = function (src, cb) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', src, true); - xhr.responseType = 'arraybuffer'; - - xhr.onload = function () { - // Error? - if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); } - - var arrayBuffer = xhr.response; - if (arrayBuffer) { cb(null, new Uint8Array(arrayBuffer)); } - }; - - xhr.send(null); - }; - - // Decryption tools - var Decrypt = { - // Create a nonce - createNonce: function () { - var n = new Uint8Array(24); - for (var i = 0; i < 24; i++) { n[i] = 0; } - return n; - }, - - // Increment a nonce - increment: function (N) { - var l = N.length; - while (l-- > 1) { - if (N[l] !== 255) { return void N[l]++; } - - // you don't need to worry about this running out. - // you'd need a REAAAALLY big file - if (l === 0) { throw new Error('E_NONCE_TOO_LARGE'); } - - N[l] = 0; - } - }, - - decodePrefix: function (A) { - return (A[0] << 8) | A[1]; - }, - joinChunks: function (chunks) { - return new Blob(chunks); - }, - - // Convert a Uint8Array into Array. - slice: function (u8) { - return Array.prototype.slice.call(u8); - }, - - // Gets the key from the key string. - getKeyFromStr: function (str) { - return window.nacl.util.decodeBase64(str); - } - }; - - // Decrypts a Uint8Array with the given key. - var decrypt = function (u8, strKey, done, progressCb) { - var Nacl = window.nacl; - - var progress = function (offset) { - progressCb((offset / u8.length) * 100); - }; - - var key = Decrypt.getKeyFromStr(strKey); - var nonce = Decrypt.createNonce(); - var i = 0; - var prefix = u8.subarray(0, 2); - var metadataLength = Decrypt.decodePrefix(prefix); - - var res = { metadata: undefined }; - - // Get metadata - var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength)); - var metaChunk = Nacl.secretbox.open(metaBox, nonce, key); - - Decrypt.increment(nonce); - - try { res.metadata = JSON.parse(Nacl.util.encodeUTF8(metaChunk)); } - catch (e) { return void done('E_METADATA_DECRYPTION'); } - - if (!res.metadata) { return void done('NO_METADATA'); } - - var takeChunk = function (cb) { - setTimeout(function () { - var start = i * cypherChunkLength + 2 + metadataLength; - var end = start + cypherChunkLength; - i++; - - // Get the chunk - var box = new Uint8Array(u8.subarray(start, end)); - - // Decrypt the chunk - var plaintext = Nacl.secretbox.open(box, nonce, key); - Decrypt.increment(nonce); - - if (!plaintext) { return void cb('DECRYPTION_FAILURE'); } - - progress(Math.min(end, u8.length)); - - cb(void 0, plaintext); - }); - }; - - var chunks = []; - - // decrypt file contents - var again = function () { - takeChunk(function (e, plaintext) { - if (e) { return setTimeout(function () { done(e); }); } - - if (plaintext) { - if (i * cypherChunkLength < u8.length) { // not done - chunks.push(plaintext); - return again(); - } - - chunks.push(plaintext); - res.content = Decrypt.joinChunks(chunks); - return void done(void 0, res); - } - done('UNEXPECTED_ENDING'); - }); - }; - again(); - }; - - // Get type - var getType = function (mediaObject, metadata, cfg) { - var mime = metadata.type; - var s = metadata.type.split('/'); - var type = s[0]; - var extension = s[1]; - - mediaObject.name = metadata.name; - if (mime && cfg.allowed.indexOf(mime) !== -1) { - mediaObject.type = type; - mediaObject.extension = extension; - mediaObject.mime = mime; - return type; - } else if (cfg.allowed.indexOf('download') !== -1) { - mediaObject.type = type; - mediaObject.extension = extension; - mediaObject.mime = mime; - return 'download'; - } else { - return; - } - }; - - // Copy attributes - var copyAttributes = function (origin, dest) { - Object.keys(origin.attributes).forEach(function (i) { - if (!/^data-attr/.test(origin.attributes[i].name)) { return; } - var name = origin.attributes[i].name.slice(10); - var value = origin.attributes[i].value; - dest.setAttribute(name, value); - }); - }; - - // Process - var process = function (mediaObject, decrypted, cfg, cb) { - var metadata = decrypted.metadata; - var blob = decrypted.content; - - var mediaType = getType(mediaObject, metadata, cfg); - - if (mediaType === 'application') { - mediaType = mediaObject.extension; - } - - if (!mediaType || !cfg.Plugins[mediaType]) { - return void cb('NO_PLUGIN_FOUND'); - } - - // Get blob URL - var url = decrypted.url; - if (!url && window.URL) { - url = decrypted.url = window.URL.createObjectURL(new Blob([blob], { - type: metadata.type - })); - } - - cfg.Plugins[mediaType](metadata, url, blob, cfg, function (err, el) { - if (err || !el) { return void cb(err || 'ERR_MEDIATAG_DISPLAY'); } - copyAttributes(mediaObject.tag, el); - mediaObject.tag.innerHTML = ''; - mediaObject.tag.appendChild(el); - cb(); - }); - }; - - var addMissingConfig = function (base, target) { - Object.keys(target).forEach(function (k) { - if (!target[k]) { return; } - // Target is an object, fix it recursively - if (typeof target[k] === "object" && !Array.isArray(target[k])) { - // Sub-object - if (base[k] && (typeof base[k] !== "object" || Array.isArray(base[k]))) { return; } - else if (base[k]) { addMissingConfig(base[k], target[k]); } - else { - base[k] = {}; - addMissingConfig(base[k], target[k]); - } - } - // Target is array or immutable, copy the value if it's missing - if (!base[k]) { - base[k] = Array.isArray(target[k]) ? JSON.parse(JSON.stringify(target[k])) - : target[k]; - } - }); - }; - - // Initialize a media-tag - var init = function (el, cfg) { - cfg = cfg || {}; - - addMissingConfig(cfg, config); - - // Add support for old mediatag library - if (!cfg.pdf.viewer && init.PdfPlugin && init.PdfPlugin.viewer) { - cfg.pdf.viewer = init.PdfPlugin.viewer; - } - - // Handle jQuery elements - if (typeof(el) === "object" && el.jQuery) { el = el[0]; } - - // Abort smoothly if the element is not a media-tag - if (el.nodeName !== "MEDIA-TAG") { - console.error("Not a media-tag!"); - return { - on: function () { return this; } - }; - } - - var handlers = cfg.handlers || { - 'progress': [], - 'complete': [], - 'error': [] - }; - - var mediaObject = el._mediaObject = { - handlers: handlers, - tag: el - }; - - var emit = function (ev, data) { - // Check if the event name is valid - if (Object.keys(handlers).indexOf(ev) === -1) { - return void console.error("Invalid mediatag event"); - } - - // Call the handlers - handlers[ev].forEach(function (h) { - // Make sure a bad handler won't break the media-tag script - try { - h(data); - } catch (err) { - console.error(err); - } - }); - }; - - mediaObject.on = function (ev, handler) { - // Check if the event name is valid - if (Object.keys(handlers).indexOf(ev) === -1) { - console.error("Invalid mediatag event"); - return mediaObject; - } - // Check if the handler is valid - if (typeof (handler) !== "function") { - console.error("Handler is not a function!"); - return mediaObject; - } - // Add the handler - handlers[ev].push(handler); - return mediaObject; - }; - - var src = el.getAttribute('src'); - var strKey = el.getAttribute('data-crypto-key'); - if (/^cryptpad:/.test(strKey)) { - strKey = strKey.slice(9); - } - var uid = [src, strKey].join(''); - - // End media-tag rendering: display the tag and emit the event - var end = function (decrypted) { - process(mediaObject, decrypted, cfg, function (err) { - if (err) { return void emit('error', err); } - emit('complete', decrypted); - }); - }; - - // If we have the blob in our cache, don't download & decrypt it again, just display - if (cache[uid]) { - end(cache[uid]); - return mediaObject; - } - - // Download the encrypted blob - download(src, function (err, u8Encrypted) { - if (err) { - return void emit('error', err); - } - // Decrypt the blob - decrypt(u8Encrypted, strKey, function (errDecryption, u8Decrypted) { - if (errDecryption) { - return void emit('error', errDecryption); - } - // Cache and display the decrypted blob - cache[uid] = u8Decrypted; - end(u8Decrypted); - }, function (progress) { - emit('progress', { - progress: progress - }); - }); - }); - - return mediaObject; - }; - - // Add the cache as a property of MediaTag - cache = init.__Cryptpad_Cache = {}; - - init.PdfPlugin = {}; - - return init; -})); diff --git a/www/teams/inner.js b/www/teams/inner.js index 64989c6b4c..9f35da7d8c 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -1049,6 +1049,7 @@ define([ var $pubLabel = $('', {'class': 'cp-default-label'}) .text(Messages.settings_publicSigningKey); $key.append($pubLabel).append(UI.dialog.selectable(userHref)); + $key.find('input').attr('aria-label', Messages.settings_publicSigningKey); } var content = [container]; cb(content); diff --git a/www/web-apps/apps/api/documents/api.js b/www/web-apps/apps/api/documents/api.js new file mode 120000 index 0000000000..c1d2442f22 --- /dev/null +++ b/www/web-apps/apps/api/documents/api.js @@ -0,0 +1 @@ +../../../../cryptpad-api.js \ No newline at end of file