Merge branch 'staging' into hide-kanban-controls

pull/1/head
ansuz 3 years ago
commit 0497d663b6

@ -1,8 +1,216 @@
# WIP
* fix opening links from temporary shared folders on iphone or other contexts that do not support shared workers
* add checkup test for disabling google FLoC
* update lodash devDependency
* Sheet export
* most exports broken by Chrome 92, mostly fixed
* we discovered that CSV export was not working in any major browser, though it's unclear why. We've disabled CSV export in the meantime
* updated translation to stop referring to Microsoft since we support OpenDocument formats
* some new browser-specific checkup tests to make it easier to detect future regressions in the APIs
* drive bug fixes
* guard against a few possible type errors
# 4.9.0
## Goals and announcements
We allocated most of this release cycle towards a schedule of one-on-one user interviews and some broad usage studies leveraging our new Form app. The remainder of our time was spent on some minor improvements. We'll continue at a slightly slower pace of implementation for the coming weeks while we complete our scheduled interviews and take some much-needed vacations.
## Update notes
It appears our promotion of the checkup page through our recent release notes and the inclusion of a link to it from the instance admin have been moderately successful. We've observed that more instance admins are noticing and fixing some common configuration issues.
This release features some minor changes to one instance configuration test which incorrectly provided an exemption for the use of `http://localhost:3000` as an `httpUnsafeOrigin` value. This exemption was provided because this value is valid for local development. However, it suppressed errors when this configuration was used for production instances where it could cause a variety of problems. As usual, we recommend checking your instance's admin page after updating to confirm that you are passing the latest tests. Information about the checkup page is included in [our documentation](https://docs.cryptpad.fr/en/admin_guide/admin_panel.html#network).
To update from 4.8.0 to 4.9.0:
1. Stop your server
2. Get the latest code with git
3. Install the latest dependencies with `bower update` and `npm i`
4. Restart your server
5. Confirm that your instance is passing all the tests included on the `/checkup/` page
## Features
* We've added the ability to store URLs in user and team drives as requested in a private support ticket and [this issue](https://github.com/xwiki-labs/cryptpad/issues/732). Links can be shared directly with contacts. Unlike pads, links are not collaborative objects, so updating a link's name will not update the entry in another user's drive if you've already shared it with them. Links are integrated into our apps' _insert_ menu to facilitate quick insertion of links you've stored into your documents. We're interested in measuring how this functionality is used in practice so we can decide whether it's worth spending more time on it. We have added some telemetry to measure (in aggregate) how often its components are used. We anonymize IP addresses in the logs for CryptPad.fr, but as always, you can disable telemetry via your settings panel.
* Our rich text editor now supports indentation with the tab key, as per [issue #634](https://github.com/xwiki-labs/cryptpad/issues/634).
* Forms received another round of improvements to styles, workflows, and some basic survey functionality to yield more accurate results.
* Ordered lists are now shuffled for each survey participant so that their initial order has less effect on the final results.
* CSV export now uses one column for each option in polls, making them easier to read.
* Unregistered users can now add a name to their response.
* Form results are displayed automatically (when available) to those who have answered.
* Authors and auditors can now click on usernames in polls to jump directly to other answers from the same user.
* Users with very large drives might notice that their account loads slightly faster now, due to some minor optimizations in an integrity check that the client performs when loading accounts.
## Bugs
* We've added a guard against a type error that could be triggered when loading teams under certain rare conditions.
* Unregistered users' drives now show the "bread-crumb" UI for navigating between folders when viewing a shared folder in read-only mode. We've also suppressed the "Files" button for displaying the tree view which was non-functional for such users.
* A change in the format of support tickets caused tickets recently created by premium users to not be recognized as such. We've fixed the categorization in the admin panel's support ticket view.
* We've fixed a number of minor issues with forms:
* The maximum number of selectable choices for checkbox questions can no longer exceed the number of available choices.
* We guard against a type error that could occur when parsing dates.
* Forms imported from templates now have their initial title corrected.
* We've disabled the use of our indexedDB caching system for form results, since it was quietly dropping older responses when more than 100 responses had been submitted. We plan to re-enable caching for results once we've updated the eviction metric to better handle the response format.
# 4.8.0
## Goals
This release cycle we decided to give people a chance to try our forms app and provide feedback before we begin developing its second round of major features and improvements. In the meantime we planned to work mostly on the activities of our [NGI DAPSI](https://dapsi.ngi.eu/) project which concerns client-side file format conversions. Otherwise, we dedicated some of our independently funded time towards some internal code review and security best-practices as a follow-up to the recent quick-scan performed by [Radically Open Security](https://radicallyopensecurity.com/) that was funded by [NLnet](https://nlnet.nl) as a part of our now-closing _CryptPad for Communities_ project.
## Update notes
We are still accepting feedback concerning our Form application via [a form hosted on CryptPad.fr](https://cryptpad.fr/form/#/2/form/view/gYs4QS7DetInCXy0z2CQoUW6CwN6kaR2utGsftDzp58/). We will accept feedback here until July 12th, 2021, so if you'd like your opinions to be represented in the app's second round of development act quickly!
Following our last release we sent out an email to the admins of each outdated instance that had included their addresses in the server's daily telemetry. This appears to have been successful, as more than half of the 700+ instances that provide this telemetry are now running **4.7.0**. Previously, only 15% of instances were running the latest version. It's worth noting that of those admins that are hosting the latest version, less than 10% have opted into future emails warning them of security issues. In case you missed it, this can be done on the admin panel's _Network_ tab. Unlike most companies, we consider excess data collection a liability rather than an asset. As such, administrator emails are no longer included in server telemetry unless the admin has consented to be contacted.
The same HTTP request that communicates server telemetry will soon begin responding with the URL of our latest release notes if it is detected that the remote instance is running an older version. The admin panel's _Network_ tab for instances running 4.7.0 or later will begin prompting admins to view the release notes and update once 4.8.0 is available.
The Network tab now includes a multiple choice form as well. If you have not disabled your instance's telemetry you can use this field to answer _why you run your instance_ (for a business, an academic institution, personal use, etc.). We intend to use this data to inform our development roadmap, though as always, the fastest way to get us to prioritize your needs is to contact us for a support contract (sales@cryptpad.fr).
Server telemetry will also include an `installMethod` property. By default this is `"unspecified"`, but we are planning to work with packagers of alternate install methods to modify this property in their installation scripts. This will help us assess what proportion of instances are installed via the steps included in our installation guide vs other methods such as the various docker images. We hope that it will also allow us to determine the source of some common misconfigurations so we can propose some improvements to the root cause.
Getting off the topic of telemetry: two types of data that were previously deleted outright (pin logs and login blocks) are now archived when the client sends a _remove_ command. This provides for the ability to restore old user credentials in cases where users claim that their new credentials do not work following a password change. Some discretion is required in such cases as a user might have intentionally invalidated their old credentials due to shoulder-surfing or the breach of another service's database where they'd reused credentials. Neither of these types of data are currently included in the scripts which evict old data as they are not likely to consume a significant amount of storage space. In any case, CryptPad's data is stored on the filesystem, so it's always possible to remove outdated files by removing them from `cryptpad/data/archive/*` or whatever path you've configured for your archives.
This release introduces some minor changes to the provided NGINX configuration file to enable support for WebAssembly where it is required for client-side file format conversions. We've added some new tests on the /checkup/ page that determine whether these changes have been applied. This page can be found via a button on the admin panel.
To update from 4.7.0 to 4.8.0:
1. Apply the documented NGINX configuration
2. Stop your server
3. Get the latest code with git
4. Install the latest dependencies with `bower update` and `npm i`
5. Restart your server
6. Confirm that your instance is passing all the tests included on the `/checkup/` page
## Features
* Those who prefer using tools localized in Japanese can thank [@Suguru](https://mstdn.progressiv.dev/@suguru) for completing the Japanese translation of the platform's text! CryptPad is a fairly big platform with a lot of text to translate, so we really appreciate how much effort went into this.
* While we're on the topic, CryptPad's _Deutsch_ translation is kept up to date largely by a single member of the German Pirate Party (Piratenpartei Deutschland). This is a huge job and we appreciate your work too!
* Anyone else who wishes to give back to the project by doing the same can contribute translations on an ongoing basis through [our Weblate instance](https://weblate.cryptpad.fr/projects/cryptpad/app/).
* We've implemented a new app for file format conversions as a part of our _INTEROFFICE_ project. At this point this page is largely a test-case for the conversion engine that we hope to integrate more tightly into the rest of the platform. It allows users to load a variety of file formats into their browser and convert to any other format that has a defined conversion process from the original format. What's special about this is that files are converted entirely in your browser, unlike other platforms which do so in the cloud and expose their contents in the process. Currently we support conversion between the following formats in every browser that supports modern web standards (ie. not safari):
* XLSX and ODS
* DOCX and ODT and TXT
* PPTX and ODP
* In addition to the /convert/ page which supports office file formats, we also put some time into improving interoperability for our existing apps. We're introducing the ability to export rich text documents as Markdown (via the [turndown](https://github.com/mixmark-io/turndown) library), to import trello's JSON format into our Kanban app (with some loss of attributes because we don't support all the same features), and to export form summaries as CSV files.
* We've added another extension to our customized markdown renderer which replaces markdown images with a warning that CryptPad blocks remote content to prevent malicious users from tracking visitors to certain pages. Such images should already be blocked by our strict use of Content-Security-Policy headers, but this will provide a better indication why images are failing to load on instances that are correctly configured and a modest improvement to users' privacy on instances that aren't.
* Up until now it was possible to include style tags in markdown documents, which some of our more advanced users used in order to customize the appearance of their rendered documents. Unfortunately, these styles were not applied strictly to the markdown preview window, but to the page as a whole, making it possible to break the platform's interface (for that pad) through the use of overly broad and powerful style rules. As of this release style tags are now treated as special elements, such that their contents are compiled as [LESS](https://lesscss.org/) within a scope that is only applied to the preview pane. This was intended as a bug fix, but it's included here as a _feature_ because advanced users might see it as such and use it to do neat things. We have no funding for further work in this direction, however, and presently have no intent of providing documentation about this behaviour.
* The checkup page uses some slightly nicer methods of displaying values returned by tests when the expected value of `true` is not returned. Some tests have been revised to return the problematic value instead of `false` when the test fails, since there were some cases where it was not clear why the test was failing, such as when a header was present but duplicated.
* We've made some server requests related to _pinning files_ moderately faster by skipping an expensive calculation and omitting the value it returned. This value was meant to be used as a checksum to ensure that all of a user's documents were included in the list which should be associated with their account, however, clients used a separate command to fetch this checksum. The value provided in response to the other commands was never used by the client.
* We've implemented a system on the client for defining default templates for particular types of documents across an entire instance in addition to the use of documents in the _templates_ section of the users drive (or that of their teams). This is intended more as a generic system for us to reuse throughout the platform's source than an API for instance admins to use. If there is sufficient interest (and funding) from other admins we'll implement this as an instance configuration point. We now provide a _poll_ template to replicate the features of our old poll app which has been deprecated in favour of forms.
* We've included some more non-sensitive information about users' teams to the debugging data to which is automatically submitted along with support tickets, such as the id of the team's drive, roster, and how large the drive's contents are.
* The _Log out everywhere_ option that is displayed in the user admin menu in the top-right corner of the page for logged-in users now displays a confirmation before terminating all remote sessions.
## Bug fixes
* It was brought to our attention that the registration page was not trimming leading and trailing whitespace from usernames as intended. We've updated the page to do so, however, accounts created with such characters in their username field must enter their credentials exactly as they were at registration time in order to log in. We have no means of detecting such accounts on the server, as usernames are not visible to server admins. We'll consider this behaviour in the future if we introduce an option to change usernames as we do with passwords.
* We now double-check that login blocks (account credentials encrypted with a key derived from a username and password) can be accessed by the client when registering or changing passwords. It should be sufficient to rely on the server to report whether the encrypted credentials were stored successfully when uploading them, but in instances where these resources don't load due to a misbehaving browser extension it's better that we detect it at registration time rather than after the user creates content that will be difficult to access without assistance determining which extension or browser customization is to blame.
* We learned that the Javascript engine used on iOS has trouble parsing an alternative representation of data strings that every other platform seems to handle. This caused calendars to display incorrect data. Because Apple prevents third-party browsers from including their own JavaScript engines this means that users were affected by this Safari bug regardless of whether they used browsers branded as Safari, Firefox, Chrome, or otherwise.
* After some internal review we now guard against a variety of cases where user-crafted input could trigger a DOMException error and prevent a whole page worth of markdown content to fail to render. While there is no impact for users' privacy or security in this bug, a malicious user could exploit it to be annoying.
* Shortly after our last release a user reported being unable to access their account due to a typeError which we were able to [guard against](https://github.com/xwiki-labs/cryptpad/commit/abc9466abe71a76d1d31ef6a3c2c9bba4d2233e4).
* Images appearing in the 'lightbox' preview modal no longer appear stretched.
* Before applying actions that modify the team's membership we now confirm that server-enforced permissions match our local state.
# 4.7.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.
## 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.
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.
* 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_.
* A third option allows admins to consent to be contacted by email. We aren't interested in spamming anyone with marketing email, rather, it's so that we can inform administrators of vulnerabilities in the software before they are publicly disclosed. Leave this unchecked if you prefer to be surprised by security flaws.
* 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:
1. Apply the documented NGINX configuration
2. Stop your server
3. Get the latest code with git
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.
## Features
* This release introduces our new _Forms_ app. This app allows users to create complex forms and to collect answers. Three roles are available with granular permissions:
* Authors can collaboratively create surveys with different types of questions and generate links to share with participants.
* 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_.
* 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.
* Embedded markdown editors' toolbars (such as that in the kanban and form apps) now include an "embed file" option.
* We've revised some text on the checkup page to better explain what some headers do and how to correct them.
* Some error messages printed by the server under rare conditions now include a little more debugging information.
* We've improved some of the UI of the "report" page (which diagnoses possible reasons why your drive, shared folders, or teams might be failing to load now includes) so that users can now copy the output of the report directly to their clipboard instead of having to select that page's text and use their OS's copy to clipboard functionality.
## Bug fixes
* The home page now displays the appropriate text ("Features" or "Pricing") for the features page depending on whether the instance in question supports subscriptions. We had made some changes to this before but missed an instance where the text was displayed.
* The admin page will now display the "General" pane if for some reason the hash in its URL does not contain a supported value.
* We found that there were two cases where localForage (a library that manages an in-browser cache) could throw a DOMExceptionerror because we didn't supply a handler. This caused the calendar app's UI to incorrectly treat a newly created event as though it had not been saved.
* A user brought it to our attention that the share menu was returning incorrect URLs for password-protected files. This has now been fixed.
* The code that is responsible for preserving your cursor position when using the code editor collaboratively was capable of interfering with active scrolling when other users' edits were applied. This is now handled more gracefully. Another fix addresses an issue that prevented the markdown preview pane from being resized under certain conditions.
* Finally, as a part of a routine security scan funded by [NLnet](https://nlnet.nl/) and executed by [Radically Open Security](https://www.radicallyopensecurity.com/) it was discovered that an unsanitized _account name_ was displayed in the users own toolbar. As a consequence, users could trigger a cross-site scripting vulnerability on themself by entering `<script>alert("pew")</script>` for their username at registration time. On a correctly configured instance this was blocked everywhere except in the sheet editor due to its more lax Content-Security Policy. This unsanitized value was never displayed for remote accounts, so the impact is extremely limited. Even so, we recommend that you update.
# 4.6.0
## 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.
## 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.
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.
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.
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/xwiki-labs/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.
To update from 4.5.0 to 4.6.0:
1. Apply the documented NGINX configuration
2. Stop your server
3. Get the latest code with git
4. Install the latest dependencies with `bower update` and `npm i`
5. Restart your server
## 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.
## Bug fixes
* Once again we fixed a bug that only occurs on Safari because Apple refuses to implement APIs that make the web a viable competitor to their app store. This one was triggered by opening a shared folder from its link as an unregistered user, then trying to open a pad stored only in that folder and not elsewhere in your drive. Literally every other browser supports _SharedWorkers_, which allow tabs on the same domain to share a background process, reducing consumption of CPU, RAM, and electricity, as well as allowing the newly opened tab to read the document's credentials from the temporarily loaded shared folder. On Safari the new tab failed to load. We fixed it by checking whether the shared folder would be accessible from newly opened tabs, and choosing to use the document's "unsafe link" instead of its "safe link".
* We updated the "Features" page to be displayed as "Pricing" in the footer when some prospective clients reported that they couldn't find a mention of what they would get by creating a premium subscription. [#683](https://github.com/xwiki-labs/cryptpad/issues/683) had the opposite problem, that they didn't support payment and they wanted to only show features. Now the footer displays the appropriate string depending on your instance's configuration.
* We fixed some inconsistent UI in our recently introduced date picker. The time formats displayed in the text field and date picker interface should now match the localization settings provided to your browser by your OS. Previously it was possible for one of these elements to appear in 24 hour time while the other appeared in 12 hour time.
* Another time-related issue appeared in the calendar for users in Hawai'i, who reported that some events were displayed on the wrong day due to the incorrect initialization of a reference date.
* We've applied a minor optimization which should reduce the size of shared folders.
* Some functionality on the admin panel has been improved with some better error handling.
* Finally, one user reported that one of their PDFs was displaying only blank pages. After a short investigation we found that the problematic PDF was trying to run some scripts which were being blocked by our strict Content-Security-Policy headers. We've updated our PDF renderer to avoid compiling and running such scripts. As a result, such PDFs should not be prevented from rendering, though they may lack some dynamic functionality that you might be expecting. We'd welcome an example of such a PDF so we can assess if there is a safe way to load their embedded scripts and how much work would be required to do so.
# 4.5.0
@ -392,7 +600,7 @@ To upgrade from 3.24.0 to 3.25.0:
## Features
* This release makes a lot of changes to how content is loaded over the network.
* Most notably, CryptPad now employs a client-side cache based on the the _indexedDB API_. Browsers that support this functionality will opportunistically store messages in a local cache for the next time they need them. This should make a considerable difference in how quickly you're able to load a pad, particularly if you accessing the server over a low-bandwidth network.
* Most notably, CryptPad now employs a client-side cache based on the _indexedDB API_. Browsers that support this functionality will opportunistically store messages in a local cache for the next time they need them. This should make a considerable difference in how quickly you're able to load a pad, particularly if you accessing the server over a low-bandwidth network.
* Uploaded files (images, PDFs, etc.) are also cached in a similar way. Once you'd loaded an asset, your client will prefer to load its local copy instead of the server.
* We've updated the code for our _full drive backup_ functionality so that it uses the local cache to load files more quickly. In addition to this, backing up the contents of your drive will also populate the cache as though you had loaded your documents in the normal fashion. This cache will persist until it is invalidated (due to the authoritative document having been deleted or had its history trimmed) or until you have logged out.
* We've added the ability to configure the maximum size for automatically downloaded files. Any encrypted files that are above this size will instead require manual interaction to begin downloading. Files that are larger than this limit which are already loaded in your cache will still be automatically displayed.
@ -575,7 +783,6 @@ Once you've reviewed these settings and you're ready to update from 3.22.0 to 3.
## Features
* As mentioned in the update notes, this release features a server update which will enable XLSX export from our sheet editor in Firefox. XLSX files are generated entirely on the client, so all information will remain confidential, it only required a server update to enable a feature in Firefox which is required to perform the conversion.
* We've also made some considerable improvements to the _history mode_ available in most of our document editors. We now display a more detailed timeline of changes according to who was present in the session, and group contiguous modifications made by a single user. Our intent is to provide an overview of the document's history which exposes the details which are most relevant to humans, rather than only allowing users to step through each individual change.
* Another change which is related to our history mode improvements is support for "version links", which allow you to link to a specific historical version of a document while you scroll through the timeline of its modifications. You can also create _named snapshots_ of documents which will subsequently be displayed as highlights in the document's timeline.
@ -637,7 +844,7 @@ If you're only reading this for instructions on how to update your instance from
2. Get the latest platform code with git
3. Install client-side dependencies with `bower update`
4. Install server-side dependencies with `npm install`
4. Restart the CryptPad API server
5. Restart the CryptPad API server
## Features
@ -685,7 +892,7 @@ 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`
3. Install the latest dependencies with `bower update` and `npm i`
3. Restart your server
4. Restart your server
# UplandMoa (3.20.0)
@ -1162,7 +1369,7 @@ This release includes updates to:
1. the server and its dependencies
2. the example nginx configuration which we recommend for production installations
4. the client code and its dependencies
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.
@ -1269,7 +1476,9 @@ Finally, in case you live in a political jurisdiction that requires web site adm
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
* don't forget to reload your server to ensure that your changes are deployed
* don't forget to reload your server to ensure that your changes are deployed
2. stop your API server
3. pull the latest server/client code with `git pull origin master`
4. install the latest clientside dependencies with `bower update`
@ -1689,9 +1898,9 @@ To update:
1. Take down your server
2. Pull the latest code
2. `npm install`
3. `bower update`
4. Launch your server
3. `npm install`
4. `bower update`
5. Launch your server
## Features
@ -1832,13 +2041,17 @@ As this is the last release in our 2.0 cycle, we're going to take some extra tim
* We've updated some dependencies that are used to lint the CryptPad codebase to detect errors. Run `npm install` if you plan to develop for CryptPad and you want to use the linter
* This release introduces a _support_ tab within the admin panel. If you generate an asymmetric keypair and add it to your server-side configuration file then users will have the option of opening support tickets if they encounter errors. Their support tickets will include some basic information about their account which might help you to solve their issues. To set up your _"encrypted support mailbox"_:
1. run `node ./scripts/generate-admin-keys.js`
2. copy the "public key" and add it to your config.js file like so:
* `supportMailboxPublicKey: "BL3kgYBM0HNw5ms8ULWU1wMTb5ePBbxAPjDZKamkuB8=",
3. copy the private key and store it in a safe place
4. navigate to the "support" tab in the admin panel and enter the private key
5. share the private key with any other administrators who should be able to read the support tickets
6. restart so that your users receive the public key stored in your configuration file
* this will allow them to submit tickets via the support page
* if you don't know how to fix the issue and want to open a ticket on our public tracker, include the information submitted along with their ticket
@ -1892,7 +2105,7 @@ Finally, we prioritized the ability to archive files for a period instead of del
3. pull the latest clientside and serverside code via git
4. `npm update` to get the latest serverside dependencies
5. update the cache-busting string if you are handling the cache manually, otherwise allow the server to handle this as per its default
5. restart the server: clients with open tabs should be prompted to reload instead of reconnecting because the server's version has changed
6. restart the server: clients with open tabs should be prompted to reload instead of reconnecting because the server's version has changed
* We recommend that you test a local version of CryptPad before deploying this latest code, as aspects of the above-mentioned migrations are not backwards-compatible.
* you can roll back, but users' CryptDrives might have errors coping with data introduced by newer features.
@ -1907,7 +2120,7 @@ Finally, we prioritized the ability to archive files for a period instead of del
* Users with existing friends on the platform will run a migration to allow them to share pads with friends directly instead of sending them a link.
* they'll receive a notification indicating the title of the pad and who shared it
* if you've already added friends on the platform, you can send them pads from the usual "sharing menu"
* Our code editor already offered the ability to set their color theme and highlighting mode, but now those values will be previewed when mousing over the the option in the dropdown.
* Our code editor already offered the ability to set their color theme and highlighting mode, but now those values will be previewed when mousing over the option in the dropdown.
* Our slide editor now offers the same theme selection as the code editor
* It's now possible to view the history of a shared folder by clicking the history button while viewing the shared folder's contents.
@ -2709,7 +2922,7 @@ latest server with npm update before updating your clientside dependencies with
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.
* registered users who have saved templates in their drives can now use those templates at any time, rather than only
* 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
* 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)

@ -122,46 +122,6 @@ module.exports = {
],
*/
/* CryptPad's administration panel includes a "support" tab
* wherein administrators with a secret key can view messages
* sent from users via the encrypted forms on the /support/ page
*
* To enable this functionality:
* run `node ./scripts/generate-admin-keys.js`
* save the public key in your config in the value below
* add the private key via the admin panel
* and back it up in a secure manner
*
*/
// supportMailboxPublicKey: "",
/* We're very proud that CryptPad is available to the public as free software!
* We do, however, still need to pay our bills as we develop the platform.
*
* By default CryptPad will prompt users to consider donating to
* our OpenCollective campaign. We publish the state of our finances periodically
* so you can decide for yourself whether our expenses are reasonable.
*
* You can disable any solicitations for donations by setting 'removeDonateButton' to true,
* but we'd appreciate it if you didn't!
*/
//removeDonateButton: false,
/* CryptPad will display a point of contact for your instance on its contact page
* (/contact.html) if you provide it below.
*/
adminEmail: 'i.did.not.read.my.config@cryptpad.fr',
/*
* By default, CryptPad contacts one of our servers once a day.
* This check-in will also send some very basic information about your instance including its
* version and the adminEmail so we can reach you if we are aware of a serious problem.
* We will never sell it or send you marketing mail.
*
* If you want to block this check-in and remain set 'blockDailyCheck' to true.
*/
//blockDailyCheck: false,
/* =====================
* STORAGE
* ===================== */
@ -316,4 +276,13 @@ module.exports = {
* (false by default)
*/
verbose: false,
/* Surplus information:
*
* 'installMethod' is included in server telemetry to voluntarily
* indicate how many instances are using unofficial installation methods
* such as Docker.
*
*/
installMethod: 'unspecified',
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

@ -23,9 +23,19 @@
<glyph unicode="&#xe90d;" glyph-name="code" horiz-adv-x="878" d="M827.746 734.667l-173.333 173.333c-20.556 20.556-61.667 37.778-91.111 37.778h-497.778c-29.444 0-53.333-23.889-53.333-53.333v-888.889c0-29.444 23.889-53.333 53.333-53.333h746.667c29.444 0 53.333 23.889 53.333 53.333v640c0 29.444-17.222 70.556-37.778 91.111zM581.079 870.222c9.444-3.333 18.889-8.333 22.778-12.222l173.889-173.889c3.889-3.889 8.889-13.333 12.222-22.778h-208.889zM794.413 21.333h-711.111v853.333h426.667v-231.111c0-29.444 23.889-53.333 53.333-53.333h231.111zM278.857 519.111l-125.556-167.222c-4.444-6.111-4.444-15 0-21.111l125.556-167.222c6.111-7.778 17.222-9.444 25-3.333l28.333 21.111c7.778 6.111 9.444 17.222 3.333 25l-101.111 135 101.111 135c6.111 7.778 4.444 18.889-3.333 25l-28.333 21.111c-7.777 6.111-18.889 4.444-25-3.333zM724.413 351.889l-125.556 167.222c-6.111 7.778-17.222 9.444-25 3.333l-28.333-21.111c-7.777-6.111-9.444-17.222-3.333-25l101.111-135-101.111-135c-6.111-7.778-4.444-18.889 3.333-25l28.333-21.111c7.778-6.111 18.889-4.444 25 3.333l125.556 167.222c4.444 6.111 4.444 15 0 21.111zM379.968 95.778l35-5.556c9.444-1.667 18.889 4.444 20.556 14.444l76.667 461.667c1.667 9.444-4.444 18.889-14.444 20.556l-35 5.556c-9.444 1.667-18.889-4.444-20.556-14.444l-76.667-461.667c-1.667-9.444 4.444-18.889 14.444-20.556z" />
<glyph unicode="&#xe90e;" glyph-name="richtext" horiz-adv-x="878" d="M827.746 734.667l-173.333 173.333c-20.556 20.556-61.667 37.778-91.111 37.778h-497.778c-29.444 0-53.333-23.889-53.333-53.333v-888.889c0-29.444 23.889-53.333 53.333-53.333h746.667c29.444 0 53.333 23.889 53.333 53.333v640c0 29.444-17.222 70.556-37.778 91.111zM581.079 870.222c9.444-3.333 18.889-8.333 22.778-12.222l173.889-173.889c3.889-3.889 8.889-13.333 12.222-22.778h-208.889zM794.413 21.333h-711.111v853.333h426.667v-231.111c0-29.444 23.889-53.333 53.333-53.333h231.111zM225.524 501.333v-35.556c0-10 7.778-17.778 17.778-17.778h391.111c10 0 17.778 7.778 17.778 17.778v35.556c0 10-7.778 17.778-17.778 17.778h-391.111c-10 0-17.778-7.778-17.778-17.778zM634.413 376.889h-391.111c-10 0-17.778-7.778-17.778-17.778v-35.556c0-10 7.778-17.778 17.778-17.778h391.111c10 0 17.778 7.778 17.778 17.778v35.556c0 10-7.778 17.778-17.778 17.778zM634.413 234.667h-391.111c-10 0-17.778-7.778-17.778-17.778v-35.556c0-10 7.778-17.778 17.778-17.778h391.111c10 0 17.778 7.778 17.778 17.778v35.556c0 10-7.778 17.778-17.778 17.778z" />
<glyph unicode="&#xe90f;" glyph-name="file" horiz-adv-x="878" d="M827.745 734.666l-173.333 173.333c-20.556 20.556-61.667 37.778-91.111 37.778h-497.777c-29.444 0-53.333-23.889-53.333-53.333v-888.887c0-29.444 23.889-53.333 53.333-53.333h746.665c29.444 0 53.333 23.889 53.333 53.333v639.999c0 29.444-17.222 70.555-37.778 91.111zM581.079 870.221c9.444-3.333 18.889-8.333 22.778-12.222l173.889-173.889c3.889-3.889 8.889-13.333 12.222-22.778h-208.889zM794.412 21.334h-711.11v853.332h426.666v-231.111c0-29.444 23.889-53.333 53.333-53.333h231.111z" />
<glyph unicode="&#xe910;" glyph-name="form-poll" horiz-adv-x="1094" d="M667.572 771.752c0 3.87-1.548 7.739-4.335 10.525l-21.050 21.051c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-101.54-101.694-45.507 45.662c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-21.051-21.051c-2.787-2.786-4.335-6.656-4.335-10.525s1.548-7.739 4.335-10.525l77.083-77.083c2.787-2.786 6.655-4.334 10.525-4.334s7.739 1.548 10.525 4.334l133.116 133.116c2.787 2.786 4.335 6.656 4.335 10.525zM964.853 659.687c0 3.87-1.548 7.739-4.335 10.525l-45.506 45.507 45.507 45.507c2.787 2.786 4.335 6.656 4.335 10.525s-1.548 7.739-4.335 10.525l-21.051 21.051c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-45.507-45.507-45.507 45.507c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-21.051-21.051c-2.787-2.786-4.335-6.656-4.335-10.525s1.548-7.739 4.335-10.525l45.507-45.507-45.507-45.507c-2.787-2.786-4.335-6.656-4.335-10.525s1.548-7.739 4.335-10.525l21.051-21.051c2.787-2.786 6.655-4.334 10.525-4.334s7.739 1.548 10.525 4.334l45.507 45.507 45.507-45.507c2.787-2.786 6.655-4.334 10.525-4.334s7.739 1.548 10.525 4.334l21.051 21.051c2.787 2.786 4.335 6.656 4.335 10.525zM300.535 659.687c0 3.87-1.548 7.739-4.335 10.525l-45.506 45.507 45.507 45.507c2.787 2.786 4.335 6.656 4.335 10.525s-1.548 7.739-4.335 10.525l-21.051 21.051c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-45.507-45.507-45.507 45.507c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-21.051-21.051c-2.787-2.786-4.335-6.656-4.335-10.525s1.548-7.739 4.335-10.525l45.507-45.507-45.507-45.507c-2.787-2.786-4.335-6.656-4.335-10.525s1.548-7.739 4.335-10.525l21.051-21.051c2.787-2.786 6.655-4.334 10.525-4.334s7.739 1.548 10.525 4.334l45.507 45.507 45.507-45.507c2.787-2.786 6.655-4.334 10.525-4.334s7.739 1.548 10.525 4.334l21.051 21.051c2.787 2.786 4.335 6.656 4.335 10.525zM330.094 505.894c0 3.87-1.548 7.739-4.335 10.525l-21.050 21.051c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-101.54-101.694-45.507 45.662c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-21.051-21.051c-2.787-2.786-4.335-6.656-4.335-10.525s1.548-7.739 4.335-10.525l77.083-77.083c2.787-2.786 6.655-4.334 10.525-4.334s7.739 1.548 10.525 4.334l133.116 133.116c2.787 2.786 4.335 6.656 4.335 10.525zM665.995 505.894c0 3.87-1.548 7.739-4.335 10.525l-21.050 21.051c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-101.54-101.694-45.507 45.662c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-21.051-21.051c-2.787-2.786-4.335-6.656-4.335-10.525s1.548-7.739 4.335-10.525l77.083-77.083c2.787-2.786 6.655-4.334 10.525-4.334s7.739 1.548 10.525 4.334l133.116 133.116c2.787 2.786 4.335 6.656 4.335 10.525zM665.995 237.227c0 3.87-1.548 7.739-4.335 10.525l-21.050 21.051c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-101.54-101.694-45.507 45.662c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-21.051-21.051c-2.787-2.786-4.335-6.656-4.335-10.525s1.548-7.739 4.335-10.525l77.083-77.083c2.787-2.786 6.655-4.334 10.525-4.334s7.739 1.548 10.525 4.334l133.116 133.116c2.787 2.786 4.335 6.656 4.335 10.525zM999.093 237.227c0 3.87-1.548 7.739-4.335 10.525l-21.050 21.051c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-101.54-101.694-45.507 45.662c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-21.051-21.051c-2.787-2.786-4.335-6.656-4.335-10.525s1.548-7.739 4.335-10.525l77.083-77.083c2.787-2.786 6.655-4.334 10.525-4.334s7.739 1.548 10.525 4.334l133.116 133.116c2.787 2.786 4.335 6.656 4.335 10.525zM832.205 352.915h-31.571q-16.264 26.256-24.024 49.855-7.76 23.705-7.76 46.985t7.76 47.091q7.866 23.918 24.024 49.961h31.571q-13.606-25.193-20.41-49.323-6.803-24.024-6.803-47.516t6.697-47.623q6.803-24.13 20.516-49.43zM913.617 352.915q13.607 25.3 20.41 49.43t6.804 47.623-6.804 47.516q-6.803 24.13-20.41 49.323h31.571q16.158-26.044 23.918-49.961 7.866-23.811 7.866-47.091t-7.76-46.985q-7.76-23.599-24.024-49.855zM169.429 84.249h-31.571q-16.264 26.256-24.024 49.855-7.76 23.705-7.76 46.985t7.76 47.091q7.866 23.918 24.024 49.961h31.571q-13.606-25.193-20.41-49.323-6.803-24.024-6.803-47.516t6.697-47.623q6.803-24.13 20.516-49.43zM250.84 84.249q13.607 25.3 20.41 49.43t6.804 47.623-6.804 47.516q-6.803 24.13-20.41 49.323h31.571q16.158-26.044 23.918-49.961 7.866-23.811 7.866-47.091t-7.76-46.985q-7.76-23.599-24.024-49.855z" />
<glyph unicode="&#xe911;" glyph-name="palette" d="M408.6 950c-198.8-38.8-359-198.6-398.2-396.8-74-374 263.4-652.8 517.6-613.4 82.4 12.8 122.8 109.2 85 183.4-46.2 90.8 19.8 196.8 121.8 196.8h159.4c71.6 0 129.6 59.2 129.8 130.6-1 315.2-287.8 563.2-615.4 499.4zM192 320c-35.4 0-64 28.6-64 64s28.6 64 64 64 64-28.6 64-64-28.6-64-64-64zM256 576c-35.4 0-64 28.6-64 64s28.6 64 64 64 64-28.6 64-64-28.6-64-64-64zM512 704c-35.4 0-64 28.6-64 64s28.6 64 64 64 64-28.6 64-64-28.6-64-64-64zM768 576c-35.4 0-64 28.6-64 64s28.6 64 64 64 64-28.6 64-64-28.6-64-64-64z" />
<glyph unicode="&#xe912;" glyph-name="folder-upload" d="M829.44 727.251h-296.84l-47.104 62.886c-25.923 34.066-66.406 55.898-111.999 56.139h-178.938c-77.457-0.137-140.211-62.891-140.348-140.335v-515.868c0.137-77.457 62.891-140.211 140.335-140.348h634.893c77.457 0.137 140.211 62.891 140.348 140.335v396.482c0 0.036 0 0.078 0 0.121 0 77.561-62.807 140.452-140.335 140.589h-0.013zM911.119 190.072c-0.068-45.083-36.597-81.611-81.673-81.679h-634.887c-45.083 0.068-81.611 36.597-81.679 81.673v515.862c0.068 45.083 36.597 81.611 81.673 81.679h178.906c26.48-0.030 50.004-12.656 64.908-32.207l0.146-0.199 47.104-62.765 17.709-24.094h326.114c45.125-0.069 81.679-36.665 81.679-81.799 0 0 0 0 0 0v0zM562.838 166.883h-102.039v203.957h-72.523l123.723 214.076 123.723-214.076h-72.885v-203.957z" />
<glyph unicode="&#xe913;" glyph-name="add-bottom" d="M108.793 271.501c-15.312 0-27.996 12.684-27.996 27.996v55.992c0 15.312 12.684 28.006 27.996 28.006h225.414v-111.993zM108.793 495.478c-15.312 0-27.996 12.694-27.996 28.006v55.992c0 15.312 12.684 27.996 27.996 27.996h403.491v-111.993zM108.793 719.465c-15.312 0-27.996 12.694-27.996 28.006v55.992c0 15.312 12.684 27.996 27.996 27.996h615.968c15.312 0 27.996-12.684 27.996-27.996v-55.992c0-15.312-12.684-28.006-27.996-28.006zM943.202 287.839c0-19.465-15.792-35.257-35.258-35.257h-152.782v-152.782c0-19.465-15.792-35.257-35.258-35.257h-70.515c-19.465 0-35.257 15.792-35.257 35.257v152.782h-152.782c-19.465 0-35.257 15.792-35.257 35.257v70.515c0 19.465 15.792 35.258 35.257 35.258h152.782v152.782c0 19.465 15.792 35.258 35.257 35.258h70.515c19.465 0 35.258-15.792 35.258-35.258v-152.782h152.782c19.465 0 35.258-15.792 35.258-35.258z" />
<glyph unicode="&#xe914;" glyph-name="add-top" d="M108.793 624.499c-15.312 0-27.996-12.684-27.996-27.996v-55.992c0-15.312 12.684-28.006 27.996-28.006h225.414v111.993zM108.793 400.522c-15.312 0-27.996-12.694-27.996-28.006v-55.992c0-15.312 12.684-27.996 27.996-27.996h403.491v111.993zM108.793 176.535c-15.312 0-27.996-12.694-27.996-28.006v-55.992c0-15.312 12.684-27.996 27.996-27.996h615.968c15.312 0 27.996 12.684 27.996 27.996v55.992c0 15.312-12.684 28.006-27.996 28.006zM943.202 608.161c0 19.465-15.792 35.257-35.258 35.257h-152.782v152.782c0 19.465-15.792 35.257-35.258 35.257h-70.515c-19.465 0-35.257-15.792-35.257-35.257v-152.782h-152.782c-19.465 0-35.257-15.792-35.257-35.257v-70.515c0-19.465 15.792-35.258 35.257-35.258h152.782v-152.782c0-19.465 15.792-35.258 35.257-35.258h70.515c19.465 0 35.258 15.792 35.258 35.258v152.782h152.782c19.465 0 35.258 15.792 35.258 35.258z" />
<glyph unicode="&#xe915;" glyph-name="destroy" d="M193.049 938.213c-25.264 0-45.745-20.492-45.745-45.757v-443.97h-67.453c-26.649-0.003-48.252-21.606-48.255-48.255 0.023-26.635 21.62-48.216 48.255-48.219h157.56l-80.102-86.481 86.481-94.352-86.396-94.23 109.353-119.164 39.535 49.965-63.439 69.247 86.347 94.181-86.262 94.085 80.344 86.748h171.688l-80.102-86.481 86.481-94.352-86.347-94.23 109.305-119.164 39.535 49.965-63.439 69.247 86.347 94.181-86.262 94.085 80.344 86.748h171.688l-80.102-86.481 86.529-94.352-86.396-94.23 109.341-119.164 39.499 49.965-63.451 69.199 86.396 94.23-86.299 94.133 80.344 86.699h105.763c26.64-0.004 48.244 21.579 48.267 48.219-0.003 26.654-21.613 48.259-48.267 48.255h-64.663v230.418c0 25.264-14.779 60.536-32.417 78.174l-148.718 148.719c-17.637 17.637-52.909 32.417-78.173 32.417zM208.317 877.2h366.078v-198.296c0-25.264 20.505-45.769 45.769-45.769h198.296v-184.65h-610.143zM635.409 873.392c8.104-2.86 16.213-7.154 19.549-10.49l149.204-149.204c3.337-3.336 7.63-11.446 10.49-19.549h-179.243z" />
<glyph unicode="&#xe915;" glyph-name="destroy" horiz-adv-x="1094" d="M191.671 946.511c-28.024 0-50.742-22.731-50.742-50.756v-399.384h-74.822c-29.561-0.003-53.524-23.966-53.527-53.527 0.025-29.545 23.982-53.484 53.527-53.487h174.773l-88.853-95.929 95.929-104.66-95.835-104.524 121.3-132.182 43.855 55.423-70.369 76.813 95.781 104.471-95.687 104.364 89.122 96.225h190.445l-88.853-95.929 95.929-104.66-95.781-104.524 121.246-132.182 43.855 55.423-70.369 76.813 95.781 104.471-95.687 104.364 89.122 96.225h190.445l-88.853-95.929 95.983-104.66-95.835-104.524 121.286-132.182 43.815 55.423-70.383 76.759 95.835 104.524-95.727 104.417 89.122 96.171h117.318c29.55-0.004 53.515 23.936 53.54 53.487-0.003 29.566-23.974 53.531-53.54 53.527h-71.728v162.501c0 28.024-16.394 67.15-35.958 86.714l-164.966 164.966c-19.564 19.564-58.69 35.958-86.714 35.958zM208.607 878.832h406.073v-219.96c0-28.024 22.745-50.769 50.769-50.769h219.96v-111.732h-676.802zM682.359 874.608c8.989-3.172 17.984-7.936 21.685-11.636l165.505-165.505c3.702-3.7 8.463-12.696 11.636-21.685h-198.826z" />
<glyph unicode="&#xe916;" glyph-name="form-list-check" horiz-adv-x="1094" d="M1012.362 763.843c0 8.831-7.792 16.623-16.623 16.623h-631.687c-8.831 0-16.623-7.792-16.623-16.623v-99.74c0-8.831 7.792-16.623 16.623-16.623h631.687c8.831 0 16.623 7.792 16.623 16.623zM1012.362 497.87c0 8.831-7.792 16.623-16.623 16.623h-631.687c-8.831 0-16.623-7.792-16.623-16.623v-99.74c0-8.831 7.792-16.623 16.623-16.623h631.687c8.831 0 16.623 7.792 16.623 16.623zM1012.362 231.897c0 8.831-7.792 16.623-16.623 16.623h-631.687c-8.831 0-16.623-7.792-16.623-16.623v-99.74c0-8.831 7.792-16.623 16.623-16.623h631.687c8.831 0 16.623 7.792 16.623 16.623zM279.136 239.834c0 22.077-17.911 39.988-39.988 39.988h-115.521c-22.077 0-39.988-17.911-39.988-39.988v-115.521c0-22.077 17.911-39.988 39.988-39.988h115.521c22.077 0 39.988 17.911 39.988 39.988zM307.396 779.338c0 3.87-1.548 7.739-4.335 10.525l-21.050 21.051c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-101.54-101.694-45.507 45.662c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-21.051-21.051c-2.787-2.786-4.335-6.656-4.335-10.525s1.548-7.739 4.335-10.525l77.083-77.083c2.787-2.786 6.655-4.334 10.525-4.334s7.739 1.548 10.525 4.334l133.116 133.116c2.787 2.786 4.335 6.656 4.335 10.525zM307.396 513.228c0 3.87-1.548 7.739-4.335 10.525l-21.050 21.051c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-101.54-101.694-45.507 45.662c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-21.051-21.051c-2.787-2.786-4.335-6.656-4.335-10.525s1.548-7.739 4.335-10.525l77.083-77.083c2.787-2.786 6.655-4.334 10.525-4.334s7.739 1.548 10.525 4.334l133.116 133.116c2.787 2.786 4.335 6.656 4.335 10.525z" />
<glyph unicode="&#xe917;" glyph-name="form-grid-check" horiz-adv-x="1094" d="M645.063 506.257c0 22.077-17.911 39.988-39.988 39.988h-115.521c-22.077 0-39.988-17.911-39.988-39.988v-115.521c0-22.077 17.911-39.988 39.988-39.988h115.521c22.077 0 39.988 17.911 39.988 39.988zM1011.782 771.036c0 22.077-17.911 39.988-39.988 39.988h-115.521c-22.077 0-39.988-17.911-39.988-39.988v-115.521c0-22.077 17.911-39.988 39.988-39.988h115.521c22.077 0 39.988 17.911 39.988 39.988zM279.090 506.257c0 22.077-17.911 39.988-39.988 39.988h-115.521c-22.077 0-39.988-17.911-39.988-39.988v-115.521c0-22.077 17.911-39.988 39.988-39.988h115.521c22.077 0 39.988 17.911 39.988 39.988zM279.136 239.834c0 22.077-17.911 39.988-39.988 39.988h-115.521c-22.077 0-39.988-17.911-39.988-39.988v-115.521c0-22.077 17.911-39.988 39.988-39.988h115.521c22.077 0 39.988 17.911 39.988 39.988zM1011.753 239.834c0 22.077-17.911 39.988-39.988 39.988h-115.521c-22.077 0-39.988-17.911-39.988-39.988v-115.521c0-22.077 17.911-39.988 39.988-39.988h115.521c22.077 0 39.988 17.911 39.988 39.988zM1047.594 513.673c0 3.87-1.548 7.739-4.335 10.525l-21.050 21.051c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-101.54-101.694-45.507 45.662c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-21.051-21.051c-2.787-2.786-4.335-6.656-4.335-10.525s1.548-7.739 4.335-10.525l77.083-77.083c2.787-2.786 6.655-4.334 10.525-4.334s7.739 1.548 10.525 4.334l133.116 133.116c2.787 2.786 4.335 6.656 4.335 10.525zM676.789 238.106c0 3.87-1.548 7.739-4.335 10.525l-21.050 21.051c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-101.54-101.694-45.507 45.662c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-21.051-21.051c-2.787-2.786-4.335-6.656-4.335-10.525s1.548-7.739 4.335-10.525l77.083-77.083c2.787-2.786 6.655-4.334 10.525-4.334s7.739 1.548 10.525 4.334l133.116 133.116c2.787 2.786 4.335 6.656 4.335 10.525zM676.789 771.743c0 3.87-1.548 7.739-4.335 10.525l-21.050 21.051c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-101.54-101.694-45.507 45.662c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-21.051-21.051c-2.787-2.786-4.335-6.656-4.335-10.525s1.548-7.739 4.335-10.525l77.083-77.083c2.787-2.786 6.655-4.334 10.525-4.334s7.739 1.548 10.525 4.334l133.116 133.116c2.787 2.786 4.335 6.656 4.335 10.525zM318.428 771.743c0 3.87-1.548 7.739-4.335 10.525l-21.050 21.051c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-101.54-101.694-45.507 45.662c-2.787 2.786-6.655 4.334-10.525 4.334s-7.739-1.548-10.525-4.334l-21.051-21.051c-2.787-2.786-4.335-6.656-4.335-10.525s1.548-7.739 4.335-10.525l77.083-77.083c2.787-2.786 6.655-4.334 10.525-4.334s7.739 1.548 10.525 4.334l133.116 133.116c2.787 2.786 4.335 6.656 4.335 10.525z" />
<glyph unicode="&#xe918;" glyph-name="form-grid-radio" horiz-adv-x="1094" d="M181.227 823.091c-60.1 0-109.091-48.991-109.091-109.091s48.991-109.091 109.091-109.091c60.1 0 108.955 48.991 108.955 109.091s-48.855 109.091-108.955 109.091zM545.091 823.091c-60.1 0-109.091-48.991-109.091-109.091s48.991-109.091 109.091-109.091c60.1 0 109.091 48.991 109.091 109.091s-48.991 109.091-109.091 109.091zM908.955 823.091c-60.1 0-109.091-48.991-109.091-109.091s48.991-109.091 109.091-109.091c60.1 0 109.091 48.991 109.091 109.091s-48.991 109.091-109.091 109.091zM181.227 747.318c19.623 0 33.364-13.695 33.364-33.318 0-19.583-13.943-33.5-33.364-33.5-19.381 0-33.5 14.119-33.5 33.5 0 19.421 13.917 33.318 33.5 33.318zM181.227 556.955c-60.1 0-109.091-48.809-109.091-108.909s48.991-109.091 109.091-109.091c60.1 0 108.955 48.991 108.955 109.091s-48.855 108.909-108.955 108.909zM545.091 556.955c-60.1 0-109.091-48.809-109.091-108.909s48.991-109.091 109.091-109.091c60.1 0 109.091 48.991 109.091 109.091s-48.991 108.909-109.091 108.909zM908.955 556.955c-60.1 0-109.091-48.809-109.091-108.909s48.991-109.091 109.091-109.091c60.1 0 109.091 48.991 109.091 109.091s-48.991 108.909-109.091 108.909zM909.045 481.364c19.623 0 33.318-13.74 33.318-33.364 0-19.583-13.897-33.5-33.318-33.5-19.381 0-33.5 14.119-33.5 33.5 0 19.421 13.917 33.364 33.5 33.364zM181.227 291.045c-60.1 0-109.091-48.855-109.091-108.955s48.991-109.091 109.091-109.091c60.1 0 108.955 48.991 108.955 109.091s-48.855 108.955-108.955 108.955zM545.091 291.045c-60.1 0-109.091-48.855-109.091-108.955s48.991-109.091 109.091-109.091c60.1 0 109.091 48.991 109.091 109.091s-48.991 108.955-109.091 108.955zM908.955 291.045c-60.1 0-109.091-48.855-109.091-108.955s48.991-109.091 109.091-109.091c60.1 0 109.091 48.991 109.091 109.091s-48.991 108.955-109.091 108.955zM545.318 214c19.623 0 33.318-13.695 33.318-33.318 0-19.583-13.897-33.5-33.318-33.5-19.381 0-33.5 14.119-33.5 33.5 0 19.421 13.917 33.318 33.5 33.318z" />
<glyph unicode="&#xe919;" glyph-name="form-list-radio" horiz-adv-x="1094" d="M1012.362 763.843c0 8.831-7.792 16.623-16.623 16.623h-631.687c-8.831 0-16.623-7.792-16.623-16.623v-99.74c0-8.831 7.792-16.623 16.623-16.623h631.687c8.831 0 16.623 7.792 16.623 16.623zM1012.362 497.87c0 8.831-7.792 16.623-16.623 16.623h-631.687c-8.831 0-16.623-7.792-16.623-16.623v-99.74c0-8.831 7.792-16.623 16.623-16.623h631.687c8.831 0 16.623 7.792 16.623 16.623zM181.227 823.091c-60.1 0-109.091-48.991-109.091-109.091s48.991-109.091 109.091-109.091c60.1 0 108.955 48.991 108.955 109.091s-48.855 109.091-108.955 109.091zM181.227 747.318c19.623 0 33.364-13.695 33.364-33.318 0-19.583-13.943-33.5-33.364-33.5-19.381 0-33.5 14.119-33.5 33.5 0 19.421 13.917 33.318 33.5 33.318zM181.227 556.955c-60.1 0-109.091-48.809-109.091-108.909s48.991-109.091 109.091-109.091c60.1 0 108.955 48.991 108.955 109.091s-48.855 108.909-108.955 108.909zM181.227 291.045c-60.1 0-109.091-48.855-109.091-108.955s48.991-109.091 109.091-109.091c60.1 0 108.955 48.991 108.955 109.091s-48.855 108.955-108.955 108.955zM1012.362 231.897c0 8.831-7.792 16.623-16.623 16.623h-631.687c-8.831 0-16.623-7.792-16.623-16.623v-99.74c0-8.831 7.792-16.623 16.623-16.623h631.687c8.831 0 16.623 7.792 16.623 16.623z" />
<glyph unicode="&#xe91a;" glyph-name="form-page-break" horiz-adv-x="1094" d="M190.545 923.136c-28.106 0-50.909-22.803-50.909-50.909v-410.318h67.864v393.364h407.273v-220.591c0-28.106 22.803-50.909 50.909-50.909h220.636v-121.864h67.864v172.773c0 28.106-16.424 67.333-36.045 86.955l-165.455 165.455c-19.621 19.621-58.894 36.045-87 36.045zM682.682 851.046c9.015-3.182 18.015-7.97 21.727-11.682l166-166c3.712-3.712 8.455-12.712 11.636-21.727h-199.364zM139.636 236.273v-212.5c0-28.106 22.803-50.909 50.909-50.909h712.727c28.106 0 50.909 22.803 50.909 50.909v212.5h-67.864v-195.545h-678.818v195.545zM66.394 387.23v-65.283h195.505v65.283zM327.009 387.23v-65.283h195.505v65.283zM587.797 387.23v-65.283h195.505v65.283zM848.412 387.23v-65.283h179.012v65.283z" />
<glyph unicode="&#xe91b;" glyph-name="form-paragraph" horiz-adv-x="1094" d="M1012.362 182.027c0 18.182-15.065 33.247-33.247 33.247h-864.413c-18.182 0-33.247-15.065-33.247-33.247v-66.493c0-18.182 15.065-33.247 33.247-33.247h864.413c18.182 0 33.247 15.065 33.247 33.247zM812.882 381.507c0 18.182-15.065 33.247-33.247 33.247h-664.933c-18.182 0-33.247-15.065-33.247-33.247v-66.493c0-18.182 15.065-33.247 33.247-33.247h664.933c18.182 0 33.247 15.065 33.247 33.247zM945.869 580.987c0 18.182-15.065 33.247-33.247 33.247h-797.92c-18.182 0-33.247-15.065-33.247-33.247v-66.493c0-18.182 15.065-33.247 33.247-33.247h797.92c18.182 0 33.247 15.065 33.247 33.247zM746.389 780.467c0 18.182-15.065 33.247-33.247 33.247h-598.44c-18.182 0-33.247-15.065-33.247-33.247v-66.493c0-18.182 15.065-33.247 33.247-33.247h598.44c18.182 0 33.247 15.065 33.247 33.247z" />
<glyph unicode="&#xe91c;" glyph-name="form-text" horiz-adv-x="1094" d="M1012.362 481.247c0 18.182-15.065 33.247-33.247 33.247h-864.413c-18.182 0-33.247-15.065-33.247-33.247v-66.493c0-18.182 15.065-33.247 33.247-33.247h864.413c18.182 0 33.247 15.065 33.247 33.247z" />
<glyph unicode="&#xe91d;" glyph-name="form-list-ordered" horiz-adv-x="1094" d="M279.377 71.897c0 35.844-22.857 62.857-57.662 71.169l49.351 59.74v45.714h-172.987v-78.961h55.065v27.532c16.623 0 33.766 1.039 50.389 1.039v-0.52c-22.338-20.259-39.48-46.753-58.182-70.649l13.506-29.091c21.299 1.558 54.545 0 54.545-29.091 0-20.779-19.221-29.61-37.402-29.61-19.74 0-41.039 10.39-55.065 23.376l-29.61-45.714c23.377-23.377 56.623-34.286 89.35-34.286 54.026 0 98.701 32.208 98.701 89.35zM280.416 397.611h-54.545v-31.169h-65.974c1.558 40.519 114.285 57.662 114.285 134.545 0 51.428-41.558 80-89.87 80-39.48 0-74.805-20.26-91.948-56.104l44.156-30.649c8.831 14.545 23.896 30.13 42.078 30.13 17.143 0 28.052-9.351 28.052-27.013 0-43.636-117.402-58.182-117.402-154.285 0-9.351 1.558-18.701 3.117-28.052h188.051zM1012.362 231.897c0 9.351-7.792 16.623-16.623 16.623h-631.687c-9.351 0-16.623-7.273-16.623-16.623v-99.74c0-8.831 7.273-16.623 16.623-16.623h631.687c8.831 0 16.623 7.792 16.623 16.623zM280.936 698.908h-56.104v209.87h-55.065l-70.649-65.974 36.883-39.48c9.87 8.831 20.26 16.623 25.974 28.052h1.039v-6.234c0-42.078-0.52-84.156-0.52-126.233h-55.584v-51.428h174.025zM1012.362 497.87c0 9.351-7.792 16.623-16.623 16.623h-631.687c-9.351 0-16.623-7.273-16.623-16.623v-99.74c0-8.831 7.273-16.623 16.623-16.623h631.687c8.831 0 16.623 7.792 16.623 16.623zM1012.362 763.843c0 8.831-7.792 16.623-16.623 16.623h-631.687c-9.351 0-16.623-7.792-16.623-16.623v-99.74c0-8.831 7.273-16.623 16.623-16.623h631.687c8.831 0 16.623 7.792 16.623 16.623z" />
<glyph unicode="&#xe91e;" glyph-name="form-poll-maybe" horiz-adv-x="1094" d="M856.492 569.333c0 9.987-3.996 19.973-11.187 27.163l-54.325 54.327c-7.191 7.191-17.175 11.185-27.163 11.185s-19.972-3.995-27.163-11.185l-262.048-262.447-117.442 117.841c-7.191 7.19-17.175 11.185-27.163 11.185s-19.972-3.995-27.163-11.185l-54.327-54.327c-7.191-7.191-11.187-17.177-11.187-27.163s3.995-19.973 11.187-27.163l198.932-198.932c7.191-7.191 17.175-11.185 27.163-11.185s19.972 3.995 27.163 11.185l343.538 343.537c7.192 7.191 11.187 17.177 11.187 27.163zM65.903 444.364q0 120.454 35 225.454 35.454 105 101.818 184.090h73.636q-65.454-87.727-98.636-192.727-32.727-105-32.727-215.908 0-109.091 33.636-213.181t96.818-189.999h-72.727q-66.818 77.273-101.818 180.454t-35 221.818zM1027.916 444.364q0-119.545-35.454-222.727-35-103.182-101.363-179.545h-72.727q63.182 85.454 96.818 189.545 33.636 104.545 33.636 213.636 0 110.909-33.182 215.908-32.727 105-98.182 192.727h73.636q66.818-79.545 101.818-184.999 35-105 35-224.545z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 39 KiB

@ -1,9 +1,10 @@
@font-face {
font-family: 'cptools';
src:
url('fonts/cptools.ttf?n9y2kz') format('truetype'),
url('fonts/cptools.woff?n9y2kz') format('woff'),
url('fonts/cptools.svg?n9y2kz#cptools') format('svg');
src: url('fonts/cptools.eot?chd5a1');
src: url('fonts/cptools.eot?chd5a1#iefix') format('embedded-opentype'),
url('fonts/cptools.ttf?chd5a1') format('truetype'),
url('fonts/cptools.woff?chd5a1') format('woff'),
url('fonts/cptools.svg?chd5a1#cptools') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
@ -25,11 +26,38 @@
-moz-osx-font-smoothing: grayscale;
}
.cptools-sheet:before {
content: "\e908";
.cptools-form-poll:before {
content: "\e910";
}
.cptools-slide:before {
content: "\e907";
.cptools-form-poll-maybe:before {
content: "\e91e";
}
.cptools-form-list-check:before {
content: "\e916";
}
.cptools-form-grid-check:before {
content: "\e917";
}
.cptools-form-grid-radio:before {
content: "\e918";
}
.cptools-form-list-radio:before {
content: "\e919";
}
.cptools-form-page-break:before {
content: "\e91a";
}
.cptools-form-paragraph:before {
content: "\e91b";
}
.cptools-form-text:before {
content: "\e91c";
}
.cptools-form-list-ordered:before {
content: "\e91d";
}
.cptools-folder-no-color:before {
content: "\e900";
}
.cptools-whiteboard:before {
content: "\e901";
@ -37,6 +65,9 @@
.cptools-new-template:before {
content: "\e902";
}
.cptools-shared-folder:before {
content: "\e903";
}
.cptools-file-upload:before {
content: "\e904";
}
@ -46,9 +77,24 @@
.cptools-poll:before {
content: "\e906";
}
.cptools-slide:before {
content: "\e907";
}
.cptools-sheet:before {
content: "\e908";
}
.cptools-folder-open:before {
content: "\e909";
}
.cptools-kanban:before {
content: "\e90a";
}
.cptools-folder:before {
content: "\e90b";
}
.cptools-shared-folder-open:before {
content: "\e90c";
}
.cptools-code:before {
content: "\e90d";
}
@ -58,8 +104,11 @@
.cptools-file:before {
content: "\e90f";
}
.cptools-destroy:before {
content: "\e915";
.cptools-palette:before {
content: "\e911";
}
.cptools-folder-upload:before {
content: "\e912";
}
.cptools-add-bottom:before {
content: "\e913";
@ -67,24 +116,6 @@
.cptools-add-top:before {
content: "\e914";
}
.cptools-folder-upload:before {
content: "\e912";
}
.cptools-folder-no-color:before {
content: "\e900";
}
.cptools-shared-folder:before {
content: "\e903";
}
.cptools-folder-open:before {
content: "\e909";
}
.cptools-folder:before {
content: "\e90b";
}
.cptools-shared-folder-open:before {
content: "\e90c";
}
.cptools-palette:before {
content: "\e911";
.cptools-destroy:before {
content: "\e915";
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 83 KiB

@ -153,7 +153,7 @@ define([
register: isRegister,
};
var RT, blockKeys, blockHash, Pinpad, rpc, userHash;
var RT, blockKeys, blockHash, blockUrl, Pinpad, rpc, userHash;
nThen(function (waitFor) {
// derive a predefined number of bytes from the user's inputs,
@ -171,7 +171,7 @@ define([
// the rest of their data
// determine where a block for your set of keys would be stored
var blockUrl = Block.getBlockUrl(res.opt.blockKeys);
blockUrl = Block.getBlockUrl(res.opt.blockKeys);
// Check whether there is a block at that location
Util.fetch(blockUrl, waitFor(function (err, block) {
@ -412,12 +412,21 @@ define([
toPublish.edPublic = RT.proxy.edPublic;
var blockRequest = Block.serialize(JSON.stringify(toPublish), res.opt.blockKeys);
rpc.writeLoginBlock(blockRequest, waitFor(function (e) {
if (e) {
console.error(e);
waitFor.abort();
return void cb(e);
}
}));
}).nThen(function (waitFor) {
// confirm that the block was actually written before considering registration successful
Util.fetch(blockUrl, waitFor(function (err /*, block */) {
if (err) {
console.error(err);
waitFor.abort();
return void cb(err);
}
console.log("blockInfo available at:", blockHash);
LocalStore.setBlockHash(blockHash);

@ -12,7 +12,7 @@ define([
// Make sure we don't display non-translated content (empty button)
$main.find('#data').removeClass('hidden');
if (LocalStore.isLoggedIn()) {
if (LocalStore.isLoggedIn() && LocalStore.getDriveRedirectPreference()) {
if (window.location.pathname === '/') {
window.location = '/drive/';
return;

@ -9,6 +9,7 @@ var map = {
'fr': 'Français',
//'hi': 'हिन्दी',
'it': 'Italiano',
'ja': '日本語',
'nb': 'Norwegian Bokmål',
//'pl': 'Polski',
'pt-br': 'Português do Brasil',

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.3 KiB

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360.4 94.7"><defs><style>.cls-1{fill:#4591c4}.cls-2{fill:#999}</style></defs><title>CryptPad_logo_color</title><g id="Layer_2" data-name="Layer 2"><g id="svg2"><g id="g4845"><path id="path4811" class="cls-1" d="M99.5 63.6a24.8 24.8 0 0 1-5.9-.6 8.5 8.5 0 0 1-3.8-1.9 7.1 7.1 0 0 1-2-3.4 19.4 19.4 0 0 1-.6-5.2v-10a19.4 19.4 0 0 1 .6-5.1 7.1 7.1 0 0 1 2-3.4 8.5 8.5 0 0 1 3.8-2 24.8 24.8 0 0 1 5.9-.6h22.3v6.2h-22a11.8 11.8 0 0 0-2.7.3 3.5 3.5 0 0 0-1.7.9 3.3 3.3 0 0 0-.9 1.6 11.1 11.1 0 0 0-.2 2.5v9.4a11.1 11.1 0 0 0 .2 2.5 3.3 3.3 0 0 0 .9 1.6 3.3 3.3 0 0 0 1.7.8l2.7.2h22v6.1z"/><path id="path4813" class="cls-1" d="M126.5 63.6V48.8a16 16 0 0 1 .7-4.9 7.5 7.5 0 0 1 2.1-3.3 8.9 8.9 0 0 1 3.7-1.9 21.4 21.4 0 0 1 5.5-.6h4.6v5.7h-4.4l-2.5.2a3.3 3.3 0 0 0-1.6.8 3.2 3.2 0 0 0-.8 1.6 10.1 10.1 0 0 0-.3 2.5v14.7z"/><path id="path4815" class="cls-1" d="M154.1 51.4a3.8 3.8 0 0 0 .9 3 4.6 4.6 0 0 0 3.1.8h15.7V38h6.8v25.2q0 4.3-2.1 6.2t-6.9 1.9h-10.2v-5.8h9.6a3.3 3.3 0 0 0 2.1-.6 2.7 2.7 0 0 0 .7-2.2v-2h-16.1a19 19 0 0 1-4.9-.5 7.8 7.8 0 0 1-3.3-1.7 6.5 6.5 0 0 1-1.8-2.8 12.7 12.7 0 0 1-.6-4V38h6.9z"/><path id="path4817" class="cls-1" d="M207.5 38a25.1 25.1 0 0 1 5.9.6 8.5 8.5 0 0 1 3.8 1.9 7.1 7.1 0 0 1 2 3.4 19.7 19.7 0 0 1 .6 5.2v3.4a19.4 19.4 0 0 1-.6 5.2 7.1 7.1 0 0 1-2 3.4 8.5 8.5 0 0 1-3.8 1.9 25.1 25.1 0 0 1-5.9.6h-14.3v7.6h-7V49.1a19.7 19.7 0 0 1 .6-5.2 7.1 7.1 0 0 1 2-3.4 8.6 8.6 0 0 1 3.8-1.9 25.1 25.1 0 0 1 5.9-.6zm5.3 11.3a11.7 11.7 0 0 0-.3-2.7 2.9 2.9 0 0 0-1-1.6 4 4 0 0 0-1.9-.8l-3-.2h-7.8l-2.7.2a3.5 3.5 0 0 0-1.7.8 3.2 3.2 0 0 0-.8 1.6 11.7 11.7 0 0 0-.2 2.6v8.4h13.4l3-.2a3.8 3.8 0 0 0 1.9-.7 2.9 2.9 0 0 0 1-1.6 11.9 11.9 0 0 0 .3-2.8z"/><path id="path4819" class="cls-1" d="M226.5 63.6V43.8H223V38h3.6v-7.2h7.2V38h8.1v5.7h-8.1v19.9z"/><path id="path4821" class="cls-2" d="M252.4 54.4v9.2h-7.2V31.4H271a25.7 25.7 0 0 1 5.8.5 8.4 8.4 0 0 1 3.7 1.8 6.8 6.8 0 0 1 2 3.2 17.1 17.1 0 0 1 .6 4.8v2.8a16.9 16.9 0 0 1-.6 4.8 6 6 0 0 1-2 3 8.3 8.3 0 0 1-3.7 1.6 31.6 31.6 0 0 1-5.9.4zm23.5-12.3q0-2.6-1.1-3.5t-4-.9h-18.4v11h18.5a6.5 6.5 0 0 0 3.9-.9q1.1-.9 1.1-3.4z"/><path id="path4823" class="cls-2" d="M296.2 63.6a18.4 18.4 0 0 1-4.6-.5 7.4 7.4 0 0 1-2.9-1.3 4.6 4.6 0 0 1-1.5-2.1 8.7 8.7 0 0 1-.4-2.8v-2.4a9.3 9.3 0 0 1 .4-2.9 4.7 4.7 0 0 1 1.4-2.1 6.5 6.5 0 0 1 2.7-1.3 17.7 17.7 0 0 1 4.4-.4h18.9v-.6q0-2.5-1-3.3a5.3 5.3 0 0 0-3.4-.8h-7V38h7a20.7 20.7 0 0 1 5.2.6 8.9 8.9 0 0 1 3.5 1.7 6.7 6.7 0 0 1 2 2.9 12.5 12.5 0 0 1 .6 4.2v6.2a17.2 17.2 0 0 1-.5 4.7 6.3 6.3 0 0 1-1.9 3.1 7.9 7.9 0 0 1-3.6 1.7 26.8 26.8 0 0 1-5.6.5zM314.7 52h-18.2a3.1 3.1 0 0 0-1.9.5 2.5 2.5 0 0 0-.7 2.1v1.7a2.1 2.1 0 0 0 .8 1.9 3.7 3.7 0 0 0 2.1.5H310l2.1-.2a3.2 3.2 0 0 0 1.5-.7 3 3 0 0 0 .9-1.4 7.8 7.8 0 0 0 .3-2.3z"/><path id="path4825" class="cls-2" d="M339.1 63.6a25.4 25.4 0 0 1-6-.6 8.6 8.6 0 0 1-3.8-1.9 7.1 7.1 0 0 1-2-3.4 19.4 19.4 0 0 1-.6-5.2v-3.4a19.7 19.7 0 0 1 .6-5.2 7.1 7.1 0 0 1 2-3.4 8.6 8.6 0 0 1 3.8-1.9 25.4 25.4 0 0 1 6-.6h14.2v-8.2h7v22.8a19.4 19.4 0 0 1-.6 5.2 7.1 7.1 0 0 1-2 3.4A8.5 8.5 0 0 1 354 63a25.1 25.1 0 0 1-5.9.6zm-5.3-11.2a11.7 11.7 0 0 0 .3 2.7 2.9 2.9 0 0 0 1 1.6 4 4 0 0 0 1.9.8l3.1.2h7.8l2.7-.2a3.3 3.3 0 0 0 1.7-.8 3.2 3.2 0 0 0 .9-1.6 11.9 11.9 0 0 0 .2-2.6v-8.4H340l-3 .2a4 4 0 0 0-1.9.8 2.9 2.9 0 0 0-1 1.6 11.7 11.7 0 0 0-.3 2.7z"/><path id="path4827" class="cls-1" d="M39.2 0L9.9 5.4A6.3 6.3 0 1 0 3.2 16v42.2c0 4 1.8 8.3 5.3 12.7A65.5 65.5 0 0 0 21.6 83a128.2 128.2 0 0 0 17.6 10.5A128.1 128.1 0 0 0 56.7 83a65.4 65.4 0 0 0 13.1-12.1c3.5-4.5 5.3-8.8 5.3-12.7V16A6.3 6.3 0 0 0 72 4.2a6.2 6.2 0 0 0-3.6 1.2zm-.1 6.2l26.7 4.9a5.9 5.9 0 0 0 .2 1.1L50.3 22.5a15.3 15.3 0 0 0-22.6.1l-15.5-10a6.3 6.3 0 0 0 .3-1.4zm28.8 9a6.5 6.5 0 0 0 1.8 1.1v41a10.4 10.4 0 0 1-.1 1.7 21.5 21.5 0 0 1-4.1 7.8 56.1 56.1 0 0 1-11.3 10.4 110.6 110.6 0 0 1-15 9 110.8 110.8 0 0 1-15-9A55.8 55.8 0 0 1 13 66.8a19.8 19.8 0 0 1-4.4-9.3V16.4a6.3 6.3 0 0 0 1.7-1l19.8 12.7a10.1 10.1 0 0 1 9-5.4 10 10 0 0 1 9 5.4z"/><g id="g4829"><path id="path4831" class="cls-2" d="M23 54.8a4.6 4.6 0 1 0 0 9.3 4.6 4.6 0 0 0 0-9.3z"/><path id="path4833" class="cls-2" d="M24.3 28.5a14.9 14.9 0 0 0 4.2 15.4l-7.3 14.8a2.8 2.8 0 0 0 2.3 3.8h11v-5.2h-6.8l6.4-12.8a2.7 2.7 0 0 0-.8-3.3 9.7 9.7 0 0 1-4.3-9.6z"/></g><g id="g4835"><path id="path4837" class="cls-2" d="M55.3 54.8a4.6 4.6 0 1 0 0 9.3 4.6 4.6 0 0 0 0-9.3z"/><path id="path4839" class="cls-2" d="M53.8 28.6l-4.7 3.1a10.2 10.2 0 0 1 .1 1.2 10 10 0 0 1-4.3 8.3 2.7 2.7 0 0 0-.8 3.3l6.3 12.8h-6.8v5.2h11a2.8 2.8 0 0 0 2.3-3.8l-7.3-14.8a15.2 15.2 0 0 0 4.8-11 15.3 15.3 0 0 0-.6-4.3z"/></g><path id="path4841" class="cls-1" d="M43.2 33.3a4.2 4.2 0 1 1-4.2-4.2 4.2 4.2 0 0 1 4.2 4.2z"/><path id="path4843" class="cls-1" d="M45.3 88.4a6.3 6.3 0 1 1-6.3-6.3 6.3 6.3 0 0 1 6.3 6.3z"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 4.7 KiB

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 393.94 476.2"><defs><style>.cls-1{opacity:0.04;}.cls-2{fill:#999;}.cls-3{fill:#4591c4;}</style></defs><title>CryptPadlogo</title><g id="Layer_2" data-name="Layer 2"><g id="svg2"><g id="g4764" class="cls-1"><path class="cls-2" d="M139.36,288.16l32.07-64.43a13.59,13.59,0,0,0-4.23-16.62,48.65,48.65,0,0,1-21.28-48.25l-23.47-15.31a75.74,75.74,0,0,0-3.36,21.87c0,22,9.52,41.45,24.35,55.54l-27.24,54.8c-.11,0-.2,0-.31,0a23.27,23.27,0,1,0,0,46.53,23,23,0,0,0,17.31-7.86h40.27V288.16Z"/><path class="cls-2" d="M278.28,275.73c-.57,0-1.11.13-1.68.17l-27.33-55.09c14.75-14.07,24.2-33.47,24.2-55.39a77.13,77.13,0,0,0-3.06-21.72l-23.62,15.45a51.69,51.69,0,0,1,.44,6.27A50.21,50.21,0,0,1,225.65,207a13.58,13.58,0,0,0-4.22,16.62l31.77,64.58h-34V314.4h41.63a23.23,23.23,0,1,0,17.41-38.67Z"/><polygon class="cls-2" points="270.41 143.7 270.41 143.7 270.41 143.7 270.41 143.7"/><circle class="cls-3" cx="196.06" cy="167.4" r="21.21"/><path class="cls-3" d="M362.25,21.36a31.14,31.14,0,0,0-18.1,5.8L197,0,50,27.16a31.62,31.62,0,1,0-33.68,53.4v212c0,19.95,8.93,41.51,26.62,64.08,15.66,20,37.82,40.46,65.89,60.83a603,603,0,0,0,57,36.21,31.54,31.54,0,0,0,60,1.25,606,606,0,0,0,59.26-37.46c28.09-20.37,50.23-40.86,65.9-60.83,17.7-22.6,26.61-44.13,26.61-64.08V80.38a31.46,31.46,0,0,0-15.39-59ZM62.82,55.94,196.61,31.32l134.33,24.8a29.58,29.58,0,0,0,.9,5.61L253.09,113a76.78,76.78,0,0,0-113.69.36L61.55,62.82A31.8,31.8,0,0,0,62.82,55.94ZM350.49,288.21a51.41,51.41,0,0,1-.73,8.51c-3.16,12.6-10.11,25.8-20.82,39.47-13.41,17.09-32.43,34.52-56.48,51.95a532.75,532.75,0,0,1-54.19,34.09,31.5,31.5,0,0,0-43.73-.62,534,534,0,0,1-53.06-33.47c-24-17.43-42.9-34.86-56.31-51.95-12.77-16.31-20.25-31.94-22.26-46.71,0-.41,0-.86,0-1.27V82.37a31.51,31.51,0,0,0,8.69-5.25l88.88,57.75.37-.36,10.5,6.88a51,51,0,0,1,45.07-27,50.37,50.37,0,0,1,45.08,27.15l22.09-14.3L341.44,76.4A32.55,32.55,0,0,0,350.49,82Z"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

@ -43,6 +43,17 @@ define([
return Pages.externalLink(el, Pages.localizeDocsLink(href));
};
var accounts = Pages.accounts = {
donateURL: AppConfig.donateURL || "https://opencollective.com/cryptpad/",
upgradeURL: AppConfig.upgradeURL
};
Pages.areSubscriptionsAllowed = function () {
try {
return ApiConfig.allowSubscriptions && accounts.upgradeURL && !ApiConfig.restrictRegistration;
} catch (err) { return void console.error(err); }
};
var languageSelector = function () {
var options = [];
var languages = Msg._languages;
@ -94,7 +105,7 @@ define([
var imprintUrl = AppConfig.imprint && (typeof(AppConfig.imprint) === "boolean" ?
'/imprint.html' : AppConfig.imprint);
Pages.versionString = "v4.5.0";
Pages.versionString = "v4.9.0";
// used for the about menu
@ -133,7 +144,7 @@ define([
footerCol('footer_product', [
footLink('/what-is-cryptpad.html', 'topbar_whatIsCryptpad'),
Pages.docsLink,
footLink('/features.html', 'pricing'),
footLink('/features.html', Pages.areSubscriptionsAllowed()? 'pricing': 'features'), // Messages.pricing, Messages.features
Pages.githubLink,
footLink('https://opencollective.com/cryptpad/contribute/', 'footer_donate'),
]),
@ -204,7 +215,7 @@ define([
h('div.collapse.navbar-collapse.justify-content-end#menuCollapse', [
h('a.nav-item.nav-link', { href: '/what-is-cryptpad.html'}, Msg.about),
h('a.nav-item.nav-link', { href: 'https://docs.cryptpad.fr'}, Msg.docs_link),
h('a.nav-item.nav-link', { href: '/features.html'}, Msg.pricing),
h('a.nav-item.nav-link', { href: '/features.html'}, Pages.areSubscriptionsAllowed()? Msg.pricing: Msg.features),
].concat(rightLinks))
);
};

@ -8,10 +8,7 @@ define([
'/api/config',
'/common/common-ui-elements.js',
], function ($, h, Msg, AppConfig, LocalStore, Pages, Config, UIElements) {
var accounts = {
donateURL: AppConfig.donateURL || "https://opencollective.com/cryptpad/",
upgradeURL: AppConfig.upgradeURL
};
var accounts = Pages.accounts;
return function () {
Msg.features_f_apps_note = AppConfig.availablePadTypes.map(function (app) {
@ -145,10 +142,11 @@ define([
]),
]),
]);
var availableFeatures =
(Config.allowSubscriptions && accounts.upgradeURL && !Config.restrictRegistration) ?
[anonymousFeatures, registeredFeatures, premiumFeatures] :
[anonymousFeatures, registeredFeatures];
var availableFeatures = [
anonymousFeatures,
registeredFeatures,
Pages.areSubscriptionsAllowed() ? premiumFeatures: undefined,
];
return h('div#cp-main', [
Pages.infopageTopbar(),

@ -31,7 +31,7 @@ define([
[ 'code', Msg.type.code],
[ 'slide', Msg.type.slide],
[ 'sheet', Msg.type.sheet],
[ 'poll', Msg.type.poll],
[ 'form', Msg.type.form],
[ 'kanban', Msg.type.kanban],
[ 'whiteboard', Msg.type.whiteboard],
[ 'drive', Msg.type.drive]

@ -10,6 +10,7 @@
code: #EAA000;
slide: #e57614;
poll: #2c9e98;
form: #2c9e98;
whiteboard: #a72ba7;
kanban: #8C4;
sheet: #40865c;
@ -52,7 +53,7 @@
@cryptpad_color_light_blue: #00b7d8;
@cryptpad_color_red: #ff1100;
@cryptpad_color_red_fade: fade(@cryptpad_color_red, 50%);
@cryptpad_color_red_fader: fade(@cryptpad_color_red, 25%);
@cryptpad_color_red_fader: fade(@cryptpad_color_red, 15%);
@cryptpad_color_warn_red: @cryptpad_color_red_fade;
@cryptpad_color_dark_red: #9e0000;
@cryptpad_color_light_red: #FFD4D4;
@ -63,7 +64,8 @@
@cryptpad_color_light_green: #c5ffa8;
@cryptpad_color_light_green_fade: fade(@cryptpad_color_light_green, 20%);
@cryptpad_color_light_yellow: #FFE69C;
@cryptpad_color_yellow_fade: fade(#FFE69C, 15%);
@cryptpad_color_yellow_fade: fade(@cryptpad_color_light_yellow, 50%);
@cryptpad_color_yellow_fader: fade(#FFE69C, 15%); // not in light theme
@cryptpad_color_lighter_blue: #d2e1f2;
@cryptpad_color_link:@cryptpad_color_brand_300;
@ -114,7 +116,7 @@
@cp_forms-disabled: @cryptpad_color_grey_500;
// Bootstrap alerts
@cp_alerts-warning-bg: @cryptpad_color_yellow_fade;
@cp_alerts-warning-bg: @cryptpad_color_yellow_fader;
@cp_alerts-warning-fg: @cryptpad_color_light_yellow;
@cp_alerts-warning-text: @cryptpad_color_light_yellow;
@cp_alerts-danger-bg: @cryptpad_color_red_fader;
@ -426,3 +428,13 @@
@cp_calendar-now: @cryptpad_color_brand_300;
@cp_calendar-now-fg: @cryptpad_color_grey_800;
// Forms
@cp_form-bg1: @cryptpad_color_grey_800;
@cp_form-bg2: @cryptpad_color_grey_900;
@cp_form-border: @cryptpad_color_grey_800;
@cp_form-poll-color: @cryptpad_color_grey_800;
@cp_form-poll-no: fade(@cryptpad_color_red, 25%);
@cp_form-poll-yes: fade(@cryptpad_color_green, 25%);
@cp_form-poll-maybe: @cryptpad_color_grey_700;
@cp_form-poll-yes-color: @cryptpad_color_green;
@cp_form-invalid: @cryptpad_color_light_red;

@ -10,6 +10,7 @@
code: #EAA000;
slide: #e57614;
poll: #2c9e98;
form: #2c9e98;
whiteboard: #a72ba7;
kanban: #8C4;
sheet: #40865c;
@ -52,7 +53,7 @@
@cryptpad_color_light_blue: #00b7d8;
@cryptpad_color_red: #ff1100;
@cryptpad_color_red_fade: fade(@cryptpad_color_red, 50%);
@cryptpad_color_red_fader: fade(@cryptpad_color_red, 25%);
@cryptpad_color_red_fader: fade(@cryptpad_color_red, 15%);
@cryptpad_color_warn_red: @cryptpad_color_red_fade;
@cryptpad_color_dark_red: #9e0000;
@cryptpad_color_light_red: #FFD4D4;
@ -425,3 +426,14 @@
@cp_calendar-border: @cryptpad_color_grey_300;
@cp_calendar-now: @cryptpad_color_brand;
@cp_calendar-now-fg: @cryptpad_color_grey_200;
// Forms
@cp_form-bg1: @cryptpad_color_grey_200;
@cp_form-bg2: @cryptpad_color_grey_100;
@cp_form-border: @cryptpad_color_grey_200;
@cp_form-poll-color: @cryptpad_color_grey_800;
@cp_form-poll-no: fade(@cryptpad_color_light_red, 75%);
@cp_form-poll-yes: fade(@cryptpad_color_light_green, 75%);
@cp_form-poll-maybe: @cryptpad_color_grey_300;
@cp_form-poll-yes-color: @cryptpad_color_green;
@cp_form-invalid: @cryptpad_color_red;

@ -71,6 +71,12 @@
div.cp-button-confirm {
display: inline-block;
&.new {
vertical-align: top;
button {
height: 35px;
}
}
button {
margin: 0 !important;
}
@ -85,7 +91,7 @@
}
}
}
button.cp-button-confirm-placeholder {
button.cp-button-confirm-placeholder:not(.new) {
margin-bottom: 3px !important;
}

@ -154,6 +154,38 @@
color: @cp_markdown-block-fg;
text-align: left;
}
div.cp-inline-img-warning {
display: inline-block;
padding: 10px;
color: @cryptpad_text_col;
background-color: @cryptpad_color_red_fader;
border: 1px solid @cryptpad_color_red;
.cp-inline-img {
display: flex;
margin-bottom: 10px;
}
.cp-alt-txt {
margin-left: 10px;
font-family: monospace;
font-size: 0.8em;
color: fade(@cryptpad_text_col, 90%);
}
a {
color: @cryptpad_text_col;
font-size: 0.8em;
&.cp-remote-img::before { // .fa.fa-question-circle
font-family: FontAwesome;
//content: "\f08e\00a0";
content: "\f08e\00a0\00a0";
}
&.cp-learn-more::before { // .fa.fa-external-link
font-family: FontAwesome;
content: "\f059\00a0";
//content: "\f059\00a0\00a0";
}
}
}
}
.markdown_cryptpad() {
@ -194,7 +226,7 @@
pre > code {
display: block;
position: relative;
width: 90%;
width: 100%;
margin: auto;
padding: 5px;
overflow-x: auto;

@ -221,6 +221,9 @@
button {
line-height: 1.5;
}
img {
align-self: center;
}
& > iframe {
width: 100%;
height: 100%;

@ -12,6 +12,18 @@ html, body {
background-color: @cp_static-bg !important;
color: @cryptpad_text_col;
font-family: "IBM Plex Mono";
#cp-report, #cp-report-ui {
max-width: 900px;
margin: auto;
padding: 15px;
}
#cp-report {
border: 1px solid @cryptpad_text_col;
}
#cp-report-ui {
text-align: right;
}
}

@ -64,7 +64,7 @@ server {
add_header Permissions-Policy interest-cohort=();
set $coop '';
if ($uri ~ ^\/(sheet|presentation|doc)\/.*$) { set $coop 'same-origin'; }
if ($uri ~ ^\/(sheet|presentation|doc|convert)\/.*$) { set $coop 'same-origin'; }
# Enable SharedArrayBuffer in Firefox (for .xlsx export)
add_header Cross-Origin-Resource-Policy cross-origin;
@ -167,6 +167,13 @@ server {
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;
}
# encrypted blobs are immutable and are thus cached for a year
@ -207,7 +214,7 @@ server {
# The nodejs server has some built-in forwarding rules to prevent
# URLs like /pad from resulting in a 404. This simply adds a trailing slash
# to a variety of applications.
location ~ ^/(register|login|settings|user|pad|drive|poll|slide|code|whiteboard|file|media|profile|contacts|todo|filepicker|debug|kanban|sheet|support|admin|notifications|teams|calendar|presentation|doc)$ {
location ~ ^/(register|login|settings|user|pad|drive|poll|slide|code|whiteboard|file|media|profile|contacts|todo|filepicker|debug|kanban|sheet|support|admin|notifications|teams|calendar|presentation|doc|form|report|convert)$ {
rewrite ^(.*)$ $1/ redirect;
}

@ -10,6 +10,7 @@ module.exports.create = function (Env) {
nThen(function (w) {
Decrees.load(Env, w(function (err) {
Env.flushCache();
if (err) {
log.error('DECREES_LOADING', {
error: err.code || err,

@ -317,6 +317,16 @@ var instanceStatus = function (Env, Server, cb) {
maxUploadSize: Env.maxUploadSize,
premiumUploadSize: Env.premiumUploadSize,
consentToContact: Env.consentToContact,
listMyInstance: Env.listMyInstance,
provideAggregateStatistics: Env.provideAggregateStatistics,
removeDonateButton: Env.removeDonateButton,
blockDailyCheck: Env.blockDailyCheck,
updateAvailable: Env.updateAvailable,
instancePurpose: Env.instancePurpose,
});
};

@ -1,14 +1,10 @@
/*jshint esversion: 6 */
/* globals Buffer*/
var Block = module.exports;
const Fs = require("fs");
const Fse = require("fs-extra");
const Path = require("path");
const Block = module.exports;
const Nacl = require("tweetnacl/nacl-fast");
const nThen = require("nthen");
const Util = require("../common-util");
const BlockStore = require("../storage/block");
/*
We assume that the server is secured against MitM attacks
@ -31,7 +27,9 @@ const Util = require("../common-util");
author of the block, since we assume that the block will have been
encrypted with xsalsa20-poly1305 which is authenticated.
*/
var validateLoginBlock = function (Env, publicKey, signature, block, cb) { // FIXME BLOCKS
Block.validateLoginBlock = function (Env, publicKey, signature, block, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
// convert the public key to a Uint8Array and validate it
if (typeof(publicKey) !== 'string') { return void cb('E_INVALID_KEY'); }
@ -69,21 +67,7 @@ var validateLoginBlock = function (Env, publicKey, signature, block, cb) { // FI
// call back with (err) if unsuccessful
if (!verified) { return void cb("E_COULD_NOT_VERIFY"); }
return void cb(null, u8_block);
};
var createLoginBlockPath = function (Env, publicKey) { // FIXME BLOCKS
// prepare publicKey to be used as a file name
var safeKey = Util.escapeKeyCharacters(publicKey);
// validate safeKey
if (typeof(safeKey) !== 'string') {
return;
}
// derive the full path
// /home/cryptpad/cryptpad/block/fg/fg32kefksjdgjkewrjksdfksjdfsdfskdjfsfd
return Path.join(Env.paths.block, safeKey.slice(0, 2), safeKey);
return void cb(null, block);
};
Block.validateAncestorProof = function (Env, proof, _cb) {
@ -103,31 +87,26 @@ Block.validateAncestorProof = function (Env, proof, _cb) {
return void cb('E_INVALID_ANCESTOR_PROOF');
}
// else fall through to next step
}).nThen(function (w) {
var path = createLoginBlockPath(Env, pub);
Fs.access(path, Fs.constants.F_OK, w(function (err) {
if (!err) { return; }
w.abort(); // else
return void cb("E_MISSING_ANCESTOR");
}));
}).nThen(function () {
BlockStore.check(Env, pub, function (err) {
if (err) { return void cb('E_MISSING_ANCESTOR'); }
cb(void 0, pub);
});
});
} catch (err) {
return void cb(err);
}
};
Block.writeLoginBlock = function (Env, safeKey, msg, _cb) { // FIXME BLOCKS
Block.writeLoginBlock = function (Env, safeKey, msg, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
//console.log(msg);
var publicKey = msg[0];
var signature = msg[1];
var block = msg[2];
var registrationProof = msg[3];
var previousKey;
var validatedBlock, parsed, path;
var validatedBlock, path;
nThen(function (w) {
if (Util.escapeKeyCharacters(publicKey) !== safeKey) {
w.abort();
@ -156,44 +135,26 @@ Block.writeLoginBlock = function (Env, safeKey, msg, _cb) { // FIXME BLOCKS
previousKey = provenKey;
}));
}).nThen(function (w) {
validateLoginBlock(Env, publicKey, signature, block, w(function (e, _validatedBlock) {
Env.validateLoginBlock(publicKey, signature, block, w(function (e, _validatedBlock) {
if (e) {
w.abort();
return void cb(e);
}
if (!(_validatedBlock instanceof Uint8Array)) {
if (typeof(_validatedBlock) !== 'string') {
w.abort();
return void cb('E_INVALID_BLOCK');
return void cb('E_INVALID_BLOCK_RETURNED');
}
validatedBlock = _validatedBlock;
// derive the filepath
path = createLoginBlockPath(Env, publicKey);
// make sure the path is valid
if (typeof(path) !== 'string') {
return void cb('E_INVALID_BLOCK_PATH');
}
parsed = Path.parse(path);
if (!parsed || typeof(parsed.dir) !== 'string') {
w.abort();
return void cb("E_INVALID_BLOCK_PATH_2");
}
}));
}).nThen(function (w) {
// make sure the path to the file exists
Fse.mkdirp(parsed.dir, w(function (e) {
if (e) {
w.abort();
cb(e);
}
}));
}).nThen(function () {
// actually write the block
Fs.writeFile(path, Buffer.from(validatedBlock), { encoding: "binary", }, function (err) {
if (err) { return void cb(err); }
var buffer;
try {
buffer = Buffer.from(Nacl.util.decodeBase64(validatedBlock));
} catch (err) {
return void cb('E_BLOCK_DESERIALIZATION');
}
BlockStore.write(Env, publicKey, buffer, function (err) {
Env.Log.info('BLOCK_WRITE_BY_OWNER', {
safeKey: safeKey,
blockId: publicKey,
@ -201,11 +162,13 @@ Block.writeLoginBlock = function (Env, safeKey, msg, _cb) { // FIXME BLOCKS
previousKey: previousKey,
path: path,
});
cb();
cb(err);
});
});
};
const DELETE_BLOCK = Nacl.util.encodeBase64(Nacl.util.decodeUTF8('DELETE_BLOCK'));
/*
When users write a block, they upload the block, and provide
a signature proving that they deserve to be able to write to
@ -216,10 +179,11 @@ Block.writeLoginBlock = function (Env, safeKey, msg, _cb) { // FIXME BLOCKS
information, we can just sign some constant and use that as proof.
*/
Block.removeLoginBlock = function (Env, safeKey, msg, cb) { // FIXME BLOCKS
Block.removeLoginBlock = function (Env, safeKey, msg, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var publicKey = msg[0];
var signature = msg[1];
var block = Nacl.util.decodeUTF8('DELETE_BLOCK'); // clients and the server will have to agree on this constant
nThen(function (w) {
if (Util.escapeKeyCharacters(publicKey) !== safeKey) {
@ -227,26 +191,14 @@ Block.removeLoginBlock = function (Env, safeKey, msg, cb) { // FIXME BLOCKS
return void cb("INCORRECT_KEY");
}
}).nThen(function () {
validateLoginBlock(Env, publicKey, signature, block, function (e /*::, validatedBlock */) {
Env.validateLoginBlock(publicKey, signature, DELETE_BLOCK, function (e) {
if (e) { return void cb(e); }
// derive the filepath
var path = createLoginBlockPath(Env, publicKey);
// make sure the path is valid
if (typeof(path) !== 'string') {
return void cb('E_INVALID_BLOCK_PATH');
}
// FIXME COLDSTORAGE
Fs.unlink(path, function (err) {
Env.Log.info('DELETION_BLOCK_BY_OWNER_RPC', {
BlockStore.archive(Env, publicKey, function (err) {
Env.Log.info('ARCHIVAL_BLOCK_BY_OWNER_RPC', {
publicKey: publicKey,
path: path,
status: err? String(err): 'SUCCESS',
});
if (err) { return void cb(err); }
cb();
cb(err);
});
});
});

@ -8,7 +8,7 @@ const nThen = require("nthen");
//const escapeKeyCharacters = Util.escapeKeyCharacters;
const unescapeKeyCharacters = Util.unescapeKeyCharacters;
var sumChannelSizes = function (sizes) {
var sumChannelSizes = function (sizes) { // FIXME this synchronous code could be done by a worker
return Object.keys(sizes).map(function (id) { return sizes[id]; })
.filter(function (x) {
// only allow positive numbers
@ -115,8 +115,8 @@ Pinning.getTotalSize = function (Env, safeKey, cb) {
*/
Pinning.removePins = function (Env, safeKey, cb) {
// FIXME respect the queue
Env.pinStore.removeChannel(safeKey, function (err) {
Env.Log.info('DELETION_PIN_BY_OWNER_RPC', {
Env.pinStore.archiveChannel(safeKey, function (err) {
Env.Log.info('ARCHIVAL_PIN_BY_OWNER_RPC', {
safeKey: safeKey,
status: err? String(err): 'SUCCESS',
});
@ -145,7 +145,7 @@ var getFreeSpace = Pinning.getFreeSpace = function (Env, safeKey, cb) {
});
};
var getHash = Pinning.getHash = function (Env, safeKey, cb) {
Pinning.getHash = function (Env, safeKey, cb) {
getChannelList(Env, safeKey, function (channels) {
Env.hashChannelList(channels, cb);
});
@ -166,12 +166,12 @@ Pinning.pinChannel = function (Env, safeKey, channels, cb) {
});
if (toStore.length === 0) {
return void getHash(Env, safeKey, cb);
return void cb();
}
getMultipleFileSize(Env, toStore, function (e, sizes) {
if (typeof(sizes) === 'undefined') { return void cb(e); }
var pinSize = sumChannelSizes(sizes);
var pinSize = sumChannelSizes(sizes); // FIXME don't do this in the main thread...
getFreeSpace(Env, safeKey, function (e, free) {
if (typeof(free) === 'undefined') {
@ -186,7 +186,7 @@ Pinning.pinChannel = function (Env, safeKey, channels, cb) {
toStore.forEach(function (channel) {
session.channels[channel] = true;
});
getHash(Env, safeKey, cb);
cb();
});
});
});
@ -208,7 +208,7 @@ Pinning.unpinChannel = function (Env, safeKey, channels, cb) {
});
if (toStore.length === 0) {
return void getHash(Env, safeKey, cb);
return void cb();
}
Env.pinStore.message(safeKey, JSON.stringify(['UNPIN', toStore, +new Date()]),
@ -217,20 +217,19 @@ Pinning.unpinChannel = function (Env, safeKey, channels, cb) {
toStore.forEach(function (channel) {
delete session.channels[channel];
});
getHash(Env, safeKey, cb);
cb();
});
});
};
Pinning.resetUserPins = function (Env, safeKey, channelList, cb) {
Pinning.resetUserPins = function (Env, safeKey, channelList, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!Array.isArray(channelList)) { return void cb('INVALID_PIN_LIST'); }
var session = Core.getSession(Env.Sessions, safeKey);
if (!channelList.length) {
return void getHash(Env, safeKey, function (e, hash) {
if (e) { return cb(e); }
cb(void 0, hash);
});
return void cb();
}
var pins = {};
@ -270,9 +269,7 @@ Pinning.resetUserPins = function (Env, safeKey, channelList, cb) {
// update in-memory cache IFF the reset was allowed.
session.channels = pins;
getHash(Env, safeKey, function (e, hash) {
cb(e, hash);
});
cb();
});
});
});

@ -4,9 +4,9 @@ const Quota = module.exports;
//const Util = require("../common-util");
const Keys = require("../keys");
const Package = require('../../package.json');
const Https = require("https");
const Util = require("../common-util");
const Stats = require("../stats");
var validLimitFields = ['limit', 'plan', 'note', 'users', 'origin'];
@ -51,24 +51,66 @@ Quota.applyCustomLimits = function (Env) {
// console.log(Env.limits);
};
var isRemoteVersionNewer = function (local, remote) {
try {
local = local.split('.').map(Number);
remote = remote.split('.').map(Number);
for (var i = 0; i < 3; i++) {
if (remote[i] < local[i]) { return false; }
if (remote[i] > local[i]) { return true; }
}
} catch (err) {
// if anything goes wrong just fall through and return false
// false negatives are better than false positives
}
return false;
};
/*
Env = {
myDomain,
mySubdomain,
adminEmail,
Package.version,
var Assert = require("assert");
[
// remote versions
['4.5.0', '4.5.0', false], // equal semver should not prompt
['4.5.0', '4.5.1', true], // patch versions should prompt
['4.5.0', '4.6.0', true], // minor versions should prompt
['4.5.0', '5.0.0', true], // major versions should prompt
// local
['5.3.1', '4.9.0', false], // newer major should not prompt
['4.7.0', '4.6.0', false], // newer minor should not prompt
['4.7.0', '4.6.1', false], // newer patch should not prompt if other values are greater
].forEach(function (x) {
var result = isRemoteVersionNewer(x[0], x[1]);
Assert.equal(result, x[2]);
});
*/
// check if the remote endpoint reported an available server version
// which is newer than your current version (Env.version)
// if so, set Env.updateAvailable to the URL of its release notes
var checkUpdateAvailability = function (Env, json) {
if (!(json && typeof(json.updateAvailable) === 'string' && typeof(json.version) === 'string')) { return; }
// expects {updateAvailable: 'https://github.com/xwiki-labs/cryptpad/releases/4.7.0', version: '4.7.0'}
// the version string is provided explicitly even though it could be parsed from GitHub's URL
// this will allow old instances to understand responses of arbitrary URLs
// as long as we keep using semver for 'version'
if (!isRemoteVersionNewer(Env.version, json.version)) {
Env.updateAvailable = undefined;
return;
}
Env.updateAvailable = json.updateAvailable;
Env.Log.info('AN_UPDATE_IS_AVAILABLE', {
version: json.version,
updateAvailable: json.updateAvaiable,
});
};
*/
var queryAccountServer = function (Env, cb) {
var done = Util.once(Util.mkAsync(cb));
var body = JSON.stringify({
domain: Env.myDomain,
subdomain: Env.mySubdomain || null,
adminEmail: Env.adminEmail,
version: Package.version
});
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',
@ -92,6 +134,7 @@ var queryAccountServer = function (Env, cb) {
response.on('end', function () {
try {
var json = JSON.parse(str);
checkUpdateAvailability(Env, json);
// don't overwrite the limits with junk data
if (json && json.message === 'EINVAL') { return void cb(); }
done(void 0, json);

@ -34,6 +34,13 @@ SET_MAINTENANCE
SET_ADMIN_EMAIL
SET_SUPPORT_MAILBOX
// COMMUNITY PARTICIPATION AND GOVERNANCE
CONSENT_TO_CONTACT
LIST_MY_INSTANCE
PROVIDE_AGGREGATE_STATISTICS
REMOVE_DONATE_BUTTON
BLOCK_DAILY_CHECK
NOT IMPLEMENTED:
// RESTRICTED REGISTRATION
@ -89,6 +96,21 @@ commands.DISABLE_INTEGRATED_EVICTION = makeBooleanSetter('disableIntegratedEvict
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['DISABLE_INTEGRATED_TASKS', [true]]], console.log)
commands.DISABLE_INTEGRATED_TASKS = makeBooleanSetter('disableIntegratedTasks');
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['CONSENT_TO_CONTACT', [true]]], console.log)
commands.CONSENT_TO_CONTACT = makeBooleanSetter('consentToContact');
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['LIST_MY_INSTANCE', [true]]], console.log)
commands.LIST_MY_INSTANCE = makeBooleanSetter('listMyInstance');
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['PROVIDE_AGGREGATE_STATISTICS', [true]]], console.log)
commands.PROVIDE_AGGREGATE_STATISTICS = makeBooleanSetter('provideAggregateStatistics');
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['REMOVE_DONATE_BUTTON', [true]]], console.log)
commands.REMOVE_DONATE_BUTTON = makeBooleanSetter('removeDonateButton');
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['BLOCK_DAILY_CHECK', [true]]], console.log)
commands.BLOCK_DAILY_CHECK = makeBooleanSetter('blockDailyCheck');
/*
var isNonNegativeNumber = function (n) {
return !(typeof(n) !== 'number' || isNaN(n) || n < 0);
@ -151,6 +173,9 @@ commands.SET_SUPPORT_MAILBOX = makeGenericSetter('supportMailbox', function (arg
return args_isString(args) && Core.isValidPublicKey(args[0]);
});
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_INSTANCE_PURPOSE', ["development"]]], console.log)
commands.SET_INSTANCE_PURPOSE = makeGenericSetter('instancePurpose', args_isString);
// Maintenance: Empty string or an object with a start and end time
var isNumber = function (value) {
return typeof(value) === "number" && !isNaN(value);

@ -10,10 +10,22 @@ const Core = require("./commands/core");
const Quota = require("./commands/quota");
const Util = require("./common-util");
const Package = require("../package.json");
module.exports.create = function (config) {
var canonicalizeOrigin = function (s) {
if (typeof(s) === 'undefined') { return; }
return (s || '').trim().replace(/\/+$/, '');
};
module.exports.create = function (config) {
const Env = {
version: Package.version,
installMethod: config.installMethod || undefined,
httpUnsafeOrigin: canonicalizeOrigin(config.httpUnsafeOrigin),
httpSafeOrigin: canonicalizeOrigin(config.httpSafeOrigin),
removeDonateButton: config.removeDonateButton,
OFFLINE_MODE: false,
FRESH_KEY: '',
FRESH_MODE: true,
@ -97,6 +109,11 @@ module.exports.create = function (config) {
allowSubscriptions: config.allowSubscriptions === true,
blockDailyCheck: config.blockDailyCheck === true,
consentToContact: false,
listMyInstance: false,
provideAggregateStatistics: false,
updateAvailable: undefined,
myDomain: config.myDomain,
mySubdomain: config.mySubdomain, // only exists for the accounts integration
customLimits: {},
@ -106,7 +123,7 @@ module.exports.create = function (config) {
maxWorkers: config.maxWorkers,
disableIntegratedTasks: config.disableIntegratedTasks || false,
disableIntegratedEviction: config.disableIntegratedEviction || false,
disableIntegratedEviction: typeof(config.disableIntegratedEviction) === 'undefined'? true: config.disableIntegratedEviction, // XXX 4.10.0 false,
lastEviction: +new Date(),
evictionReport: {},
commandTimers: {},

@ -123,6 +123,8 @@ module.exports.create = function (Env, cb) {
Store.create({
filePath: pinPath,
archivePath: Env.paths.archive,
// indicate that archives should be put in a 'pins' archvie folder
volumeId: 'pins',
}, w(function (err, s) {
if (err) { throw err; }
Env.pinStore = s;

@ -701,6 +701,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
}
if (msgCount === 0 && !metadata_cache[channelName] && Server.channelContainsUser(channelName, userId)) {
// TODO this might be a good place to reject channel creation by anonymous users
handleFirstMessage(Env, channelName, metadata);
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(metadata)]);
}
@ -962,7 +963,11 @@ HK.onChannelMessage = function (Env, Server, channel, msgStruct, cb) {
// validation can fail in multiple ways
if (err === 'FAILED') {
// we log this case, but not others for some reason
Log.info("HK_SIGNED_MESSAGE_REJECTED", 'Channel '+channel.id);
Log.info("HK_SIGNED_MESSAGE_REJECTED", {
channel: channel.id,
validateKey: metadata.validayKey,
message: signedMsg,
});
}
// always abort if there was an error...
cb('FAILED_VALIDATION');

@ -0,0 +1,68 @@
/*jshint esversion: 6 */
const Stats = module.exports;
Stats.instanceData = function (Env) {
var data = {
version: Env.version,
installMethod: Env.installMethod,
domain: Env.myDomain,
subdomain: Env.mySubdomain,
httpUnsafeOrigin: Env.httpUnsafeOrigin,
httpSafeOrigin: Env.httpSafeOrigin,
adminEmail: Env.consentToContact? Env.adminEmail: undefined,
consentToContact: Boolean(Env.consentToContact),
instancePurpose: Env.instancePurpose === 'noanswer'? undefined: Env.instancePurpose,
};
/* We reserve the right to choose not to include instances
in our public directory at our discretion.
The following details will be included in your telemetry
as factors that may contribute to that decision.
These values are publicly available via /api/config
posting them to our server just makes it easier for us.
*/
if (Env.listMyInstance) {
// clearly indicate that you want to be listed
data.listMyInstance = Env.listMyInstance;
// you should have enabled your admin panel
data.adminKeys = Env.admins.length > 0;
// we expect that you enable your support mailbox
data.supportMailbox = Boolean(Env.supportMailbox);
// do you allow registration?
data.restrictRegistration = Boolean(Env.restrictRegistration);
// have you removed the donate button?
data.removeDonateButton = Boolean(Env.removeDonateButton);
// after how long do you consider a document to be inactive?
data.inactiveTime = Env.inactiveTime;
// how much storage do you offer to registered users?
data.defaultStorageLimit = Env.defaultStorageLimit;
// what size file upload do you permit
data.maxUploadSize = Env.maxUploadSize;
// how long do you retain inactive accounts?
data.accountRetentionTime = Env.accountRetentionTime;
// how long do you retain archived data?
//data.archiveRetentionTime = Env.archiveRetentionTime,
}
// we won't consider instances for public listings
// unless they opt to provide more info about themselves
if (!Env.provideAggregateStatistics) { return data; }
return data;
};

@ -0,0 +1,88 @@
/*jshint esversion: 6 */
const Block = module.exports;
const Util = require("../common-util");
const Path = require("path");
const Fs = require("fs");
const Fse = require("fs-extra");
const nThen = require("nthen");
Block.mkPath = function (Env, publicKey) {
// prepare publicKey to be used as a file name
var safeKey = Util.escapeKeyCharacters(publicKey);
// validate safeKey
if (typeof(safeKey) !== 'string') { return; }
// derive the full path
// /home/cryptpad/cryptpad/block/fg/fg32kefksjdgjkewrjksdfksjdfsdfskdjfsfd
return Path.join(Env.paths.block, safeKey.slice(0, 2), safeKey);
};
Block.mkArchivePath = function (Env, publicKey) {
// prepare publicKey to be used as a file name
var safeKey = Util.escapeKeyCharacters(publicKey);
// validate safeKey
if (typeof(safeKey) !== 'string') {
return;
}
// derive the full path
// /home/cryptpad/cryptpad/block/fg/fg32kefksjdgjkewrjksdfksjdfsdfskdjfsfd
return Path.join(Env.paths.archive, 'block', safeKey.slice(0, 2), safeKey);
};
Block.archive = function (Env, publicKey, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
// derive the filepath
var currentPath = Block.mkPath(Env, publicKey);
// make sure the path is valid
if (typeof(currentPath) !== 'string') {
return void cb('E_INVALID_BLOCK_PATH');
}
var archivePath = Block.mkArchivePath(Env, publicKey);
// make sure the path is valid
if (typeof(archivePath) !== 'string') {
return void cb('E_INVALID_BLOCK_ARCHIVAL_PATH');
}
Fse.move(currentPath, archivePath, {
overwrite: true,
}, cb);
};
Block.check = function (Env, publicKey, _cb) { // 'check' because 'exists' implies boolean
var cb = Util.once(Util.mkAsync(_cb));
var path = Block.mkPath(Env, publicKey);
Fs.access(path, Fs.constants.F_OK, cb);
};
Block.write = function (Env, publicKey, buffer, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var path = Block.mkPath(Env, publicKey);
if (typeof(path) !== 'string') { return void cb('INVALID_PATH'); }
var parsed = Path.parse(path);
nThen(function (w) {
Fse.mkdirp(parsed.dir, w(function (err) {
if (!err) { return; }
w.abort();
cb(err);
}));
}).nThen(function (w) {
Block.archive(Env, publicKey, w(function (/* err */) {
/*
we proceed even if there are errors.
it might be ENOENT (there is no file to archive)
or EACCES (bad filesystem permissions for the existing archived block?)
or lots of other things, none of which justify preventing the write
*/
}));
}).nThen(function () {
Fs.writeFile(path, buffer, { encoding: 'binary' }, cb);
});
};

@ -51,7 +51,7 @@ var mkPath = function (env, channelId) {
};
var mkArchivePath = function (env, channelId) {
return Path.join(env.archiveRoot, 'datastore', channelId.slice(0, 2), channelId) + '.ndjson';
return Path.join(env.archiveRoot, env.volumeId, channelId.slice(0, 2), channelId) + '.ndjson';
};
var mkMetadataPath = function (env, channelId) {
@ -59,7 +59,7 @@ var mkMetadataPath = function (env, channelId) {
};
var mkArchiveMetadataPath = function (env, channelId) {
return Path.join(env.archiveRoot, 'datastore', channelId.slice(0, 2), channelId) + '.metadata.ndjson';
return Path.join(env.archiveRoot, env.volumeId, channelId.slice(0, 2), channelId) + '.metadata.ndjson';
};
var mkTempPath = function (env, channelId) {
@ -1044,6 +1044,9 @@ module.exports.create = function (conf, _cb) {
var env = {
root: conf.filePath || './datastore',
archiveRoot: conf.archivePath || './data/archive',
// supply a volumeId if you want a store to archive channels to and from
// to its own subpath within the archive directory
volumeId: conf.volumeId || 'datastore',
channels: { },
batchGetChannel: BatchRead('store_batch_channel'),
};
@ -1076,7 +1079,7 @@ module.exports.create = function (conf, _cb) {
}
}));
// make sure the cold storage directory exists
Fse.mkdirp(env.archiveRoot, PERMISSIVE, w(function (err) {
Fse.mkdirp(Path.join(env.archiveRoot, env.volumeId), PERMISSIVE, w(function (err) {
if (err && err.code !== 'EEXIST') {
w.abort();
return void cb(err);

@ -66,6 +66,9 @@ const init = function (config, _cb) {
Store.create({
filePath: config.pinPath,
archivePath: config.archivePath,
// important to initialize the pinstore with its own volume id
// otherwise archived pin logs will get mixed in with channels
volumeId: 'pins',
}, w(function (err, _pinStore) {
if (err) {
w.abort();
@ -694,6 +697,10 @@ COMMANDS.VALIDATE_ANCESTOR_PROOF = function (data, cb) {
Block.validateAncestorProof(Env, data && data.proof, cb);
};
COMMANDS.VALIDATE_LOGIN_BLOCK = function (data, cb) {
Block.validateLoginBlock(Env, data.publicKey, data.signature, data.block, cb);
};
process.on('message', function (data) {
if (!data || !data.txid || !data.pid) {
return void process.send({

@ -451,6 +451,15 @@ Workers.initialize = function (Env, config, _cb) {
}, cb);
};
Env.validateLoginBlock = function (publicKey, signature, block, cb) {
sendCommand({
command: 'VALIDATE_LOGIN_BLOCK',
publicKey: publicKey,
signature: signature,
block: block,
}, cb);
};
cb(void 0);
});
};

203
package-lock.json generated

@ -1,6 +1,6 @@
{
"name": "cryptpad",
"version": "4.5.0",
"version": "4.9.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -36,15 +36,15 @@
}
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==",
"dev": true
},
"@types/node": {
"version": "14.14.16",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.16.tgz",
"integrity": "sha512-naXYePhweTi+BMv11TgioE2/FXU4fSl29HAH1ffxVciNsH3rYXjNP2yM8wqmSm7jS20gM8TIklKiTen+1iVncw==",
"version": "15.12.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz",
"integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==",
"dev": true
},
"accepts": {
@ -57,9 +57,9 @@
}
},
"ajv": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
"integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"optional": true,
"requires": {
@ -187,16 +187,16 @@
"optional": true
},
"aws4": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
"integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==",
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
"dev": true,
"optional": true
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"base": {
@ -389,9 +389,9 @@
"optional": true
},
"chainpad-crypto": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/chainpad-crypto/-/chainpad-crypto-0.2.5.tgz",
"integrity": "sha512-K9vRsAspuX+uU1goXPz0CawpLIaOHq+1JP3WfDLqaz67LbCX/MLIUt9aMcSeIJcwZ9uMpqnbMGRktyVPoz6MCA==",
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/chainpad-crypto/-/chainpad-crypto-0.2.6.tgz",
"integrity": "sha512-yk7bBj22rs9PaX6LiqQxiw+Vj/afBStsNP1xP/B4Uh9a8iyJ7JrSZJ2Hj3d6fyqxJlBrMAX82Aj5PCZb5dzzHw==",
"requires": {
"tweetnacl": "git+https://github.com/dchest/tweetnacl-js.git#v0.12.2"
},
@ -688,15 +688,15 @@
},
"dependencies": {
"domelementtype": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
"integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
"dev": true
},
"entities": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
"dev": true
}
}
@ -754,9 +754,9 @@
"dev": true
},
"errno": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
"integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
"integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
"dev": true,
"optional": true,
"requires": {
@ -973,9 +973,9 @@
"optional": true
},
"fast-deep-equal": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"optional": true
},
@ -1069,9 +1069,9 @@
}
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
},
"fragment-cache": {
"version": "0.2.1",
@ -1134,9 +1134,9 @@
}
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
"integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
@ -1191,9 +1191,9 @@
}
},
"graceful-fs": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ=="
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
"integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ=="
},
"har-schema": {
"version": "2.0.0",
@ -1203,13 +1203,13 @@
"optional": true
},
"har-validator": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
"dev": true,
"optional": true,
"requires": {
"ajv": "^6.5.5",
"ajv": "^6.12.3",
"har-schema": "^2.0.0"
}
},
@ -1346,9 +1346,9 @@
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ipaddr.js": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
"integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA=="
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
},
"is-accessor-descriptor": {
"version": "0.1.6",
@ -1527,16 +1527,16 @@
"optional": true
},
"jshint": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/jshint/-/jshint-2.11.0.tgz",
"integrity": "sha512-ooaD/hrBPhu35xXW4gn+o3SOuzht73gdBuffgJzrZBJZPGgGiiTvJEgTyxFvBO2nz0+X1G6etF8SzUODTlLY6Q==",
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.0.tgz",
"integrity": "sha512-Nd+md9wIeyfDK+RGrbOBzwLONSTdihGMtyGYU/t7zYcN2EgUa4iuY3VK2oxtPYrW5ycTj18iC+UbhNTxe4C66g==",
"dev": true,
"requires": {
"cli": "~1.0.0",
"console-browserify": "1.1.x",
"exit": "0.1.x",
"htmlparser2": "3.8.x",
"lodash": "~4.17.11",
"lodash": "~4.17.21",
"minimatch": "~3.0.2",
"shelljs": "0.3.x",
"strip-json-comments": "1.0.x"
@ -1591,9 +1591,9 @@
}
},
"jszip": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.2.tgz",
"integrity": "sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA==",
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.6.0.tgz",
"integrity": "sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==",
"dev": true,
"requires": {
"lie": "~3.3.0",
@ -1654,15 +1654,6 @@
"promise": "^7.1.1",
"request": "^2.83.0",
"source-map": "~0.6.0"
},
"dependencies": {
"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,
"optional": true
}
}
},
"lesshint": {
@ -1791,16 +1782,16 @@
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
},
"mime-db": {
"version": "1.43.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
"integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ=="
"version": "1.48.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz",
"integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ=="
},
"mime-types": {
"version": "2.1.26",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
"integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
"version": "2.1.31",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz",
"integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==",
"requires": {
"mime-db": "1.43.0"
"mime-db": "1.48.0"
}
},
"minimatch": {
@ -1841,9 +1832,9 @@
}
},
"mkdirp": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz",
"integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"optional": true,
"requires": {
@ -1880,9 +1871,9 @@
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"netflux-websocket": {
"version": "0.1.20",
"resolved": "https://registry.npmjs.org/netflux-websocket/-/netflux-websocket-0.1.20.tgz",
"integrity": "sha512-svFkw4ol4gmkcXKnx5kF/8tR9mmtTCDzUlLy4mSlcNl/4iWlbDmgwp/+aJ3nqtv8fg12m+DAFGX2+fbC0//dcg=="
"version": "0.1.21",
"resolved": "https://registry.npmjs.org/netflux-websocket/-/netflux-websocket-0.1.21.tgz",
"integrity": "sha512-Zjl5lefg8urC0a0T7YCPGiUgRsISZBsTZl1STylmQz8Bq4ohcZ8cP3r6VoCpeVcvJ1Y/e3ZCXPxndWlNP9Jfug=="
},
"nthen": {
"version": "0.1.8",
@ -2049,22 +2040,14 @@
"dev": true
},
"postcss": {
"version": "7.0.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
"integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
"version": "7.0.36",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
"integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
"dev": true,
"requires": {
"chalk": "^2.4.2",
"source-map": "^0.6.1",
"supports-color": "^6.1.0"
},
"dependencies": {
"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
}
}
},
"postcss-less": {
@ -2115,12 +2098,12 @@
}
},
"proxy-addr": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
"integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"requires": {
"forwarded": "~0.1.2",
"ipaddr.js": "1.9.0"
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
}
},
"prr": {
@ -2131,9 +2114,9 @@
"optional": true
},
"psl": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz",
"integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==",
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==",
"dev": true,
"optional": true
},
@ -2193,9 +2176,9 @@
}
},
"repeat-element": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
"integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==",
"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==",
"dev": true
},
"repeat-string": {
@ -2412,6 +2395,12 @@
"requires": {
"is-extendable": "^0.1.0"
}
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
}
}
},
@ -2495,9 +2484,9 @@
}
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"source-map-resolve": {
@ -2514,9 +2503,9 @@
}
},
"source-map-url": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
"integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
"integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
"dev": true
},
"split-string": {
@ -2793,9 +2782,9 @@
}
},
"uri-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"optional": true,
"requires": {

@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server",
"version": "4.5.0",
"version": "4.9.0",
"license": "AGPL-3.0+",
"repository": {
"type": "git",

@ -1,20 +1,18 @@
[![An XWiki Labs Project](https://raw.githubusercontent.com/xwiki-labs/xwiki-labs-logo/master/projects/xwikilabs/xlabs-project.png "XWiki labs")](https://labs.xwiki.com/xwiki/bin/view/Main/WebHome)
# 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 is encrypted, the service and its administrators have no way of seeing the content being edited and stored.
![CryptPad screenshot](screenshot.png "Private real-time collaboration on a Rich Text document.")
CryptPad is the **Zero Knowledge** realtime collaborative editor.
# Installation
Encryption carried out in your web browser protects the data from the server, the cloud
and the NSA. It relies on the [ChainPad] realtime engine.
## For development
<!--If you'd like to know more, please read [the Whitepaper]().-->
Our [developer guide](https://docs.cryptpad.fr/en/dev_guide/setup.html) provides instructions for setting up a local instance without HTTPS or our more advanced security features.
# Installation
## For production
Installing CryptPad is pretty straightforward. You can read all about it in the
[installation guide](https://github.com/xwiki-labs/cryptpad/wiki/Installation-guide).
It also contains information on keeping your instance of CryptPad up to date.
Configuring CryptPad for production requires a little more work, but the process is described in our [admin installation guide](https://docs.cryptpad.fr/en/admin_guide/installation.html). From there you can find more information about customization and maintenance.
## Current version
@ -24,33 +22,40 @@ The most recent version and all past release notes can be found [here](https://g
See [Cryptpad-Docker](https://github.com/xwiki-labs/cryptpad-docker) repository for details on how to get up-and-running with Cryptpad in Docker. This repository is maintained by the community and not officially supported.
# Security
CryptPad is *private*, not *anonymous*. Privacy protects your data, anonymity protects you.
As such, it is possible for a collaborator on the pad to include some silly/ugly/nasty things
in a CryptPad such as an image which reveals your IP address when your browser automatically
loads it or a script which plays Rick Astleys's greatest hits. It is possible for anyone
who does not have the key to be able to change anything in the pad or add anything, even the
server, however the clients will notice this because the content hashes in CryptPad will fail to
validate.
The server does have a certain power, it can send you evil javascript which does the wrong
thing (leaks the key or the data back to the server or to someone else). This is however an
[active attack] which makes it detectable. The NSA really hates doing these because they might
get caught and laughed at and humiliated in front of the whole world (again). If you're making
the NSA mad enough for them to use an active attack against you, Great Success Highfive, now take
the battery out of your computer before it spawns Agent Smith.
Still there are other low-lives in the world so using CryptPad over HTTPS is probably a good idea.
CryptPad offers a variety of collaborative tools that encrypt your data in your browser
before it is sent to the server and your collaborators. In the event that the server is
compromized the database holds encrypted data that is not of much value to attackers.
The code which performs the encryption is still loaded from the host server like any
other web page, so you still need to trust the administrator to keep their server secure
and to send you the right code. An expert can download code from the server and check
that it isn't doing anything malicious like leaking your encryption keys, which is why
this is considered an [active attack].
The platform is designed to minimize what data is exposed to its operators. User registration
and account access is based on a cryptographic key that is derived from your username
and password so the server never needs to see either and you don't need to worry about
whether they are being stored securely. It is impossible to verify whether a server's
operators are logging your IP or other activity, so if you consider this information
sensitive it is safest to assume it is being recorded and access your preferred instance
via [Tor browser].
A correctly configured instance has safeguards to prevent collaborators from doing some
nasty things like injecting scripts into collaborative documents or uploads. The project
is actively maintained and bugs that our safeguards don't catch tend to get fixed quickly.
For this reason it is best to only use instances that are running the most recent version,
which is currently on a three-week release cycle. It is difficult for a non-expert to
determine whether an instance is otherwise configured correctly, so we are actively
working on allowing administrators to opt in to a public directory of servers that
meet our strict criteria for safety.
# Translations
We'd like to make it easy for more people to use encryption in their routine activities.
As such, we've tried to make language-specific parts of CryptPad translatable. If you're
able to translate CryptPad's interface, and would like to help, please contact us!
You can also see [our translation guide](/customize.dist/translations/README.md).
CryptPad can be translated with nothing more than a web browser via our
[Weblate instance](https://weblate.cryptpad.fr/projects/cryptpad/app/).
More information about this can be found in [our translation guide](/customize.dist/translations/README.md).
# Contacting Us
@ -61,13 +66,13 @@ via our [GitHub issue tracker](https://github.com/xwiki-labs/cryptpad/issues/),
# Team
CryptPad is actively developed by a team at [XWiki SAS](https://www.xwiki.com), a company that has been building Open-Source software since 2004 with contributors from around the world. Between 2015 and 2019 it was funded by a research grant from the French state through [BPI France](https://www.bpifrance.fr/). It is currently financed by [NLnet PET](https://nlnet.nl/PET/), subscribers of CryptPad.fr and donations to our [Open-Collective campaign](https://opencollective.com/cryptpad).
CryptPad is actively developed by a team at [XWiki SAS](https://www.xwiki.com), a company that has been building Open-Source software since 2004 with contributors from around the world. Between 2015 and 2019 it was funded by a research grant from the French state through [BPI France](https://www.bpifrance.fr/). In the years since we have been funded by [NLnet PET](https://nlnet.nl/PET/), [NGI TRUST](https://www.ngi.eu/ngi-projects/ngi-trust/), [NGI DAPSI](https://dapsi.ngi.eu/), subscribers of CryptPad.fr, and donations to our [Open-Collective campaign](https://opencollective.com/cryptpad).
# Contributing
We love Open Source and we love contribution. Learn more about [contributing](https://docs.cryptpad.fr/en/how_to_contribute.html).
If you have any questions or comments, or if you're interested in contributing to Cryptpad, come say hi on IRC, `#cryptpad` on Freenode.
If you have any questions or comments, or if you're interested in contributing to Cryptpad, come say hi in our [Matrix channel](https://app.element.io/#/room/#cryptpad:matrix.xwiki.com).
# License
@ -78,5 +83,6 @@ published by the Free Software Foundation, either version 3 of the License, or (
any later version. If you wish to use this technology in a proprietary product, please contact
sales@xwiki.com.
[ChainPad]: https://github.com/xwiki-contrib/chainpad
[Tor browser]: https://www.torproject.org/download/
[active attack]: https://en.wikipedia.org/wiki/Attack_(computing)#Types_of_attack

@ -56,6 +56,8 @@ var prepareEnv = function (Env, cb) {
Store.create({
filePath: config.pinPath,
// archive pin logs to their own subpath
volumeId: 'pins',
}, w(function (err, _) {
if (err) {
w.abort();

@ -56,6 +56,8 @@ var prepareEnv = function (Env, cb) {
Store.create({
filePath: config.pinPath,
// archive pin logs to their own subpath
volumeId: 'pins',
}, w(function (err, _) {
if (err) {
w.abort();

@ -9,6 +9,7 @@ var simpleTags = [
// FIXME
"<a href='#'>",
'<a href="#docs">',
'<h3>',
'</h3>',
@ -70,7 +71,7 @@ processLang(EN, 'en', true);
[
'ar',
'bn_BD',
//'bn_BD',
'ca',
'de',
'es',
@ -86,11 +87,15 @@ processLang(EN, 'en', true);
'ro',
'ru',
'sv',
'te',
//'te',
'tr',
'zh',
].forEach(function (lang) {
try {
var map = require("../www/common/translations/messages." + lang + ".json");
if (!Object.keys(map).length) { return; }
processLang(map, lang);
} catch (err) {
console.error(err);
}
});

@ -0,0 +1,8 @@
// TODO unify the following scripts
// unused-translations.js
// find-html-translations
// more linting
// Search for 'Cryptpad' string (should be 'CryptPad')
// Search English for -ise\s

@ -122,14 +122,11 @@ var createUser = function (config, cb) {
});
}));
}).nThen(function (w) {
user.rpc.reset([], w(function (err, hash) {
user.rpc.reset([], w(function (err) {
if (err) {
w.abort();
user.shutdown();
return console.log("RESET_ERR");
}
if (!hash || hash !== EMPTY_ARRAY_HASH) {
throw new Error("EXPECTED EMPTY ARRAY HASH");
return console.log("TEST_RESET_ERR");
}
}));
}).nThen(function (w) {
@ -214,17 +211,17 @@ var createUser = function (config, cb) {
// TODO check your quota usage
}).nThen(function (w) {
user.rpc.unpin([user.mailboxChannel], w(function (err, hash) {
user.rpc.unpin([user.mailboxChannel], w(function (err) {
if (err) {
w.abort();
return void cb(err);
}
}));
}).nThen(function (w) {
user.rpc.getServerHash(w(function (err, hash) {
console.log(hash);
if (hash[0] !== EMPTY_ARRAY_HASH) {
//console.log('UNPIN_RESPONSE', hash);
throw new Error("UNPIN_DIDNT_WORK");
}
user.latestPinHash = hash[0];
user.latestPinHash = hash;
}));
}).nThen(function (w) {
// clean up the pin list to avoid lots of accounts on the server
@ -304,7 +301,8 @@ nThen(function (w) {
}, w(function (err, roster) {
if (err) {
w.abort();
return void console.trace(err);
console.error(err);
return void console.error("ROSTER_ERROR");
}
oscar.roster = roster;
oscar.destroy.reg(function () {

@ -4,7 +4,6 @@
var Express = require('express');
var Http = require('http');
var Fs = require('fs');
var Package = require('./package.json');
var Path = require("path");
var nThen = require("nthen");
var Util = require("./lib/common-util");
@ -16,21 +15,20 @@ var Env = require("./lib/env").create(config);
var app = Express();
var canonicalizeOrigin = function (s) {
return (s || '').trim().replace(/\/+$/, '');
var fancyURL = function (domain, path) {
try {
if (domain && path) { return new URL(path, domain).href; }
return new URL(domain);
} catch (err) {}
return false;
};
(function () {
// you absolutely must provide an 'httpUnsafeOrigin'
if (typeof(config.httpUnsafeOrigin) !== 'string') {
// you absolutely must provide an 'httpUnsafeOrigin' (a truthy string)
if (!Env.httpUnsafeOrigin || typeof(Env.httpUnsafeOrigin) !== 'string') {
throw new Error("No 'httpUnsafeOrigin' provided");
}
config.httpUnsafeOrigin = canonicalizeOrigin(config.httpUnsafeOrigin);
if (typeof(config.httpSafeOrigin) === 'string') {
config.httpSafeOrigin = canonicalizeOrigin(config.httpSafeOrigin);
}
// fall back to listening on a local address
// if httpAddress is not a string
if (typeof(config.httpAddress) !== 'string') {
@ -42,26 +40,11 @@ var canonicalizeOrigin = function (s) {
config.httpPort = 3000;
}
if (typeof(config.httpSafeOrigin) !== 'string') {
if (typeof(Env.httpSafeOrigin) !== 'string') {
Env.NO_SANDBOX = true;
if (typeof(config.httpSafePort) !== 'number') {
config.httpSafePort = config.httpPort + 1;
}
if (Env.DEV_MODE) { return; }
console.log(`
m m mm mmmmm mm m mmmmm mm m mmm m
# # # ## # "# #"m # # #"m # m" " #
" #"# # # # #mmmm" # #m # # # #m # # mm #
## ##" #mm# # "m # # # # # # # # #
# # # # # " # ## mm#mm # ## "mmm" #
`);
console.log("\nNo 'httpSafeOrigin' provided.");
console.log("Your configuration probably isn't taking advantage of all of CryptPad's security features!");
console.log("This is acceptable for development, otherwise your users may be at risk.\n");
console.log("Serving sandboxed content via port %s.\nThis is probably not what you want for a production instance!\n", config.httpSafePort);
}
}());
@ -94,42 +77,35 @@ var setHeaders = (function () {
}
} else {
// use the default CSP headers constructed with your domain
headers['Content-Security-Policy'] = Default.contentSecurity(config.httpUnsafeOrigin);
headers['Content-Security-Policy'] = Default.contentSecurity(Env.httpUnsafeOrigin);
}
const padHeaders = Util.clone(headers);
if (typeof(config.padContentSecurity) === 'string') {
padHeaders['Content-Security-Policy'] = config.padContentSecurity;
} else {
padHeaders['Content-Security-Policy'] = Default.padContentSecurity(config.httpUnsafeOrigin);
padHeaders['Content-Security-Policy'] = Default.padContentSecurity(Env.httpUnsafeOrigin);
}
if (Object.keys(headers).length) {
return function (req, res) {
// apply a bunch of cross-origin headers for XLSX export in FF and printing elsewhere
applyHeaderMap(res, {
"Cross-Origin-Opener-Policy": /^\/sheet\//.test(req.url)? 'same-origin': '',
"Cross-Origin-Embedder-Policy": 'require-corp',
"Cross-Origin-Opener-Policy": /^\/(sheet|presentation|doc|convert)\//.test(req.url)? 'same-origin': '',
});
if (Env.NO_SANDBOX) { // handles correct configuration for local development
// https://stackoverflow.com/questions/11531121/add-duplicate-http-response-headers-in-nodejs
applyHeaderMap(res, {
"Cross-Origin-Resource-Policy": 'cross-origin',
"Cross-Origin-Embedder-Policy": 'require-corp',
});
}
// Don't set CSP headers on /api/config because they aren't necessary and they cause problems
// Don't set CSP headers on /api/ endpoints
// because they aren't necessary and they cause problems
// when duplicated by NGINX in production environments
if (/^\/api\/(broadcast|config)/.test(req.url)) {
/*
if (Env.NO_SANDBOX) {
applyHeaderMap(res, {
"Cross-Origin-Resource-Policy": 'cross-origin',
});
}
*/
return;
}
if (/^\/api\/(broadcast|config)/.test(req.url)) { return; }
applyHeaderMap(res, {
"Cross-Origin-Resource-Policy": 'cross-origin',
});
@ -165,7 +141,7 @@ app.head(/^\/common\/feedback\.html/, function (req, res, next) {
app.use('/blob', function (req, res, next) {
if (req.method === 'HEAD') {
Express.static(Path.join(__dirname, (config.blobPath || './blob')), {
Express.static(Path.join(__dirname, Env.paths.blob), {
setHeaders: function (res, path, stat) {
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Headers', 'Content-Length');
@ -205,13 +181,13 @@ var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$');
app.get(mainPagePattern, Express.static(__dirname + '/customize'));
app.get(mainPagePattern, Express.static(__dirname + '/customize.dist'));
app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob')), {
app.use("/blob", Express.static(Path.join(__dirname, Env.paths.blob), {
maxAge: Env.DEV_MODE? "0d": "365d"
}));
app.use("/datastore", Express.static(Path.join(__dirname, (config.filePath || './datastore')), {
app.use("/datastore", Express.static(Path.join(__dirname, Env.paths.data), {
maxAge: "0d"
}));
app.use("/block", Express.static(Path.join(__dirname, (config.blockPath || '/block')), {
app.use("/block", Express.static(Path.join(__dirname, Env.paths.block), {
maxAge: "0d",
}));
@ -266,12 +242,12 @@ var serveConfig = makeRouteCache(function (host) {
'var obj = ' + JSON.stringify({
requireConf: {
waitSeconds: 600,
urlArgs: 'ver=' + Package.version + cacheString(),
urlArgs: 'ver=' + Env.version + cacheString(),
},
removeDonateButton: (config.removeDonateButton === true),
allowSubscriptions: (config.allowSubscriptions === true),
removeDonateButton: (Env.removeDonateButton === true),
allowSubscriptions: (Env.allowSubscriptions === true),
websocketPath: config.externalWebsocketURL,
httpUnsafeOrigin: config.httpUnsafeOrigin,
httpUnsafeOrigin: Env.httpUnsafeOrigin,
adminEmail: Env.adminEmail,
adminKeys: Env.admins,
inactiveTime: Env.inactiveTime,
@ -279,10 +255,10 @@ var serveConfig = makeRouteCache(function (host) {
defaultStorageLimit: Env.defaultStorageLimit,
maxUploadSize: Env.maxUploadSize,
premiumUploadSize: Env.premiumUploadSize,
restrictRegistration: Env.restrictRegistration, // FIXME see the race condition in env.js
restrictRegistration: Env.restrictRegistration,
}, null, '\t'),
'obj.httpSafeOrigin = ' + (function () {
if (config.httpSafeOrigin) { return '"' + config.httpSafeOrigin + '"'; }
if (Env.httpSafeOrigin) { return '"' + Env.httpSafeOrigin + '"'; }
if (config.httpSafePort) {
return "(function () { return window.location.origin.replace(/\:[0-9]+$/, ':" +
config.httpSafePort + "'); }())";
@ -345,7 +321,19 @@ nThen(function (w) {
var port = config.httpPort;
var ps = port === 80? '': ':' + port;
console.log('[%s] server available http://%s%s', new Date().toISOString(), hostName, ps);
var roughAddress = 'http://' + hostName + ps;
var betterAddress = fancyURL(Env.httpUnsafeOrigin);
if (betterAddress) {
console.log('Serving content for %s via %s.\n', betterAddress, roughAddress);
} else {
console.log('Serving content via %s.\n', roughAddress);
}
if (!Env.admins.length) {
console.log("Your instance is not correctly configured for safe use in production.\nSee %s for more information.\n",
fancyURL(Env.httpUnsafeOrigin, '/checkup/') || 'https://your-domain.com/checkup/'
);
}
});
if (config.httpSafePort) {

@ -203,6 +203,17 @@
}
}
.cp-admin-radio-container {
display: flex;
align-items: left; //center;
flex-wrap: wrap;
flex-direction: column;
label {
margin-right: 40px;
margin-top: 5px;
}
}
.cp-admin-broadcast-form {
input.flatpickr-input {
width: 307.875px !important; // same width as flatpickr calendar

@ -16,6 +16,7 @@ define([
'/support/ui.js',
'/lib/datepicker/flatpickr.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'css!/lib/datepicker/flatpickr.min.css',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
@ -44,6 +45,7 @@ define([
'instanceStatus': {}
};
var Nacl = window.nacl;
var common;
var sFrameChan;
@ -54,6 +56,7 @@ define([
'cp-admin-archive',
'cp-admin-unarchive',
'cp-admin-registration',
'cp-admin-email'
],
'quota': [ // Msg.admin_cat_quota
'cp-admin-defaultlimit',
@ -71,7 +74,8 @@ define([
],
'support': [ // Msg.admin_cat_support
'cp-admin-support-list',
'cp-admin-support-init'
'cp-admin-support-init',
'cp-admin-support-priv',
],
'broadcast': [ // Msg.admin_cat_broadcast
'cp-admin-maintenance',
@ -81,15 +85,28 @@ define([
'performance': [ // Msg.admin_cat_performance
'cp-admin-refresh-performance',
'cp-admin-performance-profiling',
]
],
'network': [ // Msg.admin_cat_network
'cp-admin-update-available',
'cp-admin-checkup',
'cp-admin-block-daily-check',
//'cp-admin-provide-aggregate-statistics',
'cp-admin-list-my-instance',
'cp-admin-consent-to-contact',
'cp-admin-remove-donate-button',
'cp-admin-instance-purpose',
],
};
var create = {};
var keyToCamlCase = function (key) {
return key.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
};
var makeBlock = function (key, addButton) { // Title, Hint, maybeButton
// Convert to camlCase for translation keys
var safeKey = key.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
var safeKey = keyToCamlCase(key);
var $div = $('<div>', {'class': 'cp-admin-' + key + ' cp-sidebarlayout-element'});
$('<label>').text(Messages['admin_'+safeKey+'Title'] || key).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
@ -256,7 +273,7 @@ define([
var $div = makeBlock(key); // Msg.admin_registrationHint, .admin_registrationTitle, .admin_registrationButton
var state = APP.instanceStatus.restrictRegistration;
var $cbox = $(UI.createCheckbox('cp-settings-userfeedback',
var $cbox = $(UI.createCheckbox('cp-settings-' + key,
Messages.admin_registrationTitle,
state, { label: { class: 'noTitle' } }));
var spinner = UI.makeSpinner($cbox);
@ -267,8 +284,11 @@ define([
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['RESTRICT_REGISTRATION', [val]]
}, function (e) {
if (e) { UI.warn(Messages.error); console.error(e); }
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
spinner.done();
state = APP.instanceStatus.restrictRegistration;
@ -282,6 +302,94 @@ define([
return $div;
};
var makeAdminCheckbox = function (data) {
return function () {
var state = data.getState();
var key = data.key;
var $div = makeBlock(key);
var labelKey = 'admin_' + keyToCamlCase(key) + 'Label';
var titleKey = 'admin_' + keyToCamlCase(key) + 'Title';
var $cbox = $(UI.createCheckbox('cp-admin-' + key,
Messages[labelKey] || Messages[titleKey],
state, { label: { class: 'noTitle' } }));
var spinner = UI.makeSpinner($cbox);
var $checkbox = $cbox.find('input').on('change', function() {
spinner.spin();
var val = $checkbox.is(':checked') || false;
$checkbox.attr('disabled', 'disabled');
data.query(val, function (state) {
spinner.done();
$checkbox[0].checked = state;
$checkbox.removeAttr('disabled');
});
});
$cbox.appendTo($div);
return $div;
};
};
// Msg.admin_registrationHint, .admin_registrationTitle, .admin_registrationButton
create['registration'] = makeAdminCheckbox({
key: 'registration',
getState: function () {
return APP.instanceStatus.restrictRegistration;
},
query: function (val, setState) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['RESTRICT_REGISTRATION', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
setState(APP.instanceStatus.restrictRegistration);
});
});
},
});
create['email'] = function () {
var key = 'email';
var $div = makeBlock(key, true); // Msg.admin_emailHint, Msg.admin_emailTitle, Msg.admin_emailButton
var $button = $div.find('button');
var input = h('input', {
type: 'email',
value: ApiConfig.adminEmail || ''
});
var $input = $(input);
var innerDiv = h('div.cp-admin-setlimit-form', input);
var spinner = UI.makeSpinner($(innerDiv));
$button.click(function () {
if (!$input.val()) { return; }
spinner.spin();
$button.attr('disabled', 'disabled');
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_ADMIN_EMAIL', [$input.val()]]
}, function (e, response) {
$button.removeAttr('disabled');
if (e || response.error) {
UI.warn(Messages.error);
$input.val('');
console.error(e, response);
spinner.hide();
return;
}
spinner.done();
UI.log(Messages.saved);
});
});
$button.before(innerDiv);
return $div;
};
var getPrettySize = UIElements.prettySize;
create['defaultlimit'] = function () {
@ -316,8 +424,11 @@ define([
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['UPDATE_DEFAULT_STORAGE', data]
}, function (e) {
if (e) { UI.warn(Messages.error); return void console.error(e); }
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
return void console.error(e, response);
}
var limit = getPrettySize(l);
$div.find('.cp-admin-defaultlimit-value').text(Messages._getKey('admin_limit', [limit]));
});
@ -448,8 +559,12 @@ define([
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['RM_QUOTA', data]
}, function (e) {
if (e) { UI.warn(Messages.error); console.error(e); }
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
return;
}
APP.refreshLimits();
$key.val('');
});
@ -462,8 +577,12 @@ define([
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_QUOTA', data]
}, function (e) {
if (e) { UI.warn(Messages.error); console.error(e); }
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
return;
}
APP.refreshLimits();
$key.val('');
});
@ -637,8 +756,13 @@ define([
};
var supportKey = ApiConfig.supportMailbox;
var checkAdminKey = function (priv) {
if (!supportKey) { return; }
return Hash.checkBoxKeyPair(priv, supportKey);
};
create['support-list'] = function () {
if (!supportKey || !APP.privateKey) { return; }
if (!supportKey || !APP.privateKey || !checkAdminKey(APP.privateKey)) { return; }
var $container = makeBlock('support-list'); // Msg.admin_supportListHint, .admin_supportListTitle
var $div = $(h('div.cp-support-container')).appendTo($container);
@ -709,7 +833,8 @@ define([
var premium = t.some(function (msg) {
var _ed = Util.find(msg, ['content', 'msg', 'content', 'sender', 'edPublic']);
if (ed !== _ed) { return; }
return Util.find(msg, ['content', 'msg', 'content', 'sender', 'plan']);
return Util.find(msg, ['content', 'msg', 'content', 'sender', 'plan']) ||
Util.find(msg, ['content', 'msg', 'content', 'sender', 'quota', 'plan']);
});
var lastMsg = t[t.length - 1];
var lastMsgEd = Util.find(lastMsg, ['content', 'msg', 'content', 'sender', 'edPublic']);
@ -898,16 +1023,63 @@ define([
return $container;
};
create['support-priv'] = function () {
if (!supportKey || !APP.privateKey || !checkAdminKey(APP.privateKey)) { return; }
var checkAdminKey = function (priv) {
if (!supportKey) { return; }
return Hash.checkBoxKeyPair(priv, supportKey);
var $div = makeBlock('support-priv', true); // Msg.admin_supportPrivHint, .admin_supportPrivTitle, .admin_supportPrivButton
var $button = $div.find('button').click(function () {
$button.remove();
var $selectable = $(UI.dialog.selectable(APP.privateKey)).css({ 'max-width': '28em' });
$div.append($selectable);
});
return $div;
};
create['support-init'] = function () {
var $div = makeBlock('support-init'); // Msg.admin_supportInitHint, .admin_supportInitTitle
if (!supportKey) {
(function () {
$div.append(h('p', Messages.admin_supportInitHelp));
var button = h('button.btn.btn-primary', Messages.admin_supportInitGenerate);
var $button = $(button).appendTo($div);
$div.append($button);
var spinner = UI.makeSpinner($div);
$button.click(function () {
spinner.spin();
$button.attr('disabled', 'disabled');
var keyPair = Nacl.box.keyPair();
var pub = Nacl.util.encodeBase64(keyPair.publicKey);
var priv = Nacl.util.encodeBase64(keyPair.secretKey);
// Store the private key first. It won't be used until the decree is accepted.
sFrameChan.query("Q_ADMIN_MAILBOX", priv, function (err, obj) {
if (err || (obj && obj.error)) {
console.error(err || obj.error);
UI.warn(Messages.error);
spinner.hide();
return;
}
// Then send the decree
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_SUPPORT_MAILBOX', [pub]]
}, function (e, response) {
$button.removeAttr('disabled');
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
spinner.hide();
return;
}
spinner.done();
UI.log(Messages.saved);
supportKey = pub;
APP.privateKey = priv;
$('.cp-admin-support-init').hide();
APP.$rightside.append(create['support-list']());
APP.$rightside.append(create['support-priv']());
});
});
});
})();
return $div;
}
if (!APP.privateKey || !checkAdminKey(APP.privateKey)) {
@ -937,6 +1109,7 @@ define([
APP.privateKey = key;
$('.cp-admin-support-init').hide();
APP.$rightside.append(create['support-list']());
APP.$rightside.append(create['support-priv']());
});
});
return $div;
@ -1030,9 +1203,10 @@ define([
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_LAST_BROADCAST_HASH', [lastHash]]
}, function (e) {
if (e) {
console.error(e);
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
return;
}
console.log('lastBroadcastHash updated');
@ -1298,20 +1472,21 @@ define([
var end = h('input');
var $start = $(start);
var $end = $(end);
var is24h = false;
try {
is24h = !new Intl.DateTimeFormat(navigator.language, { hour: 'numeric' }).format(0).match(/AM/);
} catch (e) {}
var is24h = UIElements.is24h();
var dateFormat = "Y-m-d H:i";
if (!is24h) { dateFormat = "Y-m-d h:i K"; }
var endPickr = Flatpickr(end, {
enableTime: true,
time_24hr: is24h,
dateFormat: dateFormat,
minDate: new Date()
});
Flatpickr(start, {
enableTime: true,
time_24hr: is24h,
minDate: new Date(),
dateFormat: dateFormat,
onChange: function () {
endPickr.set('minDate', new Date($start.val()));
}
@ -1336,9 +1511,10 @@ define([
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_MAINTENANCE', [data]]
}, function (e) {
if (e) {
UI.warn(Messages.error); console.error(e);
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
$button.prop('disabled', '');
return;
}
@ -1419,7 +1595,7 @@ define([
var getData = function () {
var url = $input.val();
if (!Util.isValidURL(url)) {
console.error('Invalid URL');
console.error('Invalid URL', url);
return false;
}
return url;
@ -1430,10 +1606,11 @@ define([
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_SURVEY_URL', [data]]
}, function (e) {
if (e) {
}, function (e, response) {
if (e || response.error) {
$button.prop('disabled', '');
UI.warn(Messages.error); console.error(e);
UI.warn(Messages.error);
console.error(e, response);
return;
}
// Maintenance applied, send notification
@ -1529,11 +1706,12 @@ define([
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'GET_WORKER_PROFILES',
}, function (e, data) {
if (e) { return void console.error(e); }
if (e || data.error) {
UI.warn(Messages.error);
return void console.error(e, data);
}
//console.info(data);
$div.find("table").remove();
process(data);
$div.append(table);
});
@ -1545,6 +1723,202 @@ define([
return $div;
};
create['update-available'] = function () { // Messages.admin_updateAvailableTitle.admin_updateAvailableHint.admin_updateAvailableLabel.admin_updateAvailableButton
if (!APP.instanceStatus.updateAvailable) { return; }
var $div = makeBlock('update-available', true);
var updateURL = 'https://github.com/xwiki-labs/cryptpad/releases/latest';
if (typeof(APP.instanceStatus.updateAvailable) === 'string') {
updateURL = APP.instanceStatus.updateAvailable;
}
$div.find('button').click(function () {
common.openURL(updateURL);
});
return $div;
};
create['checkup'] = function () {
var $div = makeBlock('checkup', true); // Messages.admin_checkupButton.admin_checkupHint.admin_checkupTitle
$div.find('button').click(function () {
common.openURL('/checkup/');
});
return $div;
};
create['consent-to-contact'] = makeAdminCheckbox({ // Messages.admin_consentToContactTitle.admin_consentToContactHint.admin_consentToContactLabel
key: 'consent-to-contact',
getState: function () {
return APP.instanceStatus.consentToContact;
},
query: function (val, setState) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['CONSENT_TO_CONTACT', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
setState(APP.instanceStatus.consentToContact);
});
});
},
});
create['list-my-instance'] = makeAdminCheckbox({ // Messages.admin_listMyInstanceTitle.admin_listMyInstanceHint.admin_listMyInstanceLabel
key: 'list-my-instance',
getState: function () {
return APP.instanceStatus.listMyInstance;
},
query: function (val, setState) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['LIST_MY_INSTANCE', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
setState(APP.instanceStatus.listMyInstance);
});
});
},
});
create['provide-aggregate-statistics'] = makeAdminCheckbox({ // Messages.admin_provideAggregateStatisticsTitle.admin_provideAggregateStatisticsHint.admin_provideAggregateStatisticsLabel
key: 'provide-aggregate-statistics',
getState: function () {
return APP.instanceStatus.provideAggregateStatistics;
},
query: function (val, setState) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['PROVIDE_AGGREGATE_STATISTICS', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
setState(APP.instanceStatus.provideAggregateStatistics);
});
});
},
});
create['remove-donate-button'] = makeAdminCheckbox({ // Messages.admin_removeDonateButtonTitle.admin_removeDonateButtonHint.admin_removeDonateButtonLabel
key: 'remove-donate-button',
getState: function () {
return APP.instanceStatus.removeDonateButton;
},
query: function (val, setState) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['REMOVE_DONATE_BUTTON', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
setState(APP.instanceStatus.removeDonateButton);
});
});
},
});
create['block-daily-check'] = makeAdminCheckbox({ // Messages.admin_blockDailyCheckTitle.admin_blockDailyCheckHint.admin_blockDailyCheckLabel
key: 'block-daily-check',
getState: function () {
return APP.instanceStatus.blockDailyCheck;
},
query: function (val, setState) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['BLOCK_DAILY_CHECK', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
setState(APP.instanceStatus.blockDailyCheck);
});
});
},
});
var sendDecree = function (data, cb) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: data,
}, cb);
};
create['instance-purpose'] = function () {
var key = 'instance-purpose';
var $div = makeBlock(key); // Messages.admin_instancePurposeTitle.admin_instancePurposeHint
var values = [
'noanswer', // Messages.admin_purpose_noanswer
'experiment', // Messages.admin_purpose_experiment
'personal', // Messages.admin_purpose_personal
'education', // Messages.admin_purpose_education
'org', // Messages.admin_purpose_org
'business', // Messages.admin_purpose_business
'public', // Messages.admin_purpose_public
];
var defaultPurpose = 'noanswer';
var purpose = APP.instanceStatus.instancePurpose || defaultPurpose;
var opts = h('div.cp-admin-radio-container', [
values.map(function (key) {
var full_key = 'admin_purpose_' + key;
return UI.createRadio('cp-instance-purpose-radio', 'cp-instance-purpose-radio-'+key,
Messages[full_key] || Messages._getKey(full_key, [defaultPurpose]),
key === purpose, {
input: { value: key },
label: { class: 'noTitle' }
});
})
]);
var $opts = $(opts);
//var $br = $(h('br',));
//$div.append($br);
$div.append(opts);
var setPurpose = function (value, cb) {
sendDecree([
'SET_INSTANCE_PURPOSE',
[ value]
], cb);
};
$opts.on('change', function () {
var val = $opts.find('input:radio:checked').val();
console.log(val);
//spinner.spin();
setPurpose(val, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
//spinner.hide();
return;
}
//spinner.done();
UI.log(Messages.saved);
});
});
return $div;
};
var hideCategories = function () {
APP.$rightside.find('> div').hide();
};
@ -1562,6 +1936,7 @@ define([
support: 'fa fa-life-ring',
broadcast: 'fa fa-bullhorn',
performance: 'fa fa-heartbeat',
network: 'fa fa-sitemap', // or fa-university ?
};
var createLeftside = function () {
@ -1573,6 +1948,7 @@ define([
if (active.indexOf('-') !== -1) {
active = active.split('-')[0];
}
if (!categories[active]) { active = 'general'; }
common.setHash(active);
Object.keys(categories).forEach(function (key) {
var $category = $('<div>', {'class': 'cp-sidebarlayout-category'}).appendTo($categories);

@ -250,6 +250,12 @@ define([
secret.hashData.ownerKey === "uPmJDtDJ9okhdIyQ-8zphYlpaAonJDOC6MAcYY6iBwWBQr+XmrQ9uGY9WkApJTfEfAu5QcqaDCw1Ul+JXKcYkA" &&
!secret.hashData.present);
}, "test support for owner key in version 1 file hash failed to parse");
assert(function (cb) {
var parsed = Hash.parsePadUrl('/file/#/2/file/JQU88aX+ieXR58L5T787434a/');
var secret = Hash.getSecrets('file', parsed.hash);
return cb(secret.type === 'file' && secret.password === undefined &&
secret.channel === "2031a9b51247a07ad398227367c1b95efaad969b209a279c");
}, "test support for v2 file hash");
assert(function (cb) {
var secret = Hash.parsePadUrl('/invite/#/2/invite/edit/oRE0oLCtEXusRDyin7GyLGcS/p/');

@ -17,6 +17,7 @@ define([
'/customize/application_config.js',
'/lib/calendar/tui-calendar.min.js',
'/calendar/export.js',
'/lib/datepicker/flatpickr.js',
'/common/inner/share.js',
'/common/inner/access.js',
@ -46,6 +47,7 @@ define([
AppConfig,
Calendar,
Export,
Flatpickr,
Share, Access, Properties
)
{
@ -119,7 +121,7 @@ define([
};
var getWeekDays = function (large) {
var baseDate = new Date(Date.UTC(2017, 0, 1)); // just a Sunday
var baseDate = new Date(2017, 0, 1); // just a Sunday
var weekDays = [];
for(var i = 0; i < 7; i++) {
weekDays.push(baseDate.toLocaleDateString(undefined, { weekday: 'long' }));
@ -169,9 +171,9 @@ define([
var obj = data.content[uid];
obj.title = obj.title || "";
obj.location = obj.location || "";
if (obj.isAllDay && obj.startDay) { obj.start = +new Date(obj.startDay); }
if (obj.isAllDay && obj.startDay) { obj.start = +Flatpickr.parseDate((obj.startDay)); }
if (obj.isAllDay && obj.endDay) {
var endDate = new Date(obj.endDay);
var endDate = Flatpickr.parseDate(obj.endDay);
endDate.setHours(23);
endDate.setMinutes(59);
endDate.setSeconds(59);

@ -14,13 +14,17 @@ html, body {
.report {
font-size: 30px;
max-width: 50%;
max-width: 26em;
margin: auto;
padding-top: 15px;
}
.summary, .failure, .error, .success {
margin-bottom: 1em;
}
.pending {
border: 1px solid white;
border: 1px solid @cryptpad_text_col;
.fa {
margin-right: 20px;
}
@ -42,10 +46,18 @@ html, body {
padding: 15px;
}
.table-container {
overflow-x: auto;
width: 100%;
table {
td {
padding: 5px;
border: 1px solid white;
border: 1px solid @cryptpad_text_col;
font-size: 60%;
}
td:nth-child(2) {
word-break: break-word;
}
}
}
@ -72,7 +84,12 @@ html, body {
color: @cryptpad_color_link;
}
}
.cp-app-checkup-version {
.cp-notice-browser, .cp-notice-details, .cp-notice-other {
font-size: 70%;
}
.cp-app-checkup-version, .cp-app-checkup-browser {
text-decoration: underline;
}

@ -0,0 +1,36 @@
define([
], function () {
var Tools = {};
Tools.supportsSharedArrayBuffers = function () {
try {
return Object.prototype.toString.call(new window.WebAssembly.Memory({
shared: true,
initial: 0,
maximum: 0,
}).buffer) === '[object SharedArrayBuffer]';
} catch (err) {
console.error(err);
}
return false;
};
Tools.isSafari = function () {
return navigator.vendor.match(/apple/i);
};
Tools.isChrome = function () {
return navigator.vendor.match(/google/i);
};
Tools.guessBrowser = function () {
if (Tools.isChrome()) { return 'chrome/blink'; }
if (Tools.isSafari()) { return 'safari/webkit'; }
if (navigator.userAgent.match(/firefox\//i)) { return 'firefox/gecko'; }
if (navigator.userAgent.match(/edge\//i)) { return 'edge/edgehtml'; }
if (navigator.userAgent.match(/trident\//i)) { return 'ie/trident'; }
return navigator.userAgent + "\n" + navigator.vendor;
};
return Tools;
});

@ -13,13 +13,14 @@ define([
'/common/pinpad.js',
'/common/outer/network-config.js',
'/customize/pages.js',
'/checkup/checkup-tools.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/checkup/app-checkup.less',
], function ($, ApiConfig, Assertions, h, Messages, DomReady,
nThen, SFCommonO, Login, Hash, Util, Pinpad,
NetConfig, Pages) {
NetConfig, Pages, Tools) {
var Assert = Assertions();
var trimSlashes = function (s) {
if (typeof(s) !== 'string') { return s; }
@ -30,8 +31,12 @@ define([
Assert(f, msg || h('span.advisory-text.cp-danger'));
};
var code = function (content) {
return h('code', content);
};
var CONFIG_PATH = function () {
return h('code', 'cryptpad/config/config.js');
return code('cryptpad/config/config.js');
};
var API_CONFIG_LINK = function () {
return h('a', {
@ -50,15 +55,31 @@ define([
]);
};
var link = function (href, text) {
return h('a', {
href: href,
rel: 'noopener noreferrer',
target: '_blank',
}, text);
};
var setWarningClass = function (msg) {
$(msg).removeClass('cp-danger').addClass('cp-warning');
};
var cacheBuster = function (url) {
return url + '?test=' + (+new Date());
};
var trimmedSafe = trimSlashes(ApiConfig.httpSafeOrigin);
var trimmedUnsafe = trimSlashes(ApiConfig.httpUnsafeOrigin);
assert(function (cb, msg) {
msg.appendChild(h('span', [
"CryptPad's sandbox requires that both ",
h('code', 'httpUnsafeOrigin'),
code('httpUnsafeOrigin'),
' and ',
h('code', 'httpSafeOrigin'),
code('httpSafeOrigin'),
" be configured in ",
CONFIG_PATH(),
'. ',
@ -71,9 +92,9 @@ define([
assert(function (cb, msg) {
msg.appendChild(h('span', [
h('code', 'httpUnsafeOrigin'),
code('httpUnsafeOrigin'),
' and ',
h('code', 'httpSafeOrigin'),
code('httpSafeOrigin'),
' are equivalent. ',
"In order for CryptPad's security features to be as effective as intended they must be different. ",
"See ",
@ -87,9 +108,9 @@ define([
assert(function (cb, msg) {
msg.appendChild(h('span', [
h('code', 'httpUnsafeOrigin'),
code('httpUnsafeOrigin'),
' and ',
h('code', 'httpSafeOrigin'),
code('httpSafeOrigin'),
' must not contain trailing slashes. This can be configured in ',
CONFIG_PATH(),
'. ',
@ -101,10 +122,10 @@ define([
assert(function (cb, msg) {
msg.appendChild(h("span", [
"It appears that you are trying to load this page via an origin other than its main domain (",
h('code', ApiConfig.httpUnsafeOrigin),
code(ApiConfig.httpUnsafeOrigin),
"). See the ",
h('code', 'httpUnsafeOrigin'),
code('httpUnsafeOrigin'),
" option in ",
CONFIG_PATH(),
" which is exposed via ",
@ -117,7 +138,7 @@ define([
var checkAvailability = function (url, cb) {
$.ajax({
url: url,
url: cacheBuster(url),
data: {},
complete: function (xhr) {
cb(xhr.status === 200);
@ -128,7 +149,7 @@ define([
assert(function (cb, msg) {
msg.appendChild(h('span', [
"The main domain (configured via ",
h('code', 'httpUnsafeOrigin'),
code('httpUnsafeOrigin'),
' as ',
ApiConfig.httpUnsafeOrigin,
' in ',
@ -145,13 +166,13 @@ define([
assert(function (cb, msg) {
msg.appendChild(h('span', [
"Your browser was not able to load an iframe using the origin specified as ",
h('code', "httpSafeOrigin"),
code("httpSafeOrigin"),
" (",
ApiConfig.httpSafeOrigin,
") in ",
CONFIG_PATH(),
". This can be caused by an invalid ",
h('code', 'httpUnsafeDomain'),
code('httpUnsafeDomain'),
', invalid CSP configuration in your reverse proxy, invalid SSL certificates, and many other factors. ',
'More information about your particular error may be found in your browser console. ',
RESTART_WARNING(),
@ -169,10 +190,13 @@ define([
}).nThen(function () {
// Iframe is loaded
clearTimeout(to);
console.log("removing sandbox iframe");
$('iframe#sbox-iframe').remove();
cb(true);
});
});
var shared_websocket;
// Test Websocket
var evWSError = Util.mkEvent(true);
assert(function (_cb, msg) {
@ -185,6 +209,7 @@ define([
}));
var ws = new WebSocket(NetConfig.getWebsocketURL());
shared_websocket = ws;
var to = setTimeout(function () {
console.error('Websocket TIMEOUT');
evWSError.fire();
@ -203,6 +228,7 @@ define([
});
// Test login block
var shared_realtime;
assert(function (_cb, msg) {
var websocketErr = "No WebSocket available";
var cb = Util.once(Util.both(_cb, function (status) {
@ -221,7 +247,7 @@ define([
msg.appendChild(h('span', [
"Unable to create, retrieve, or remove encrypted credentials from the server. ",
"This is most commonly caused by a mismatch between the value of the ",
h('code', 'blockPath'),
code('blockPath'),
' value configured in ',
CONFIG_PATH(),
" and the corresponding settings in your reverse proxy's configuration file,",
@ -230,6 +256,11 @@ define([
]));
}));
// time out after 30 seconds
setTimeout(function () {
cb('TIMEOUT');
}, 30000);
var bytes = new Uint8Array(Login.requiredBytes);
var opt = Login.allocateBytes(bytes);
@ -237,7 +268,7 @@ define([
var blockUrl = Login.Block.getBlockUrl(opt.blockKeys);
var blockRequest = Login.Block.serialize("{}", opt.blockKeys);
var removeRequest = Login.Block.remove(opt.blockKeys);
console.log('Test block URL:', blockUrl);
console.warn('Testing block URL (%s). One 404 is normal.', blockUrl);
var userHash = '/2/drive/edit/000000000000000000000000';
var secret = Hash.getSecrets('drive', userHash);
@ -264,7 +295,7 @@ define([
console.error("Can't create new channel. This may also be a websocket issue.");
return void cb(false);
}
RT = rt;
shared_realtime = RT = rt;
var proxy = rt.proxy;
proxy.edPublic = opt.edPublic;
proxy.edPrivate = opt.edPrivate;
@ -330,14 +361,13 @@ define([
}).nThen(function () {
cb(true);
});
});
var sheetURL = '/common/onlyoffice/v4/web-apps/apps/spreadsheeteditor/main/index.html';
assert(function (cb, msg) {
msg.innerText = "Missing HTTP headers required for .xlsx export from sheets. ";
var url = sheetURL;
var url = cacheBuster(sheetURL);
var expect = {
'cross-origin-resource-policy': 'cross-origin',
'cross-origin-embedder-policy': 'require-corp',
@ -351,11 +381,11 @@ define([
if (response !== expect[k]) {
msg.appendChild(h('span', [
'A value of ',
h('code', expect[k]),
code(expect[k]),
' was expected for the ',
h('code', k),
code(k),
' HTTP header, but instead a value of "',
h('code', response),
code(response),
'" was received.',
]));
return true; // returning true indicates that a value is incorrect
@ -366,67 +396,55 @@ define([
});
assert(function (cb, msg) {
msg.innerText = "Missing HTTP header required to disable Google's Floc.";
$.ajax('/?'+ (+new Date()), {
complete: function (xhr) {
cb(xhr.getResponseHeader('permissions-policy') === 'interest-cohort=()');
},
});
});
setWarningClass(msg);
assert(function (cb, msg) {
msg = msg;
return void cb(true);
/*
var printMessage = function (value) {
msg.appendChild(h('span', [
"The spreadsheet editor's code was not served with the required Content-Security Policy headers. ",
"This is most often caused by incorrectly configured sandbox parameters (",
h('code', 'httpUnsafeOrigin'),
' and ',
h('code', 'httpSafeOrigin'),
' in ',
CONFIG_PATH,
"), or settings in your reverse proxy's configuration which don't match your application server's config. ",
RESTART_WARNING(),
"This instance hasn't opted out of participation in Google's ",
code('FLoC'),
" targeted advertizing network. ",
"This can be done by setting a ",
code('permissions-policy'),
" HTTP header with a value of ",
code('"interest-cohort=()"'),
" in the configuration of its reverse proxy instead of the current value (",
code(value),
"). See the provided NGINX configuration file for an example. ",
h('p', [
link("https://www.eff.org/deeplinks/2021/04/am-i-floced-launch", 'Learn more'),
]),
]));
};
$.ajax(sheetURL, {
$.ajax('/?'+ (+new Date()), {
complete: function (xhr) {
var csp = xhr.getResponseHeader('Content-Security-Policy');
if (!/unsafe\-eval/.test(csp)) {
// OnlyOffice requires unsafe-eval
console.error('CSP', csp);
return cb("expected 'unsafe-eval'");
}
if (!/unsafe\-inline/.test(csp)) {
// OnlyOffice also requires unsafe-inline
console.error('CSP', csp);
return cb("expected 'unsafe-inline'");
}
cb(true);
var header = xhr.getResponseHeader('permissions-policy');
printMessage(JSON.stringify(header));
cb(header === 'interest-cohort=()' || header);
},
}); */
});
});
assert(function (cb, msg) {
msg.appendChild(h('span', [
h('code', '/api/broadcast'),
code('/api/broadcast'),
" could not be loaded. This can be caused by an outdated application server or an incorrectly configured reverse proxy. ",
"Even if the most recent code has been downloaded it's possible the application server has not been restarted. ",
"Your browser console may provide more details as to why this resource could not be loaded. ",
]));
$.ajax('/api/broadcast', {
$.ajax(cacheBuster('/api/broadcast'), {
dataType: 'text',
complete: function (xhr) {
console.log(xhr);
cb(xhr.status === 200);
},
});
});
var checkAPIHeaders = function (url, cb) {
$.ajax(url, {
var checkAPIHeaders = function (url, msg, cb) {
$.ajax(cacheBuster(url), {
dataType: 'text',
complete: function (xhr) {
var allHeaders = xhr.getAllResponseHeaders();
@ -445,15 +463,31 @@ define([
var expect = {
'cross-origin-resource-policy': 'cross-origin',
'cross-origin-embedder-policy': 'require-corp',
};
var incorrect = Object.keys(expect).some(function (k) {
var incorrect = false;
Object.keys(expect).forEach(function (k) {
var response = xhr.getResponseHeader(k);
if (response !== expect[k]) {
return true;
var expected = expect[k];
if (response !== expected) {
incorrect = true;
msg.appendChild(h('p', [
'The ',
code(k),
' header for ',
code(url),
" is '",
code(response),
"' instead of '",
code(expected),
"' as expected.",
]));
}
});
if (duplicated || incorrect) { console.error(allHeaders); }
if (duplicated || incorrect) { console.debug(allHeaders); }
cb(!duplicated && !incorrect);
},
});
@ -464,19 +498,15 @@ define([
assert(function (cb, msg) {
var url = '/api/config';
msg.innerText = url + INCORRECT_HEADER_TEXT;
checkAPIHeaders(url, cb);
checkAPIHeaders(url, msg, cb);
});
assert(function (cb, msg) {
var url = '/api/broadcast';
msg.innerText = url + INCORRECT_HEADER_TEXT;
checkAPIHeaders(url, cb);
checkAPIHeaders(url, msg, cb);
});
var setWarningClass = function (msg) {
$(msg).removeClass('cp-danger').addClass('cp-warning');
};
assert(function (cb, msg) {
var email = ApiConfig.adminEmail;
if (typeof(email) === 'string' && email && email !== 'i.did.not.read.my.config@cryptpad.fr') {
@ -486,10 +516,11 @@ define([
setWarningClass(msg);
msg.appendChild(h('span', [
'This instance does not provide a valid ',
h('code', 'adminEmail'),
code('adminEmail'),
' which can make it difficult to contact its adminstrator to report vulnerabilities or abusive content.',
' This can be configured in ', CONFIG_PATH(), '. ',
RESTART_WARNING(),
" This can be configured on your instance's admin panel. Use the provided ",
code("Flush cache'"),
" button for this change to take effect for all users.",
]));
cb(email);
});
@ -499,12 +530,9 @@ define([
setWarningClass(msg);
msg.appendChild(h('span', [
"This instance's encrypted support ticket functionality has not been enabled. This can make it difficult for its users to safely report issues that concern sensitive information. ",
"This can be configured via the ",
h('code', 'supportMailbox'),
" attribute in ",
CONFIG_PATH(),
". ",
RESTART_WARNING(),
"This can be configured via the admin panel's ",
code('Support'),
" tab.",
]));
cb(support && typeof(support) === 'string' && support.length === 44);
});
@ -517,7 +545,7 @@ define([
setWarningClass(msg);
msg.appendChild(h('span', [
"This instance has not been configured to support web administration. This can be enabled by adding a registered user's public signing key to the ",
h('code', 'adminKeys'),
code('adminKeys'),
' array in ',
CONFIG_PATH(),
'. ',
@ -526,6 +554,331 @@ define([
cb(false);
});
var response = Util.response(function (err) {
console.error('SANDBOX_ERROR', err);
});
var sandboxIframe = h('iframe', {
class: 'sandbox-test',
src: cacheBuster(trimmedSafe + '/checkup/sandbox/index.html'),
});
document.body.appendChild(sandboxIframe);
var sandboxIframeReady = Util.mkEvent(true);
setTimeout(function () {
sandboxIframeReady.fire("TIMEOUT");
}, 10 * 1000);
var postMessage = function (content, cb) {
try {
var txid = Util.uid();
content.txid = txid;
response.expect(txid, cb, 15000);
sandboxIframe.contentWindow.postMessage(JSON.stringify(content), '*');
} catch (err) {
console.error(err);
}
};
var deferredPostMessage = function (content, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
nThen(function (w) {
sandboxIframeReady.reg(w(function (err) {
if (!err) { return; }
w.abort();
cb(err);
}));
}).nThen(function () {
postMessage(content, cb);
});
};
window.addEventListener('message', function (event) {
try {
var msg = JSON.parse(event.data);
if (msg.command === 'READY') { return void sandboxIframeReady.fire(); }
if (msg.q === "READY") { return; } // ignore messages from the usual sandboxed iframe
var txid = msg.txid;
if (!txid) { return console.log("no handler for ", txid); }
response.handle(txid, msg.content);
} catch (err) {
console.error(event);
console.error(err);
}
});
var parseCSP = function (CSP) {
//console.error(CSP);
var CSP_headers = {};
CSP.split(";")
.forEach(function (rule) {
rule = (rule || "").trim();
if (!rule) { return; }
var parts = rule.split(/\s/);
var first = parts[0];
var rest = rule.slice(first.length + 1);
CSP_headers[first] = rest;
//console.error(rule.trim());
//console.info("[%s] '%s'", first, rest);
});
return CSP_headers;
};
var hasUnsafeEval = function (CSP_headers) {
return /unsafe\-eval/.test(CSP_headers['script-src']);
};
var hasUnsafeInline = function (CSP_headers) {
return /unsafe\-inline/.test(CSP_headers['script-src']);
};
var hasOnlyOfficeHeaders = function (CSP_headers) {
if (!hasUnsafeEval(CSP_headers)) {
console.error("NO_UNSAFE_EVAL");
console.log(CSP_headers);
return false;
}
if (!hasUnsafeInline(CSP_headers)) {
console.error("NO_UNSAFE_INLINE");
return void false;
}
return true;
};
var CSP_WARNING = function (url) {
return h('span', [
code(url),
' does not have the required ',
code("'content-security-policy'"),
' headers set. This is most often related to incorrectly configured sandbox domains or reverse proxies.',
]);
};
assert(function (_cb, msg) {
var url = '/sheet/inner.html';
var cb = Util.once(Util.mkAsync(_cb));
msg.appendChild(CSP_WARNING(url));
deferredPostMessage({
command: 'GET_HEADER',
content: {
url: url,
header: 'content-security-policy',
},
}, function (content) {
var CSP_headers = parseCSP(content);
cb(hasOnlyOfficeHeaders(CSP_headers));
});
});
assert(function (cb, msg) {
var url = '/common/onlyoffice/v4/web-apps/apps/spreadsheeteditor/main/index.html';
msg.appendChild(CSP_WARNING(url));
deferredPostMessage({
command: 'GET_HEADER',
content: {
url: url,
header: 'content-security-policy',
},
}, function (content) {
var CSP_headers = parseCSP(content);
cb(hasOnlyOfficeHeaders(CSP_headers));
});
});
assert(function (cb, msg) {
var url = '/sheet/inner.html';
msg.appendChild(h('span', [
code(url),
' does not have the required ',
code("'cross-origin-opener-policy'"),
' headers set.',
]));
deferredPostMessage({
command: 'GET_HEADER',
content: {
url: url,
header: 'cross-origin-opener-policy',
},
}, function (content) {
cb(content === 'same-origin');
});
});
var safariGripe = function () {
return h('p.cp-notice-other', 'This is expected because Safari and platforms that use its engine lack commonly supported functionality.');
};
var browserIssue = function () {
return h('p.cp-notice-other', 'This test checks for the presence of features in your browser and is not necessarily caused by server misconfiguration.');
};
assert(function (cb, msg) {
cb = Util.once(cb);
setWarningClass(msg);
var notice = h('span', [
h('p', 'It appears that some features required for Office file format conversion are not present.'),
Tools.isSafari()? safariGripe(): undefined,
browserIssue(),
]);
msg.appendChild(notice);
var expected = [
'Atomics',
'SharedArrayBuffer',
'WebAssembly',
['WebAssembly', 'Memory'],
['WebAssembly', 'instantiate'],
['WebAssembly', 'instantiateStreaming'],
['Buffer', 'from'],
'SharedWorker',
'worker',
'crossOriginIsolated',
];
var responses = {};
nThen(function (w) {
deferredPostMessage({
command: 'CHECK_JS_APIS',
content: {
globals: expected,
},
}, w(function (response) {
Util.extend(responses, response);
}));
deferredPostMessage({
command: 'FANCY_API_CHECKS',
content: {
},
}, w(function (response) {
Util.extend(responses, response);
}));
}).nThen(function () {
if (!responses.Atomics || !responses.WebAssembly) {
return void cb(responses);
}
if (responses.SharedArrayBuffer || responses.SharedArrayBufferFallback) {
return cb(true);
}
return void cb(response);
});
});
var isHTTPS = function (host) {
return /^https:\/\//.test(host);
};
var isOnion = function (host) {
return /\.onion$/.test(host);
};
var isLocalhost = function (host) {
return /^http:\/\/localhost/.test(host);
};
assert(function (cb, msg) {
// provide an exception for development instances
if (isLocalhost(trimmedUnsafe) && isLocalhost(window.location.href)) { return void cb(true); }
// if both the main and sandbox domains are onion addresses
// then the HTTPS requirement is unnecessary
if (isOnion(trimmedUnsafe) && isOnion(trimmedSafe)) { return void cb(true); }
// otherwise expect that both inner and outer domains use HTTPS
msg.appendChild(h('span', [
"Both ",
code('httpUnsafeOrigin'),
' and ',
code('httpSafeOrigin'),
' should be accessed via HTTPS for production use. ',
"This can be configured via ",
CONFIG_PATH(),
'. ',
RESTART_WARNING(),
]));
cb(isHTTPS(trimmedUnsafe) && isHTTPS(trimmedSafe));
});
[
'sheet',
'presentation',
'doc',
'convert',
].forEach(function (url) {
assert(function (cb, msg) {
var header = 'cross-origin-opener-policy';
var expected = 'same-origin';
deferredPostMessage({
command: 'GET_HEADER',
content: {
url: '/' + url + '/',
header: header,
}
}, function (content) {
msg.appendChild(h('span', [
code(url),
' was served without the correct ',
code(header),
' HTTP header value (',
code(expected),
'). This will interfere with your ability to convert between office file formats.'
]));
cb(content === expected);
});
});
});
/*
assert(function (cb, msg) {
setWarningClass(msg);
$.ajax(cacheBuster('/'), {
dataType: 'text',
complete: function (xhr) {
var serverToken = xhr.getResponseHeader('server');
if (serverToken === null) { return void cb(true); }
var lowered = (serverToken || '').toLowerCase();
var family;
['Apache', 'Caddy', 'NGINX'].some(function (pattern) {
if (lowered.indexOf(pattern.toLowerCase()) !== -1) {
family = pattern;
return true;
}
});
var text = [
"This instance is set to respond with an HTTP ",
code("server"),
" header. This information can make it easier for attackers to find and exploit known vulnerabilities. ",
];
if (family === 'NGINX') { // FIXME incorrect instructions for HTTP2. needs a recompile?
msg.appendChild(h('span', text.concat([
"This can be addressed by setting ",
code("server_tokens off"),
" in your global NGINX config."
])));
return void cb(serverToken);
}
// handle other
msg.appendChild(h('span', text.concat([
"In this case, it appears that the host server is running ",
code(serverToken),
" instead of ",
code("NGINX"),
" as recommended. As such, you may not benefit from the latest security enhancements that are tested and maintained by the CryptPad development team.",
])));
cb(serverToken);
}
});
});
*/
if (false) {
assert(function (cb, msg) {
msg.innerText = 'fake test to simulate failure';
@ -540,12 +893,21 @@ define([
};
var failureReport = function (obj) {
var printableValue = obj.output;
try {
printableValue = JSON.stringify(obj.output, null, ' ');
} catch (err) {
console.error(err);
}
return h('div.error', [
h('h5', obj.message),
h('div.table-container',
h('table', [
row(["Failed test number", obj.test + 1]),
row(["Returned value", obj.output]),
]),
row(["Returned value", h('pre', code(printableValue))]),
])
),
]);
};
@ -553,7 +915,7 @@ define([
var $progress = $('#cp-progress');
var versionStatement = function () {
return h('p', [
return h('p.cp--notice-version', [
"This instance is running ",
h('span.cp-app-checkup-version',[
"CryptPad",
@ -564,6 +926,16 @@ define([
]);
};
var browserStatement = function () {
var name = Tools.guessBrowser();
if (!name) { return; }
return h('p.cp-notice-browser', [
"You appear to be using a ",
h('span.cp-app-checkup-browser', name),
' browser to view this page.',
]);
};
Assert.run(function (state) {
var errors = state.errors;
var failed = errors.length;
@ -574,10 +946,11 @@ define([
var failedDetails = "Details found below";
var successDetails = "This checkup only tests the most common configuration issues. You may still experience errors or incorrect behaviour.";
var details = h('p', failed? failedDetails: successDetails);
var details = h('p.cp-notice-details', failed? failedDetails: successDetails);
var summary = h('div.summary.' + statusClass, [
versionStatement(),
browserStatement(),
h('p', Messages._getKey('assert_numberOfTestsPassed', [
state.passed,
state.total
@ -592,6 +965,14 @@ define([
$progress.remove();
$('body').prepend(report);
try {
console.log('closing shared websocket');
shared_websocket.close();
} catch (err) { console.error(err); }
try {
console.log('closing shared realtime');
shared_realtime.network.disconnect();
} catch (err) { console.error(err); }
}, function (i, total) {
console.log('test '+ i +' completed');
completed++;

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
</head>
<body>
<div id="cp-progress"></div>
<iframe-placeholder>

@ -0,0 +1,78 @@
define([
'jquery',
'/common/common-util.js',
'/checkup/checkup-tools.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/checkup/app-checkup.less',
], function ($, Util, Tools) {
var postMessage = function (content) {
window.parent.postMessage(JSON.stringify(content), '*');
};
postMessage({ command: "READY", });
var getHeaders = function (url, cb) {
$.ajax(url + "?test=" + (+new Date()), {
dataType: 'text',
complete: function (xhr) {
var allHeaders = xhr.getAllResponseHeaders();
return void cb(void 0, allHeaders, xhr);
},
});
};
var COMMANDS = {};
COMMANDS.GET_HEADER = function (content, cb) {
var url = content.url;
getHeaders(url, function (err, headers, xhr) {
cb(xhr.getResponseHeader(content.header));
});
};
COMMANDS.CHECK_JS_APIS = function (content, cb) {
var globalAPIs = content['globals'] || [];
var response = {};
globalAPIs.forEach(function (key) {
if (Array.isArray(key)) {
response[key.join('.')] = Boolean(Util.find(window, key));
return;
}
response[key] = Boolean(window[key]);
});
cb(response);
};
COMMANDS.FANCY_API_CHECKS = function (content, cb) {
cb({
SharedArrayBufferFallback: Tools.supportsSharedArrayBuffers(),
});
};
window.addEventListener("message", function (event) {
var txid, command;
if (event && event.data) {
try {
//console.log(JSON.parse(event.data));
var msg = JSON.parse(event.data);
command = msg.command;
txid = msg.txid;
if (!txid) { return; }
COMMANDS[command](msg.content, function (response) {
// postMessage with same txid
postMessage({
txid: txid,
content: response,
});
});
} catch (err) {
postMessage({
txid: txid,
content: err,
});
console.error(err, command);
}
} else {
console.error(event);
}
});
});

@ -257,8 +257,8 @@ define([
'class': 'cp-splitter'
}).appendTo($previewContainer);
$preview.on('scroll', function() {
splitter.css('top', $preview.scrollTop() + 'px');
$previewContainer.on('scroll', function() {
splitter.css('top', $previewContainer.scrollTop() + 'px');
});
var $target = $codeMirrorContainer;
@ -368,7 +368,7 @@ define([
var mkFilePicker = function (framework, editor, evModeChange) {
evModeChange.reg(function (mode) {
if (MEDIA_TAG_MODES.indexOf(mode) !== -1) {
// Embedding is endabled
// Embedding is enabled
framework.setMediaTagEmbedder(function (mt) {
editor.focus();
editor.replaceSelection($(mt)[0].outerHTML);
@ -507,7 +507,7 @@ define([
var fileHost = privateData.fileHost || privateData.origin;
var src = fileHost + Hash.getBlobPathFromHex(secret.channel);
var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
var mt = UI.mediaTag(src, key).outerHTML;
editor.replaceSelection(mt);
}
};

@ -4,15 +4,15 @@
* file (make a copy from /customize.dist/application_config.js)
*/
define(function() {
var config = {};
var AppConfig = {};
/* Select the buttons displayed on the main page to create new collaborative sessions.
* Removing apps from the list will prevent users from accessing them. They will instead be
* redirected to the drive.
* You should never remove the drive from this list.
*/
config.availablePadTypes = ['drive', 'teams', 'pad', 'sheet', 'code', 'slide', 'poll', 'kanban', 'whiteboard',
/*'doc', 'presentation',*/ 'file', /*'todo',*/ 'contacts' /*, 'calendar' */];
AppConfig.availablePadTypes = ['drive', 'teams', 'pad', 'sheet', 'code', 'slide', 'poll', 'kanban', 'whiteboard',
/*'doc', 'presentation',*/ 'file', /*'todo',*/ 'contacts', 'form', 'convert'];
/* The registered only types are apps restricted to registered users.
* You should never remove apps from this list unless you know what you're doing. The apps
* listed here by default can't work without a user account.
@ -20,7 +20,13 @@ define(function() {
* users and these users will be redirected to the login page if they still try to access
* the app
*/
config.registeredOnlyTypes = ['file', 'contacts', 'notifications', 'support'];
AppConfig.registeredOnlyTypes = ['file', 'contacts', 'notifications', 'support'];
// to prevent apps that aren't officially supported from showing up
// in the document creation modal
AppConfig.hiddenTypes = ['drive', 'teams', 'contacts', 'todo', 'file', 'accounts', 'calendar', 'poll', 'convert',
//'doc', 'presentation'
];
/* CryptPad is available is multiple languages, but only English and French are maintained
* by the developers. The other languages may be outdated, and any missing string for a langauge
@ -30,37 +36,37 @@ define(function() {
* can be found at the top of the file `/customize.dist/messages.js`. The list should only
* contain languages code ('en', 'fr', 'de', 'pt-br', etc.), not their full name.
*/
//config.availableLanguages = ['en', 'fr', 'de'];
//AppConfig.availableLanguages = ['en', 'fr', 'de'];
/* You can display a link to the imprint (legal notice) of your website in the static pages
* footer. To do so, you can either set the following value to `true` and create an imprint.html page
* in the `customize` directory. You can also set it to an absolute URL if your imprint page already exists.
*/
config.imprint = false;
// config.imprint = true;
// config.imprint = 'https://xwiki.com/en/company/legal-notice';
AppConfig.imprint = false;
// AppConfig.imprint = true;
// AppConfig.imprint = 'https://xwiki.com/en/company/legal-notice';
/* You can display a link to your own privacy policy in the static pages footer.
* To do so, set the following value to the absolute URL of your privacy policy.
*/
// config.privacy = 'https://xwiki.com/en/company/PrivacyPolicy';
// AppConfig.privacy = 'https://xwiki.com/en/company/PrivacyPolicy';
/* We (the project's developers) include the ability to display a 'Roadmap' in static pages footer.
* This is disabled by default.
* We use this to publish the project's development roadmap, but you can use it however you like.
* To do so, set the following value to an absolute URL.
*/
//config.roadmap = 'https://cryptpad.fr/kanban/#/2/kanban/view/PLM0C3tFWvYhd+EPzXrbT+NxB76Z5DtZhAA5W5hG9wo/';
//AppConfig.roadmap = 'https://cryptpad.fr/kanban/#/2/kanban/view/PLM0C3tFWvYhd+EPzXrbT+NxB76Z5DtZhAA5W5hG9wo/';
/* Cryptpad apps use a common API to display notifications to users
* by default, notifications are hidden after 5 seconds
* You can change their duration here (measured in milliseconds)
*/
config.notificationTimeout = 5000;
config.disableUserlistNotifications = false;
AppConfig.notificationTimeout = 5000;
AppConfig.disableUserlistNotifications = false;
// Update the default colors available in the whiteboard application
config.whiteboardPalette = [
AppConfig.whiteboardPalette = [
'#000000', // black
'#FFFFFF', // white
'#848484', // grey
@ -82,14 +88,14 @@ define(function() {
// Background color in the apps with centered content:
// - file app in view mode
// - rich text app when editor's width reduced in settings
config.appBackgroundColor = '#666';
AppConfig.appBackgroundColor = '#666';
// Set enableTemplates to false to remove the button allowing users to save a pad as a template
// and remove the template category in CryptDrive
config.enableTemplates = true;
AppConfig.enableTemplates = true;
// Set enableHistory to false to remove the "History" button in all the apps.
config.enableHistory = true;
AppConfig.enableHistory = true;
/* user passwords are hashed with scrypt, and salted with their username.
this value will be appended to the username, causing the resulting hash
@ -101,22 +107,24 @@ define(function() {
created. Changing it at a later time will break logins for all existing
users.
*/
config.loginSalt = '';
config.minimumPasswordLength = 8;
AppConfig.loginSalt = '';
AppConfig.minimumPasswordLength = 8;
// Amount of time (ms) before aborting the session when the algorithm cannot synchronize the pad
config.badStateTimeout = 30000;
AppConfig.badStateTimeout = 30000;
// Customize the icon used for each application.
// You can update the colors by making a copy of /customize.dist/src/less2/include/colortheme.less
config.applicationsIcon = {
AppConfig.applicationsIcon = {
file: 'cptools-file',
fileupload: 'cptools-file-upload',
folderupload: 'cptools-folder-upload',
link: 'fa-link',
pad: 'cptools-richtext',
code: 'cptools-code',
slide: 'cptools-slide',
poll: 'cptools-poll',
form: 'cptools-poll',
whiteboard: 'cptools-whiteboard',
todo: 'cptools-todo',
contacts: 'fa-address-book',
@ -130,50 +138,49 @@ define(function() {
// Ability to create owned pads and expiring pads through a new pad creation screen.
// The new screen can be disabled by the users in their settings page
config.displayCreationScreen = true;
AppConfig.displayCreationScreen = true;
// Prevent anonymous users from storing pads in their drive
config.disableAnonymousStore = false;
// NOTE: this is only enforced client-side as the server does not distinguish between users drives and pads
AppConfig.disableAnonymousStore = false;
// Prevent anonymous users from creating new pads (they can still access and edit existing ones)
// NOTE: this is only enforced client-side and will not prevent malicious clients from storing data
AppConfig.disableAnonymousPadCreation = false;
// Hide the usage bar in settings and drive
//config.hideUsageBar = true;
//AppConfig.hideUsageBar = true;
// Disable feedback for all the users and hide the settings part about feedback
//config.disableFeedback = true;
// Add new options in the share modal (extend an existing tab or add a new tab).
// More info about how to use it on the wiki:
// https://github.com/xwiki-labs/cryptpad/wiki/Application-config#configcustomizeshareoptions
//config.customizeShareOptions = function (hashes, tabs, config) {};
//AppConfig.disableFeedback = true;
// Add code to be executed on every page before loading the user object. `isLoggedIn` (bool) is
// indicating if the user is registered or anonymous. Here you can change the way anonymous users
// work in CryptPad, use an external SSO or even force registration
// *NOTE*: You have to call the `callback` function to continue the loading process
//config.beforeLogin = function(isLoggedIn, callback) {};
//AppConfig.beforeLogin = function(isLoggedIn, callback) {};
// Add code to be executed on every page after the user object is loaded (also work for
// unregistered users). This allows you to interact with your users' drive
// *NOTE*: You have to call the `callback` function to continue the loading process
//config.afterLogin = function(api, callback) {};
//AppConfig.afterLogin = function(api, callback) {};
// Disabling the profile app allows you to import the profile informations (display name, avatar)
// from an external source and make sure the users can't change them from CryptPad.
// You can use config.afterLogin to import these values in the users' drive.
//config.disableProfile = true;
// You can use AppConfig.afterLogin to import these values in the users' drive.
//AppConfig.disableProfile = true;
// Disable the use of webworkers and sharedworkers in CryptPad.
// Workers allow us to run the websockets connection and open the user drive in a separate thread.
// SharedWorkers allow us to load only one websocket and one user drive for all the browser tabs,
// making it much faster to open new tabs.
config.disableWorkers = false;
AppConfig.disableWorkers = false;
// Teams are always loaded during the initial loading screen (for the first tab only if
// SharedWorkers are available). Allowing users to be members of multiple teams can
// make them have a very slow loading time. To avoid impacting the user experience
// significantly, we're limiting the number of teams per user to 3 by default.
// You can change this value here.
//config.maxTeamsSlots = 5;
//AppConfig.maxTeamsSlots = 5;
// Each team is considered as a registered user by the server. Users and teams are indistinguishable
// in the database so teams will offer the same storage limits as users by default.
@ -181,7 +188,7 @@ define(function() {
// We're limiting the number of teams each user is able to own to 1 in order to make sure
// users don't use "fake" teams (1 member) just to increase their storage limit.
// You can change the value here.
// config.maxOwnedTeams = 5;
// AppConfig.maxOwnedTeams = 5;
// The userlist displayed in collaborative documents is stored alongside the document data.
// Everytime someone with edit rights joins a document or modify their user data (display
@ -192,14 +199,14 @@ define(function() {
// position of other users' cursor. You can configure the number of user from which the session
// will enter into degraded mode. A big number may result in collaborative edition being broken,
// but this number depends on the network and CPU performances of each user's device.
config.degradedLimit = 8;
AppConfig.degradedLimit = 8;
// In "legacy" mode, one-time users were always creating an "anonymous" drive when visiting CryptPad
// in which they could store their pads. The new "driveless" mode allow users to open an existing
// pad without creating a drive in the background. The drive will only be created if they visit
// a different page (Drive, Settings, etc.) or try to create a new pad themselves. You can disable
// the driveless mode by changing the following value to "false"
config.allowDrivelessMode = true;
AppConfig.allowDrivelessMode = true;
return config;
return AppConfig;
});

@ -1,13 +1,15 @@
define(['jquery'], function ($) {
var Clipboard = {};
// copy arbitrary text to the clipboard
// return boolean indicating success
Clipboard.copy = function (text) {
var copy = function (text, multiline) {
var $ta = $('<input>', {
type: 'text',
}).val(text);
if (multiline) {
$ta = $('<textarea>').val(text);
}
$('body').append($ta);
if (!($ta.length && $ta[0].select)) {
@ -29,5 +31,15 @@ define(['jquery'], function ($) {
return success;
};
// copy arbitrary text to the clipboard
// return boolean indicating success
Clipboard.copy = function (text) {
return copy(text);
};
Clipboard.copy.multiline = function (text) {
return copy(text, true);
};
return Clipboard;
});

@ -10,6 +10,7 @@ define(['/customize/application_config.js'], function (AppConfig) {
oldStorageKey: 'CryptPad_RECENTPADS',
storageKey: 'filesData',
tokenKey: 'loginToken',
prefersDriveRedirectKey: 'prefersDriveRedirect',
displayPadCreationScreen: 'displayPadCreationScreen',
deprecatedKey: 'deprecated',
MAX_TEAMS_SLOTS: AppConfig.maxTeamsSlots || 5,

@ -34,6 +34,12 @@ var factory = function (Util, Crypto, Keys, Nacl) {
var keyPair = Nacl.sign.keyPair.fromSecretKey(privateKey);
return Nacl.util.encodeBase64(keyPair.publicKey);
};
Hash.getCurvePublicFromPrivate = function (curvePrivateSafeStr) {
var curvePrivateStr = Crypto.b64AddSlashes(curvePrivateSafeStr);
var privateKey = Nacl.util.decodeBase64(curvePrivateStr);
var keyPair = Nacl.box.keyPair.fromSecretKey(privateKey);
return Nacl.util.encodeBase64(keyPair.publicKey);
};
var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) {
var version = secret.version;
@ -209,6 +215,17 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app)
});
return k ? Crypto.b64AddSlashes(k) : '';
};
var getAuditorKey = function (hashArr) {
var k;
// Check if we have a ownerKey for this pad
hashArr.some(function (data) {
if (/^auditor=/.test(data)) {
k = data.slice(8);
return true;
}
});
return k ? Crypto.b64AddSlashes(k) : '';
};
var getOwnerKey = function (hashArr) {
var k;
// Check if we have a ownerKey for this pad
@ -231,6 +248,7 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app)
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
parsed.versionHash = getVersionHash(options);
parsed.auditorKey = getAuditorKey(options);
parsed.newPadOpts = getNewPadOpts(options);
parsed.loginOpts = getLoginOpts(options);
parsed.ownerKey = getOwnerKey(options);
@ -272,6 +290,7 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app)
present: parsed.present,
ownerKey: parsed.ownerKey,
versionHash: parsed.versionHash,
auditorKey: parsed.auditorKey,
newPadOpts: parsed.newPadOpts,
loginOpts: parsed.loginOpts,
password: parsed.password
@ -298,6 +317,10 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app)
if (versionHash) {
hash += 'hash=' + Crypto.b64RemoveSlashes(versionHash) + '/';
}
var auditorKey = typeof(opts.auditorKey) !== "undefined" ? opts.auditorKey : parsed.auditorKey;
if (auditorKey) {
hash += 'auditor=' + Crypto.b64RemoveSlashes(auditorKey) + '/';
}
if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; }
if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; }
return hash;
@ -621,6 +644,27 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app)
return hashes;
};
Hash.getFormData = function (secret, hash, password) {
secret = secret || Hash.getSecrets('form', hash, password);
var keys = secret && secret.keys;
var secondary = keys && keys.secondaryKey;
if (!secondary) { return; }
var curvePair = Nacl.box.keyPair.fromSecretKey(Nacl.util.decodeUTF8(secondary).slice(0,32));
var ret = {};
ret.form_public = Nacl.util.encodeBase64(curvePair.publicKey);
var privateKey = ret.form_private = Nacl.util.encodeBase64(curvePair.secretKey);
var auditorHash = Hash.getViewHashFromKeys({
version: 1,
channel: secret.channel,
keys: { viewKeyStr: Nacl.util.encodeBase64(keys.cryptKey) }
});
var _parsed = Hash.parseTypeHash('pad', auditorHash);
ret.form_auditorHash = _parsed.getHash({auditorKey: privateKey});
return ret;
};
// STORAGE
Hash.hrefToHexChannelId = function (href, password) {
var parsed = Hash.parsePadUrl(href);

@ -41,6 +41,15 @@ define([
return e;
};
// FIXME almost everywhere this is used would also be
// a good candidate for sframe-common's getMediatagFromHref
UI.mediaTag = function (src, key) {
return h('media-tag', {
src: src,
'data-crypto-key': 'cryptpad:' + key,
});
};
var findCancelButton = UI.findCancelButton = function (root) {
if (root) {
return $(root).find('button.cancel').last();
@ -747,6 +756,7 @@ define([
cb = Util.once(cb);
}
var classes = 'btn ' + (config.classes || 'btn-primary');
var newCls = config.new ? '.new' : '';
var button = h('button', {
"class": classes,
@ -759,7 +769,7 @@ define([
});
var timer = h('div.cp-button-timer', div);
var content = h('div.cp-button-confirm', [
var content = h('div.cp-button-confirm'+newCls, [
button,
timer
]);
@ -795,7 +805,8 @@ define([
to = setTimeout(todo, INTERVAL);
};
$(originalBtn).addClass('cp-button-confirm-placeholder').click(function (e) {
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; }
@ -1039,6 +1050,7 @@ define([
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 = $('<span>', {'class': font + ' ' + icon + appClass});
}
@ -1050,6 +1062,7 @@ define([
if (!data) { return $icon; }
var href = data.href || data.roHref;
var type = data.type;
if (data.static) { type = 'link'; }
if (!href && !type) { return $icon; }
if (!type) { type = Hash.parsePadUrl(href).type; }
@ -1175,6 +1188,7 @@ define([
var label = h('span.cp-checkmark-label', labelTxt);
$mark.keydown(function (e) {
if ($input.is(':disabled')) { return; }
if (e.which === 32) {
e.stopPropagation();
e.preventDefault();
@ -1220,20 +1234,22 @@ define([
$.extend(markOpts, opts.mark || {});
var input = h('input', inputOpts);
var $input = $(input);
var mark = h('span.cp-radio-mark', markOpts);
var label = h('span.cp-checkmark-label', labelTxt);
$(mark).keydown(function (e) {
if ($input.is(':disabled')) { return; }
if (e.which === 32) {
e.stopPropagation();
e.preventDefault();
if ($(input).is(':checked')) { return; }
$(input).prop('checked', !$(input).is(':checked'));
$(input).change();
if ($input.is(':checked')) { return; }
$input.prop('checked', !$input.is(':checked'));
$input.change();
}
});
$(input).change(function () { $(mark).focus(); });
$input.change(function () { $(mark).focus(); });
var radio = h('label', labelOpts, [
input,

@ -936,7 +936,8 @@ define([
return button;
};
var createMdToolbar = function (common, editor) {
var createMdToolbar = function (common, editor, cfg) {
cfg = cfg || {};
var $toolbar = $('<div>', {
'class': 'cp-markdown-toolbar'
});
@ -1025,9 +1026,50 @@ define([
icon: 'fa-newspaper-o'
}
};
if (typeof(cfg.embed) === "function") {
actions.embed = { // Messages.mdToolbar_embed
icon: 'fa-picture-o',
action: function () {
var _cfg = {
types: ['file', 'link'],
where: ['root']
};
common.openFilePicker(_cfg, function (data) {
// Embed links
if (data.static) {
var a = h('a', {
href: data.href
}, data.name);
cfg.embed(a, data);
return;
}
// Embed files
if (data.type !== 'file') {
console.log("Unexpected data type picked " + data.type);
return;
}
if (data.type !== 'file') { console.log('unhandled embed type ' + data.type); return; }
common.setPadAttribute('atime', +new Date(), null, data.href);
var privateDat = common.getMetadataMgr().getPrivateData();
var origin = privateDat.fileHost || privateDat.origin;
var src = data.src = data.src.slice(0,1) === '/' ? origin + data.src : data.src;
cfg.embed(h('media-tag', {
src: src,
'data-crypto-key': 'cryptpad:' + data.key,
}), data);
});
}
};
}
var onClick = function () {
var type = $(this).attr('data-type');
var texts = editor.getSelections();
if (actions[type].action) {
return actions[type].action();
}
var newTexts = texts.map(function (str) {
str = str || Messages.mdToolbar_defaultText;
if (actions[type].apply) {
@ -1054,7 +1096,7 @@ define([
}).appendTo($toolbar);
return $toolbar;
};
UIElements.createMarkdownToolbar = function (common, editor) {
UIElements.createMarkdownToolbar = function (common, editor, opts) {
var readOnly = common.getMetadataMgr().getPrivateData().readOnly;
if (readOnly) {
return {
@ -1064,7 +1106,7 @@ define([
};
}
var $toolbar = createMdToolbar(common, editor);
var $toolbar = createMdToolbar(common, editor, opts);
var cfg = {
title: Messages.mdToolbar_button,
element: $toolbar
@ -1133,6 +1175,7 @@ define([
sheet: 'sheets',
poll: 'poll',
kanban: 'kanban',
form: 'form',
whiteboard: 'whiteboard',
};
@ -1472,11 +1515,13 @@ define([
if (config.isSelect) {
var pressed = '';
var to;
$container.onChange = Util.mkEvent();
$container.on('click', 'a', function () {
value = $(this).data('value');
var $val = $(this);
var textValue = $val.html() || value;
$button.find('.cp-dropdown-button-title').html(textValue);
$container.onChange.fire(textValue, value);
});
$container.keydown(function (e) {
var $value = $innerblock.find('[data-value].cp-dropdown-element-active:visible');
@ -1616,7 +1661,7 @@ define([
var $displayedName = $('<span>', {'class': displayNameCls});
var priv = metadataMgr.getPrivateData();
var accountName = priv.accountName;
var accountName = Util.fixHTML(priv.accountName);
var origin = priv.origin;
var padType = metadataMgr.getMetadata().type;
@ -1626,7 +1671,8 @@ define([
var $userAdminContent = $('<p>');
if (accountName) {
var $userAccount = $('<span>').append(Messages.user_accountName + ': ');
$userAdminContent.append($userAccount).append(Util.fixHTML(accountName));
$userAdminContent.append($userAccount).append(accountName);
$userAdminContent.append($('<br>'));
}
if (config.displayName && !AppConfig.disableProfile) {
@ -1849,9 +1895,12 @@ define([
},
content: h('span', Messages.logoutEverywhere),
action: function () {
UI.confirm(Messages.settings_logoutEverywhereConfirm, function (yes) {
if (!yes) { return; }
Common.getSframeChannel().query('Q_LOGOUT_EVERYWHERE', null, function () {
Common.gotoURL(origin + '/');
});
});
},
});
options.push({
@ -2038,14 +2087,9 @@ define([
var $container = $('<div>');
var i = 0;
var types = AppConfig.availablePadTypes.filter(function (p) {
if (p === 'drive') { return; }
if (p === 'teams') { return; }
if (p === 'contacts') { return; }
if (p === 'todo') { return; }
if (p === 'file') { return; }
if (p === 'accounts') { return; }
if (p === 'calendar') { return; }
if (AppConfig.hiddenTypes.indexOf(p) !== -1) { return; }
if (!common.isLoggedIn() && AppConfig.registeredOnlyTypes &&
AppConfig.registeredOnlyTypes.indexOf(p) !== -1) { return; }
return true;
@ -2224,7 +2268,7 @@ define([
// Title
//$creation.append(h('h2.cp-creation-title', Messages.newButtonTitle));
var newPadH3Title = Messages['button_new' + type];
var newPadH3Title = Messages['button_new' + type]; // Messages.button_newform
var title = h('div.cp-creation-title', [
UI.getFileIcon({type: type})[0],
@ -2414,6 +2458,7 @@ define([
'title': name,
}).appendTo($container);
$span.data('id', obj.id);
if (obj.content) { $span.data('content', obj.content); }
if (idx === selected) { $span.addClass('cp-creation-template-selected'); }
if (!obj.thumbnail) {
$span.append(obj.icon || h('span.cptools.cptools-template'));
@ -2569,6 +2614,7 @@ define([
var $template = $creation.find('.cp-creation-template-selected');
var templateId = $template.data('id') || undefined;
var templateContent = $template.data('content') || undefined;
// Team
var team;
if (teamValue) {
@ -2581,6 +2627,7 @@ define([
password: passwordVal,
expire: expireVal,
templateId: templateId,
templateContent: templateContent,
team: team
};
};
@ -2626,6 +2673,17 @@ define([
$creation.focus();
};
UIElements.loginErrorScreenContent = function (common) {
var msg = Pages.setHTML(h('span'), Messages.restrictedLoginPrompt);
$(msg).find('a').attr({
href: '/login/',
}).click(function (ev) {
ev.preventDefault();
common.setLoginRedirect('login');
});
return msg;
};
var autoStoreModal = {};
UIElements.onServerError = function (common, err, toolbar, cb) {
//if (["EDELETED", "EEXPIRED", "ERESTRICTED"].indexOf(err.type) === -1) { return; }
@ -2673,6 +2731,10 @@ define([
if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
} else if (err.type === 'ERESTRICTED') {
msg = Messages.restrictedError;
if (!common.isLoggedIn()) {
msg = UIElements.loginErrorScreenContent(common);
}
if (toolbar && typeof toolbar.failed === "function") { toolbar.failed(true); }
} else if (err.type === 'HASH_NOT_FOUND' && priv.isHistoryVersion) {
msg = Messages.oo_deletedVersion;
@ -2833,7 +2895,13 @@ define([
UIElements.displayStorePadPopup = function (common, data) {
if (storePopupState) { return; }
storePopupState = true;
if (data && data.stored) { return; } // We won't display the popup for dropped files
// We won't display the popup for dropped files or already stored pads
if (data && data.stored) {
if (!data.inMyDrive) {
$('.cp-toolbar-storeindrive').show();
}
return;
}
var priv = common.getMetadataMgr().getPrivateData();
// This pad will be deleted automatically, it shouldn't be stored
@ -2962,6 +3030,75 @@ define([
UI.proposal(content, todo);
};
UIElements.displayOpenLinkModal = function (common, data, dismiss) {
var name = Util.fixHTML(data.title);
var url = data.href;
var user = data.name;
//Messages.link_open = "Open URL";
// openLinkInNewTab ("Open Link in New Tab")
// fc_open ("Open")
// share_linkOpen ("Preview")
// resources_openInNewTab ("Open it in a new tab")
Messages.link_open = Messages.fc_open; // XXX 4.10.0
//Messages.link_store = "Store link in drive";
// toolbar_storeInDrive ? ("Store in CryptDrive")
// autostore_store ? ("Store")
Messages.link_store = Messages.toolbar_storeInDrive; // XXX 4.10.0
var content = h('div', [
UI.setHTML(h('p'), Messages._getKey('notification_openLink', [name, user])),
h('pre', url),
UIElements.getVerifiedFriend(common, data.curve, user)
]);
var clicked = false;
var modal;
var buttons = [{
name: Messages.friendRequest_later,
onClick: function () {
if (clicked) { return true; }
clicked = true;
Feedback.send('LINK_RECEIVED_LATER');
},
keys: [27]
}, {
className: 'primary',
name: Messages.link_open,
onClick: function () {
if (clicked) { return true; }
clicked = true;
common.openUnsafeURL(url);
Feedback.send("LINK_RECEIVED_OPEN");
},
keys: [13]
}, {
className: 'primary',
name: Messages.link_store,
onClick: function () {
if (clicked) { return; }
clicked = true;
common.getSframeChannel().query("Q_DRIVE_USEROBJECT", {
cmd: "addLink",
data: {
name: name,
href: url,
path: ['root']
}
}, function () {
modal.closeModal();
dismiss();
Feedback.send("LINK_RECEIVED_STORE");
});
return true;
},
keys: [[13, 'ctrl']]
}];
var _modal = UI.dialog.customModal(content, {buttons: buttons});
modal = UI.openCustomModal(_modal);
return modal;
};
UIElements.displayAddOwnerModal = function (common, data) {
var priv = common.getMetadataMgr().getPrivateData();
var sframeChan = common.getSframeChannel();
@ -3012,6 +3149,7 @@ define([
// ACCEPT
sframeChan.query('Q_SET_PAD_METADATA', {
channel: msg.content.channel,
channels: msg.content.channels,
command: 'ADD_OWNERS',
value: [priv.edPublic]
}, function (err, res) {
@ -3061,6 +3199,7 @@ define([
// Remove yourself from the pending owners
sframeChan.query('Q_SET_PAD_METADATA', {
channel: msg.content.channel,
channels: msg.content.channels,
command: 'RM_PENDING_OWNERS',
value: [priv.edPublic]
}, function (err, res) {
@ -3077,6 +3216,7 @@ define([
// Remove yourself from the pending owners
sframeChan.query('Q_SET_PAD_METADATA', {
channel: msg.content.channel,
channels: msg.content.channels,
command: 'RM_PENDING_OWNERS',
value: [priv.edPublic]
}, function (err, res) {
@ -3556,6 +3696,13 @@ define([
return (pos.bottom < size) && (pos.y > 0);
};
UIElements.is24h = function () {
try {
return !new Intl.DateTimeFormat(navigator.language, { hour: 'numeric' }).format(0).match(/AM/);
} catch (e) {}
return false;
};
UIElements.openSnapshotsModal = function (common, load, make, remove) {
var modal;
var readOnly = common.getMetadataMgr().getPrivateData().readOnly;

@ -9,6 +9,15 @@
return Array.prototype.slice.call(A, start, end);
};
Util.shuffleArray = function (a) {
for (var i = a.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
};
Util.bake = function (f, args) {
if (typeof(args) === 'undefined') { args = []; }
if (!Array.isArray(args)) { args = [args]; }
@ -152,6 +161,10 @@
};
};
Util.inc = function (map, key, val) {
map[key] = (map[key] || 0) + (typeof(val) === 'number'? val: 1);
};
Util.find = function (map, path) {
var l = path.length;
for (var i = 0; i < l; i++) {
@ -559,8 +572,8 @@
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
'(\\#[-a-z\\d_]*)?$','i'); // fragment locator
'(\\?[;&a-z\\d%_.~+=-]*)?'); // query string
//'(\\#[-a-z\\d_]*)?$','i'); // fragment locator
return !!pattern.test(str);
};

@ -76,7 +76,7 @@ define([
postMessage("GET", {
key: ['edPrivate'],
}, waitFor(function (obj) {
if (obj.error) { return; }
if (!obj || obj.error) { return; }
try {
keys.push({
edPrivate: obj,
@ -84,14 +84,21 @@ define([
});
} catch (e) { console.error(e); }
}));
// Push teams keys
postMessage("GET", {
key: ['teams'],
}, waitFor(function (obj) {
if (obj.error) { return; }
if (!obj || obj.error) { return; }
Object.keys(obj || {}).forEach(function (id) {
var t = obj[id];
var _keys = t.keys.drive || {};
var _keys = {};
try {
_keys = t.keys.drive || {};
} catch (err) {
console.error(err);
}
_keys.id = id;
if (!_keys.edPrivate) { return; }
keys.push(t.keys.drive);
});
@ -101,6 +108,57 @@ define([
});
};
common.getFormKeys = function (cb) {
var curvePrivate;
var formSeed;
Nthen(function (waitFor) {
postMessage("GET", {
key: ['curvePrivate'],
}, waitFor(function (obj) {
if (!obj || obj.error) { return; }
curvePrivate = obj;
}));
postMessage("GET", {
key: ['form_seed'],
}, waitFor(function (obj) {
if (!obj || obj.error) { return; }
formSeed = obj;
}));
}).nThen(function () {
cb({
curvePrivate: curvePrivate,
curvePublic: curvePrivate && Hash.getCurvePublicFromPrivate(curvePrivate),
formSeed: formSeed
});
});
};
common.getFormAnswer = function (data, cb) {
postMessage("GET", {
key: ['forms', data.channel],
}, cb);
};
common.storeFormAnswer = function (data) {
postMessage("SET", {
key: ['forms', data.channel],
value: {
hash: data.hash,
curvePrivate: data.curvePrivate,
anonymous: data.anonymous
}
}, function (obj) {
if (obj && obj.error) {
if (obj.error === "ENODRIVE") {
var answered = JSON.parse(localStorage.CP_formAnswered || "[]");
if (answered.indexOf(data.channel) === -1) { answered.push(data.channel); }
localStorage.CP_formAnswered = JSON.stringify(answered);
return;
}
console.error(obj.error);
}
});
};
common.makeNetwork = function (cb) {
require([
'/bower_components/netflux-websocket/netflux-client.js',
@ -712,6 +770,10 @@ define([
delete meta.chat2;
delete meta.chat;
delete meta.cursor;
if (meta.type === "form") {
delete parsed.answers;
}
}
};
@ -1103,6 +1165,11 @@ define([
postMessage('BURN_PAD', data);
};
common.setDriveRedirectPreference = function (data, cb) {
LocalStore.setDriveRedirectPreference(data && data.value);
cb();
};
common.changePadPassword = function (Crypt, Crypto, data, cb) {
var href = data.href;
var oldPassword = data.oldPassword;
@ -1870,6 +1937,17 @@ define([
waitFor.abort();
return void cb(obj);
}
}));
}).nThen(function (waitFor) {
var blockUrl = Block.getBlockUrl(blockKeys);
Util.fetch(blockUrl, waitFor(function (err /* block */) {
if (err) {
console.error(err);
waitFor.abort();
return cb({
error: err,
});
}
console.log("new login block written");
var newBlockHash = Block.getBlockHash(blockKeys);
LocalStore.setBlockHash(newBlockHash);
@ -2506,6 +2584,11 @@ define([
}
if (data.anonHash && !cfg.userHash) { LocalStore.setFSHash(data.anonHash); }
var prefersDriveRedirect = data[Constants.prefersDriveRedirectKey];
if (typeof(prefersDriveRedirect) === 'boolean') {
LocalStore.setDriveRedirectPreference(prefersDriveRedirect);
}
initialized = true;
channelIsReady();
});

@ -8,11 +8,14 @@ define([
'/common/inner/common-mediatag.js',
'/common/media-tag.js',
'/customize/messages.js',
'/common/less.min.js',
'/customize/pages.js',
'/common/highlight/highlight.pack.js',
'/lib/diff-dom/diffDOM.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'css!/common/highlight/styles/'+ (window.CryptPad_theme === 'dark' ? 'dark.css' : 'github.css')
],function ($, ApiConfig, Marked, Hash, Util, h, MT, MediaTag, Messages) {
],function ($, ApiConfig, Marked, Hash, Util, h, MT, MediaTag, Messages, Less, Pages) {
var DiffMd = {};
var Highlight = window.hljs;
@ -172,12 +175,16 @@ define([
return h('div.cp-md-toc', content).outerHTML;
};
DiffMd.render = function (md, sanitize, restrictedMd) {
var noHeadingId = false;
DiffMd.render = function (md, sanitize, restrictedMd, noId) {
Marked.setOptions({
renderer: restrictedMd ? restrictedRenderer : renderer,
});
noHeadingId = noId;
var r = Marked(md, {
sanitize: sanitize
sanitize: sanitize,
headerIds: !noId,
gfm: true,
});
// Add Table of Content
@ -207,7 +214,11 @@ define([
};
restrictedRenderer.code = renderer.code;
var _heading = renderer.heading;
renderer.heading = function (text, level) {
if (noHeadingId) {
return _heading.apply(this, arguments);
}
var i = 0;
var safeText = text.toLowerCase().replace(/[^\w]+/g, '-');
var getId = function () {
@ -266,9 +277,43 @@ define([
return '<li>' + text + '</li>\n';
};
var qualifiedHref = function (href) {
if (typeof(window.URL) === 'undefined') { return href; }
try {
var url = new URL(href, ApiConfig.httpUnsafeOrigin);
return url.href;
} catch (err) {
console.error(err);
return href;
}
};
var isLocalURL = function (href) {
// treat all URLs as remote if you are using an ancient browser
if (typeof(window.URL) === 'undefined') { return false; }
try {
var url = new URL(href, ApiConfig.httpUnsafeOrigin);
// FIXME data URLs can be quite large, but that should be addressed
// in the source markdown's, not the renderer
if (url.protocol === 'data:') { return true; }
var localURL = new URL(ApiConfig.httpUnsafeOrigin);
return url.host === localURL.host;
} catch (err) {
return true;
}
};
renderer.image = function (href, title, text) {
if (isLocalURL(href) && href.slice(0, 6) !== '/file/') {
return h('img', {
src: href,
title: title || '',
alt: text,
}).outerHTML;
}
if (href.slice(0,6) === '/file/') {
// DEPRECATED
// Mediatag using markdown syntax should not be used anymore so they don't support
// password-protected files
console.log('DEPRECATED: mediatag using markdown syntax!');
@ -276,19 +321,38 @@ define([
var secret = Hash.getSecrets('file', parsed.hash);
var src = (ApiConfig.fileHost || '') +Hash.getBlobPathFromHex(secret.channel);
var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
if (mediaMap[src]) {
mt += mediaMap[src];
}
mt += '</media-tag>';
return mt;
}
var out = '<img src="' + href + '" alt="' + text + '"';
if (title) {
out += ' title="' + title + '"';
}
out += this.options.xhtml ? '/>' : '>';
return out;
var mt = h('media-tag', {
src: src,
'data-crypto-key': 'cryptpad:' + key,
});
return mt.outerHTML;
}
var warning = h('div.cp-inline-img-warning', [
h('div.cp-inline-img', [
h('img.cp-inline-img', {
src: '/images/broken.png',
//title: title || '', // FIXME sort out tippy issues (double-title)
}),
h('p.cp-alt-txt', text),
]),
h('span.cp-img-block-notice', {
}, Messages.resources_imageBlocked),
h('br'),
h('a.cp-remote-img', {
href: qualifiedHref(href),
}, [
Messages.resources_openInNewTab
]),
h('br'),
h('a.cp-learn-more', {
href: Pages.localizeDocsLink('https://docs.cryptpad.fr/en/user_guide/security.html#remote-content'),
}, [
Messages.resources_learnWhy
]),
]);
return warning.outerHTML;
};
restrictedRenderer.image = renderer.image;
@ -473,6 +537,75 @@ define([
}
};
var applyCSS = function (el, css) {
var style = h('style');
style.appendChild(document.createTextNode(css));
el.innerText = '';
el.appendChild(style);
};
// trim non-functional text from less input so that
// the compiler is only triggered when there has been a functional change
var canonicalizeLess = function (source) {
return (source || '')
// leading and trailing spaces are irrelevant
.trim()
// line comments are easy to disregard
.replace(/\/\/[^\n]*/g, '')
// lines with nothing but spaces and tabs can be ignored
.replace(/^[ \t]*$/g, '')
// consecutive newlines make no difference
.replace(/\n+/g, '');
};
var rendered_less = {};
var getRenderedLess = (function () {
var timeouts = {};
return function (src) {
if (!rendered_less[src]) { return; }
if (timeouts[src]) {
clearTimeout(timeouts[src]);
}
// avoid memory leaks by deleting cached content
// 15s after it was last accessed
timeouts[src] = setTimeout(function () {
delete rendered_less[src];
delete timeouts[src];
}, 15000);
return rendered_less[src];
};
}());
plugins.less = {
name: 'less',
attr: 'less-src',
render: function renderLess ($el, opt) {
var src = canonicalizeLess($el.text());
if (!src) { return; }
var el = $el[0];
var rendered = getRenderedLess(src);
if (rendered) { return void applyCSS(el, rendered); }
var scope = opt.scope.attr('id') || 'cp-app-code-preview-content';
var scoped_src = '#' + scope + ' { ' + src + '}';
//console.error("RENDERING LESS");
Less.render(scoped_src, {}, function (err, result) {
// the console is the only feedback for users to know that they did something wrong
// but less rendering isn't intended so much as a feature but a useful tool to avoid
// leaking styles from the preview into the rest of the DOM. This is an improvement.
if (err) {
// we assume the compiler is deterministic. Something that returns an error once
// will do it again, so avoid successive calls by caching a truthy
// but non-functional string to block them.
rendered_less[src] = ' ';
return void console.error(err);
}
var css = rendered_less[src] = result.css;
applyCSS(el, css);
});
},
};
var getAvailableCachedElement = function ($content, cache, src) {
var cached = cache[src];
if (!Array.isArray(cached)) { return; }
@ -546,7 +679,8 @@ define([
// caching their source as you go
$(newDomFixed).find('pre[data-plugin]').each(function (index, el) {
if (el.childNodes.length === 1 && el.childNodes[0].nodeType === 3) {
var plugin = plugins[el.getAttribute('data-plugin')];
var type = el.getAttribute('data-plugin');
var plugin = plugins[type];
if (!plugin) { return; }
var src = canonicalizeMermaidSource(el.childNodes[0].wholeText);
el.setAttribute(plugin.attr, src);
@ -559,7 +693,8 @@ define([
var scrollTop = $parent.scrollTop();
// iterate over rendered mermaid charts
$content.find('pre[data-plugin]:not([processed="true"])').each(function (index, el) {
var plugin = plugins[el.getAttribute('data-plugin')];
var type = el.getAttribute('data-plugin');
var plugin = plugins[type];
if (!plugin) { return; }
// retrieve the attached source code which it was drawn
@ -666,7 +801,11 @@ define([
if (typeof(patch) === 'string') {
throw new Error(patch);
} else {
try {
DD.apply($content[0], patch);
} catch (err) {
console.error(err);
}
var $mts = $content.find('media-tag');
$mts.each(function (i, el) {
var $mt = $(el).contextmenu(function (e) {
@ -721,9 +860,35 @@ define([
if (target) { target.scrollIntoView(); }
});
// replace remote images with links to those images
$content.find('div.cp-inline-img-warning').each(function (index, el) {
if (!el) { return; }
var link = el.querySelector('a.cp-remote-img');
if (!link) { return; }
link.onclick = function (ev) {
ev.preventDefault();
ev.stopPropagation();
common.openURL(link.href);
};
});
// transform style tags into pre tags with the same content
// to be handled by the less rendering plugin
$content.find('style').each(function (index, el) {
var parent = el.parentElement;
var pre = h('pre', {
'data-plugin': 'less',
'less-src': canonicalizeLess(el.innerText),
style: 'display: none',
}, el.innerText);
parent.replaceChild(pre, el);
});
// loop over plugin elements in the rendered content
$content.find('pre[data-plugin]').each(function (index, el) {
var plugin = plugins[el.getAttribute('data-plugin')];
var type = el.getAttribute('data-plugin');
var plugin = plugins[type];
if (!plugin) { return; }
var $el = $(el);
$el.off('contextmenu').on('contextmenu', function (e) {
@ -742,13 +907,17 @@ define([
// you can assume that the index of your rendered charts matches that
// of those in the markdown source.
var src = plugin.source[index];
if (src) {
el.setAttribute(plugin.attr, src);
}
var cached = getAvailableCachedElement($content, plugin.cache, src);
// check if you had cached a pre-rendered instance of the supplied source
if (typeof(cached) !== 'object') {
try {
plugin.render($el);
plugin.render($el, {
scope: $content,
});
} catch (e) { console.error(e); }
return;
}

@ -351,7 +351,7 @@ define([
h('li', h('a.cp-app-drive-context-openro.dropdown-item', {
'tabindex': '-1',
'data-icon': faReadOnly,
}, Messages.fc_open_ro)),
}, h('span.cp-text', Messages.fc_open_ro))),
h('li', h('a.cp-app-drive-context-openincode.dropdown-item', {
'tabindex': '-1',
'data-icon': faOpenInCode,
@ -443,6 +443,11 @@ define([
'data-icon': AppConfig.applicationsIcon.poll,
'data-type': 'poll'
}, Messages.button_newpoll)),
h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
'data-icon': AppConfig.applicationsIcon.link,
'data-type': 'link'
}, Messages.fm_link_new)),
]),
]),
$separator.clone()[0],
@ -1106,11 +1111,24 @@ define([
common.getMediaTagPreview(mts, idx);
};
var refresh = APP.refresh = function () {
APP.displayDirectory(currentPath);
};
// `app`: true (force open wiht the app), false (force open in preview),
// falsy (open in preview if default is not using the app)
var defaultInApp = ['application/pdf'];
var openFile = function (el, isRo, app) {
var data = manager.getFileData(el);
if (data.static) {
if (data.href) {
common.openUnsafeURL(data.href);
manager.updateStaticAccess(el, refresh);
}
return;
}
if (!data || (!data.href && !data.roHref)) {
return void logError("Missing data for the file", el, data);
}
@ -1147,10 +1165,6 @@ define([
common.openURL(Hash.getNewPadURL(href, obj));
};
var refresh = APP.refresh = function () {
APP.displayDirectory(currentPath);
};
var pickFolderColor = function ($element, currentColor, cb) {
var colors = ["", "#f23c38", "#ff0073", "#da0eba", "#9d00ac", "#6c19b3", "#4a42b1", "#3d8af0", "#30a0f1", "#1fb9d1", "#009686", "#45b354", "#84c750", "#c6e144", "#faf147", "#fbc423", "#fc9819", "#fd5227", "#775549", "#9c9c9c", "#607a89"];
@ -1263,6 +1277,9 @@ define([
if ($element.is('.cp-border-color-sheet')) {
hide.push('download');
}
if ($element.is('.cp-app-drive-static')) {
hide.push('access', 'hashtag', 'properties', 'download');
}
if ($element.is('.cp-app-drive-element-file')) {
// No folder in files
hide.push('color');
@ -1646,6 +1663,19 @@ define([
}
paths = getSelectedPaths($element);
$('.cp-app-drive-context-openro .cp-text').text(Messages.fc_open_ro);
if (paths.length === 1) {
var metadata = manager.getFileData(manager.find(paths[0].path));
if (metadata.roHref) {
var parsed = Hash.parsePadUrl(metadata.roHref);
// Forms: change "Open (read-only)" to "Open (as participant)"
if (parsed.type === "form") {
$('.cp-app-drive-context-openro .cp-text').text(Messages.fc_open_formro);
}
}
}
}
$contextMenu.attr('data-menu-type', type);
@ -1886,6 +1916,31 @@ define([
// In list mode, display metadata from the filesData object
var addStaticData = function (element, $element, data) {
$element.addClass('cp-border-color-drive');
var name = data.name;
var $name = $('<span>', {'class': 'cp-app-drive-element-name'}).text(name);
$element.append($name);
if (getViewMode() === 'grid') {
//console.error(name, Util.fixHTML(name));
// this is only safe because our build of tippy sets titles as
// 'textContent' instead of innerHTML, otherwise
// we would need to use Util.fixHTML
$element.attr('title', name);
}
var type = Messages.fm_link_type;
var $type = $('<span>', {
'class': 'cp-app-drive-element-type cp-app-drive-element-list'
}).text(type);
var $adate = $('<span>', {
'class': 'cp-app-drive-element-atime cp-app-drive-element-list'
}).text(getDate(data.atime));
var $cdate = $('<span>', {
'class': 'cp-app-drive-element-ctime cp-app-drive-element-list'
}).text(getDate(data.ctime));
$element.append($type).append($adate).append($cdate);
};
var _addOwnership = function ($span, $state, data) {
if (data && Array.isArray(data.owners) && data.owners.indexOf(edPublic) !== -1) {
var $owned = $ownedIcon.clone().appendTo($state);
@ -1901,6 +1956,9 @@ define([
if (!manager.isFile(element)) { return; }
var data = manager.getFileData(element);
if (data.static) {
return addStaticData(element, $element, data);
}
if (!Object.keys(data).length) {
return true;
@ -2054,6 +2112,7 @@ define([
// can't share the read-only URL and we don't have access to the edit one.
// We should hide the share button.
if (!data.href && !ro) { return; }
$shareBlock.click(function () {
Share.getShareModal(common, {
teamId: APP.team,
@ -2110,7 +2169,9 @@ define([
$icon = manager.isFolderEmpty(root[key]) ? $folderEmptyIcon.clone() : $folderIcon.clone();
$icon.css("color", getFolderColor(path.concat(elPath)));
}
var classes = restrictedClass + roClass + liClass;
var staticClass = manager.isStaticFile(element) ? '.cp-app-drive-static' : '';
var classes = restrictedClass + roClass + liClass + staticClass;
var $element = $(h('li.cp-app-drive-element.cp-app-drive-element-row' + classes, {
draggable: true
}));
@ -2275,7 +2336,8 @@ define([
var createTitle = function ($container, path, noStyle) {
if (!path || path.length === 0) { return; }
var isTrash = manager.isPathIn(path, [TRASH]);
if (APP.mobile() && !noStyle) { // noStyle means title in search result
// we assume that users viewing shared folders may want to see the "bread-crumb"
if (!APP.newSharedFolder && APP.mobile() && !noStyle) { // noStyle means title in search result
return $container;
}
var isVirtual = virtualCategories.indexOf(path[0]) !== -1;
@ -2387,7 +2449,14 @@ define([
}
if (!APP.loggedIn) {
msg = APP.newSharedFolder ? Messages.fm_info_sharedFolder : Messages._getKey('fm_info_anonymous', [ApiConfig.inactiveTime || 90]);
return $(common.fixLinks($box.html(msg)));
var docsLink = 'https://docs.cryptpad.fr/en/user_guide/user_account.html#account-types';
$box.html(msg).find('a[href="#docs"]').each(function () {
$(this).attr({
href: Pages.localizeDocsLink(docsLink),
target: '_blank',
});
});
return $(common.fixLinks($box));
}
if (!msg || APP.store['hide-info-' + path[0]] === '1') {
$box.hide();
@ -2437,8 +2506,7 @@ define([
setViewMode(viewMode || 'grid');
showMode(viewMode);
$button.click(function (e) {
console.error(e);
$button.click(function () {
var viewMode = getViewMode();
var newViewMode = getOppositeViewMode(viewMode);
setViewMode(newViewMode);
@ -2550,13 +2618,7 @@ define([
var getNewPadTypes = function () {
var arr = [];
AppConfig.availablePadTypes.forEach(function (type) {
if (type === 'drive') { return; }
if (type === 'teams') { return; }
if (type === 'contacts') { return; }
if (type === 'todo') { return; }
if (type === 'file') { return; }
if (type === 'accounts') { return; }
if (type === 'calendar') { return; }
if (AppConfig.hiddenTypes.indexOf(type) !== -1) { return; }
if (!APP.loggedIn && AppConfig.registeredOnlyTypes &&
AppConfig.registeredOnlyTypes.indexOf(type) !== -1) {
return;
@ -2670,6 +2732,74 @@ define([
});
$input.click();
};
var showLinkModal = function () {
var name, url;
var warning = h('div.alert.alert-warning', [
h('i.fa.fa-exclamation-triangle'),
h('span', Messages.fm_link_warning)
]);
var content = h('p', [
h('label', {for: 'cp-app-drive-link-name'}, Messages.fm_link_name),
name = h('input#cp-app-drive-link-name', { autocomplete: 'off', placeholder: Messages.fm_link_name_placeholder }),
h('label', {for: 'cp-app-drive-link-url'}, Messages.fm_link_url),
url = h('input#cp-app-drive-link-url', { type: 'url', autocomplete: 'off', placeholder: Messages.form_input_ph_url }),
warning,
]);
var protocolPattern = /https*:\/\//;
var fragmentPattern = /#.*$/;
var setNamePlaceholder = function (val) {
var temp = val.replace(protocolPattern, '').replace(fragmentPattern, '').trim().slice(0, 48);
if (!protocolPattern.test(val) || !temp) {
temp = Messages.fm_link_name_placeholder;
}
name.setAttribute('placeholder', temp);
};
var $warning = $(warning).hide();
var $url = $(url).on('change keypress keyup keydown', function () {
var v = $url.val().trim();
if (v.length > 200) {
$warning.show();
return;
}
setNamePlaceholder(v);
$warning.hide();
});
var buttons = [{
className: 'cancel',
name: Messages.cancelButton,
onClick: function () {},
keys: [27]
}];
buttons.push({
className: 'primary',
// We may want to use a new key here
iconClass: '.fa.fa-plus',
name: Messages.tag_add,
onClick: function () {
var $name = $(name);
var n = $name.val().trim() || $name.attr('placeholder');
var u = $url.val().trim();
if (!n || !u) { return true; }
if (!Util.isValidURL(u)) {
// XXX 4.10.0 add style for invalid input? input:invalid
UI.warn(Messages.fm_link_invalid);
return true;
}
manager.addLink(currentPath, {
name: n,
url: u
}, refresh);
Feedback.send("LINK_CREATED");
},
keys: [13]
});
var m = UI.dialog.customModal(content, {
buttons: buttons
});
UI.openCustomModal(m);
};
var addNewPadHandlers = function ($block, isInRoot) {
// Handlers
if (isInRoot) {
@ -2698,6 +2828,7 @@ define([
}
$block.find('a.cp-app-drive-new-fileupload, li.cp-app-drive-new-fileupload').click(showUploadFilesModal);
$block.find('a.cp-app-drive-new-folderupload, li.cp-app-drive-new-folderupload').click(showUploadFolderModal);
$block.find('a.cp-app-drive-new-link, li.cp-app-drive-new-link').click(showLinkModal);
}
$block.find('a.cp-app-drive-new-doc, li.cp-app-drive-new-doc')
.click(function () {
@ -2741,6 +2872,12 @@ define([
});
}
options.push({tag: 'hr'});
options.push({
tag: 'a',
attributes: {'class': 'cp-app-drive-new-link'},
content: $('<div>').append(getIcon('link')).html() + Messages.fm_link_new
});
options.push({tag: 'hr'});
}
getNewPadTypes().forEach(function (type) {
var attributes = {
@ -3057,6 +3194,13 @@ define([
$elementFolderUpload.append($('<span>', {'class': 'cp-app-drive-new-name'})
.text(Messages.uploadFolderButton));
}
// Link
var $elementLink = $('<li>', {
'class': 'cp-app-drive-new-link cp-app-drive-element-row ' +
'cp-app-drive-element-grid'
}).prepend(getIcon('link')).appendTo($container);
$elementLink.append($('<span>', {'class': 'cp-app-drive-new-name'})
.text(Messages.fm_link_type));
}
// Pads
getNewPadTypes().forEach(function (type) {
@ -3132,7 +3276,8 @@ define([
if (APP.$burnThisDrive) {
APP.toolbar.$bottomR.append(APP.$burnThisDrive);
}
collapseTreeButton();
// this button is not useful for unregistered users who do not have a tree worth looking at
if (APP.loggedIn) { collapseTreeButton(); }
return $toolbar;
};
@ -3462,6 +3607,7 @@ define([
var path = paths[0];
if (manager.isPathIn(path, [TRASH])) { return; }
if (!file.channel) { file.channel = id; }
if (channels.indexOf(file.channel) !== -1) { return; }
channels.push(file.channel);
@ -4434,6 +4580,17 @@ define([
data = sf ? manager.getSharedFolderData(el) : manager.getFileData(el);
}
parsed = (data.href && data.href.indexOf('#') !== -1) ? Hash.parsePadUrl(data.href) : {};
// Form: get auditor hash
var auditorHash;
if (parsed.hash && parsed.type === "form") {
var formData = Hash.getFormData(null, parsed.hash, data.password);
console.log(formData);
if (formData) {
auditorHash = formData.form_auditorHash;
}
}
var roParsed = Hash.parsePadUrl(data.roHref);
var padType = parsed.type || roParsed.type;
var ro = !sf || (folders[el] && folders[el].version >= 2);
@ -4448,13 +4605,15 @@ define([
viewHash: ro && roParsed.hash,
fileHash: parsed.hash
},
auditorHash: auditorHash,
fileData: {
hash: parsed.hash,
password: data.password
},
isTemplate: paths[0].path[0] === 'template',
title: data.title,
title: data.title || data.name,
sharedFolder: sf,
static: data.static ? data.href : undefined,
common: common
};
if (padType === 'file') {
@ -4472,6 +4631,20 @@ define([
data = manager.getSharedFolderData(el);
}
if (!data) { return; }
if (data.static) {
sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "addLink",
teamId: -1,
data: {
name: data.name,
href: data.href,
path: ['root']
}
}, function () {
UI.log(Messages.saved);
});
return;
}
sframeChan.query('Q_STORE_IN_TEAM', {
href: data.href || data.rohref,
password: data.password,
@ -4510,6 +4683,9 @@ define([
}
else if ($this.hasClass("cp-app-drive-context-newdoc")) {
var ntype = $this.data('type') || 'pad';
if (ntype === 'link') {
return void showLinkModal();
}
var path2 = manager.isPathIn(currentPath, [TRASH]) ? '' : currentPath;
openIn(ntype, path2, APP.team);
}

@ -32,6 +32,12 @@ define([
var teamOwner = data.teamId;
var title = opts.title;
var p = priv.propChannels;
var otherChan;
if (p && p.answersChannel) {
otherChan = [p.answersChannel];
}
opts = opts || {};
var redrawAll = function () {};
@ -255,6 +261,7 @@ define([
// Send the command
sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel,
channels: otherChan,
command: 'ADD_OWNERS',
value: toAddTeams.map(function (obj) { return obj.edPublic; }),
teamId: teamOwner
@ -290,6 +297,7 @@ define([
// Send the command
sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel,
channels: otherChan,
command: 'ADD_PENDING_OWNERS',
value: toAdd,
teamId: teamOwner
@ -310,6 +318,7 @@ define([
// Send the command
sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel,
channels: otherChan,
command: 'ADD_OWNERS',
value: [priv.edPublic],
teamId: teamOwner
@ -338,6 +347,7 @@ define([
if (!friend) { return; }
common.mailbox.sendTo("ADD_OWNER", {
channel: channel,
channels: otherChan,
href: href,
calendar: opts.calendar,
password: data.password || priv.password,
@ -417,6 +427,12 @@ define([
var allowed = data.allowed || [];
var teamOwner = data.teamId;
var p = priv.propChannels;
var otherChan;
if (p && p.answersChannel) {
otherChan = [p.answersChannel];
}
var redrawAll = function () {};
var addBtn = h('button.btn.btn-primary.cp-access-add', [h('i.fa.fa-arrow-left'), h('i.fa.fa-arrow-up')]);
@ -495,6 +511,7 @@ define([
// Send the command
sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel,
channels: otherChan,
command: 'RM_ALLOWED',
value: [ed],
teamId: teamOwner
@ -524,6 +541,7 @@ define([
var val = $checkbox.is(':checked');
sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel,
channels: otherChan,
command: 'RESTRICT_ACCESS',
value: [Boolean(val)],
teamId: teamOwner
@ -659,6 +677,7 @@ define([
// Send the command
sframeChan.query('Q_SET_PAD_METADATA', {
channel: channel,
channels: otherChan,
command: 'ADD_ALLOWED',
value: toAdd,
teamId: teamOwner
@ -987,6 +1006,15 @@ define([
UI.findCancelButton().click();
if (err || (obj && obj.error)) { UI.warn(Messages.error); }
});
// If this is a form wiht a answer channel, delete it too
var p = priv.propChannels;
if (p.answersChannel) {
sframeChan.query('Q_DELETE_OWNED', {
teamId: typeof(owned) !== "boolean" ? owned : undefined,
channel: p.answersChannel
}, function () {});
}
});
if (!opts.noEditPassword) { $d.append(h('br')); }
$d.append(h('div', [
@ -1020,7 +1048,7 @@ define([
var owned = Modal.isOwned(Env, data);
// Request edit access
if (common.isLoggedIn() && ((data.roHref && !data.href) || data.fakeHref) && !owned && !opts.calendar) {
if (common.isLoggedIn() && ((data.roHref && !data.href) || data.fakeHref) && !owned && !opts.calendar && priv.app !== 'form') {
var requestButton = h('button.btn.btn-secondary.no-margin.cp-access-margin-right',
Messages.requestEdit_button);
var requestBlock = h('p', requestButton);
@ -1058,7 +1086,7 @@ define([
var canMute = data.mailbox && owned === true && (
(typeof (data.mailbox) === "string" && data.owners[0] === edPublic) ||
data.mailbox[edPublic]);
if (owned === true && !opts.calendar) {
if (owned === true && !opts.calendar && priv.app !== 'form') {
var cbox = UI.createCheckbox('cp-access-mute', Messages.access_muteRequests, !canMute);
var $cbox = $(cbox);
var spinner = UI.makeSpinner($cbox);

@ -127,9 +127,8 @@ define([
if (e || !data) { return void displayDefault(); }
if (typeof data !== "number") { return void displayDefault(); }
if (Util.bytesToMegabytes(data) > 0.5) { return void displayDefault(); }
var $img = $('<media-tag>').appendTo($container);
$img.attr('src', src);
$img.attr('data-crypto-key', 'cryptpad:' + cryptKey);
var mt = UI.mediaTag(src, cryptKey);
var $img = $(mt).appendTo($container);
MT.displayMediatagImage(common, $img, function (err, $image) {
if (err) { return void console.error(err); }
centerImage($img, $image);

@ -24,6 +24,7 @@ define([
if (privateData.propChannels) {
var p = privateData.propChannels;
data.channel = data.channel || p.channel;
data.answersChannel = data.answersChannel || p.answersChannel;
data.rtChannel = data.rtChannel || p.rtChannel;
data.lastVersion = data.lastVersion || p.lastVersion;
data.lastCpHash = data.lastCpHash || p.lastCpHash;
@ -75,6 +76,7 @@ define([
var bytes = 0;
var historyBytes;
var chan = [data.channel];
if (data.answersChannel) { chan.push(data.answersChannel); }
if (data.rtChannel) { chan.push(data.rtChannel); }
if (data.lastVersion) { chan.push(Hash.hrefToHexChannelId(data.lastVersion)); }

@ -90,7 +90,11 @@ define([
setTimeout(w);
});
if (res && /^http/.test(res)) {
href = Hash.getRelativeHref(res);
var _href = Hash.getRelativeHref(res);
if (_href) { href = _href; }
else {
href = res;
}
setTimeout(w);
return;
}
@ -109,6 +113,7 @@ define([
if (mailbox.notifications && mailbox.curvePublic) {
common.mailbox.sendTo("SHARE_PAD", {
href: href,
isStatic: Boolean(config.static),
password: config.password,
isTemplate: config.isTemplate,
name: myName,
@ -119,6 +124,9 @@ define([
channel: mailbox.notifications,
curvePublic: mailbox.curvePublic
});
if (config.static) {
Feedback.send("LINK_SHARED_WITH_CONTACT");
}
return;
}
}
@ -137,6 +145,21 @@ define([
});
return;
}
if (config.static) {
common.getSframeChannel().query("Q_DRIVE_USEROBJECT", {
cmd: "addLink",
teamId: team.id,
data: {
name: title,
href: href,
path: ['root']
}
}, function () {
UI.log(Messages.saved);
});
Feedback.send("LINK_ADDED_TO_DRIVE");
return;
}
sframeChan.query('Q_STORE_IN_TEAM', {
href: href,
password: config.password,
@ -346,6 +369,9 @@ define([
] : [
UI.createCheckbox('cp-share-embed', Messages.share_linkEmbed, false, { mark: {tabindex:1} }),
];
if (opts.static) { linkContent = []; }
linkContent.push(h('div.cp-spacer'));
linkContent.push(UI.dialog.selectableArea('', { id: 'cp-share-link-preview', tabindex: 1, rows:3}));
@ -361,7 +387,7 @@ define([
// warning about sharing links
// when sharing a version hash, there is a similar warning and we want
// to avoid alert fatigue
if (!opts.versionHash) {
if (!opts.versionHash && !opts.static) {
var localStore = window.cryptpadStore;
var dismissButton = h('span.fa.fa-times');
var shareLinkWarning = h('div.alert.alert-warning.dismissable',
@ -405,6 +431,10 @@ define([
var v = opts.getLinkValue({
embed: Util.isChecked($link.find('#cp-share-embed'))
});
if (opts.static) {
common.openUnsafeURL(v);
return true;
}
window.open(v);
return true;
},
@ -494,7 +524,20 @@ define([
var parsed = Hash.parsePadUrl(pathname);
var canPresent = ['code', 'slide'].indexOf(parsed.type) !== -1;
var versionHash = hashes.viewHash && opts.versionHash;
var canBAR = parsed.type !== 'drive' && !versionHash;
var isForm = parsed.type === "form"; // && opts.auditorHash;
var canBAR = parsed.type !== 'drive' && !versionHash && !isForm;
var labelEdit = Messages.share_linkEdit;
var labelView = Messages.share_linkView;
var auditor;
if (isForm) {
labelEdit = Messages.share_formEdit;
labelView = Messages.share_formView;
auditor = UI.createRadio('accessRights', 'cp-share-form', Messages.share_formAuditor, false, {
mark: {tabindex:1},
});
}
var burnAfterReading = (hashes.viewHash && canBAR) ?
UI.createRadio('accessRights', 'cp-share-bar', Messages.burnAfterReading_linkBurnAfterReading, false, {
@ -505,12 +548,13 @@ define([
h('label', Messages.share_linkAccess),
h('div.radio-group',[
UI.createRadio('accessRights', 'cp-share-editable-false',
Messages.share_linkView, true, { mark: {tabindex:1} }),
labelView, true, { mark: {tabindex:1} }),
canPresent ? UI.createRadio('accessRights', 'cp-share-present',
Messages.share_linkPresent, false, { mark: {tabindex:1} }) : undefined,
UI.createRadio('accessRights', 'cp-share-editable-true',
Messages.share_linkEdit, false, { mark: {tabindex:1} })]),
burnAfterReading
labelEdit, false, { mark: {tabindex:1} }),
auditor]),
burnAfterReading,
]);
// Burn after reading
@ -548,11 +592,13 @@ define([
});
};
opts.getLinkValue = function (initValue, cb) {
if (opts.static) { return opts.static; }
var val = initValue || {};
var edit = val.edit !== undefined ? val.edit : Util.isChecked($rights.find('#cp-share-editable-true'));
var embed = val.embed;
var present = val.present !== undefined ? val.present : Util.isChecked($rights.find('#cp-share-present'));
var burnAfterReading = Util.isChecked($rights.find('#cp-share-bar'));
var formAuditor = Util.isChecked($rights.find('#cp-share-form'));
if (versionHash) {
edit = false;
present = false;
@ -569,6 +615,9 @@ define([
}
var hash = (!hashes.viewHash || (edit && hashes.editHash)) ? hashes.editHash
: hashes.viewHash;
if (formAuditor && opts.auditorHash) {
hash = opts.auditorHash;
}
var href = burnAfterReading ? opts.burnAfterReadingUrl
: (origin + pathname + '#' + hash);
var parsed = Hash.parsePadUrl(href);
@ -594,6 +643,9 @@ define([
$rights.find('#cp-share-present').removeAttr('checked').attr('disabled', true);
$rights.find('#cp-share-editable-true').attr('checked', true);
}
if (isForm && !opts.auditorHash) {
$rights.find('#cp-share-form').removeAttr('checked').attr('disabled', true);
}
var getLink = function () {
return $rights.parent().find('#cp-share-link-preview');
@ -665,7 +717,7 @@ define([
opts.access = true; // Allow the use of the modal even if the pad is not stored
var hashes = opts.hashes;
if (!hashes || (!hashes.editHash && !hashes.viewHash)) { return; }
if (!hashes || (!hashes.editHash && !hashes.viewHash && !opts.static)) { return; }
var teams = getEditableTeams(common, opts);
opts.teams = teams;
@ -684,19 +736,23 @@ define([
var $rights = opts.$rights = getRightsHeader(common, opts);
var resetTab = function () {
if (opts.static) { return; }
$rights.show();
$rights.find('label.cp-radio').show();
};
var onShowEmbed = function () {
if (opts.static) { return; }
$rights.find('#cp-share-bar').closest('label').hide();
$rights.find('input[type="radio"]:enabled').first().prop('checked', 'checked');
$rights.find('input[type="radio"]').trigger('change');
};
var onShowContacts = function () {
if (opts.static) { return; }
if (!hasFriends || priv.offline) {
$rights.hide();
}
};
if (opts.static) { $rights.hide(); }
var contactsActive = hasFriends && !priv.offline;
var tabs = [{
@ -711,13 +767,16 @@ define([
title: Messages.share_linkCategory,
icon: "fa fa-link",
active: !contactsActive,
}, {
}];
if (!opts.static) {
tabs.push({
getTab: getEmbedTab,
title: Messages.share_embedCategory,
icon: "fa fa-code",
onShow: onShowEmbed,
onHide: resetTab
}];
});
}
Modal.getModal(common, opts, tabs, function (err, modal) {
// Hide the burn-after-reading option by default
var $modal = $(modal);

@ -8,7 +8,8 @@ define([
'/common/common-constants.js',
'/customize/messages.js',
'/customize/pages.js',
], function($, h, Hash, UI, UIElements, Util, Constants, Messages, Pages) {
'/lib/datepicker/flatpickr.js',
], function($, h, Hash, UI, UIElements, Util, Constants, Messages, Pages, Flatpickr) {
var handlers = {};
@ -91,6 +92,10 @@ define([
(type === 'file' ? 'notification_fileShared' : // Msg.notification_fileSharedTeam
'notification_padShared'); // Msg.notification_padSharedTeam
if (msg.content.isStatic) {
key = 'notification_linkShared'; // Msg.notification_linkShared;
}
var teamNotification = /^team-/.test(data.type) && Number(data.type.slice(5));
var teamName = '';
if (teamNotification) {
@ -108,6 +113,15 @@ define([
return Messages._getKey(key, [name, title, teamName]);
};
content.handler = function() {
if (msg.content.isStatic) {
UIElements.displayOpenLinkModal(common, {
curve: msg.author,
href: msg.content.href,
name: name,
title: title
}, defaultDismiss(common, data));
return;
}
var obj = {
p: msg.content.isTemplate ? ['template'] : undefined,
t: teamNotification || undefined,
@ -477,7 +491,7 @@ define([
var nowDateStr = new Date().toLocaleDateString();
var startDate = new Date(start);
if (msg.isAllDay && msg.startDay) {
startDate = new Date(msg.startDay);
startDate = Flatpickr.parseDate(msg.startDay);
}
// Missed events

@ -1,7 +1,7 @@
define(['/api/config'], function (ApiConfig) {
var Module = {};
var apps = ['code', 'slide', 'pad', 'kanban', 'whiteboard', 'sheet', 'poll', 'teams'];
var apps = ['code', 'slide', 'pad', 'kanban', 'whiteboard', 'sheet', 'poll', 'teams', 'form'];
var app = window.location.pathname.slice(1, -1); // remove "/" at the beginnin and the end
var suffix = apps.indexOf(app) !== -1 ? '-'+app : '';

@ -72,8 +72,31 @@ define([
return JSONSortify(obj);
};
/* Chrome 92 dropped support for SharedArrayBuffer in cross-origin contexts
where window.crossOriginIsolated is false.
Their blog (https://blog.chromium.org/2021/02/restriction-on-sharedarraybuffers.html)
isn't clear about why they're doing this, but since it's related to site-isolation
it seems they're trying to do vague security things.
In any case, there seems to be a workaround where you can still create them
by using `new WebAssembly.Memory({shared: true, ...})` instead of `new SharedArrayBuffer`.
This seems unreliable, but it's better than not being able to export, since
we actively rely on postMessage between iframes and therefore can't afford
to opt for full isolation.
*/
var supportsSharedArrayBuffers = function () {
try {
return Object.prototype.toString.call(new window.WebAssembly.Memory({shared: true, initial: 0, maximum: 0}).buffer) === '[object SharedArrayBuffer]';
} catch (err) {
console.error(err);
}
return false;
};
var supportsXLSX = function () {
return !(typeof(Atomics) === "undefined" || typeof (SharedArrayBuffer) === "undefined" || typeof(WebAssembly) === 'undefined');
return !(typeof(Atomics) === "undefined" || !supportsSharedArrayBuffers() /* || typeof (SharedArrayBuffer) === "undefined" */ || typeof(WebAssembly) === 'undefined');
};
@ -1915,7 +1938,9 @@ define([
var exportXLSXFile = function() {
var text = getContent();
var suggestion = Title.suggestTitle(Title.defaultTitle);
var ext = ['.xlsx', '.ods', '.bin', '.csv', '.pdf'];
var ext = ['.xlsx', '.ods', '.bin',
//'.csv', // XXX
'.pdf'];
var type = common.getMetadataMgr().getPrivateData().ooType;
var warning = '';
if (type==="presentation") {

@ -12867,13 +12867,13 @@ deleted){self.dependencyFormulas.delColumnTable(tableName,deleted);var wsActive=
wsActive.getHidden())wsActive.setHidden(false);if(!bNoBuildDep)this.dependencyFormulas.initOpen();if(bSnapshot)this.snapshot=this._getSnapshot()};Workbook.prototype.initPostOpenZip=function(pivotCaches){var t=this;this.forEach(function(ws){ws.initPostOpenZip(pivotCaches,t.oNumFmtsOpen)})};Workbook.prototype.setCommonIndexObjectsFrom=function(wb){this.oStyleManager=wb.oStyleManager;this.sharedStrings=wb.sharedStrings;this.workbookFormulas=wb.workbookFormulas};Workbook.prototype.forEach=function(callback,
isCopyPaste){if(isCopyPaste||isCopyPaste===false)callback(this.getActiveWs(),this.getActive());else for(var i=0,l=this.aWorksheets.length;i<l;++i)callback(this.aWorksheets[i],i)};Workbook.prototype.rebuildColors=function(){AscCommonExcel.g_oColorManager.rebuildColors();this.forEach(function(ws){ws.rebuildColors()})};Workbook.prototype.getDefaultFont=function(){return g_oDefaultFormat.Font.getName()};Workbook.prototype.getDefaultSize=function(){return g_oDefaultFormat.Font.getSize()};Workbook.prototype.getActive=
function(){return this.nActive};Workbook.prototype.getActiveWs=function(){return this.getWorksheet(this.nActive)};Workbook.prototype.setActive=function(index){if(index>=0&&index<this.aWorksheets.length){this.nActive=index;this.cleanFindResults();return true}return false};Workbook.prototype.setActiveById=function(sheetId){var ws=this.getWorksheetById(sheetId);if(!ws&&Array.isArray(this.aWorksheets))ws=this.aWorksheets[this.aWorksheets.length-1];return this.setActive(ws.getIndex())};Workbook.prototype.getSheetIdByIndex=
function(index){var ws=this.getWorksheet(index);return ws?ws.getId():null};Workbook.prototype.getWorksheet=function(index){if(index>=0&&index<this.aWorksheets.length)return this.aWorksheets[index];return null};Workbook.prototype.getWorksheetById=function(id){return this.aWorksheetsById[id]};Workbook.prototype.getWorksheetByName=function(name){for(var i=0;i<this.aWorksheets.length;i++)if(this.aWorksheets[i].getName()==name)return this.aWorksheets[i];return null};Workbook.prototype.getWorksheetIndexByName=
function(name){for(var i=0;i<this.aWorksheets.length;i++)if(this.aWorksheets[i].getName()==name)return i;return null};Workbook.prototype.getWorksheetCount=function(){return this.aWorksheets.length};Workbook.prototype.createWorksheet=function(indexBefore,sName,sId){this.dependencyFormulas.lockRecal();History.Create_NewPoint();History.TurnOff();var wsActive=this.getActiveWs();var oNewWorksheet=new Worksheet(this,this.aWorksheets.length,sId);if(this.checkValidSheetName(sName))oNewWorksheet.sName=sName;
oNewWorksheet.initPostOpen(this.wsHandlers,{});if(null!=indexBefore&&indexBefore>=0&&indexBefore<this.aWorksheets.length)this.aWorksheets.splice(indexBefore,0,oNewWorksheet);else{indexBefore=this.aWorksheets.length;this.aWorksheets.push(oNewWorksheet)}this.aWorksheetsById[oNewWorksheet.getId()]=oNewWorksheet;this._updateWorksheetIndexes(wsActive);History.TurnOn();this._insertWorksheetFormula(oNewWorksheet.index);History.Add(AscCommonExcel.g_oUndoRedoWorkbook,AscCH.historyitem_Workbook_SheetAdd,null,
null,new UndoRedoData_SheetAdd(indexBefore,oNewWorksheet.getName(),null,oNewWorksheet.getId()));History.SetSheetUndo(wsActive.getId());History.SetSheetRedo(oNewWorksheet.getId());this.dependencyFormulas.unlockRecal();return oNewWorksheet};Workbook.prototype.copyWorksheet=function(index,insertBefore,sName,sId,bFromRedo,tableNames,opt_sheet,opt_base64){var renameParams;if(index>=0&&index<this.aWorksheets.length){this.dependencyFormulas.buildDependency();History.TurnOff();var wsActive=this.getActiveWs();
var wsFrom=opt_sheet?opt_sheet:this.aWorksheets[index];var newSheet=new Worksheet(this,-1,sId);if(null!=insertBefore&&insertBefore>=0&&insertBefore<this.aWorksheets.length)this.aWorksheets.splice(insertBefore,0,newSheet);else this.aWorksheets.push(newSheet);if(opt_sheet)this.addingWorksheet=newSheet;this.aWorksheetsById[newSheet.getId()]=newSheet;this._updateWorksheetIndexes(wsActive);renameParams=newSheet.copyFrom(wsFrom,sName,tableNames);newSheet.copyFromFormulas(renameParams);newSheet.initPostOpen(this.wsHandlers,
{},{});History.TurnOn();this.dependencyFormulas.copyDefNameByWorksheet(wsFrom,newSheet,renameParams,opt_sheet);if(opt_sheet)this.dependencyFormulas.copyDefNameByWorkbook(wsFrom,newSheet,renameParams,opt_sheet);this._insertWorksheetFormula(insertBefore);if(!tableNames)tableNames=newSheet.getTableNames();History.Add(AscCommonExcel.g_oUndoRedoWorkbook,AscCH.historyitem_Workbook_SheetAdd,null,null,new UndoRedoData_SheetAdd(insertBefore,newSheet.getName(),wsFrom.getId(),newSheet.getId(),tableNames,opt_base64));
History.SetSheetUndo(wsActive.getId());History.SetSheetRedo(newSheet.getId());newSheet.copyFromAfterInsert(wsFrom);if(!(bFromRedo===true)){wsFrom.copyObjects(newSheet,renameParams);var i;if(wsFrom.aNamedSheetViews)for(i=0;i<wsFrom.aNamedSheetViews.length;++i)newSheet.addNamedSheetView(wsFrom.aNamedSheetViews[i].clone(renameParams.tableNameMap));if(wsFrom.dataValidations&&wsFrom.dataValidations.elems)for(i=0;i<wsFrom.dataValidations.elems.length;++i)newSheet.addDataValidation(wsFrom.dataValidations.elems[i].clone(),
function(index){var ws=this.getWorksheet(index);return ws?ws.getId():null};Workbook.prototype.getWorksheet=function(index){if(index>=0&&index<this.aWorksheets.length)return this.aWorksheets[index];return null};Workbook.prototype.getWorksheetById=function(id){if(this.aWorksheetsById[id])return this.aWorksheetsById[id];var s;this.aWorksheets.some(function(_s){if(_s.Id===id){s=_s;return true}});return s};Workbook.prototype.getWorksheetByName=function(name){for(var i=0;i<this.aWorksheets.length;i++)if(this.aWorksheets[i].getName()==
name)return this.aWorksheets[i];return null};Workbook.prototype.getWorksheetIndexByName=function(name){for(var i=0;i<this.aWorksheets.length;i++)if(this.aWorksheets[i].getName()==name)return i;return null};Workbook.prototype.getWorksheetCount=function(){return this.aWorksheets.length};Workbook.prototype.createWorksheet=function(indexBefore,sName,sId){this.dependencyFormulas.lockRecal();History.Create_NewPoint();History.TurnOff();var wsActive=this.getActiveWs();var oNewWorksheet=new Worksheet(this,
this.aWorksheets.length,sId);if(this.checkValidSheetName(sName))oNewWorksheet.sName=sName;oNewWorksheet.initPostOpen(this.wsHandlers,{});if(null!=indexBefore&&indexBefore>=0&&indexBefore<this.aWorksheets.length)this.aWorksheets.splice(indexBefore,0,oNewWorksheet);else{indexBefore=this.aWorksheets.length;this.aWorksheets.push(oNewWorksheet)}this.aWorksheetsById[oNewWorksheet.getId()]=oNewWorksheet;this._updateWorksheetIndexes(wsActive);History.TurnOn();this._insertWorksheetFormula(oNewWorksheet.index);
History.Add(AscCommonExcel.g_oUndoRedoWorkbook,AscCH.historyitem_Workbook_SheetAdd,null,null,new UndoRedoData_SheetAdd(indexBefore,oNewWorksheet.getName(),null,oNewWorksheet.getId()));History.SetSheetUndo(wsActive.getId());History.SetSheetRedo(oNewWorksheet.getId());this.dependencyFormulas.unlockRecal();return oNewWorksheet};Workbook.prototype.copyWorksheet=function(index,insertBefore,sName,sId,bFromRedo,tableNames,opt_sheet,opt_base64){var renameParams;if(index>=0&&index<this.aWorksheets.length){this.dependencyFormulas.buildDependency();
History.TurnOff();var wsActive=this.getActiveWs();var wsFrom=opt_sheet?opt_sheet:this.aWorksheets[index];var newSheet=new Worksheet(this,-1,sId);if(null!=insertBefore&&insertBefore>=0&&insertBefore<this.aWorksheets.length)this.aWorksheets.splice(insertBefore,0,newSheet);else this.aWorksheets.push(newSheet);if(opt_sheet)this.addingWorksheet=newSheet;this.aWorksheetsById[newSheet.getId()]=newSheet;this._updateWorksheetIndexes(wsActive);renameParams=newSheet.copyFrom(wsFrom,sName,tableNames);newSheet.copyFromFormulas(renameParams);
newSheet.initPostOpen(this.wsHandlers,{},{});History.TurnOn();this.dependencyFormulas.copyDefNameByWorksheet(wsFrom,newSheet,renameParams,opt_sheet);if(opt_sheet)this.dependencyFormulas.copyDefNameByWorkbook(wsFrom,newSheet,renameParams,opt_sheet);this._insertWorksheetFormula(insertBefore);if(!tableNames)tableNames=newSheet.getTableNames();History.Add(AscCommonExcel.g_oUndoRedoWorkbook,AscCH.historyitem_Workbook_SheetAdd,null,null,new UndoRedoData_SheetAdd(insertBefore,newSheet.getName(),wsFrom.getId(),
newSheet.getId(),tableNames,opt_base64));History.SetSheetUndo(wsActive.getId());History.SetSheetRedo(newSheet.getId());newSheet.copyFromAfterInsert(wsFrom);if(!(bFromRedo===true)){wsFrom.copyObjects(newSheet,renameParams);var i;if(wsFrom.aNamedSheetViews)for(i=0;i<wsFrom.aNamedSheetViews.length;++i)newSheet.addNamedSheetView(wsFrom.aNamedSheetViews[i].clone(renameParams.tableNameMap));if(wsFrom.dataValidations&&wsFrom.dataValidations.elems)for(i=0;i<wsFrom.dataValidations.elems.length;++i)newSheet.addDataValidation(wsFrom.dataValidations.elems[i].clone(),
true)}this.sortDependency();if(opt_sheet)this.addingWorksheet=null}return renameParams};Workbook.prototype.insertWorksheet=function(index,sheet){var wsActive=this.getActiveWs();if(null!=index&&index>=0&&index<this.aWorksheets.length)this.aWorksheets.splice(index,0,sheet);else this.aWorksheets.push(sheet);this.aWorksheetsById[sheet.getId()]=sheet;this._updateWorksheetIndexes(wsActive);this._insertWorksheetFormula(index);this._insertTablePartsName(sheet);sheet._BuildDependencies(sheet.getCwf());this.sortDependency()};
Workbook.prototype._insertTablePartsName=function(sheet){if(sheet&&sheet.TableParts&&sheet.TableParts.length)for(var i=0;i<sheet.TableParts.length;i++){var tablePart=sheet.TableParts[i];this.dependencyFormulas.addTableName(sheet,tablePart);tablePart.buildDependencies()}};Workbook.prototype._insertWorksheetFormula=function(index){if(index>0&&index<this.aWorksheets.length){var oWsBefore=this.aWorksheets[index-1];this.dependencyFormulas.changeSheet(this.dependencyFormulas.prepareChangeSheet(oWsBefore.getId(),
{insert:index}))}};Workbook.prototype.replaceWorksheet=function(indexFrom,indexTo){if(indexFrom>=0&&indexFrom<this.aWorksheets.length&&indexTo>=0&&indexTo<this.aWorksheets.length){var wsActive=this.getActiveWs();var oWsFrom=this.aWorksheets[indexFrom];var tempW={wF:oWsFrom,wFI:indexFrom,wTI:indexTo};if(tempW.wFI<tempW.wTI)tempW.wTI++;this.dependencyFormulas.lockRecal();var prepared=this.dependencyFormulas.prepareChangeSheet(oWsFrom.getId(),{replace:tempW},null);var movedSheet=this.aWorksheets.splice(indexFrom,

@ -10071,13 +10071,13 @@ deleted){self.dependencyFormulas.delColumnTable(tableName,deleted);var wsActive=
wsActive.getHidden())wsActive.setHidden(false);if(!bNoBuildDep)this.dependencyFormulas.initOpen();if(bSnapshot)this.snapshot=this._getSnapshot()};Workbook.prototype.initPostOpenZip=function(pivotCaches){var t=this;this.forEach(function(ws){ws.initPostOpenZip(pivotCaches,t.oNumFmtsOpen)})};Workbook.prototype.setCommonIndexObjectsFrom=function(wb){this.oStyleManager=wb.oStyleManager;this.sharedStrings=wb.sharedStrings;this.workbookFormulas=wb.workbookFormulas};Workbook.prototype.forEach=function(callback,
isCopyPaste){if(isCopyPaste||isCopyPaste===false)callback(this.getActiveWs(),this.getActive());else for(var i=0,l=this.aWorksheets.length;i<l;++i)callback(this.aWorksheets[i],i)};Workbook.prototype.rebuildColors=function(){AscCommonExcel.g_oColorManager.rebuildColors();this.forEach(function(ws){ws.rebuildColors()})};Workbook.prototype.getDefaultFont=function(){return g_oDefaultFormat.Font.getName()};Workbook.prototype.getDefaultSize=function(){return g_oDefaultFormat.Font.getSize()};Workbook.prototype.getActive=
function(){return this.nActive};Workbook.prototype.getActiveWs=function(){return this.getWorksheet(this.nActive)};Workbook.prototype.setActive=function(index){if(index>=0&&index<this.aWorksheets.length){this.nActive=index;this.cleanFindResults();return true}return false};Workbook.prototype.setActiveById=function(sheetId){var ws=this.getWorksheetById(sheetId);if(!ws&&Array.isArray(this.aWorksheets))ws=this.aWorksheets[this.aWorksheets.length-1];return this.setActive(ws.getIndex())};Workbook.prototype.getSheetIdByIndex=
function(index){var ws=this.getWorksheet(index);return ws?ws.getId():null};Workbook.prototype.getWorksheet=function(index){if(index>=0&&index<this.aWorksheets.length)return this.aWorksheets[index];return null};Workbook.prototype.getWorksheetById=function(id){return this.aWorksheetsById[id]};Workbook.prototype.getWorksheetByName=function(name){for(var i=0;i<this.aWorksheets.length;i++)if(this.aWorksheets[i].getName()==name)return this.aWorksheets[i];return null};Workbook.prototype.getWorksheetIndexByName=
function(name){for(var i=0;i<this.aWorksheets.length;i++)if(this.aWorksheets[i].getName()==name)return i;return null};Workbook.prototype.getWorksheetCount=function(){return this.aWorksheets.length};Workbook.prototype.createWorksheet=function(indexBefore,sName,sId){this.dependencyFormulas.lockRecal();History.Create_NewPoint();History.TurnOff();var wsActive=this.getActiveWs();var oNewWorksheet=new Worksheet(this,this.aWorksheets.length,sId);if(this.checkValidSheetName(sName))oNewWorksheet.sName=sName;
oNewWorksheet.initPostOpen(this.wsHandlers,{});if(null!=indexBefore&&indexBefore>=0&&indexBefore<this.aWorksheets.length)this.aWorksheets.splice(indexBefore,0,oNewWorksheet);else{indexBefore=this.aWorksheets.length;this.aWorksheets.push(oNewWorksheet)}this.aWorksheetsById[oNewWorksheet.getId()]=oNewWorksheet;this._updateWorksheetIndexes(wsActive);History.TurnOn();this._insertWorksheetFormula(oNewWorksheet.index);History.Add(AscCommonExcel.g_oUndoRedoWorkbook,AscCH.historyitem_Workbook_SheetAdd,null,
null,new UndoRedoData_SheetAdd(indexBefore,oNewWorksheet.getName(),null,oNewWorksheet.getId()));History.SetSheetUndo(wsActive.getId());History.SetSheetRedo(oNewWorksheet.getId());this.dependencyFormulas.unlockRecal();return oNewWorksheet};Workbook.prototype.copyWorksheet=function(index,insertBefore,sName,sId,bFromRedo,tableNames,opt_sheet,opt_base64){var renameParams;if(index>=0&&index<this.aWorksheets.length){this.dependencyFormulas.buildDependency();History.TurnOff();var wsActive=this.getActiveWs();
var wsFrom=opt_sheet?opt_sheet:this.aWorksheets[index];var newSheet=new Worksheet(this,-1,sId);if(null!=insertBefore&&insertBefore>=0&&insertBefore<this.aWorksheets.length)this.aWorksheets.splice(insertBefore,0,newSheet);else this.aWorksheets.push(newSheet);if(opt_sheet)this.addingWorksheet=newSheet;this.aWorksheetsById[newSheet.getId()]=newSheet;this._updateWorksheetIndexes(wsActive);renameParams=newSheet.copyFrom(wsFrom,sName,tableNames);newSheet.copyFromFormulas(renameParams);newSheet.initPostOpen(this.wsHandlers,
{},{});History.TurnOn();this.dependencyFormulas.copyDefNameByWorksheet(wsFrom,newSheet,renameParams,opt_sheet);if(opt_sheet)this.dependencyFormulas.copyDefNameByWorkbook(wsFrom,newSheet,renameParams,opt_sheet);this._insertWorksheetFormula(insertBefore);if(!tableNames)tableNames=newSheet.getTableNames();History.Add(AscCommonExcel.g_oUndoRedoWorkbook,AscCH.historyitem_Workbook_SheetAdd,null,null,new UndoRedoData_SheetAdd(insertBefore,newSheet.getName(),wsFrom.getId(),newSheet.getId(),tableNames,opt_base64));
History.SetSheetUndo(wsActive.getId());History.SetSheetRedo(newSheet.getId());newSheet.copyFromAfterInsert(wsFrom);if(!(bFromRedo===true)){wsFrom.copyObjects(newSheet,renameParams);var i;if(wsFrom.aNamedSheetViews)for(i=0;i<wsFrom.aNamedSheetViews.length;++i)newSheet.addNamedSheetView(wsFrom.aNamedSheetViews[i].clone(renameParams.tableNameMap));if(wsFrom.dataValidations&&wsFrom.dataValidations.elems)for(i=0;i<wsFrom.dataValidations.elems.length;++i)newSheet.addDataValidation(wsFrom.dataValidations.elems[i].clone(),
function(index){var ws=this.getWorksheet(index);return ws?ws.getId():null};Workbook.prototype.getWorksheet=function(index){if(index>=0&&index<this.aWorksheets.length)return this.aWorksheets[index];return null};Workbook.prototype.getWorksheetById=function(id){if(this.aWorksheetsById[id])return this.aWorksheetsById[id];var s;this.aWorksheets.some(function(_s){if(_s.Id===id){s=_s;return true}});return s};Workbook.prototype.getWorksheetByName=function(name){for(var i=0;i<this.aWorksheets.length;i++)if(this.aWorksheets[i].getName()==
name)return this.aWorksheets[i];return null};Workbook.prototype.getWorksheetIndexByName=function(name){for(var i=0;i<this.aWorksheets.length;i++)if(this.aWorksheets[i].getName()==name)return i;return null};Workbook.prototype.getWorksheetCount=function(){return this.aWorksheets.length};Workbook.prototype.createWorksheet=function(indexBefore,sName,sId){this.dependencyFormulas.lockRecal();History.Create_NewPoint();History.TurnOff();var wsActive=this.getActiveWs();var oNewWorksheet=new Worksheet(this,
this.aWorksheets.length,sId);if(this.checkValidSheetName(sName))oNewWorksheet.sName=sName;oNewWorksheet.initPostOpen(this.wsHandlers,{});if(null!=indexBefore&&indexBefore>=0&&indexBefore<this.aWorksheets.length)this.aWorksheets.splice(indexBefore,0,oNewWorksheet);else{indexBefore=this.aWorksheets.length;this.aWorksheets.push(oNewWorksheet)}this.aWorksheetsById[oNewWorksheet.getId()]=oNewWorksheet;this._updateWorksheetIndexes(wsActive);History.TurnOn();this._insertWorksheetFormula(oNewWorksheet.index);
History.Add(AscCommonExcel.g_oUndoRedoWorkbook,AscCH.historyitem_Workbook_SheetAdd,null,null,new UndoRedoData_SheetAdd(indexBefore,oNewWorksheet.getName(),null,oNewWorksheet.getId()));History.SetSheetUndo(wsActive.getId());History.SetSheetRedo(oNewWorksheet.getId());this.dependencyFormulas.unlockRecal();return oNewWorksheet};Workbook.prototype.copyWorksheet=function(index,insertBefore,sName,sId,bFromRedo,tableNames,opt_sheet,opt_base64){var renameParams;if(index>=0&&index<this.aWorksheets.length){this.dependencyFormulas.buildDependency();
History.TurnOff();var wsActive=this.getActiveWs();var wsFrom=opt_sheet?opt_sheet:this.aWorksheets[index];var newSheet=new Worksheet(this,-1,sId);if(null!=insertBefore&&insertBefore>=0&&insertBefore<this.aWorksheets.length)this.aWorksheets.splice(insertBefore,0,newSheet);else this.aWorksheets.push(newSheet);if(opt_sheet)this.addingWorksheet=newSheet;this.aWorksheetsById[newSheet.getId()]=newSheet;this._updateWorksheetIndexes(wsActive);renameParams=newSheet.copyFrom(wsFrom,sName,tableNames);newSheet.copyFromFormulas(renameParams);
newSheet.initPostOpen(this.wsHandlers,{},{});History.TurnOn();this.dependencyFormulas.copyDefNameByWorksheet(wsFrom,newSheet,renameParams,opt_sheet);if(opt_sheet)this.dependencyFormulas.copyDefNameByWorkbook(wsFrom,newSheet,renameParams,opt_sheet);this._insertWorksheetFormula(insertBefore);if(!tableNames)tableNames=newSheet.getTableNames();History.Add(AscCommonExcel.g_oUndoRedoWorkbook,AscCH.historyitem_Workbook_SheetAdd,null,null,new UndoRedoData_SheetAdd(insertBefore,newSheet.getName(),wsFrom.getId(),
newSheet.getId(),tableNames,opt_base64));History.SetSheetUndo(wsActive.getId());History.SetSheetRedo(newSheet.getId());newSheet.copyFromAfterInsert(wsFrom);if(!(bFromRedo===true)){wsFrom.copyObjects(newSheet,renameParams);var i;if(wsFrom.aNamedSheetViews)for(i=0;i<wsFrom.aNamedSheetViews.length;++i)newSheet.addNamedSheetView(wsFrom.aNamedSheetViews[i].clone(renameParams.tableNameMap));if(wsFrom.dataValidations&&wsFrom.dataValidations.elems)for(i=0;i<wsFrom.dataValidations.elems.length;++i)newSheet.addDataValidation(wsFrom.dataValidations.elems[i].clone(),
true)}this.sortDependency();if(opt_sheet)this.addingWorksheet=null}return renameParams};Workbook.prototype.insertWorksheet=function(index,sheet){var wsActive=this.getActiveWs();if(null!=index&&index>=0&&index<this.aWorksheets.length)this.aWorksheets.splice(index,0,sheet);else this.aWorksheets.push(sheet);this.aWorksheetsById[sheet.getId()]=sheet;this._updateWorksheetIndexes(wsActive);this._insertWorksheetFormula(index);this._insertTablePartsName(sheet);sheet._BuildDependencies(sheet.getCwf());this.sortDependency()};
Workbook.prototype._insertTablePartsName=function(sheet){if(sheet&&sheet.TableParts&&sheet.TableParts.length)for(var i=0;i<sheet.TableParts.length;i++){var tablePart=sheet.TableParts[i];this.dependencyFormulas.addTableName(sheet,tablePart);tablePart.buildDependencies()}};Workbook.prototype._insertWorksheetFormula=function(index){if(index>0&&index<this.aWorksheets.length){var oWsBefore=this.aWorksheets[index-1];this.dependencyFormulas.changeSheet(this.dependencyFormulas.prepareChangeSheet(oWsBefore.getId(),
{insert:index}))}};Workbook.prototype.replaceWorksheet=function(indexFrom,indexTo){if(indexFrom>=0&&indexFrom<this.aWorksheets.length&&indexTo>=0&&indexTo<this.aWorksheets.length){var wsActive=this.getActiveWs();var oWsFrom=this.aWorksheets[indexFrom];var tempW={wF:oWsFrom,wFI:indexFrom,wTI:indexTo};if(tempW.wFI<tempW.wTI)tempW.wTI++;this.dependencyFormulas.lockRecal();var prepared=this.dependencyFormulas.prepareChangeSheet(oWsFrom.getId(),{replace:tempW},null);var movedSheet=this.aWorksheets.splice(indexFrom,

@ -10019,13 +10019,13 @@ deleted){self.dependencyFormulas.delColumnTable(tableName,deleted);var wsActive=
wsActive.getHidden())wsActive.setHidden(false);if(!bNoBuildDep)this.dependencyFormulas.initOpen();if(bSnapshot)this.snapshot=this._getSnapshot()};Workbook.prototype.initPostOpenZip=function(pivotCaches){var t=this;this.forEach(function(ws){ws.initPostOpenZip(pivotCaches,t.oNumFmtsOpen)})};Workbook.prototype.setCommonIndexObjectsFrom=function(wb){this.oStyleManager=wb.oStyleManager;this.sharedStrings=wb.sharedStrings;this.workbookFormulas=wb.workbookFormulas};Workbook.prototype.forEach=function(callback,
isCopyPaste){if(isCopyPaste||isCopyPaste===false)callback(this.getActiveWs(),this.getActive());else for(var i=0,l=this.aWorksheets.length;i<l;++i)callback(this.aWorksheets[i],i)};Workbook.prototype.rebuildColors=function(){AscCommonExcel.g_oColorManager.rebuildColors();this.forEach(function(ws){ws.rebuildColors()})};Workbook.prototype.getDefaultFont=function(){return g_oDefaultFormat.Font.getName()};Workbook.prototype.getDefaultSize=function(){return g_oDefaultFormat.Font.getSize()};Workbook.prototype.getActive=
function(){return this.nActive};Workbook.prototype.getActiveWs=function(){return this.getWorksheet(this.nActive)};Workbook.prototype.setActive=function(index){if(index>=0&&index<this.aWorksheets.length){this.nActive=index;this.cleanFindResults();return true}return false};Workbook.prototype.setActiveById=function(sheetId){var ws=this.getWorksheetById(sheetId);if(!ws&&Array.isArray(this.aWorksheets))ws=this.aWorksheets[this.aWorksheets.length-1];return this.setActive(ws.getIndex())};Workbook.prototype.getSheetIdByIndex=
function(index){var ws=this.getWorksheet(index);return ws?ws.getId():null};Workbook.prototype.getWorksheet=function(index){if(index>=0&&index<this.aWorksheets.length)return this.aWorksheets[index];return null};Workbook.prototype.getWorksheetById=function(id){return this.aWorksheetsById[id]};Workbook.prototype.getWorksheetByName=function(name){for(var i=0;i<this.aWorksheets.length;i++)if(this.aWorksheets[i].getName()==name)return this.aWorksheets[i];return null};Workbook.prototype.getWorksheetIndexByName=
function(name){for(var i=0;i<this.aWorksheets.length;i++)if(this.aWorksheets[i].getName()==name)return i;return null};Workbook.prototype.getWorksheetCount=function(){return this.aWorksheets.length};Workbook.prototype.createWorksheet=function(indexBefore,sName,sId){this.dependencyFormulas.lockRecal();History.Create_NewPoint();History.TurnOff();var wsActive=this.getActiveWs();var oNewWorksheet=new Worksheet(this,this.aWorksheets.length,sId);if(this.checkValidSheetName(sName))oNewWorksheet.sName=sName;
oNewWorksheet.initPostOpen(this.wsHandlers,{});if(null!=indexBefore&&indexBefore>=0&&indexBefore<this.aWorksheets.length)this.aWorksheets.splice(indexBefore,0,oNewWorksheet);else{indexBefore=this.aWorksheets.length;this.aWorksheets.push(oNewWorksheet)}this.aWorksheetsById[oNewWorksheet.getId()]=oNewWorksheet;this._updateWorksheetIndexes(wsActive);History.TurnOn();this._insertWorksheetFormula(oNewWorksheet.index);History.Add(AscCommonExcel.g_oUndoRedoWorkbook,AscCH.historyitem_Workbook_SheetAdd,null,
null,new UndoRedoData_SheetAdd(indexBefore,oNewWorksheet.getName(),null,oNewWorksheet.getId()));History.SetSheetUndo(wsActive.getId());History.SetSheetRedo(oNewWorksheet.getId());this.dependencyFormulas.unlockRecal();return oNewWorksheet};Workbook.prototype.copyWorksheet=function(index,insertBefore,sName,sId,bFromRedo,tableNames,opt_sheet,opt_base64){var renameParams;if(index>=0&&index<this.aWorksheets.length){this.dependencyFormulas.buildDependency();History.TurnOff();var wsActive=this.getActiveWs();
var wsFrom=opt_sheet?opt_sheet:this.aWorksheets[index];var newSheet=new Worksheet(this,-1,sId);if(null!=insertBefore&&insertBefore>=0&&insertBefore<this.aWorksheets.length)this.aWorksheets.splice(insertBefore,0,newSheet);else this.aWorksheets.push(newSheet);if(opt_sheet)this.addingWorksheet=newSheet;this.aWorksheetsById[newSheet.getId()]=newSheet;this._updateWorksheetIndexes(wsActive);renameParams=newSheet.copyFrom(wsFrom,sName,tableNames);newSheet.copyFromFormulas(renameParams);newSheet.initPostOpen(this.wsHandlers,
{},{});History.TurnOn();this.dependencyFormulas.copyDefNameByWorksheet(wsFrom,newSheet,renameParams,opt_sheet);if(opt_sheet)this.dependencyFormulas.copyDefNameByWorkbook(wsFrom,newSheet,renameParams,opt_sheet);this._insertWorksheetFormula(insertBefore);if(!tableNames)tableNames=newSheet.getTableNames();History.Add(AscCommonExcel.g_oUndoRedoWorkbook,AscCH.historyitem_Workbook_SheetAdd,null,null,new UndoRedoData_SheetAdd(insertBefore,newSheet.getName(),wsFrom.getId(),newSheet.getId(),tableNames,opt_base64));
History.SetSheetUndo(wsActive.getId());History.SetSheetRedo(newSheet.getId());newSheet.copyFromAfterInsert(wsFrom);if(!(bFromRedo===true)){wsFrom.copyObjects(newSheet,renameParams);var i;if(wsFrom.aNamedSheetViews)for(i=0;i<wsFrom.aNamedSheetViews.length;++i)newSheet.addNamedSheetView(wsFrom.aNamedSheetViews[i].clone(renameParams.tableNameMap));if(wsFrom.dataValidations&&wsFrom.dataValidations.elems)for(i=0;i<wsFrom.dataValidations.elems.length;++i)newSheet.addDataValidation(wsFrom.dataValidations.elems[i].clone(),
function(index){var ws=this.getWorksheet(index);return ws?ws.getId():null};Workbook.prototype.getWorksheet=function(index){if(index>=0&&index<this.aWorksheets.length)return this.aWorksheets[index];return null};Workbook.prototype.getWorksheetById=function(id){if(this.aWorksheetsById[id])return this.aWorksheetsById[id];var s;this.aWorksheets.some(function(_s){if(_s.Id===id){s=_s;return true}});return s};Workbook.prototype.getWorksheetByName=function(name){for(var i=0;i<this.aWorksheets.length;i++)if(this.aWorksheets[i].getName()==
name)return this.aWorksheets[i];return null};Workbook.prototype.getWorksheetIndexByName=function(name){for(var i=0;i<this.aWorksheets.length;i++)if(this.aWorksheets[i].getName()==name)return i;return null};Workbook.prototype.getWorksheetCount=function(){return this.aWorksheets.length};Workbook.prototype.createWorksheet=function(indexBefore,sName,sId){this.dependencyFormulas.lockRecal();History.Create_NewPoint();History.TurnOff();var wsActive=this.getActiveWs();var oNewWorksheet=new Worksheet(this,
this.aWorksheets.length,sId);if(this.checkValidSheetName(sName))oNewWorksheet.sName=sName;oNewWorksheet.initPostOpen(this.wsHandlers,{});if(null!=indexBefore&&indexBefore>=0&&indexBefore<this.aWorksheets.length)this.aWorksheets.splice(indexBefore,0,oNewWorksheet);else{indexBefore=this.aWorksheets.length;this.aWorksheets.push(oNewWorksheet)}this.aWorksheetsById[oNewWorksheet.getId()]=oNewWorksheet;this._updateWorksheetIndexes(wsActive);History.TurnOn();this._insertWorksheetFormula(oNewWorksheet.index);
History.Add(AscCommonExcel.g_oUndoRedoWorkbook,AscCH.historyitem_Workbook_SheetAdd,null,null,new UndoRedoData_SheetAdd(indexBefore,oNewWorksheet.getName(),null,oNewWorksheet.getId()));History.SetSheetUndo(wsActive.getId());History.SetSheetRedo(oNewWorksheet.getId());this.dependencyFormulas.unlockRecal();return oNewWorksheet};Workbook.prototype.copyWorksheet=function(index,insertBefore,sName,sId,bFromRedo,tableNames,opt_sheet,opt_base64){var renameParams;if(index>=0&&index<this.aWorksheets.length){this.dependencyFormulas.buildDependency();
History.TurnOff();var wsActive=this.getActiveWs();var wsFrom=opt_sheet?opt_sheet:this.aWorksheets[index];var newSheet=new Worksheet(this,-1,sId);if(null!=insertBefore&&insertBefore>=0&&insertBefore<this.aWorksheets.length)this.aWorksheets.splice(insertBefore,0,newSheet);else this.aWorksheets.push(newSheet);if(opt_sheet)this.addingWorksheet=newSheet;this.aWorksheetsById[newSheet.getId()]=newSheet;this._updateWorksheetIndexes(wsActive);renameParams=newSheet.copyFrom(wsFrom,sName,tableNames);newSheet.copyFromFormulas(renameParams);
newSheet.initPostOpen(this.wsHandlers,{},{});History.TurnOn();this.dependencyFormulas.copyDefNameByWorksheet(wsFrom,newSheet,renameParams,opt_sheet);if(opt_sheet)this.dependencyFormulas.copyDefNameByWorkbook(wsFrom,newSheet,renameParams,opt_sheet);this._insertWorksheetFormula(insertBefore);if(!tableNames)tableNames=newSheet.getTableNames();History.Add(AscCommonExcel.g_oUndoRedoWorkbook,AscCH.historyitem_Workbook_SheetAdd,null,null,new UndoRedoData_SheetAdd(insertBefore,newSheet.getName(),wsFrom.getId(),
newSheet.getId(),tableNames,opt_base64));History.SetSheetUndo(wsActive.getId());History.SetSheetRedo(newSheet.getId());newSheet.copyFromAfterInsert(wsFrom);if(!(bFromRedo===true)){wsFrom.copyObjects(newSheet,renameParams);var i;if(wsFrom.aNamedSheetViews)for(i=0;i<wsFrom.aNamedSheetViews.length;++i)newSheet.addNamedSheetView(wsFrom.aNamedSheetViews[i].clone(renameParams.tableNameMap));if(wsFrom.dataValidations&&wsFrom.dataValidations.elems)for(i=0;i<wsFrom.dataValidations.elems.length;++i)newSheet.addDataValidation(wsFrom.dataValidations.elems[i].clone(),
true)}this.sortDependency();if(opt_sheet)this.addingWorksheet=null}return renameParams};Workbook.prototype.insertWorksheet=function(index,sheet){var wsActive=this.getActiveWs();if(null!=index&&index>=0&&index<this.aWorksheets.length)this.aWorksheets.splice(index,0,sheet);else this.aWorksheets.push(sheet);this.aWorksheetsById[sheet.getId()]=sheet;this._updateWorksheetIndexes(wsActive);this._insertWorksheetFormula(index);this._insertTablePartsName(sheet);sheet._BuildDependencies(sheet.getCwf());this.sortDependency()};
Workbook.prototype._insertTablePartsName=function(sheet){if(sheet&&sheet.TableParts&&sheet.TableParts.length)for(var i=0;i<sheet.TableParts.length;i++){var tablePart=sheet.TableParts[i];this.dependencyFormulas.addTableName(sheet,tablePart);tablePart.buildDependencies()}};Workbook.prototype._insertWorksheetFormula=function(index){if(index>0&&index<this.aWorksheets.length){var oWsBefore=this.aWorksheets[index-1];this.dependencyFormulas.changeSheet(this.dependencyFormulas.prepareChangeSheet(oWsBefore.getId(),
{insert:index}))}};Workbook.prototype.replaceWorksheet=function(indexFrom,indexTo){if(indexFrom>=0&&indexFrom<this.aWorksheets.length&&indexTo>=0&&indexTo<this.aWorksheets.length){var wsActive=this.getActiveWs();var oWsFrom=this.aWorksheets[indexFrom];var tempW={wF:oWsFrom,wFI:indexFrom,wTI:indexTo};if(tempW.wFI<tempW.wTI)tempW.wTI++;this.dependencyFormulas.lockRecal();var prepared=this.dependencyFormulas.prepareChangeSheet(oWsFrom.getId(),{replace:tempW},null);var movedSheet=this.aWorksheets.splice(indexFrom,

@ -1,3 +1,8 @@
function SUPPORTS_SHARED_MEMORY() {
return typeof(SharedArrayBuffer) !== 'undefined';
}
// Support for growable heap + pthreads, where the buffer may change, so JS views
// must be updated.
function GROWABLE_HEAP_STORE_I8(ptr, value) {
@ -1030,7 +1035,7 @@ if (ENVIRONMENT_IS_PTHREAD) {
"maximum": 1073741824 / WASM_PAGE_SIZE,
"shared": true
});
if (!(wasmMemory.buffer instanceof SharedArrayBuffer)) {
if (Object.prototype.toString.call(wasmMemory.buffer) !== '[object SharedArrayBuffer]') {
err("requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag");
if (ENVIRONMENT_HAS_NODE) {
console.log("(on node you may need: --experimental-wasm-threads --experimental-wasm-bulk-memory and also use a recent version)");
@ -2161,7 +2166,7 @@ var PThread = {
}),
receiveObjectTransfer: (function(data) {}),
allocateUnusedWorkers: (function(numWorkers, onFinishedLoading) {
if (typeof SharedArrayBuffer === "undefined") return;
if (!SUPPORTS_SHARED_MEMORY()) return;
var workers = [];
var numWorkersToCreate = numWorkers;
if (PThread.preallocatedWorkers.length > 0) {
@ -2276,7 +2281,7 @@ var PThread = {
}
}),
createNewWorkers: (function(numWorkers) {
if (typeof SharedArrayBuffer === "undefined") return [];
if (!SUPPORTS_SHARED_MEMORY()) return [];
var pthreadMainJs = "x2t.worker.js";
pthreadMainJs = locateFile(pthreadMainJs);
var newWorkers = [];
@ -5683,7 +5688,7 @@ function _emscripten_get_sbrk_ptr() {
}
Module["_emscripten_get_sbrk_ptr"] = _emscripten_get_sbrk_ptr;
function _emscripten_has_threading_support() {
return typeof SharedArrayBuffer !== "undefined";
return SUPPORTS_SHARED_MEMORY();
}
Module["_emscripten_has_threading_support"] = _emscripten_has_threading_support;
function _emscripten_is_main_browser_thread() {
@ -6761,7 +6766,7 @@ function _pthread_self() {
}
Module["_pthread_self"] = _pthread_self;
function _pthread_create(pthread_ptr, attr, start_routine, arg) {
if (typeof SharedArrayBuffer === "undefined") {
if (!SUPPORTS_SHARED_MEMORY()) {
err("Current environment does not support SharedArrayBuffer, pthreads are not available!");
return 6;
}

@ -112,6 +112,7 @@ define([
Store.set = function (clientId, data, cb) {
var s = getStore(data.teamId);
if (!s) { return void cb({ error: 'ENOTFOUND' }); }
if (!s.proxy) { return void cb({ error: 'ENODRIVE' }); }
var path = data.key.slice();
var key = path.pop();
var obj = Util.find(s.proxy, path);
@ -273,9 +274,9 @@ define([
}
var pads = data.pads || data;
s.rpc.pin(pads, function (e, hash) {
s.rpc.pin(pads, function (e) {
if (e) { return void cb({error: e}); }
cb({hash: hash});
cb({});
});
};
@ -288,9 +289,9 @@ define([
if (!s.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
var pads = data.pads || data;
s.rpc.unpin(pads, function (e, hash) {
s.rpc.unpin(pads, function (e) {
if (e) { return void cb({error: e}); }
cb({hash: hash});
cb({});
});
};
@ -393,9 +394,9 @@ define([
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
var list = getCanonicalChannelList(false);
store.rpc.reset(list, function (e, hash) {
store.rpc.reset(list, function (e) {
if (e) { return void cb(e); }
cb(null, hash);
cb(null);
});
};
@ -629,6 +630,7 @@ define([
if (!proxy.uid) {
store.noDriveUid = store.noDriveUid || Hash.createChannelId();
}
var metadata = {
// "user" is shared with everybody via the userlist
user: {
@ -655,7 +657,7 @@ define([
accountName: proxy.login_name || '',
offline: store.proxy && store.offline,
teams: teams,
plan: account.plan
plan: account.plan,
}
};
cb(JSON.parse(JSON.stringify(metadata)));
@ -1226,7 +1228,7 @@ define([
});
// Add the pad if it does not exist in our drive
if (!contains) { // || (ownedByMe && !inMyDrive)) {
if (!contains || (data.forceSave && !inMyDrive)) {
var autoStore = Util.find(store.proxy, ['settings', 'general', 'autostore']);
if (autoStore !== 1 && !data.forceSave && !data.path) {
// send event to inner to display the corner popup
@ -1267,7 +1269,8 @@ define([
});
// Let inner know that dropped files shouldn't trigger the popup
postMessage(clientId, "AUTOSTORE_DISPLAY_POPUP", {
stored: true
stored: true,
inMyDrive: inMyDrive
});
nThen(function (waitFor) {
sendTo.forEach(function (teamId) {
@ -1303,9 +1306,14 @@ define([
getAllStores().forEach(function (s) {
s.manager.getSecureFilesList(where).forEach(function (obj) {
var data = obj.data;
if (channels.indexOf(data.channel) !== -1) { return; }
if (channels.indexOf(data.channel || data.id) !== -1) { return; }
var id = obj.id;
if (data.channel) { channels.push(data.channel); }
if (data.channel) { channels.push(data.channel || data.id); }
// Only include static links if "link" is requested
if (data.static) {
if (types.indexOf('link') !== -1) { list[id] = data; }
return;
}
var parsed = Hash.parsePadUrl(data.href || data.roHref);
if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) &&
!isFiltered(parsed.type, data)) {
@ -2050,8 +2058,17 @@ define([
} catch (e) {
console.error(e);
}
// Tell all the owners that the pad was deleted from the server
var curvePublic = store.proxy.curvePublic;
var curvePublic;
try {
// users in noDrive mode don't have a proxy and
// unregistered users don't have a curvePublic
curvePublic = store.proxy.curvePublic;
} catch (err) {
console.error(err);
return;
}
m.forEach(function (obj) {
var mb = JSON.parse(obj);
if (mb.curvePublic === curvePublic) { return; }
@ -2139,11 +2156,23 @@ define([
if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); }
if (!data.command) { return void cb({ error: 'EINVAL' }); }
var s = getStore(data.teamId);
var otherChannels = data.channels;
delete data.channels;
s.rpc.setMetadata(data, function (err, res) {
if (err) { return void cb({ error: err }); }
if (!Array.isArray(res) || !res.length) { return void cb({}); }
cb(res[0]);
});
// If we have other related channels, send the command for them too
if (Array.isArray(otherChannels)) {
otherChannels.forEach(function (chan) {
var _d = Util.clone(data);
_d.channel = chan;
Store.setPadMetadata(clientId, _d, function () {
});
});
}
};
// GET_FULL_HISTORY from sframe-common-outer
@ -2696,7 +2725,12 @@ define([
nThen(function (waitFor) {
if (!proxy.settings) { proxy.settings = NEW_USER_SETTINGS; }
if (!proxy.forms) { proxy.forms = {}; }
if (!proxy.friends_pending) { proxy.friends_pending = {}; }
// Form seed is used to generate a box encryption keypair when
// answering a form anonymously
if (!proxy.form_seed) { proxy.form_seed = Hash.createChannelId(); }
// Call onCacheReady if the manager is not yet defined
if (!manager) {
@ -3167,6 +3201,10 @@ define([
initialized = false;
}
var redirect = Constants.prefersDriveRedirectKey;
var redirectPreference = Util.find(store, [ 'proxy', 'settings', 'general', redirect, ]);
ret[redirect] = redirectPreference;
callback(ret);
});

@ -49,7 +49,10 @@ define([
}
cb(null, obj.c);
obj.t = +new Date();
cache.setItem(id, obj);
cache.setItem(id, obj, function (err) {
if (!err) { return; }
console.error(err);
});
});
});
};
@ -81,7 +84,10 @@ define([
}
cb(null, obj);
obj.t = +new Date();
cache.setItem(id, obj);
cache.setItem(id, obj, function (err) {
if (!err) { return; }
console.error(err);
});
});
});
};
@ -91,7 +97,7 @@ define([
var checkCheckpoints = function (array) {
if (!Array.isArray(array)) { return; }
// Keep the last 100 messages
if (array.length > 100) {
if (array.length > 100) { // XXX 4.10.0
array.splice(0, array.length - 100);
}
// Remove every message before the first checkpoint

@ -76,6 +76,16 @@ define([
return window.CP_logged_in || typeof getUserHash() === "string";
};
LocalStore.getDriveRedirectPreference = function () {
try {
return JSON.parse(localStorage[Constants.redirectToDriveKey]);
} catch (err) { return; }
};
LocalStore.setDriveRedirectPreference = function (bool) {
localStorage.setItem(Constants.redirectToDriveKey, Boolean(bool));
};
LocalStore.login = function (hash, name, cb) {
if (!hash) { throw new Error('expected a user hash'); }
if (!name) { throw new Error('expected a user name'); }

@ -238,8 +238,9 @@ define([
// content.name, content.title, content.href, content.password
if (isMuted(ctx, data)) { return void cb(true); }
var channel = Hash.hrefToHexChannelId(content.href, content.password);
// if the shared content is a 'link' then we can't use the channel to deduplicate notifications
// use href instead.
var channel = content.isStatic ? content.href : Hash.hrefToHexChannelId(content.href, content.password);
var parsed = Hash.parsePadUrl(content.href);
var mode = parsed.hashData && parsed.hashData.mode || 'n/a';

@ -195,7 +195,7 @@ define([
Pinpad.create(ctx.store.network, data, function (e, call) {
if (e) { return void cb(e); }
team.rpc = call;
team.onRpcReadyEvt.fire();
if (team && team.onRpcReadyEvt) { team.onRpcReadyEvt.fire(); }
cb();
}, Cache);
});
@ -986,8 +986,20 @@ define([
var state = team.roster.getState() || {};
var members = state.members || {};
var md;
nThen(function (waitFor) {
// Get pending owners
var md = team.listmap.metadata || {};
ctx.Store.getPadMetadata(null, {
channel: teamData.channel
}, waitFor(function (obj) {
if (obj && obj.error) {
md = team.listmap.metadata || {};
return;
}
md = obj;
}));
}).nThen(function () {
ctx.pending_owners = md.pending_owners;
if (Array.isArray(md.pending_owners)) {
// Get the members associated to the pending_owners' edPublic and mark them as such
md.pending_owners.forEach(function (ed) {
@ -1027,6 +1039,7 @@ define([
}
cb(members);
});
};
// Return folders with edit rights available to everybody (decrypted pad href)
@ -1144,8 +1157,7 @@ define([
if (!teamData) { return void cb ({error: 'ENOENT'}); }
var team = ctx.teams[teamId];
if (!team) { return void cb ({error: 'ENOENT'}); }
var md = team.listmap.metadata || {};
var isPendingOwner = (md.pending_owners || []).indexOf(user.edPublic) !== -1;
var isPendingOwner = user.pendingOwner;
nThen(function (waitFor) {
var cmd = isPendingOwner ? 'RM_PENDING_OWNERS' : 'RM_OWNERS';
@ -1364,13 +1376,30 @@ define([
var describeUser = function (ctx, data, cId, cb) {
var teamId = data.teamId;
if (!teamId) { return void cb({error: 'EINVAL'}); }
var teamData = Util.find(ctx, ['store', 'proxy', 'teams', teamId]);
var team = ctx.teams[teamId];
if (!team) { return void cb ({error: 'ENOENT'}); }
if (!teamData || !team) { return void cb ({error: 'ENOENT'}); }
if (!team.roster) { return void cb({error: 'NO_ROSTER'}); }
if (!data.curvePublic || !data.data) { return void cb({error: 'MISSING_DATA'}); }
var state = team.roster.getState();
var user = state.members[data.curvePublic];
var md;
nThen(function (waitFor) {
// Get pending owners
ctx.Store.getPadMetadata(null, {
channel: teamData.channel
}, waitFor(function (obj) {
if (obj && obj.error) {
md = team.listmap.metadata || {};
return;
}
md = obj;
}));
}).nThen(function () {
user.pendingOwner = Array.isArray(md.pending_owners) &&
md.pending_owners.indexOf(user.edPublic) !== -1;
// It it is an ownership revocation, we have to set it in pad metadata first
if (user.role === "OWNER" && data.data.role !== "OWNER") {
revokeOwnership(ctx, teamId, user, function (err) {
@ -1401,6 +1430,7 @@ define([
if (err) { return void cb({error: err}); }
cb();
});
});
};
var inviteToTeam = function (ctx, data, cId, cb) {
@ -2010,9 +2040,16 @@ define([
if (['drive', 'teams', 'settings'].indexOf(app) !== -1) { safe = true; }
Object.keys(teams).forEach(function (id) {
if (!ctx.teams[id]) { return; }
var proxy = ctx.teams[id].proxy || {};
var nPads = proxy.drive && Object.keys(proxy.drive.filesData || {}).length;
var nSf = proxy.drive && Object.keys(proxy.drive.sharedFolders || {}).length;
t[id] = {
owner: teams[id].owner,
name: teams[id].metadata.name,
channel: teams[id].channel,
numberPads: nPads,
numberSf: nSf,
roster: Util.find(teams[id], ['keys', 'roster', 'channel']),
edPublic: Util.find(teams[id], ['keys', 'drive', 'edPublic']),
avatar: Util.find(teams[id], ['metadata', 'avatar']),
viewer: !Util.find(teams[id], ['keys', 'drive', 'edPrivate']),

@ -20,6 +20,7 @@ define([
var ROOT = exp.ROOT;
var FILES_DATA = exp.FILES_DATA;
var STATIC_DATA = exp.STATIC_DATA;
var OLD_FILES_DATA = exp.OLD_FILES_DATA;
var UNSORTED = exp.UNSORTED;
var TRASH = exp.TRASH;
@ -78,6 +79,14 @@ define([
files[FILES_DATA][id] = data;
cb(null, id);
};
exp.pushLink = function (_data, cb) {
if (typeof cb !== "function") { cb = function () {}; }
if (readOnly) { return void cb('EFORBIDDEN'); }
var id = Util.createRandomInteger();
var data = clone(_data);
files[STATIC_DATA][id] = data;
cb(null, id);
};
exp.pushSharedFolder = function (_data, cb) {
if (typeof cb !== "function") { cb = function () {}; }
@ -136,7 +145,7 @@ define([
var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
var toClean = [];
exp.getFiles([FILES_DATA, SHARED_FOLDERS]).forEach(function (id) {
exp.getFiles([FILES_DATA, SHARED_FOLDERS, STATIC_DATA]).forEach(function (id) {
if (filesList.indexOf(id) === -1) {
var fd = exp.isSharedFolder(id) ? files[SHARED_FOLDERS][id] : exp.getFileData(id);
var channelId = fd.channel;
@ -146,6 +155,8 @@ define([
if (exp.isSharedFolder(id)) {
delete files[SHARED_FOLDERS][id];
if (config.removeProxy) { config.removeProxy(id); }
} else if (files[STATIC_DATA][id]) {
delete files[STATIC_DATA][id];
} else {
spliceFileData(id);
}
@ -242,6 +253,12 @@ define([
id = Number(id);
// Find and maybe update existing pads with the same channel id
var d = data[id];
// If we were given a static link, copy to STATIC_DATA
if (d.static) {
delete d.static;
files[STATIC_DATA][id] = d;
return;
}
// If we were given an edit link, encrypt its value if needed
if (d.href) { d.href = exp.cryptor.encrypt(d.href); }
var found = false;
@ -398,7 +415,7 @@ define([
if (!loggedIn && !config.testMode) { return; }
id = Number(id);
var data = files[FILES_DATA][id] || files[SHARED_FOLDERS][id];
var data = files[FILES_DATA][id] || files[STATIC_DATA][id] || files[SHARED_FOLDERS][id];
if (!data || typeof(data) !== "object") { return; }
var newPath = path, parentEl;
if (path && !Array.isArray(path)) {
@ -599,13 +616,18 @@ define([
var element = elem || files[ROOT];
if (!element) { return console.error("Invalid element in root"); }
var nbMetadataFolders = 0;
// caching this variables saves a lot of hashmap lookups in this loop
var static_data = files[STATIC_DATA];
var files_data = files[FILES_DATA];
var element_el;
for (var el in element) {
if (element[el] === null) {
element_el = element[el];
if (element_el === null) {
console.error('element[%s] is null', el);
delete element[el];
continue;
}
if (exp.isFolderData(element[el])) {
if (exp.isFolderData(element_el)) {
if (nbMetadataFolders !== 0) {
debug("Multiple metadata files in folder");
delete element[el];
@ -613,30 +635,30 @@ define([
nbMetadataFolders++;
continue;
}
if (!exp.isFile(element[el], true) && !exp.isFolder(element[el])) {
debug("An element in ROOT was not a folder nor a file. ", element[el]);
if (!exp.isFile(element_el, true) && !exp.isFolder(element_el)) {
debug("An element in ROOT was not a folder nor a file. ", element_el);
delete element[el];
continue;
}
if (exp.isFolder(element[el])) {
fixRoot(element[el]);
if (exp.isFolder(element_el)) {
fixRoot(element_el);
continue;
}
if (typeof element[el] === "string") {
if (typeof element_el === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger();
var key = Hash.createChannelId();
files[FILES_DATA][id] = {
href: exp.cryptor.encrypt(element[el]),
files_data[id] = {
href: exp.cryptor.encrypt(element_el),
filename: el
};
element[key] = id;
delete element[el];
}
if (typeof element[el] === "number") {
var data = files[FILES_DATA][element[el]];
if (typeof element_el === "number") {
var data = files_data[element_el] || static_data[element_el];
if (!data) {
debug("An element in ROOT doesn't have associated data", element[el], el);
debug("An element in ROOT doesn't have associated data", element_el, el);
delete element[el];
}
}
@ -845,6 +867,26 @@ define([
toClean.forEach(function (id) {
spliceFileData(id);
});
// make sure that links are displayed at least once in your drive if you are going to keep them
var sd = files[STATIC_DATA];
var toCleanSD = [];
for (var id2 in sd) {
id2 = Number(id2);
var el2 = sd[id2];
if (!el2 || typeof(el2) !== "object" || !el2.href) {
toCleanSD.push(id2);
continue;
}
if ((loggedIn || config.testMode) && rootFiles.indexOf(id2) === -1) {
toCleanSD.push(id2);
continue;
}
}
var spliceSD = function (id) {
if (readOnly) { return; }
delete files[STATIC_DATA][id];
};
toCleanSD.forEach(spliceSD);
};
var fixSharedFolders = function () {
if (sharedFolder) { return; }
@ -892,6 +934,12 @@ define([
}
}
};
var fixStaticData = function () {
if (!Util.isObject(files[STATIC_DATA])) {
debug("STATIC_DATA was not an object");
files[STATIC_DATA] = {};
}
};
var fixDrive = function () {
@ -900,6 +948,7 @@ define([
});
};
fixStaticData();
fixRoot();
fixTrashRoot();
fixTemplate();

@ -5355,10 +5355,12 @@ var PDFFunction = function PDFFunctionClosure() {
var domain = IR[1];
var range = IR[2];
var code = IR[3];
/*
var compiled = new PostScriptCompiler().compile(code, domain, range);
if (compiled) {
return new Function('src', 'srcOffset', 'dest', 'destOffset', compiled);
}
*/
(0, _util.info)('Unable to compile PS function');
var numOutputs = range.length >> 1;
var numInputs = domain.length >> 1;

@ -26,23 +26,19 @@ var factory = function (Util, Rpc) {
exp.send = rpc.send;
// you can ask the server to pin a particular channel for you
exp.pin = function (channels, cb) {
exp.pin = function (channels, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!Array.isArray(channels)) {
setTimeout(function () {
cb('[TypeError] pin expects an array');
});
return;
return void cb('[TypeError] pin expects an array');
}
rpc.send('PIN', channels, cb);
};
// you can also ask to unpin a particular channel
exp.unpin = function (channels, cb) {
exp.unpin = function (channels, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!Array.isArray(channels)) {
setTimeout(function () {
cb('[TypeError] pin expects an array');
});
return;
return void cb('[TypeError] pin expects an array');
}
rpc.send('UNPIN', channels, cb);
};
@ -70,23 +66,12 @@ var factory = function (Util, Rpc) {
};
// if local and remote hashes don't match, send a reset
exp.reset = function (channels, cb) {
exp.reset = function (channels, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!Array.isArray(channels)) {
setTimeout(function () {
cb('[TypeError] pin expects an array');
});
return;
return void cb('[TypeError] pin expects an array');
}
rpc.send('RESET', channels, function (e, response) {
if (e) {
return void cb(e);
}
if (!response.length) {
console.log(response);
return void cb('INVALID_RESPONSE');
}
cb(e, response[0]);
});
rpc.send('RESET', channels, cb);
};
// get the combined size of all channels (in bytes) for all the

@ -22,11 +22,11 @@ define([
// a cached version
if (Env.folders[id].offline && !lm.cache) {
Env.folders[id].offline = false;
if (Env.folders[id].userObject.fixFiles) { Env.folders[id].userObject.fixFiles(); }
Env.Store.refreshDriveUI();
}
return;
}
if (Env.folders[id]) { console.warn(Env.folders[id]); }
var cfg = getConfig(Env);
cfg.sharedFolder = true;
cfg.id = id;
@ -237,7 +237,15 @@ define([
var getSharedFolderData = function (Env, id) {
if (!Env.folders[id]) { return {}; }
var obj = Env.folders[id].proxy.metadata || {};
var proxy = Env.folders[id].proxy;
// Clean deprecated values
if (Object.keys(proxy.metadata || {}).length > 1) {
proxy.metadata = { title: proxy.metadata.title };
}
var obj = Util.clone(proxy.metadata || {});
for (var k in Env.user.proxy[UserObject.SHARED_FOLDERS][id] || {}) {
if (typeof(Env.user.proxy[UserObject.SHARED_FOLDERS][id][k]) === "undefined") { // XXX "deleted folder" for restricted shared folders when viewer in a team
continue;
@ -576,6 +584,24 @@ define([
});
});
};
// Add a link
var _addLink = function (Env, data, cb) {
data = data || {};
var resolved = _resolvePath(Env, data.path);
if (!resolved || !resolved.userObject) { return void cb({error: 'E_NOTFOUND'}); }
var uo = resolved.userObject;
var now = +new Date();
uo.pushLink({
name: data.name,
href: data.href,
atime: now,
ctime: now
}, function (e, id) {
if (e) { return void cb({error: e}); }
uo.add(id, resolved.path);
Env.onSync(cb);
});
};
var _restoreSharedFolder = function (Env, _data, cb) {
var fId = _data.id;
@ -810,6 +836,7 @@ define([
_findChannels(Env, toUnpin).forEach(function (id) {
var data = _getFileData(Env, id);
var arr = [data.channel];
if (data.answersChannel) { arr.push(data.answersChannel); }
if (data.rtChannel) { arr.push(data.rtChannel); }
if (data.lastVersion) { arr.push(Hash.hrefToHexChannelId(data.lastVersion)); }
Array.prototype.push.apply(toKeep, arr);
@ -1010,35 +1037,40 @@ define([
});
};
var _updateStaticAccess = function (Env, id, cb) {
var uo = _getUserObjectFromId(Env, id);
var sd = uo.getFileData(id, true);
sd.atime = +new Date();
Env.onSync(cb);
};
var COMMANDS = {
move: _move,
restore: _restore,
addFolder: _addFolder,
addSharedFolder: _addSharedFolder,
addLink: _addLink,
restoreSharedFolder: _restoreSharedFolder,
convertFolderToSharedFolder: _convertFolderToSharedFolder,
delete: _delete,
deleteOwned: _deleteOwned,
emptyTrash: _emptyTrash,
rename: _rename,
setFolderData: _setFolderData,
updateStaticAccess: _updateStaticAccess,
};
var onCommand = function (Env, cmdData, cb) {
var cmd = cmdData.cmd;
var data = cmdData.data || {};
switch (cmd) {
case 'move':
_move(Env, data, cb); break;
case 'restore':
_restore(Env, data, cb); break;
case 'addFolder':
_addFolder(Env, data, cb); break;
case 'addSharedFolder':
_addSharedFolder(Env, data, cb); break;
case 'restoreSharedFolder':
_restoreSharedFolder(Env, data, cb); break;
case 'convertFolderToSharedFolder':
_convertFolderToSharedFolder(Env, data, cb); break;
case 'delete':
_delete(Env, data, cb); break;
case 'deleteOwned':
_deleteOwned(Env, data, cb); break;
case 'emptyTrash':
_emptyTrash(Env, data, cb); break;
case 'rename':
_rename(Env, data, cb); break;
case 'setFolderData':
_setFolderData(Env, data, cb); break;
default:
cb();
var method = COMMANDS[cmd];
if (typeof(method) === 'function') {
return void method(Env, data, cb);
}
// if the command was not handled then call back
cb();
};
// Set the value everywhere the given pad is stored (main and shared folders)
@ -1120,8 +1152,8 @@ define([
data: uo.getFileData(id)
};
}).filter(function (d) {
if (channels.indexOf(d.data.channel) === -1) {
channels.push(d.data.channel);
if (channels.indexOf(d.data.channel || d.id) === -1) {
channels.push(d.data.channel || d.id);
return true;
}
});
@ -1176,6 +1208,10 @@ define([
result.push(otherChan);
}
}
// Pin form answers channels
if (data.answersChannel && result.indexOf(data.answersChannel) === -1) {
result.push(data.answersChannel);
}
// Pin onlyoffice realtime patches
if (data.rtChannel && result.indexOf(data.rtChannel) === -1) {
result.push(data.rtChannel);
@ -1220,7 +1256,10 @@ define([
Array.prototype.push.apply(result, sfChannels);
}
return result;
return result.filter(function (channel) {
if (typeof(channel) !== 'string') { return; }
return [32, 48].indexOf(channel.length) !== -1;
});
};
var addPad = function (Env, path, pad, cb) {
@ -1370,6 +1409,16 @@ define([
}
}, cb);
};
var addLinkInner = function (Env, path, data, cb) {
return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "addLink",
data: {
path: path,
name: data.name,
href: data.url
}
}, cb);
};
var restoreSharedFolderInner = function (Env, fId, password, cb) {
return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "restoreSharedFolder",
@ -1420,6 +1469,14 @@ define([
}, cb);
};
var updateStaticAccessInner = function (Env, id, cb) {
return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "updateStaticAccess",
data: id
}, cb);
};
/* Tools */
var findChannels = _findChannels;
@ -1437,6 +1494,11 @@ define([
return String(uo.getTitle(id, type));
};
var isStaticFile = function (Env, id) {
var uo = _getUserObjectFromId(Env, id);
return uo.isStaticFile(id);
};
var isReadOnlyFile = function (Env, id) {
var uo = _getUserObjectFromId(Env, id);
return uo.isReadOnlyFile(id);
@ -1478,7 +1540,7 @@ define([
var files = [];
var userObjects = _getUserObjects(Env);
userObjects.forEach(function (uo) {
var data = uo.getFiles([UserObject.FILES_DATA]).map(function (id) {
var data = uo.getFiles([UserObject.FILES_DATA, UserObject.STATIC_DATA]).map(function (id) {
return [Number(id), uo.getFileData(id)];
});
Array.prototype.push.apply(files, data);
@ -1595,17 +1657,20 @@ define([
emptyTrash: callWithEnv(emptyTrashInner),
addFolder: callWithEnv(addFolderInner),
addSharedFolder: callWithEnv(addSharedFolderInner),
addLink: callWithEnv(addLinkInner),
restoreSharedFolder: callWithEnv(restoreSharedFolderInner),
convertFolderToSharedFolder: callWithEnv(convertFolderToSharedFolderInner),
delete: callWithEnv(deleteInner),
deleteOwned: callWithEnv(deleteOwnedInner),
restore: callWithEnv(restoreInner),
setFolderData: callWithEnv(setFolderDataInner),
updateStaticAccess: callWithEnv(updateStaticAccessInner),
// Tools
getFileData: callWithEnv(getFileData),
find: callWithEnv(find),
getTitle: callWithEnv(getTitle),
isReadOnlyFile: callWithEnv(isReadOnlyFile),
isStaticFile: callWithEnv(isStaticFile),
getFiles: callWithEnv(getFiles),
search: callWithEnv(search),
getRecentPads: callWithEnv(getRecentPads),

@ -572,7 +572,9 @@ define([
if (!readOnly) { onLocal(); }
evOnReady.fire(newPad);
common.openPadChat(onLocal);
// In forms, only editors can see the chat
if (!readOnly || type !== 'form') { common.openPadChat(onLocal); }
if (!readOnly && cursorGetter) {
common.openCursorChannel(onLocal);
cursor = common.createCursor(onLocal);
@ -731,11 +733,20 @@ define([
if (!common.isLoggedIn()) { return; }
$embedButton = common.createButton('mediatag', true).click(function () {
var cfg = {
types: ['file'],
types: ['file', 'link'],
where: ['root']
};
if ($embedButton.data('filter')) { cfg.filter = $embedButton.data('filter'); }
common.openFilePicker(cfg, function (data) {
// Embed links
if (data.static) {
var a = h('a', {
href: data.href
}, data.name);
mediaTagEmbedder($(a), data);
return;
}
// Embed files
if (data.type !== 'file') {
console.log("Unexpected data type picked " + data.type);
return;
@ -746,8 +757,8 @@ define([
var privateDat = cpNfInner.metadataMgr.getPrivateData();
var origin = privateDat.fileHost || privateDat.origin;
var src = data.src = data.src.slice(0,1) === '/' ? origin + data.src : data.src;
mediaTagEmbedder($('<media-tag src="' + src +
'" data-crypto-key="cryptpad:' + data.key + '"></media-tag>'), data);
var mt = UI.mediaTag(src, data.key);
mediaTagEmbedder($(mt), data);
});
}).appendTo(toolbar.$bottomL).hide();
};
@ -925,7 +936,9 @@ define([
}
var $importTemplateButton = common.createButton('importtemplate', true);
if (!readOnly) {
toolbar.$drawer.append($importTemplateButton);
}
/* add a forget button */
toolbar.$drawer.append(common.createButton('forget', true, {}, function (err) {

@ -73,6 +73,8 @@ define([
else {
editor.setSelection(posToCursor(selects[0], remoteDoc), posToCursor(selects[1], remoteDoc));
}
editor.scrollTo(scroll.left, scroll.top);
};
module.handleImagePaste = function (editor) {

@ -570,7 +570,7 @@ define([
var defaultTitle = Utils.UserObject.getDefaultName(parsed);
var edPublic, curvePublic, notifications, isTemplate;
var settings = {};
var isSafe = ['debug', 'profile', 'drive', 'teams', 'calendar'].indexOf(currentPad.app) !== -1;
var isSafe = ['debug', 'profile', 'drive', 'teams', 'calendar', 'file'].indexOf(currentPad.app) !== -1;
var isDeleted = isNewFile && currentPad.hash.length > 0;
if (isDeleted) {
@ -615,6 +615,7 @@ define([
newTemplate: Array.isArray(Cryptpad.initialPath)
&& Cryptpad.initialPath[0] === "template",
feedbackAllowed: Utils.Feedback.state,
prefersDriveRedirect: Utils.LocalStore.getDriveRedirectPreference(),
isPresent: parsed.hashData && parsed.hashData.present,
isEmbed: parsed.hashData && parsed.hashData.embed,
oldVersionHash: parsed.hashData && parsed.hashData.version < 2, // password
@ -1477,13 +1478,24 @@ define([
return 'thumbnail-' + parsed.type + '-' + channel;
};
sframeChan.on('Q_CREATE_TEMPLATES', function (type, cb) {
var templates;
nThen(function (waitFor) {
var next = waitFor();
require([
'/'+type+'/templates.js'
], function (Templates) {
templates = Templates;
next();
}, function () {
next();
});
}).nThen(function () {
Cryptpad.getSecureFilesList({
types: [type],
where: ['template']
}, function (err, data) {
// NOTE: Never return data directly!
if (err) { return void cb({error: err}); }
var res = [];
nThen(function (waitFor) {
Object.keys(data).map(function (el) {
@ -1498,10 +1510,16 @@ define([
}));
});
}).nThen(function () {
if (Array.isArray(templates)) {
templates.forEach(function (obj) {
res.push(obj);
});
}
cb({data: res});
});
});
});
});
sframeChan.on('Q_GET_FILE_THUMBNAIL', function (data, cb) {
if (!Cryptpad.fromFileData || !Cryptpad.fromFileData.href) {
@ -1891,6 +1909,7 @@ define([
Utils.rtConfig = rtConfig;
var templatePw;
nThen(function(waitFor) {
if (data.templateContent) { return; }
if (data.templateId) {
if (data.templateId === -1) {
isTemplate = true;
@ -1904,6 +1923,13 @@ define([
}
}).nThen(function () {
var cryptputCfg = $.extend(true, {}, rtConfig, {password: password});
if (data.templateContent) {
Cryptget.put(currentPad.hash, JSON.stringify(data.templateContent), function () {
startRealtime();
cb();
}, cryptputCfg);
return;
}
if (data.template) {
// Start OO with a template...
// Cryptget and give href, password and content to inner
@ -1977,6 +2003,8 @@ define([
sframeChan.on('EV_BURN_AFTER_READING', function () {
startRealtime();
// feedback fails for users in noDrive mode
Utils.Feedback.send("BURN_AFTER_READING", Boolean(cfg.noDrive));
});
sframeChan.ready();

@ -23,6 +23,12 @@ define([
var $title;
exp.setToolbar = function (toolbar) {
$title = toolbar && (toolbar.title || toolbar.pageTitle);
if ($title && exp.defaultTitle) {
var md = metadataMgr.getMetadata();
$title.find('input').prop('placeholder', md.defaultTitle);
$title.find('span.cp-toolbar-title-value').text(md.title || md.defaultTitle);
$title.find('input').val(md.title || md.defaultTitle);
}
};
exp.getTitle = function () { return exp.title; };

@ -145,8 +145,7 @@ define([
var hexFileName = secret.channel;
var origin = data.fileHost || data.origin;
var src = origin + Hash.getBlobPathFromHex(hexFileName);
return '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '">' +
'</media-tag>';
return UI.mediaTag(src, key).outerHTML;
}
return;
};
@ -448,13 +447,18 @@ define([
}
};
funcs.createPad = function (cfg, cb) {
//var priv = ctx.metadataMgr.getPrivateData();
if (AppConfig.disableAnonymousPadCreation && !funcs.isLoggedIn()) {
return void UI.errorLoadingScreen(Messages.mustLogin);
}
ctx.sframeChan.query("Q_CREATE_PAD", {
owned: cfg.owned,
expire: cfg.expire,
password: cfg.password,
team: cfg.team,
template: cfg.template,
templateId: cfg.templateId
templateId: cfg.templateId,
templateContent: cfg.templateContent
}, cb);
};
@ -917,7 +921,7 @@ define([
});
ctx.sframeChan.on('EV_WORKER_TIMEOUT', function () {
UI.errorLoadingScreen(Messages.timeoutError, false, function () {
UI.errorLoadingScreen(Messages.timeoutError, false, function () { // XXX mobile users can't necessarily hit 'ESC' as this message suggests. provice a click option
funcs.gotoURL('');
});
});

@ -166,6 +166,7 @@ MessengerUI, Messages, Pages) {
var showColors = false;
var updateUserList = function (toolbar, config, forceOffline) {
if (!config.displayed || config.displayed.indexOf('userlist') === -1) { return; }
if (toolbar.isAlone) { return; }
// Make sure the elements are displayed
var $userButtons = toolbar.userlist;
var $userlistContent = toolbar.userlistContent;
@ -553,11 +554,13 @@ MessengerUI, Messages, Pages) {
if (toolbar.isDeleted) {
return void UI.warn(Messages.deletedFromServer);
}
var privateData = config.metadataMgr.getPrivateData();
var title = (config.title && config.title.getTitle && config.title.getTitle())
|| (config.title && config.title.defaultName)
|| "";
Common.getSframeChannel().event('EV_SHARE_OPEN', {
title: title
title: title,
auditorHash: privateData.form_auditorHash
});
});
@ -864,10 +867,6 @@ MessengerUI, Messages, Pages) {
'class': "cp-toolbar-link-logo"
}).append(UIElements.getSvgLogo());
/*.append($('<img>', {
//src: '/customize/images/logo_white.png?' + ApiConfig.requireConf.urlArgs
src: '/customize/favicon/main-favicon.png?' + ApiConfig.requireConf.urlArgs
}));*/
var onClick = function (e) {
e.preventDefault();
if (e.ctrlKey) {
@ -1214,6 +1213,7 @@ MessengerUI, Messages, Pages) {
if (!config.metadataMgr) { return; }
var metadataMgr = config.metadataMgr;
var notify = function(type, name, oldname) {
if (toolbar.isAlone) { return; }
// type : 1 (+1 user), 0 (rename existing user), -1 (-1 user)
if (typeof name === "undefined") { return; }
name = name || Messages.anonymous;
@ -1513,6 +1513,15 @@ MessengerUI, Messages, Pages) {
}
};
// disable notification, userlist and chat
toolbar.alone = function () {
toolbar.userlist.hide();
toolbar.chat.hide();
$('.cp-toolbar-userlist-drawer').remove();
$('.cp-toolbar-chat-drawer').remove();
toolbar.isAlone = true;
};
// On log out, remove permanently the realtime elements of the toolbar
Common.onLogout(function () {
failed();

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

Loading…
Cancel
Save