mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2025-07-23 23:00:08 +02:00
Compare commits
253 commits
56a614267f
...
016ec8c38d
Author | SHA1 | Date | |
---|---|---|---|
![]() |
016ec8c38d | ||
![]() |
a64663190c | ||
![]() |
f56e00faeb | ||
![]() |
438a5d2930 | ||
![]() |
7bad59fb14 | ||
![]() |
f4dc8f7ebc | ||
![]() |
456f751a18 | ||
![]() |
48df8c9f38 | ||
![]() |
7540bc9cbe | ||
![]() |
5aacccc9d0 | ||
![]() |
3fe4cd31b9 | ||
![]() |
8d8142ba2b | ||
![]() |
cf668d897d | ||
![]() |
71a33925b6 | ||
![]() |
98af0e3d82 | ||
![]() |
5208ad39ec | ||
![]() |
2e82852ed0 | ||
![]() |
b0776da02c | ||
![]() |
ec2ab62f4d | ||
![]() |
be4a11484f | ||
![]() |
97eceb9c58 | ||
![]() |
63ca134fc5 | ||
![]() |
ee8e1915ab | ||
![]() |
f054a5658f | ||
![]() |
5df6047fd8 | ||
![]() |
29d9408a69 | ||
![]() |
9c16a85295 | ||
![]() |
5669a7d1c8 | ||
![]() |
93c6353b90 | ||
![]() |
9720413218 | ||
![]() |
8c39add865 | ||
![]() |
3e29af53a8 | ||
![]() |
1833bd3d00 | ||
![]() |
1daa49d9d2 | ||
![]() |
9e22ed9c3f | ||
![]() |
1375f695d3 | ||
![]() |
5b5e650754 | ||
![]() |
ce074ecf11 | ||
![]() |
42a408c6ae | ||
![]() |
488d0fdf9e | ||
![]() |
524402e817 | ||
![]() |
3c24c9bcc9 | ||
![]() |
1768bef22a | ||
![]() |
0f1d672a57 | ||
![]() |
2e2415c56e | ||
![]() |
caedd0f850 | ||
![]() |
1b70f09bee | ||
![]() |
fe8964cc0a | ||
![]() |
10f4722641 | ||
![]() |
4c03ebe4ee | ||
![]() |
c20c17b68e | ||
![]() |
ea2ba8cdbe | ||
![]() |
2ac46151f8 | ||
![]() |
122c618e53 | ||
![]() |
a9fa1aa598 | ||
![]() |
6abd4fe23e | ||
![]() |
e35f79bf32 | ||
![]() |
35f5588be4 | ||
![]() |
39b4581e1d | ||
![]() |
72aca352fc | ||
![]() |
5351488f0e | ||
![]() |
406312d495 | ||
![]() |
d5e00845c6 | ||
![]() |
bc49968908 | ||
![]() |
2f2862ecd7 | ||
![]() |
85ecef4d96 | ||
![]() |
8a07389ce6 | ||
![]() |
028d4b481f | ||
![]() |
fd8a4ac020 | ||
![]() |
e38b04a3ee | ||
![]() |
e7918247b4 | ||
![]() |
beb3d87a77 | ||
![]() |
d014db9893 | ||
![]() |
f0d2df3c43 | ||
![]() |
8fab1b54a3 | ||
![]() |
97f1425326 | ||
![]() |
f5dd356df3 | ||
![]() |
8efe921326 | ||
![]() |
ca426842c1 | ||
![]() |
da0967e902 | ||
![]() |
95b5c1f659 | ||
![]() |
6bdcd1f803 | ||
![]() |
14afc82acf | ||
![]() |
029395ebb9 | ||
![]() |
9e6ca01e7f | ||
![]() |
4ab4fbdf51 | ||
![]() |
6b6ff921ef | ||
![]() |
bfa0459e72 | ||
![]() |
e4a9b13e9a | ||
![]() |
102f3a5d5c | ||
![]() |
1083a05d69 | ||
![]() |
698d2aebb1 | ||
![]() |
8ea682cba3 | ||
![]() |
c62c2d6157 | ||
![]() |
1e9dcd9983 | ||
![]() |
da203d6e96 | ||
![]() |
298f56cbe5 | ||
![]() |
3c63c1ac3e | ||
![]() |
11cb140fe3 | ||
![]() |
d617fd0754 | ||
![]() |
3e9d60c25a | ||
![]() |
bd64f09b91 | ||
![]() |
b410de7c39 | ||
![]() |
b87b83e33c | ||
![]() |
b843603bb1 | ||
![]() |
b165fea0de | ||
![]() |
7219043c6b | ||
![]() |
bae2eb3861 | ||
![]() |
59b34d7c2b | ||
![]() |
905cc5d45f | ||
![]() |
9237d8227b | ||
![]() |
349fb62f60 | ||
![]() |
8adc7526d9 | ||
![]() |
97d2954cc0 | ||
![]() |
6969caf690 | ||
![]() |
e17eaf51d2 | ||
![]() |
faabd91e43 | ||
![]() |
496093203a | ||
![]() |
74063b14e0 | ||
![]() |
6ce114da77 | ||
![]() |
66b7fd8844 | ||
![]() |
ebf810e836 | ||
![]() |
9d1641a1ab | ||
![]() |
aa9dcb19a3 | ||
![]() |
9b43b82c95 | ||
![]() |
c5fad346f9 | ||
![]() |
37b63f9823 | ||
![]() |
cb945472c7 | ||
![]() |
9fc0373fab | ||
![]() |
1a1f283245 | ||
![]() |
f0827f0e67 | ||
![]() |
519b18e4a1 | ||
![]() |
1b03bb6e5d | ||
![]() |
8a5e2b0ea3 | ||
![]() |
6176f4d074 | ||
![]() |
132ccbc4cc | ||
![]() |
b2fb70c2b6 | ||
![]() |
5ed1225162 | ||
![]() |
83a00e7546 | ||
![]() |
16f3795c3e | ||
![]() |
b3c339464e | ||
![]() |
d4ab715a9b | ||
![]() |
5973c90e39 | ||
![]() |
2cacf8f645 | ||
![]() |
16135bde64 | ||
![]() |
23403a7ef2 | ||
![]() |
c10b95ae06 | ||
![]() |
b89951cf5d | ||
![]() |
56232b48a7 | ||
![]() |
bd3ef799f3 | ||
![]() |
913caca379 | ||
![]() |
c6b576b25a | ||
![]() |
d7a0cbefea | ||
![]() |
cf472f54d0 | ||
![]() |
d57d0ca738 | ||
![]() |
7d5bf2c0dd | ||
![]() |
0c462d45d1 | ||
![]() |
07b1701b72 | ||
![]() |
09a534f48b | ||
![]() |
bb5b9afd0e | ||
![]() |
3681be7a71 | ||
![]() |
2b8a14c4d2 | ||
![]() |
a7e3ce67ea | ||
![]() |
425344b40b | ||
![]() |
cbe96f21cb | ||
![]() |
4e40f24a9e | ||
![]() |
1b02a26d1f | ||
![]() |
8cd8d8239b | ||
![]() |
10b6f82677 | ||
![]() |
b7032e925f | ||
![]() |
9de32e1570 | ||
![]() |
e660718c63 | ||
![]() |
d701b8ff9b | ||
![]() |
f5038b5de3 | ||
![]() |
381a106b46 | ||
![]() |
90226043c7 | ||
![]() |
a74c0abdf5 | ||
![]() |
4eee7cd6ef | ||
![]() |
3f1a930046 | ||
![]() |
97863f62ca | ||
![]() |
58303de9f4 | ||
![]() |
06153de0aa | ||
![]() |
d8d53f76ca | ||
![]() |
d55cb6af5e | ||
![]() |
36c4c65609 | ||
![]() |
75d443934f | ||
![]() |
c538091fa8 | ||
![]() |
747224ac10 | ||
![]() |
af0c0e5bcd | ||
![]() |
7c92e0719c | ||
![]() |
40136785dd | ||
![]() |
67342e9c33 | ||
![]() |
ebb2cff2af | ||
![]() |
affd1bea49 | ||
![]() |
904fac958d | ||
![]() |
dc97da1276 | ||
![]() |
6a56c64d9a | ||
![]() |
79e5a884f5 | ||
![]() |
2a9d3bb168 | ||
![]() |
28c227157e | ||
![]() |
fe5d6ddf5c | ||
![]() |
8c8f4177d9 | ||
![]() |
ffb7f23dfd | ||
![]() |
f7a68cb503 | ||
![]() |
3bdc8316e9 | ||
![]() |
00d5d879c5 | ||
![]() |
735cc2272f | ||
![]() |
a38eefd2ab | ||
![]() |
04e026904f | ||
![]() |
6d7b13ac18 | ||
![]() |
1950585514 | ||
![]() |
99e580047c | ||
![]() |
6965618c34 | ||
![]() |
3b65a674d0 | ||
![]() |
13d3d0c552 | ||
![]() |
169e37cf31 | ||
![]() |
77d5c8fa64 | ||
![]() |
9d267f0803 | ||
![]() |
4fd9c6fd18 | ||
![]() |
e562e3d1bf | ||
![]() |
73ae6f480f | ||
![]() |
886bad1071 | ||
![]() |
87de73333a | ||
![]() |
19980b41b8 | ||
![]() |
1543f0a53e | ||
![]() |
12a1a85a2f | ||
![]() |
4550cfc6a0 | ||
![]() |
d3d5674436 | ||
![]() |
e76a8ff267 | ||
![]() |
c7ae851d39 | ||
![]() |
903277c222 | ||
![]() |
34b674aa6f | ||
![]() |
745043ca92 | ||
![]() |
6ed488cc65 | ||
![]() |
f2b0a982ac | ||
![]() |
797f29d40a | ||
![]() |
b42b0a8d3e | ||
![]() |
392037efd6 | ||
![]() |
3600aa4b75 | ||
![]() |
25a911d4cb | ||
![]() |
4157f8ca00 | ||
![]() |
48379a9d86 | ||
![]() |
50b5aa33b1 | ||
![]() |
fda96b2fdf | ||
![]() |
f53a13ae1e | ||
![]() |
f0c63b6ccd | ||
![]() |
e6d09ca748 | ||
![]() |
9e189e7d59 | ||
![]() |
ab140f578d | ||
![]() |
de1ab31bfd | ||
![]() |
2d5a3efc12 | ||
![]() |
46555d27b0 | ||
![]() |
c40ab3234d |
854 changed files with 17079 additions and 13771 deletions
|
@ -23,6 +23,11 @@
|
||||||
|
|
||||||
[Overleaf](https://www.overleaf.com) is an open-source online real-time collaborative LaTeX editor. We run a hosted version at [www.overleaf.com](https://www.overleaf.com), but you can also run your own local version, and contribute to the development of Overleaf.
|
[Overleaf](https://www.overleaf.com) is an open-source online real-time collaborative LaTeX editor. We run a hosted version at [www.overleaf.com](https://www.overleaf.com), but you can also run your own local version, and contribute to the development of Overleaf.
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> Overleaf Community Edition is intended for use in environments where **all** users are trusted. Community Edition is **not** appropriate for scenarios where isolation of users is required due to Sandbox Compiles not being available. When not using Sandboxed Compiles, users have full read and write access to the `sharelatex` container resources (filesystem, network, environment variables) when running LaTeX compiles.
|
||||||
|
|
||||||
|
For more information on Sandbox Compiles check out our [documentation](https://docs.overleaf.com/on-premises/configuration/overleaf-toolkit/server-pro-only-configuration/sandboxed-compiles).
|
||||||
|
|
||||||
## Enterprise
|
## Enterprise
|
||||||
|
|
||||||
If you want help installing and maintaining Overleaf in your lab or workplace, we offer an officially supported version called [Overleaf Server Pro](https://www.overleaf.com/for/enterprises). It also includes more features for security (SSO with LDAP or SAML), administration and collaboration (e.g. tracked changes). [Find out more!](https://www.overleaf.com/for/enterprises)
|
If you want help installing and maintaining Overleaf in your lab or workplace, we offer an officially supported version called [Overleaf Server Pro](https://www.overleaf.com/for/enterprises). It also includes more features for security (SSO with LDAP or SAML), administration and collaboration (e.g. tracked changes). [Find out more!](https://www.overleaf.com/for/enterprises)
|
||||||
|
|
|
@ -42,7 +42,7 @@ To do this, use the included `bin/dev` script:
|
||||||
bin/dev
|
bin/dev
|
||||||
```
|
```
|
||||||
|
|
||||||
This will start all services using `nodemon`, which will automatically monitor the code and restart the services as necessary.
|
This will start all services using `node --watch`, which will automatically monitor the code and restart the services as necessary.
|
||||||
|
|
||||||
To improve performance, you can start only a subset of the services in development mode by providing a space-separated list to the `bin/dev` script:
|
To improve performance, you can start only a subset of the services in development mode by providing a space-separated list to the `bin/dev` script:
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,17 @@ DOCUMENT_UPDATER_HOST=document-updater
|
||||||
FILESTORE_HOST=filestore
|
FILESTORE_HOST=filestore
|
||||||
GRACEFUL_SHUTDOWN_DELAY_SECONDS=0
|
GRACEFUL_SHUTDOWN_DELAY_SECONDS=0
|
||||||
HISTORY_V1_HOST=history-v1
|
HISTORY_V1_HOST=history-v1
|
||||||
|
HISTORY_REDIS_HOST=redis
|
||||||
LISTEN_ADDRESS=0.0.0.0
|
LISTEN_ADDRESS=0.0.0.0
|
||||||
MONGO_HOST=mongo
|
MONGO_HOST=mongo
|
||||||
MONGO_URL=mongodb://mongo/sharelatex?directConnection=true
|
MONGO_URL=mongodb://mongo/sharelatex?directConnection=true
|
||||||
NOTIFICATIONS_HOST=notifications
|
NOTIFICATIONS_HOST=notifications
|
||||||
PROJECT_HISTORY_HOST=project-history
|
PROJECT_HISTORY_HOST=project-history
|
||||||
|
QUEUES_REDIS_HOST=redis
|
||||||
REALTIME_HOST=real-time
|
REALTIME_HOST=real-time
|
||||||
REDIS_HOST=redis
|
REDIS_HOST=redis
|
||||||
SESSION_SECRET=foo
|
SESSION_SECRET=foo
|
||||||
|
V1_HISTORY_HOST=history-v1
|
||||||
WEBPACK_HOST=webpack
|
WEBPACK_HOST=webpack
|
||||||
WEB_API_PASSWORD=overleaf
|
WEB_API_PASSWORD=overleaf
|
||||||
WEB_API_USER=overleaf
|
WEB_API_USER=overleaf
|
||||||
|
|
|
@ -113,7 +113,7 @@ services:
|
||||||
- ../services/real-time/config:/overleaf/services/real-time/config
|
- ../services/real-time/config:/overleaf/services/real-time/config
|
||||||
|
|
||||||
web:
|
web:
|
||||||
command: ["node", "--watch", "app.js", "--watch-locales"]
|
command: ["node", "--watch", "app.mjs", "--watch-locales"]
|
||||||
environment:
|
environment:
|
||||||
- NODE_OPTIONS=--inspect=0.0.0.0:9229
|
- NODE_OPTIONS=--inspect=0.0.0.0:9229
|
||||||
ports:
|
ports:
|
||||||
|
|
BIN
doc/logo.png
BIN
doc/logo.png
Binary file not shown.
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 13 KiB |
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -5,6 +5,6 @@ access-token-encryptor
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -5,6 +5,6 @@ fetch-utils
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -5,6 +5,6 @@ logger
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -5,6 +5,6 @@ metrics
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -5,6 +5,6 @@ mongo-utils
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -5,6 +5,6 @@ o-error
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -5,6 +5,6 @@ object-persistor
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -33,6 +33,10 @@ const AES256_KEY_LENGTH = 32
|
||||||
* @property {() => Promise<Array<RootKeyEncryptionKey>>} getRootKeyEncryptionKeys
|
* @property {() => Promise<Array<RootKeyEncryptionKey>>} getRootKeyEncryptionKeys
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('./types').ListDirectoryResult} ListDirectoryResult
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to make TS happy when accessing error properties
|
* Helper function to make TS happy when accessing error properties
|
||||||
* AWSError is not an actual class, so we cannot use instanceof.
|
* AWSError is not an actual class, so we cannot use instanceof.
|
||||||
|
@ -391,9 +395,9 @@ class PerProjectEncryptedS3Persistor extends S3Persistor {
|
||||||
* A general "cache" for project keys is another alternative. For now, use a helper class.
|
* A general "cache" for project keys is another alternative. For now, use a helper class.
|
||||||
*/
|
*/
|
||||||
class CachedPerProjectEncryptedS3Persistor {
|
class CachedPerProjectEncryptedS3Persistor {
|
||||||
/** @type SSECOptions */
|
/** @type SSECOptions */
|
||||||
#projectKeyOptions
|
#projectKeyOptions
|
||||||
/** @type PerProjectEncryptedS3Persistor */
|
/** @type PerProjectEncryptedS3Persistor */
|
||||||
#parent
|
#parent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -424,6 +428,16 @@ class CachedPerProjectEncryptedS3Persistor {
|
||||||
return await this.#parent.getObjectSize(bucketName, path)
|
return await this.#parent.getObjectSize(bucketName, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} bucketName
|
||||||
|
* @param {string} path
|
||||||
|
* @return {Promise<ListDirectoryResult>}
|
||||||
|
*/
|
||||||
|
async listDirectory(bucketName, path) {
|
||||||
|
return await this.#parent.listDirectory(bucketName, path)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} bucketName
|
* @param {string} bucketName
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
|
|
|
@ -20,6 +20,18 @@ const { URL } = require('node:url')
|
||||||
const { WriteError, ReadError, NotFoundError } = require('./Errors')
|
const { WriteError, ReadError, NotFoundError } = require('./Errors')
|
||||||
const zlib = require('node:zlib')
|
const zlib = require('node:zlib')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('aws-sdk/clients/s3').ListObjectsV2Output} ListObjectsV2Output
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('aws-sdk/clients/s3').Object} S3Object
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('./types').ListDirectoryResult} ListDirectoryResult
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper with private fields to avoid revealing them on console, JSON.stringify or similar.
|
* Wrapper with private fields to avoid revealing them on console, JSON.stringify or similar.
|
||||||
*/
|
*/
|
||||||
|
@ -266,26 +278,12 @@ class S3Persistor extends AbstractPersistor {
|
||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async deleteDirectory(bucketName, key, continuationToken) {
|
async deleteDirectory(bucketName, key, continuationToken) {
|
||||||
let response
|
const { contents, response } = await this.listDirectory(
|
||||||
const options = { Bucket: bucketName, Prefix: key }
|
bucketName,
|
||||||
if (continuationToken) {
|
key,
|
||||||
options.ContinuationToken = continuationToken
|
continuationToken
|
||||||
}
|
)
|
||||||
|
const objects = contents.map(item => ({ Key: item.Key || '' }))
|
||||||
try {
|
|
||||||
response = await this._getClientForBucket(bucketName)
|
|
||||||
.listObjectsV2(options)
|
|
||||||
.promise()
|
|
||||||
} catch (err) {
|
|
||||||
throw PersistorHelper.wrapError(
|
|
||||||
err,
|
|
||||||
'failed to list objects in S3',
|
|
||||||
{ bucketName, key },
|
|
||||||
ReadError
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const objects = response.Contents?.map(item => ({ Key: item.Key || '' }))
|
|
||||||
if (objects?.length) {
|
if (objects?.length) {
|
||||||
try {
|
try {
|
||||||
await this._getClientForBucket(bucketName)
|
await this._getClientForBucket(bucketName)
|
||||||
|
@ -316,6 +314,36 @@ class S3Persistor extends AbstractPersistor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} bucketName
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} [continuationToken]
|
||||||
|
* @return {Promise<ListDirectoryResult>}
|
||||||
|
*/
|
||||||
|
async listDirectory(bucketName, key, continuationToken) {
|
||||||
|
let response
|
||||||
|
const options = { Bucket: bucketName, Prefix: key }
|
||||||
|
if (continuationToken) {
|
||||||
|
options.ContinuationToken = continuationToken
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = await this._getClientForBucket(bucketName)
|
||||||
|
.listObjectsV2(options)
|
||||||
|
.promise()
|
||||||
|
} catch (err) {
|
||||||
|
throw PersistorHelper.wrapError(
|
||||||
|
err,
|
||||||
|
'failed to list objects in S3',
|
||||||
|
{ bucketName, key },
|
||||||
|
ReadError
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { contents: response.Contents ?? [], response }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} bucketName
|
* @param {string} bucketName
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
|
|
6
libraries/object-persistor/src/types.d.ts
vendored
Normal file
6
libraries/object-persistor/src/types.d.ts
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import type { ListObjectsV2Output, Object } from 'aws-sdk/clients/s3'
|
||||||
|
|
||||||
|
export type ListDirectoryResult = {
|
||||||
|
contents: Array<Object>
|
||||||
|
response: ListObjectsV2Output
|
||||||
|
}
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -5,6 +5,6 @@ overleaf-editor-core
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -13,7 +13,7 @@ const V2DocVersions = require('./v2_doc_versions')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @import Author from "./author"
|
* @import Author from "./author"
|
||||||
* @import { BlobStore, RawChange } from "./types"
|
* @import { BlobStore, RawChange, ReadonlyBlobStore } from "./types"
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -219,7 +219,7 @@ class Change {
|
||||||
* If this Change contains any File objects, load them.
|
* If this Change contains any File objects, load them.
|
||||||
*
|
*
|
||||||
* @param {string} kind see {File#load}
|
* @param {string} kind see {File#load}
|
||||||
* @param {BlobStore} blobStore
|
* @param {ReadonlyBlobStore} blobStore
|
||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async loadFiles(kind, blobStore) {
|
async loadFiles(kind, blobStore) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ const Change = require('./change')
|
||||||
const Snapshot = require('./snapshot')
|
const Snapshot = require('./snapshot')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @import { BlobStore } from "./types"
|
* @import { BlobStore, ReadonlyBlobStore } from "./types"
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class History {
|
class History {
|
||||||
|
@ -85,7 +85,7 @@ class History {
|
||||||
* If this History contains any File objects, load them.
|
* If this History contains any File objects, load them.
|
||||||
*
|
*
|
||||||
* @param {string} kind see {File#load}
|
* @param {string} kind see {File#load}
|
||||||
* @param {BlobStore} blobStore
|
* @param {ReadonlyBlobStore} blobStore
|
||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async loadFiles(kind, blobStore) {
|
async loadFiles(kind, blobStore) {
|
||||||
|
|
|
@ -13,7 +13,7 @@ let EditFileOperation = null
|
||||||
let SetFileMetadataOperation = null
|
let SetFileMetadataOperation = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @import { BlobStore } from "../types"
|
* @import { ReadonlyBlobStore } from "../types"
|
||||||
* @import Snapshot from "../snapshot"
|
* @import Snapshot from "../snapshot"
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ class Operation {
|
||||||
* If this operation references any files, load the files.
|
* If this operation references any files, load the files.
|
||||||
*
|
*
|
||||||
* @param {string} kind see {File#load}
|
* @param {string} kind see {File#load}
|
||||||
* @param {BlobStore} blobStore
|
* @param {ReadOnlyBlobStore} blobStore
|
||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async loadFiles(kind, blobStore) {}
|
async loadFiles(kind, blobStore) {}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -5,6 +5,6 @@ promise-utils
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -5,6 +5,6 @@ ranges-tracker
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -5,6 +5,6 @@ redis-wrapper
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -5,6 +5,6 @@ settings
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -5,6 +5,6 @@ stream-utils
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -145,6 +145,24 @@ class LoggerStream extends Transform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MeteredStream extends Transform {
|
||||||
|
#Metrics
|
||||||
|
#metric
|
||||||
|
#labels
|
||||||
|
|
||||||
|
constructor(Metrics, metric, labels) {
|
||||||
|
super()
|
||||||
|
this.#Metrics = Metrics
|
||||||
|
this.#metric = metric
|
||||||
|
this.#labels = labels
|
||||||
|
}
|
||||||
|
|
||||||
|
_transform(chunk, encoding, callback) {
|
||||||
|
this.#Metrics.count(this.#metric, chunk.byteLength, 1, this.#labels)
|
||||||
|
callback(null, chunk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Export our classes
|
// Export our classes
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -153,6 +171,7 @@ module.exports = {
|
||||||
LoggerStream,
|
LoggerStream,
|
||||||
LimitedStream,
|
LimitedStream,
|
||||||
TimeoutStream,
|
TimeoutStream,
|
||||||
|
MeteredStream,
|
||||||
SizeExceededError,
|
SizeExceededError,
|
||||||
AbortError,
|
AbortError,
|
||||||
}
|
}
|
||||||
|
|
2447
package-lock.json
generated
2447
package-lock.json
generated
File diff suppressed because it is too large
Load diff
29
package.json
29
package.json
|
@ -8,8 +8,8 @@
|
||||||
"@types/chai": "^4.3.0",
|
"@types/chai": "^4.3.0",
|
||||||
"@types/chai-as-promised": "^7.1.8",
|
"@types/chai-as-promised": "^7.1.8",
|
||||||
"@types/mocha": "^10.0.6",
|
"@types/mocha": "^10.0.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.30.1",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.30.1",
|
||||||
"eslint": "^8.15.0",
|
"eslint": "^8.15.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-config-standard": "^17.0.0",
|
"eslint-config-standard": "^17.0.0",
|
||||||
|
@ -18,28 +18,21 @@
|
||||||
"eslint-plugin-cypress": "^2.15.1",
|
"eslint-plugin-cypress": "^2.15.1",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-mocha": "^10.1.0",
|
"eslint-plugin-mocha": "^10.1.0",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-n": "^15.7.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"eslint-plugin-promise": "^6.0.0",
|
"eslint-plugin-promise": "^6.0.0",
|
||||||
"eslint-plugin-unicorn": "^56.0.0",
|
"eslint-plugin-unicorn": "^56.0.0",
|
||||||
"prettier": "3.3.3",
|
"prettier": "3.6.2",
|
||||||
"typescript": "^5.5.4"
|
"typescript": "^5.8.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"npm": "11.4.2"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"cross-env": {
|
"swagger-tools@0.10.4": {
|
||||||
"cross-spawn": "^7.0.6"
|
|
||||||
},
|
|
||||||
"fetch-mock": {
|
|
||||||
"path-to-regexp": "3.3.0"
|
|
||||||
},
|
|
||||||
"google-gax": {
|
|
||||||
"protobufjs": "^7.2.5"
|
|
||||||
},
|
|
||||||
"swagger-tools": {
|
|
||||||
"body-parser": "1.20.3",
|
|
||||||
"multer": "2.0.1",
|
|
||||||
"path-to-regexp": "3.3.0",
|
"path-to-regexp": "3.3.0",
|
||||||
"qs": "6.13.0"
|
"body-parser": "1.20.3",
|
||||||
|
"multer": "2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -115,9 +115,3 @@ ENV LOG_LEVEL="info"
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
ENTRYPOINT ["/sbin/my_init"]
|
ENTRYPOINT ["/sbin/my_init"]
|
||||||
|
|
||||||
# Store the revision
|
|
||||||
# ------------------
|
|
||||||
# This should be the last step to optimize docker image caching.
|
|
||||||
ARG MONOREPO_REVISION
|
|
||||||
RUN echo "monorepo-server-ce,$MONOREPO_REVISION" > /var/www/revisions.txt
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ build-community:
|
||||||
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
||||||
--progress=plain \
|
--progress=plain \
|
||||||
--build-arg OVERLEAF_BASE_TAG \
|
--build-arg OVERLEAF_BASE_TAG \
|
||||||
--build-arg MONOREPO_REVISION \
|
--label "com.overleaf.ce.revision=$(MONOREPO_REVISION)" \
|
||||||
--cache-from $(OVERLEAF_LATEST) \
|
--cache-from $(OVERLEAF_LATEST) \
|
||||||
--cache-from $(OVERLEAF_BRANCH) \
|
--cache-from $(OVERLEAF_BRANCH) \
|
||||||
--file Dockerfile \
|
--file Dockerfile \
|
||||||
|
|
|
@ -184,7 +184,10 @@ const settings = {
|
||||||
siteUrl: (siteUrl = process.env.OVERLEAF_SITE_URL || 'http://localhost'),
|
siteUrl: (siteUrl = process.env.OVERLEAF_SITE_URL || 'http://localhost'),
|
||||||
|
|
||||||
// Status page URL as displayed on the maintenance/500 pages.
|
// Status page URL as displayed on the maintenance/500 pages.
|
||||||
statusPageUrl: process.env.OVERLEAF_STATUS_PAGE_URL,
|
statusPageUrl: process.env.OVERLEAF_STATUS_PAGE_URL ?
|
||||||
|
// Add https:// protocol prefix if not set (Allow plain-text http:// for Server Pro/CE).
|
||||||
|
(process.env.OVERLEAF_STATUS_PAGE_URL.startsWith('http://') || process.env.OVERLEAF_STATUS_PAGE_URL.startsWith('https://')) ? process.env.OVERLEAF_STATUS_PAGE_URL : `https://${process.env.OVERLEAF_STATUS_PAGE_URL}`
|
||||||
|
: undefined,
|
||||||
|
|
||||||
// The name this is used to describe your Overleaf Community Edition Installation
|
// The name this is used to describe your Overleaf Community Edition Installation
|
||||||
appName: process.env.OVERLEAF_APP_NAME || 'Overleaf Community Edition',
|
appName: process.env.OVERLEAF_APP_NAME || 'Overleaf Community Edition',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -eux
|
set -eu
|
||||||
|
|
||||||
echo "-------------------------"
|
echo "-------------------------"
|
||||||
echo "Deactivating old projects"
|
echo "Deactivating old projects"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -eux
|
set -eu
|
||||||
|
|
||||||
echo "-------------------------"
|
echo "-------------------------"
|
||||||
echo "Expiring deleted projects"
|
echo "Expiring deleted projects"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -eux
|
set -eu
|
||||||
|
|
||||||
echo "----------------------"
|
echo "----------------------"
|
||||||
echo "Expiring deleted users"
|
echo "Expiring deleted users"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -eux
|
set -eu
|
||||||
|
|
||||||
echo "---------------------------------"
|
echo "---------------------------------"
|
||||||
echo "Flush all project-history changes"
|
echo "Flush all project-history changes"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -eux
|
set -eu
|
||||||
|
|
||||||
echo "--------------------------"
|
echo "--------------------------"
|
||||||
echo "Flush project-history queue"
|
echo "Flush project-history queue"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -eux
|
set -eu
|
||||||
|
|
||||||
echo "-----------------------------------"
|
echo "-----------------------------------"
|
||||||
echo "Retry project-history errors (hard)"
|
echo "Retry project-history errors (hard)"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -eux
|
set -eu
|
||||||
|
|
||||||
echo "-----------------------------------"
|
echo "-----------------------------------"
|
||||||
echo "Retry project-history errors (soft)"
|
echo "Retry project-history errors (soft)"
|
||||||
|
|
27
server-ce/hotfix/5.5.2/Dockerfile
Normal file
27
server-ce/hotfix/5.5.2/Dockerfile
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
FROM sharelatex/sharelatex:5.5.1
|
||||||
|
|
||||||
|
# https://github.com/overleaf/internal/pull/25944
|
||||||
|
# Removed changes to services/web/frontend/js/features/ide-redesign/components/rail.tsx due to incompatibility with 5.5.1
|
||||||
|
COPY pr_25944.patch .
|
||||||
|
RUN patch -p1 < pr_25944.patch && rm pr_25944.patch
|
||||||
|
|
||||||
|
# https://github.com/overleaf/internal/pull/26637
|
||||||
|
# Removed changes to server-ce/test/create-and-compile-project.spec.ts and server-ce/test/helpers/compile.ts due to incompatibility with 5.5.1
|
||||||
|
COPY pr_26637.patch .
|
||||||
|
RUN patch -p1 < pr_26637.patch && rm pr_26637.patch
|
||||||
|
|
||||||
|
# https://github.com/overleaf/internal/pull/26783
|
||||||
|
COPY pr_26783.patch .
|
||||||
|
RUN patch -p1 < pr_26783.patch && rm pr_26783.patch
|
||||||
|
|
||||||
|
# https://github.com/overleaf/internal/pull/26697
|
||||||
|
COPY pr_26697.patch .
|
||||||
|
RUN patch -p1 < pr_26697.patch && rm pr_26697.patch
|
||||||
|
|
||||||
|
# Apply security updates to base image
|
||||||
|
RUN apt update && apt install -y linux-libc-dev \
|
||||||
|
&& unattended-upgrade --verbose --no-minimal-upgrade-steps \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Recompile frontend assets
|
||||||
|
RUN node genScript compile | bash
|
219
server-ce/hotfix/5.5.2/pr_25944.patch
Normal file
219
server-ce/hotfix/5.5.2/pr_25944.patch
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
diff --git a/services/web/frontend/js/features/review-panel-new/context/review-panel-providers.tsx b/services/web/frontend/js/features/review-panel-new/context/review-panel-providers.tsx
|
||||||
|
index 20e157dfee9..ad943772d0d 100644
|
||||||
|
--- a/services/web/frontend/js/features/review-panel-new/context/review-panel-providers.tsx
|
||||||
|
+++ b/services/web/frontend/js/features/review-panel-new/context/review-panel-providers.tsx
|
||||||
|
@@ -4,10 +4,16 @@ import { ChangesUsersProvider } from './changes-users-context'
|
||||||
|
import { TrackChangesStateProvider } from './track-changes-state-context'
|
||||||
|
import { ThreadsProvider } from './threads-context'
|
||||||
|
import { ReviewPanelViewProvider } from './review-panel-view-context'
|
||||||
|
+import { useProjectContext } from '@/shared/context/project-context'
|
||||||
|
|
||||||
|
export const ReviewPanelProviders: FC<React.PropsWithChildren> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
+ const { features } = useProjectContext()
|
||||||
|
+ if (!features.trackChangesVisible) {
|
||||||
|
+ return children
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
return (
|
||||||
|
<ReviewPanelViewProvider>
|
||||||
|
<ChangesUsersProvider>
|
||||||
|
diff --git a/services/web/frontend/js/features/share-project-modal/components/add-collaborators.tsx b/services/web/frontend/js/features/share-project-modal/components/add-collaborators.tsx
|
||||||
|
index 8606fb11fad..e80fb037116 100644
|
||||||
|
--- a/services/web/frontend/js/features/share-project-modal/components/add-collaborators.tsx
|
||||||
|
+++ b/services/web/frontend/js/features/share-project-modal/components/add-collaborators.tsx
|
||||||
|
@@ -176,24 +176,34 @@ export default function AddCollaborators({ readOnly }: { readOnly?: boolean }) {
|
||||||
|
])
|
||||||
|
|
||||||
|
const privilegeOptions = useMemo(() => {
|
||||||
|
- return [
|
||||||
|
+ const options: {
|
||||||
|
+ key: string
|
||||||
|
+ label: string
|
||||||
|
+ description?: string | null
|
||||||
|
+ }[] = [
|
||||||
|
{
|
||||||
|
key: 'readAndWrite',
|
||||||
|
label: t('editor'),
|
||||||
|
},
|
||||||
|
- {
|
||||||
|
+ ]
|
||||||
|
+
|
||||||
|
+ if (features.trackChangesVisible) {
|
||||||
|
+ options.push({
|
||||||
|
key: 'review',
|
||||||
|
label: t('reviewer'),
|
||||||
|
description: !features.trackChanges
|
||||||
|
? t('comment_only_upgrade_for_track_changes')
|
||||||
|
: null,
|
||||||
|
- },
|
||||||
|
- {
|
||||||
|
- key: 'readOnly',
|
||||||
|
- label: t('viewer'),
|
||||||
|
- },
|
||||||
|
- ]
|
||||||
|
- }, [features.trackChanges, t])
|
||||||
|
+ })
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ options.push({
|
||||||
|
+ key: 'readOnly',
|
||||||
|
+ label: t('viewer'),
|
||||||
|
+ })
|
||||||
|
+
|
||||||
|
+ return options
|
||||||
|
+ }, [features.trackChanges, features.trackChangesVisible, t])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OLForm className="add-collabs">
|
||||||
|
diff --git a/services/web/frontend/js/features/share-project-modal/components/edit-member.tsx b/services/web/frontend/js/features/share-project-modal/components/edit-member.tsx
|
||||||
|
index 6d806968b12..9f24cddc4ad 100644
|
||||||
|
--- a/services/web/frontend/js/features/share-project-modal/components/edit-member.tsx
|
||||||
|
+++ b/services/web/frontend/js/features/share-project-modal/components/edit-member.tsx
|
||||||
|
@@ -244,14 +244,22 @@ function SelectPrivilege({
|
||||||
|
const { features } = useProjectContext()
|
||||||
|
|
||||||
|
const privileges = useMemo(
|
||||||
|
- (): Privilege[] => [
|
||||||
|
- { key: 'owner', label: t('make_owner') },
|
||||||
|
- { key: 'readAndWrite', label: t('editor') },
|
||||||
|
- { key: 'review', label: t('reviewer') },
|
||||||
|
- { key: 'readOnly', label: t('viewer') },
|
||||||
|
- { key: 'removeAccess', label: t('remove_access') },
|
||||||
|
- ],
|
||||||
|
- [t]
|
||||||
|
+ (): Privilege[] =>
|
||||||
|
+ features.trackChangesVisible
|
||||||
|
+ ? [
|
||||||
|
+ { key: 'owner', label: t('make_owner') },
|
||||||
|
+ { key: 'readAndWrite', label: t('editor') },
|
||||||
|
+ { key: 'review', label: t('reviewer') },
|
||||||
|
+ { key: 'readOnly', label: t('viewer') },
|
||||||
|
+ { key: 'removeAccess', label: t('remove_access') },
|
||||||
|
+ ]
|
||||||
|
+ : [
|
||||||
|
+ { key: 'owner', label: t('make_owner') },
|
||||||
|
+ { key: 'readAndWrite', label: t('editor') },
|
||||||
|
+ { key: 'readOnly', label: t('viewer') },
|
||||||
|
+ { key: 'removeAccess', label: t('remove_access') },
|
||||||
|
+ ],
|
||||||
|
+ [features.trackChangesVisible, t]
|
||||||
|
)
|
||||||
|
|
||||||
|
const downgradedPseudoPrivilege: Privilege = {
|
||||||
|
diff --git a/services/web/frontend/js/features/source-editor/components/codemirror-editor.tsx b/services/web/frontend/js/features/source-editor/components/codemirror-editor.tsx
|
||||||
|
index c1808cbb301..4bdfe2682c8 100644
|
||||||
|
--- a/services/web/frontend/js/features/source-editor/components/codemirror-editor.tsx
|
||||||
|
+++ b/services/web/frontend/js/features/source-editor/components/codemirror-editor.tsx
|
||||||
|
@@ -18,6 +18,7 @@ import {
|
||||||
|
} from './codemirror-context'
|
||||||
|
import MathPreviewTooltip from './math-preview-tooltip'
|
||||||
|
import { useToolbarMenuBarEditorCommands } from '@/features/ide-redesign/hooks/use-toolbar-menu-editor-commands'
|
||||||
|
+import { useProjectContext } from '@/shared/context/project-context'
|
||||||
|
|
||||||
|
// TODO: remove this when definitely no longer used
|
||||||
|
export * from './codemirror-context'
|
||||||
|
@@ -67,6 +68,7 @@ function CodeMirrorEditor() {
|
||||||
|
|
||||||
|
function CodeMirrorEditorComponents() {
|
||||||
|
useToolbarMenuBarEditorCommands()
|
||||||
|
+ const { features } = useProjectContext()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReviewPanelProviders>
|
||||||
|
@@ -83,8 +85,8 @@ function CodeMirrorEditorComponents() {
|
||||||
|
<CodeMirrorCommandTooltip />
|
||||||
|
|
||||||
|
<MathPreviewTooltip />
|
||||||
|
- <ReviewTooltipMenu />
|
||||||
|
- <ReviewPanelNew />
|
||||||
|
+ {features.trackChangesVisible && <ReviewTooltipMenu />}
|
||||||
|
+ {features.trackChangesVisible && <ReviewPanelNew />}
|
||||||
|
|
||||||
|
{sourceEditorComponents.map(
|
||||||
|
({ import: { default: Component }, path }) => (
|
||||||
|
diff --git a/services/web/frontend/js/features/source-editor/components/toolbar/toolbar-items.tsx b/services/web/frontend/js/features/source-editor/components/toolbar/toolbar-items.tsx
|
||||||
|
index e70663683fc..c5d9f3d3e47 100644
|
||||||
|
--- a/services/web/frontend/js/features/source-editor/components/toolbar/toolbar-items.tsx
|
||||||
|
+++ b/services/web/frontend/js/features/source-editor/components/toolbar/toolbar-items.tsx
|
||||||
|
@@ -14,6 +14,7 @@ import { LegacyTableDropdown } from './table-inserter-dropdown-legacy'
|
||||||
|
import { withinFormattingCommand } from '@/features/source-editor/utils/tree-operations/formatting'
|
||||||
|
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
|
||||||
|
import { isMac } from '@/shared/utils/os'
|
||||||
|
+import { useProjectContext } from '@/shared/context/project-context'
|
||||||
|
|
||||||
|
export const ToolbarItems: FC<{
|
||||||
|
state: EditorState
|
||||||
|
@@ -31,6 +32,7 @@ export const ToolbarItems: FC<{
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { toggleSymbolPalette, showSymbolPalette, writefullInstance } =
|
||||||
|
useEditorContext()
|
||||||
|
+ const { features } = useProjectContext()
|
||||||
|
const isActive = withinFormattingCommand(state)
|
||||||
|
|
||||||
|
const symbolPaletteAvailable = getMeta('ol-symbolPaletteAvailable')
|
||||||
|
@@ -127,13 +129,15 @@ export const ToolbarItems: FC<{
|
||||||
|
command={commands.wrapInHref}
|
||||||
|
icon="add_link"
|
||||||
|
/>
|
||||||
|
- <ToolbarButton
|
||||||
|
- id="toolbar-add-comment"
|
||||||
|
- label={t('add_comment')}
|
||||||
|
- disabled={state.selection.main.empty}
|
||||||
|
- command={commands.addComment}
|
||||||
|
- icon="add_comment"
|
||||||
|
- />
|
||||||
|
+ {features.trackChangesVisible && (
|
||||||
|
+ <ToolbarButton
|
||||||
|
+ id="toolbar-add-comment"
|
||||||
|
+ label={t('add_comment')}
|
||||||
|
+ disabled={state.selection.main.empty}
|
||||||
|
+ command={commands.addComment}
|
||||||
|
+ icon="add_comment"
|
||||||
|
+ />
|
||||||
|
+ )}
|
||||||
|
<ToolbarButton
|
||||||
|
id="toolbar-ref"
|
||||||
|
label={t('toolbar_insert_cross_reference')}
|
||||||
|
diff --git a/services/web/test/frontend/features/review-panel/review-panel.spec.tsx b/services/web/test/frontend/features/review-panel/review-panel.spec.tsx
|
||||||
|
index d6677878108..58ac3e443da 100644
|
||||||
|
--- a/services/web/test/frontend/features/review-panel/review-panel.spec.tsx
|
||||||
|
+++ b/services/web/test/frontend/features/review-panel/review-panel.spec.tsx
|
||||||
|
@@ -181,6 +181,7 @@ describe('<ReviewPanel />', function () {
|
||||||
|
removeChangeIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
+ projectFeatures: { trackChangesVisible: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.wrap(scope).as('scope')
|
||||||
|
@@ -626,7 +627,7 @@ describe('<ReviewPanel /> for free users', function () {
|
||||||
|
function mountEditor(ownerId = USER_ID) {
|
||||||
|
const scope = mockScope(undefined, {
|
||||||
|
permissions: { write: true, trackedWrite: false, comment: true },
|
||||||
|
- projectFeatures: { trackChanges: false },
|
||||||
|
+ projectFeatures: { trackChanges: false, trackChangesVisible: true },
|
||||||
|
projectOwner: {
|
||||||
|
_id: ownerId,
|
||||||
|
},
|
||||||
|
diff --git a/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx b/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx
|
||||||
|
index b86207fb0f7..dfce8134d1c 100644
|
||||||
|
--- a/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx
|
||||||
|
+++ b/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx
|
||||||
|
@@ -694,6 +694,7 @@ describe('<ShareProjectModal/>', function () {
|
||||||
|
features: {
|
||||||
|
collaborators: 0,
|
||||||
|
compileGroup: 'standard',
|
||||||
|
+ trackChangesVisible: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
@@ -723,6 +724,7 @@ describe('<ShareProjectModal/>', function () {
|
||||||
|
...project,
|
||||||
|
features: {
|
||||||
|
collaborators: 1,
|
||||||
|
+ trackChangesVisible: true,
|
||||||
|
},
|
||||||
|
members: [
|
||||||
|
{
|
86
server-ce/hotfix/5.5.2/pr_26637.patch
Normal file
86
server-ce/hotfix/5.5.2/pr_26637.patch
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
diff --git a/services/clsi/app/js/LocalCommandRunner.js b/services/clsi/app/js/LocalCommandRunner.js
|
||||||
|
index ce274733585..aa62825443c 100644
|
||||||
|
--- a/services/clsi/app/js/LocalCommandRunner.js
|
||||||
|
+++ b/services/clsi/app/js/LocalCommandRunner.js
|
||||||
|
@@ -54,6 +54,7 @@ module.exports = CommandRunner = {
|
||||||
|
cwd: directory,
|
||||||
|
env,
|
||||||
|
stdio: ['pipe', 'pipe', 'ignore'],
|
||||||
|
+ detached: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
let stdout = ''
|
||||||
|
diff --git a/services/clsi/test/acceptance/js/StopCompile.js b/services/clsi/test/acceptance/js/StopCompile.js
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000000..103a70f37d7
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/services/clsi/test/acceptance/js/StopCompile.js
|
||||||
|
@@ -0,0 +1,47 @@
|
||||||
|
+const Client = require('./helpers/Client')
|
||||||
|
+const ClsiApp = require('./helpers/ClsiApp')
|
||||||
|
+const { expect } = require('chai')
|
||||||
|
+
|
||||||
|
+describe('Stop compile', function () {
|
||||||
|
+ before(function (done) {
|
||||||
|
+ this.request = {
|
||||||
|
+ options: {
|
||||||
|
+ timeout: 100,
|
||||||
|
+ }, // seconds
|
||||||
|
+ resources: [
|
||||||
|
+ {
|
||||||
|
+ path: 'main.tex',
|
||||||
|
+ content: `\
|
||||||
|
+\\documentclass{article}
|
||||||
|
+\\begin{document}
|
||||||
|
+\\def\\x{Hello!\\par\\x}
|
||||||
|
+\\x
|
||||||
|
+\\end{document}\
|
||||||
|
+`,
|
||||||
|
+ },
|
||||||
|
+ ],
|
||||||
|
+ }
|
||||||
|
+ this.project_id = Client.randomId()
|
||||||
|
+ ClsiApp.ensureRunning(() => {
|
||||||
|
+ // start the compile in the background
|
||||||
|
+ Client.compile(this.project_id, this.request, (error, res, body) => {
|
||||||
|
+ this.compileResult = { error, res, body }
|
||||||
|
+ })
|
||||||
|
+ // wait for 1 second before stopping the compile
|
||||||
|
+ setTimeout(() => {
|
||||||
|
+ Client.stopCompile(this.project_id, (error, res, body) => {
|
||||||
|
+ this.stopResult = { error, res, body }
|
||||||
|
+ setTimeout(done, 1000) // allow time for the compile request to terminate
|
||||||
|
+ })
|
||||||
|
+ }, 1000)
|
||||||
|
+ })
|
||||||
|
+ })
|
||||||
|
+
|
||||||
|
+ it('should force a compile response with an error status', function () {
|
||||||
|
+ expect(this.stopResult.error).to.be.null
|
||||||
|
+ expect(this.stopResult.res.statusCode).to.equal(204)
|
||||||
|
+ expect(this.compileResult.res.statusCode).to.equal(200)
|
||||||
|
+ expect(this.compileResult.body.compile.status).to.equal('terminated')
|
||||||
|
+ expect(this.compileResult.body.compile.error).to.equal('terminated')
|
||||||
|
+ })
|
||||||
|
+})
|
||||||
|
diff --git a/services/clsi/test/acceptance/js/helpers/Client.js b/services/clsi/test/acceptance/js/helpers/Client.js
|
||||||
|
index a0bdce734f3..49bf7390c6f 100644
|
||||||
|
--- a/services/clsi/test/acceptance/js/helpers/Client.js
|
||||||
|
+++ b/services/clsi/test/acceptance/js/helpers/Client.js
|
||||||
|
@@ -42,6 +42,16 @@ module.exports = Client = {
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
+ stopCompile(projectId, callback) {
|
||||||
|
+ if (callback == null) {
|
||||||
|
+ callback = function () {}
|
||||||
|
+ }
|
||||||
|
+ return request.post(
|
||||||
|
+ { url: `${this.host}/project/${projectId}/compile/stop` },
|
||||||
|
+ callback
|
||||||
|
+ )
|
||||||
|
+ },
|
||||||
|
+
|
||||||
|
clearCache(projectId, callback) {
|
||||||
|
if (callback == null) {
|
||||||
|
callback = function () {}
|
172
server-ce/hotfix/5.5.2/pr_26697.patch
Normal file
172
server-ce/hotfix/5.5.2/pr_26697.patch
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
diff --git a/services/web/frontend/js/features/project-list/components/project-list-ds-nav.tsx b/services/web/frontend/js/features/project-list/components/project-list-ds-nav.tsx
|
||||||
|
index 8f3b3a8e5d0..f8c8014e1c0 100644
|
||||||
|
--- a/services/web/frontend/js/features/project-list/components/project-list-ds-nav.tsx
|
||||||
|
+++ b/services/web/frontend/js/features/project-list/components/project-list-ds-nav.tsx
|
||||||
|
@@ -55,7 +55,11 @@ export function ProjectListDsNav() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="project-ds-nav-page website-redesign">
|
||||||
|
- <DefaultNavbar {...navbarProps} customLogo={overleafLogo} showCloseIcon />
|
||||||
|
+ <DefaultNavbar
|
||||||
|
+ {...navbarProps}
|
||||||
|
+ overleafLogo={overleafLogo}
|
||||||
|
+ showCloseIcon
|
||||||
|
+ />
|
||||||
|
<main className="project-list-wrapper">
|
||||||
|
<SidebarDsNav />
|
||||||
|
<div className="project-ds-nav-content-and-messages">
|
||||||
|
diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/default-navbar.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/default-navbar.tsx
|
||||||
|
index 2480b7f061f..8e5429dbde6 100644
|
||||||
|
--- a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/default-navbar.tsx
|
||||||
|
+++ b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/default-navbar.tsx
|
||||||
|
@@ -1,4 +1,4 @@
|
||||||
|
-import { useState } from 'react'
|
||||||
|
+import React, { useState } from 'react'
|
||||||
|
import { sendMB } from '@/infrastructure/event-tracking'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Button, Container, Nav, Navbar } from 'react-bootstrap'
|
||||||
|
@@ -13,9 +13,15 @@ import MaterialIcon from '@/shared/components/material-icon'
|
||||||
|
import { useContactUsModal } from '@/shared/hooks/use-contact-us-modal'
|
||||||
|
import { UserProvider } from '@/shared/context/user-context'
|
||||||
|
import { X } from '@phosphor-icons/react'
|
||||||
|
+import overleafWhiteLogo from '@/shared/svgs/overleaf-white.svg'
|
||||||
|
+import overleafBlackLogo from '@/shared/svgs/overleaf-black.svg'
|
||||||
|
+import type { CSSPropertiesWithVariables } from '../../../../../../../types/css-properties-with-variables'
|
||||||
|
|
||||||
|
-function DefaultNavbar(props: DefaultNavbarMetadata) {
|
||||||
|
+function DefaultNavbar(
|
||||||
|
+ props: DefaultNavbarMetadata & { overleafLogo?: string }
|
||||||
|
+) {
|
||||||
|
const {
|
||||||
|
+ overleafLogo,
|
||||||
|
customLogo,
|
||||||
|
title,
|
||||||
|
canDisplayAdminMenu,
|
||||||
|
@@ -49,10 +55,20 @@ function DefaultNavbar(props: DefaultNavbarMetadata) {
|
||||||
|
className="navbar-default navbar-main"
|
||||||
|
expand="lg"
|
||||||
|
onToggle={expanded => setExpanded(expanded)}
|
||||||
|
+ style={
|
||||||
|
+ {
|
||||||
|
+ '--navbar-brand-image-default-url': `url("${overleafWhiteLogo}")`,
|
||||||
|
+ '--navbar-brand-image-redesign-url': `url("${overleafBlackLogo}")`,
|
||||||
|
+ } as CSSPropertiesWithVariables
|
||||||
|
+ }
|
||||||
|
>
|
||||||
|
<Container className="navbar-container" fluid>
|
||||||
|
<div className="navbar-header">
|
||||||
|
- <HeaderLogoOrTitle title={title} customLogo={customLogo} />
|
||||||
|
+ <HeaderLogoOrTitle
|
||||||
|
+ title={title}
|
||||||
|
+ overleafLogo={overleafLogo}
|
||||||
|
+ customLogo={customLogo}
|
||||||
|
+ />
|
||||||
|
{enableUpgradeButton ? (
|
||||||
|
<Button
|
||||||
|
as="a"
|
||||||
|
diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/header-logo-or-title.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/header-logo-or-title.tsx
|
||||||
|
index 44500f1b826..3eefc8e2d1c 100644
|
||||||
|
--- a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/header-logo-or-title.tsx
|
||||||
|
+++ b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/header-logo-or-title.tsx
|
||||||
|
@@ -2,11 +2,13 @@ import type { DefaultNavbarMetadata } from '@/features/ui/components/types/defau
|
||||||
|
import getMeta from '@/utils/meta'
|
||||||
|
|
||||||
|
export default function HeaderLogoOrTitle({
|
||||||
|
+ overleafLogo,
|
||||||
|
customLogo,
|
||||||
|
title,
|
||||||
|
-}: Pick<DefaultNavbarMetadata, 'customLogo' | 'title'>) {
|
||||||
|
+}: Pick<DefaultNavbarMetadata, 'customLogo' | 'title'> & {
|
||||||
|
+ overleafLogo?: string
|
||||||
|
+}) {
|
||||||
|
const { appName } = getMeta('ol-ExposedSettings')
|
||||||
|
-
|
||||||
|
if (customLogo) {
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||||
|
@@ -24,9 +26,16 @@ export default function HeaderLogoOrTitle({
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
+ const style = overleafLogo
|
||||||
|
+ ? {
|
||||||
|
+ style: {
|
||||||
|
+ backgroundImage: `url("${overleafLogo}")`,
|
||||||
|
+ },
|
||||||
|
+ }
|
||||||
|
+ : null
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||||
|
- <a href="/" aria-label={appName} className="navbar-brand" />
|
||||||
|
+ <a href="/" aria-label={appName} className="navbar-brand" {...style} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff --git a/services/web/frontend/js/shared/svgs/overleaf-black.svg b/services/web/frontend/js/shared/svgs/overleaf-black.svg
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000000..ea0678438ba
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/services/web/frontend/js/shared/svgs/overleaf-black.svg
|
||||||
|
@@ -0,0 +1,9 @@
|
||||||
|
+<svg width="129" height="38" viewBox="0 0 129 38" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
+<mask id="mask0_2579_355" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="72" height="38">
|
||||||
|
+<path d="M71.7643 37.6327H0.0244141V0.0717773H71.7643V37.6327Z" fill="white"/>
|
||||||
|
+</mask>
|
||||||
|
+<g mask="url(#mask0_2579_355)">
|
||||||
|
+<path d="M47.2509 26.4555C47.3948 27.7507 47.8985 28.7821 48.81 29.5257C49.6974 30.2692 50.8487 30.653 52.2638 30.653C53.1993 30.653 54.0387 30.4611 54.7823 30.0773C55.5258 29.6696 56.1255 29.1419 56.5572 28.4223H61.0664C60.2989 30.3891 59.1716 31.9002 57.6365 33.0035C56.1255 34.0829 54.3506 34.6345 52.3598 34.6345C51.0166 34.6345 49.7934 34.3947 48.666 33.915C47.5387 33.4352 46.5314 32.7397 45.6199 31.8043C44.7804 30.9168 44.1089 29.9094 43.6531 28.7341C43.1974 27.5589 42.9576 26.3836 42.9576 25.1603C42.9576 23.9131 43.1734 22.7138 43.6052 21.6105C44.0369 20.5072 44.6605 19.4998 45.5 18.6124C46.4114 17.629 47.4668 16.8854 48.6181 16.3817C49.7694 15.8541 50.9686 15.5902 52.1919 15.5902C53.7509 15.5902 55.214 15.95 56.5572 16.6456C57.9004 17.3651 59.0517 18.3485 60.0111 19.6437C60.5867 20.4113 61.0185 21.2747 61.3063 22.2581C61.5941 23.2175 61.714 24.3209 61.714 25.5681C61.714 25.664 61.714 25.8079 61.69 26.0238C61.69 26.2397 61.6661 26.3836 61.6661 26.4795H47.2509V26.4555ZM57.2048 23.1216C56.845 21.9223 56.2454 21.0109 55.4059 20.3873C54.5664 19.7637 53.4871 19.4519 52.2159 19.4519C51.0886 19.4519 50.1052 19.7876 49.2177 20.4592C48.3303 21.1308 47.7306 22.0183 47.4188 23.1216H57.2048ZM71.7638 19.7637C70.1328 19.8836 69.0055 20.3153 68.3579 21.0349C67.7103 21.7544 67.3985 23.0496 67.3985 24.9205V34.1068H63.2011V16.1179H67.1347V18.2046C67.7583 17.3891 68.4539 16.8135 69.2214 16.4297C69.9649 16.0459 70.8284 15.8541 71.7638 15.8541V19.7637ZM32.428 1.24705C27.3432 -0.743722 8.9465 -1.46328 8.92251 9.52196C3.54982 12.9519 0 18.5404 0 24.5367C0 31.7803 5.87638 37.6567 13.1199 37.6567C20.3635 37.6567 26.2399 31.7803 26.2399 24.5367C26.2399 18.9482 22.738 14.1511 17.797 12.2803C16.8376 11.9205 14.7749 11.2729 13.1439 11.4168C10.7934 12.9039 7.91513 15.974 6.57196 19.0441C8.58672 16.6216 11.7288 15.5662 14.5351 16.022C18.6365 16.6936 21.7786 20.2434 21.7786 24.5607C21.7786 29.3338 17.917 33.1954 13.1439 33.1954C10.5055 33.1954 8.15498 32.0201 6.57196 30.1733C4.19742 27.415 3.59779 24.4408 4.07749 21.5386C5.73247 11.3688 17.797 5.58838 26.7675 3.35775C23.8413 4.9168 18.5646 7.45923 14.8708 10.2175C25.6402 14.391 27.3911 5.30056 32.428 1.24705ZM36.7934 34.1308H33.5074L26.6716 16.1179H31.1328L35.3303 28.0865L39.6476 16.1179H43.9889L36.7934 34.1308Z" fill="#1B222C"/>
|
||||||
|
+</g>
|
||||||
|
+<path d="M83.6127 26.4556C83.7567 27.7508 84.2843 28.7822 85.1718 29.5257C86.0592 30.2692 87.2105 30.653 88.6257 30.653C89.5611 30.653 90.4006 30.4611 91.1441 30.0774C91.8877 29.6696 92.4873 29.1419 92.919 28.4224H97.4282C96.6607 30.3892 95.5334 31.9002 93.9984 33.0036C92.4873 34.0829 90.7124 34.6346 88.7216 34.6346C87.3784 34.6346 86.1552 34.3947 85.0279 33.915C83.9006 33.4353 82.8932 32.7397 81.9817 31.8043C81.1423 30.9168 80.4707 29.9095 80.015 28.7342C79.5353 27.5829 79.3194 26.3836 79.3194 25.1604C79.3194 23.9131 79.5353 22.7139 79.967 21.6106C80.3987 20.5072 81.0223 19.4999 81.8618 18.6124C82.7733 17.629 83.8286 16.8855 84.9799 16.3818C86.1312 15.8541 87.3305 15.5903 88.5537 15.5903C90.1128 15.5903 91.5758 15.95 92.919 16.6456C94.2622 17.3652 95.4135 18.3486 96.3729 19.6438C96.9485 20.4113 97.3803 21.2748 97.6681 22.2582C97.9559 23.2176 98.0758 24.3209 98.0758 25.5681C98.0758 25.6641 98.0758 25.808 98.0519 26.0238C98.0519 26.2397 98.0279 26.3836 98.0279 26.4796H83.6127V26.4556ZM93.5426 23.1216C93.1829 21.9224 92.5832 21.0109 91.7437 20.3873C90.9043 19.7637 89.8249 19.4519 88.5537 19.4519C87.4264 19.4519 86.443 19.7877 85.5556 20.4593C84.6681 21.1309 84.0685 22.0183 83.7567 23.1216H93.5426ZM114.698 34.1309V31.9242C114.194 32.8117 113.498 33.4833 112.587 33.915C111.675 34.3467 110.5 34.5626 109.085 34.5626C106.423 34.5626 104.192 33.6512 102.417 31.8283C100.642 30.0054 99.7308 27.7508 99.7308 25.0644C99.7308 23.7932 99.9467 22.594 100.402 21.4667C100.858 20.3393 101.482 19.332 102.321 18.4685C103.209 17.5091 104.216 16.8135 105.295 16.3578C106.375 15.9021 107.622 15.6862 108.989 15.6862C110.308 15.6862 111.436 15.9021 112.371 16.3338C113.306 16.7655 114.074 17.4371 114.65 18.3246V16.1419H118.727V34.1548H114.698V34.1309ZM104.024 24.9685C104.024 26.4796 104.528 27.7508 105.535 28.7822C106.543 29.8135 107.766 30.3172 109.229 30.3172C110.548 30.3172 111.699 29.8135 112.707 28.7822C113.714 27.7508 114.218 26.5515 114.218 25.1844C114.218 23.7213 113.714 22.474 112.707 21.4187C111.699 20.3633 110.524 19.8357 109.157 19.8357C107.742 19.8357 106.543 20.3393 105.535 21.3227C104.528 22.3301 104.024 23.5294 104.024 24.9685ZM129.904 16.1179V19.8596H126.882V34.1309H122.829V19.8596H120.694V16.1179H122.709V15.6382C122.709 13.7434 123.236 12.3283 124.268 11.3929C125.323 10.4574 126.906 10.0017 129.041 10.0017C129.113 10.0017 129.257 10.0017 129.449 10.0257C129.64 10.0257 129.784 10.0497 129.904 10.0497V13.8154H129.616C128.657 13.8154 127.985 13.9833 127.578 14.2711C127.17 14.5829 126.954 15.0866 126.954 15.8301V16.1659H129.904V16.1179ZM73.5869 34.1309H77.6884V10.2895H73.5869V34.1309Z" fill="#1B222C"/>
|
||||||
|
+</svg>
|
||||||
|
diff --git a/services/web/frontend/js/shared/svgs/overleaf-white.svg b/services/web/frontend/js/shared/svgs/overleaf-white.svg
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000000..2ced81aa46d
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/services/web/frontend/js/shared/svgs/overleaf-white.svg
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 542 157" enable-background="new 0 0 542 157"><style>.st0{filter:url(#Adobe_OpacityMaskFilter);} .st1{fill:#FFFFFF;} .st2{mask:url(#mask-2);fill:#FFFFFF;}</style><g id="Page-1"><g id="Overleaf"><g id="Group-3"><defs><filter id="Adobe_OpacityMaskFilter" filterUnits="userSpaceOnUse" x="0" y=".3" width="299.2" height="156.7"><feColorMatrix values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/></filter></defs><mask maskUnits="userSpaceOnUse" x="0" y=".3" width="299.2" height="156.7" id="mask-2"><g class="st0"><path id="path-1" class="st1" d="M299.2 156.9H.1V.3h299.1z"/></g></mask><path id="Fill-1" class="st2" d="M197 110.3c.6 5.4 2.7 9.7 6.5 12.8 3.7 3.1 8.5 4.7 14.4 4.7 3.9 0 7.4-.8 10.5-2.4 3.1-1.7 5.6-3.9 7.4-6.9h18.8c-3.2 8.2-7.9 14.5-14.3 19.1-6.3 4.5-13.7 6.8-22 6.8-5.6 0-10.7-1-15.4-3-4.7-2-8.9-4.9-12.7-8.8-3.5-3.7-6.3-7.9-8.2-12.8s-2.9-9.8-2.9-14.9c0-5.2.9-10.2 2.7-14.8 1.8-4.6 4.4-8.8 7.9-12.5 3.8-4.1 8.2-7.2 13-9.3 4.8-2.2 9.8-3.3 14.9-3.3 6.5 0 12.6 1.5 18.2 4.4 5.6 3 10.4 7.1 14.4 12.5 2.4 3.2 4.2 6.8 5.4 10.9 1.2 4 1.7 8.6 1.7 13.8 0 .4 0 1-.1 1.9 0 .9-.1 1.5-.1 1.9H197v-.1zm41.5-13.9c-1.5-5-4-8.8-7.5-11.4-3.5-2.6-8-3.9-13.3-3.9-4.7 0-8.8 1.4-12.5 4.2-3.7 2.8-6.2 6.5-7.5 11.1h40.8zm60.7-14c-6.8.5-11.5 2.3-14.2 5.3-2.7 3-4 8.4-4 16.2v38.3h-17.5v-75h16.4v8.7c2.6-3.4 5.5-5.8 8.7-7.4 3.1-1.6 6.7-2.4 10.6-2.4v16.3zm-164-77.2C114-3.1 37.3-6.1 37.2 39.7 14.8 54 0 77.3 0 102.3 0 132.5 24.5 157 54.7 157c30.2 0 54.7-24.5 54.7-54.7 0-23.3-14.6-43.3-35.2-51.1-4-1.5-12.6-4.2-19.4-3.6-9.8 6.2-21.8 19-27.4 31.8 8.4-10.1 21.5-14.5 33.2-12.6 17.1 2.8 30.2 17.6 30.2 35.6 0 19.9-16.1 36-36 36-11 0-20.8-4.9-27.4-12.6-9.9-11.5-12.4-23.9-10.4-36 6.9-42.4 57.2-66.5 94.6-75.8C99.4 20.5 77.4 31.1 62 42.6c44.9 17.4 52.2-20.5 73.2-37.4zm18.2 137.1h-13.7l-28.5-75.1h18.6l17.5 49.9 18-49.9h18.1l-30 75.1z"/></g><path id="Fill-4" class="st1" d="M348.6 110.3c.6 5.4 2.8 9.7 6.5 12.8 3.7 3.1 8.5 4.7 14.4 4.7 3.9 0 7.4-.8 10.5-2.4 3.1-1.7 5.6-3.9 7.4-6.9h18.8c-3.2 8.2-7.9 14.5-14.3 19.1-6.3 4.5-13.7 6.8-22 6.8-5.6 0-10.7-1-15.4-3-4.7-2-8.9-4.9-12.7-8.8-3.5-3.7-6.3-7.9-8.2-12.8-2-4.8-2.9-9.8-2.9-14.9 0-5.2.9-10.2 2.7-14.8 1.8-4.6 4.4-8.8 7.9-12.5 3.8-4.1 8.2-7.2 13-9.3 4.8-2.2 9.8-3.3 14.9-3.3 6.5 0 12.6 1.5 18.2 4.4 5.6 3 10.4 7.1 14.4 12.5 2.4 3.2 4.2 6.8 5.4 10.9 1.2 4 1.7 8.6 1.7 13.8 0 .4 0 1-.1 1.9 0 .9-.1 1.5-.1 1.9h-60.1v-.1zM390 96.4c-1.5-5-4-8.8-7.5-11.4-3.5-2.6-8-3.9-13.3-3.9-4.7 0-8.8 1.4-12.5 4.2-3.7 2.8-6.2 6.5-7.5 11.1H390zm88.2 45.9v-9.2c-2.1 3.7-5 6.5-8.8 8.3-3.8 1.8-8.7 2.7-14.6 2.7-11.1 0-20.4-3.8-27.8-11.4-7.4-7.6-11.2-17-11.2-28.2 0-5.3.9-10.3 2.8-15 1.9-4.7 4.5-8.9 8-12.5 3.7-4 7.9-6.9 12.4-8.8s9.7-2.8 15.4-2.8c5.5 0 10.2.9 14.1 2.7 3.9 1.8 7.1 4.6 9.5 8.3v-9.1h17v75.1h-16.8v-.1zm-44.5-38.2c0 6.3 2.1 11.6 6.3 15.9 4.2 4.3 9.3 6.4 15.4 6.4 5.5 0 10.3-2.1 14.5-6.4 4.2-4.3 6.3-9.3 6.3-15 0-6.1-2.1-11.3-6.3-15.7-4.2-4.4-9.1-6.6-14.8-6.6-5.9 0-10.9 2.1-15.1 6.2-4.2 4.2-6.3 9.2-6.3 15.2zm107.9-36.9v15.6H529v59.5h-16.9V82.8h-8.9V67.2h8.4v-2c0-7.9 2.2-13.8 6.5-17.7 4.4-3.9 11-5.8 19.9-5.8.3 0 .9 0 1.7.1.8 0 1.4.1 1.9.1v15.7h-1.2c-4 0-6.8.7-8.5 1.9-1.7 1.3-2.6 3.4-2.6 6.5v1.4h12.3v-.2zm-234.8 75.1h17.1V42.9h-17.1v99.4z"/></g></g></svg>
|
||||||
|
\ No newline at end of file
|
||||||
|
diff --git a/services/web/frontend/stylesheets/bootstrap-5/components/nav.scss b/services/web/frontend/stylesheets/bootstrap-5/components/nav.scss
|
||||||
|
index 5d28341cf53..dd0600ed15d 100644
|
||||||
|
--- a/services/web/frontend/stylesheets/bootstrap-5/components/nav.scss
|
||||||
|
+++ b/services/web/frontend/stylesheets/bootstrap-5/components/nav.scss
|
||||||
|
@@ -8,7 +8,10 @@
|
||||||
|
--navbar-padding-h: var(--spacing-05);
|
||||||
|
--navbar-padding: 0 var(--navbar-padding-h);
|
||||||
|
--navbar-brand-width: 130px;
|
||||||
|
- --navbar-brand-image-url: url('../../../../public/img/ol-brand/overleaf-white.svg');
|
||||||
|
+ --navbar-brand-image-url: var(
|
||||||
|
+ --navbar-brand-image-default-url,
|
||||||
|
+ url('../../../../public/img/ol-brand/overleaf-white.svg')
|
||||||
|
+ );
|
||||||
|
|
||||||
|
// Title, when used instead of a logo
|
||||||
|
--navbar-title-font-size: var(--font-size-05);
|
||||||
|
diff --git a/services/web/frontend/stylesheets/bootstrap-5/components/navbar.scss b/services/web/frontend/stylesheets/bootstrap-5/components/navbar.scss
|
||||||
|
index 3b984bb6f36..a8855ea1ca3 100644
|
||||||
|
--- a/services/web/frontend/stylesheets/bootstrap-5/components/navbar.scss
|
||||||
|
+++ b/services/web/frontend/stylesheets/bootstrap-5/components/navbar.scss
|
||||||
|
@@ -216,7 +216,10 @@
|
||||||
|
.website-redesign .navbar-default {
|
||||||
|
--navbar-title-color: var(--content-primary);
|
||||||
|
--navbar-title-color-hover: var(--content-secondary);
|
||||||
|
- --navbar-brand-image-url: url('../../../../public/img/ol-brand/overleaf-black.svg');
|
||||||
|
+ --navbar-brand-image-url: var(
|
||||||
|
+ --navbar-brand-image-redesign-url,
|
||||||
|
+ url('../../../../public/img/ol-brand/overleaf-black.svg')
|
||||||
|
+ );
|
||||||
|
--navbar-subdued-color: var(--content-primary);
|
||||||
|
--navbar-subdued-hover-bg: var(--bg-dark-primary);
|
||||||
|
--navbar-subdued-hover-color: var(--content-primary-dark);
|
||||||
|
diff --git a/services/web/types/css-properties-with-variables.tsx b/services/web/types/css-properties-with-variables.tsx
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000000..fe0e85902a6
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/services/web/types/css-properties-with-variables.tsx
|
||||||
|
@@ -0,0 +1,4 @@
|
||||||
|
+import { CSSProperties } from 'react'
|
||||||
|
+
|
||||||
|
+export type CSSPropertiesWithVariables = CSSProperties &
|
||||||
|
+ Record<`--${string}`, number | string>
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
58
server-ce/hotfix/5.5.2/pr_26783.patch
Normal file
58
server-ce/hotfix/5.5.2/pr_26783.patch
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
diff --git a/services/web/modules/server-ce-scripts/scripts/check-mongodb.mjs b/services/web/modules/server-ce-scripts/scripts/check-mongodb.mjs
|
||||||
|
index 29f5e7ffd26..46be91a1d9c 100644
|
||||||
|
--- a/services/web/modules/server-ce-scripts/scripts/check-mongodb.mjs
|
||||||
|
+++ b/services/web/modules/server-ce-scripts/scripts/check-mongodb.mjs
|
||||||
|
@@ -9,6 +9,34 @@ const { ObjectId } = mongodb
|
||||||
|
const MIN_MONGO_VERSION = [6, 0]
|
||||||
|
const MIN_MONGO_FEATURE_COMPATIBILITY_VERSION = [6, 0]
|
||||||
|
|
||||||
|
+// Allow ignoring admin check failures via an environment variable
|
||||||
|
+const OVERRIDE_ENV_VAR_NAME = 'ALLOW_MONGO_ADMIN_CHECK_FAILURES'
|
||||||
|
+
|
||||||
|
+function shouldSkipAdminChecks() {
|
||||||
|
+ return process.env[OVERRIDE_ENV_VAR_NAME] === 'true'
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+function handleUnauthorizedError(err, feature) {
|
||||||
|
+ if (
|
||||||
|
+ err instanceof mongodb.MongoServerError &&
|
||||||
|
+ err.codeName === 'Unauthorized'
|
||||||
|
+ ) {
|
||||||
|
+ console.warn(`Warning: failed to check ${feature} (not authorised)`)
|
||||||
|
+ if (!shouldSkipAdminChecks()) {
|
||||||
|
+ console.error(
|
||||||
|
+ `Please ensure the MongoDB user has the required admin permissions, or\n` +
|
||||||
|
+ `set the environment variable ${OVERRIDE_ENV_VAR_NAME}=true to ignore this check.`
|
||||||
|
+ )
|
||||||
|
+ process.exit(1)
|
||||||
|
+ }
|
||||||
|
+ console.warn(
|
||||||
|
+ `Ignoring ${feature} check failure (${OVERRIDE_ENV_VAR_NAME}=${process.env[OVERRIDE_ENV_VAR_NAME]})`
|
||||||
|
+ )
|
||||||
|
+ } else {
|
||||||
|
+ throw err
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
async function main() {
|
||||||
|
let mongoClient
|
||||||
|
try {
|
||||||
|
@@ -18,8 +46,16 @@ async function main() {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
- await checkMongoVersion(mongoClient)
|
||||||
|
- await checkFeatureCompatibilityVersion(mongoClient)
|
||||||
|
+ try {
|
||||||
|
+ await checkMongoVersion(mongoClient)
|
||||||
|
+ } catch (err) {
|
||||||
|
+ handleUnauthorizedError(err, 'MongoDB version')
|
||||||
|
+ }
|
||||||
|
+ try {
|
||||||
|
+ await checkFeatureCompatibilityVersion(mongoClient)
|
||||||
|
+ } catch (err) {
|
||||||
|
+ handleUnauthorizedError(err, 'MongoDB feature compatibility version')
|
||||||
|
+ }
|
||||||
|
|
||||||
|
try {
|
||||||
|
await testTransactions(mongoClient)
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:22.15.1
|
FROM node:22.17.0
|
||||||
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - \
|
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - \
|
||||||
&& echo \
|
&& echo \
|
||||||
"deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
|
"deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
|
||||||
|
|
|
@ -40,9 +40,15 @@ describe('Project creation and compilation', function () {
|
||||||
cy.get('.cm-line').should('have.length', 1)
|
cy.get('.cm-line').should('have.length', 1)
|
||||||
cy.get('.cm-line').type(markdownContent)
|
cy.get('.cm-line').type(markdownContent)
|
||||||
cy.findByText('main.tex').click()
|
cy.findByText('main.tex').click()
|
||||||
cy.get('.cm-content').should('contain.text', '\\maketitle')
|
cy.findByRole('textbox', { name: /Source Editor editing/i }).should(
|
||||||
|
'contain.text',
|
||||||
|
'\\maketitle'
|
||||||
|
)
|
||||||
cy.findByText(fileName).click()
|
cy.findByText(fileName).click()
|
||||||
cy.get('.cm-content').should('contain.text', markdownContent)
|
cy.findByRole('textbox', { name: /Source Editor editing/i }).should(
|
||||||
|
'contain.text',
|
||||||
|
markdownContent
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can link and display linked image from other project', function () {
|
it('can link and display linked image from other project', function () {
|
||||||
|
|
|
@ -91,6 +91,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/e2e
|
- ./:/e2e
|
||||||
- /tmp/.X11-unix:/tmp/.X11-unix
|
- /tmp/.X11-unix:/tmp/.X11-unix
|
||||||
|
- ${XAUTHORITY:-/dev/null}:/home/node/.Xauthority
|
||||||
user: "${DOCKER_USER:-1000:1000}"
|
user: "${DOCKER_USER:-1000:1000}"
|
||||||
environment:
|
environment:
|
||||||
CYPRESS_SHARD:
|
CYPRESS_SHARD:
|
||||||
|
|
|
@ -104,7 +104,10 @@ describe('editor', () => {
|
||||||
force: true,
|
force: true,
|
||||||
})
|
})
|
||||||
cy.get('button').contains('𝜉').click()
|
cy.get('button').contains('𝜉').click()
|
||||||
cy.get('.cm-content').should('contain.text', '\\xi')
|
cy.findByRole('textbox', { name: /Source Editor editing/i }).should(
|
||||||
|
'contain.text',
|
||||||
|
'\\xi'
|
||||||
|
)
|
||||||
|
|
||||||
cy.log('recompile to force flush and avoid "unsaved changes" prompt')
|
cy.log('recompile to force flush and avoid "unsaved changes" prompt')
|
||||||
recompile()
|
recompile()
|
||||||
|
@ -147,8 +150,9 @@ describe('editor', () => {
|
||||||
|
|
||||||
it('can download project sources', () => {
|
it('can download project sources', () => {
|
||||||
cy.get('a').contains('Source').click()
|
cy.get('a').contains('Source').click()
|
||||||
|
const zipName = projectName.replaceAll('-', '_')
|
||||||
cy.task('readFileInZip', {
|
cy.task('readFileInZip', {
|
||||||
pathToZip: `cypress/downloads/${projectName}.zip`,
|
pathToZip: `cypress/downloads/${zipName}.zip`,
|
||||||
fileToRead: 'main.tex',
|
fileToRead: 'main.tex',
|
||||||
}).should('contain', 'Your introduction goes here')
|
}).should('contain', 'Your introduction goes here')
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,6 +9,14 @@ export function throttledRecompile() {
|
||||||
return recompile
|
return recompile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stopCompile(options: { delay?: number } = {}) {
|
||||||
|
const { delay = 0 } = options
|
||||||
|
cy.wait(delay)
|
||||||
|
cy.log('Stop compile')
|
||||||
|
cy.findByRole('button', { name: 'Toggle compile options menu' }).click()
|
||||||
|
cy.findByRole('menuitem', { name: 'Stop compilation' }).click()
|
||||||
|
}
|
||||||
|
|
||||||
export function prepareWaitForNextCompileSlot() {
|
export function prepareWaitForNextCompileSlot() {
|
||||||
let lastCompile = 0
|
let lastCompile = 0
|
||||||
function queueReset() {
|
function queueReset() {
|
||||||
|
|
|
@ -44,8 +44,9 @@ describe('Project List', () => {
|
||||||
cy.findByRole('button', { name: 'Download .zip file' }).click()
|
cy.findByRole('button', { name: 'Download .zip file' }).click()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const zipName = projectName.replaceAll('-', '_')
|
||||||
cy.task('readFileInZip', {
|
cy.task('readFileInZip', {
|
||||||
pathToZip: `cypress/downloads/${projectName}.zip`,
|
pathToZip: `cypress/downloads/${zipName}.zip`,
|
||||||
fileToRead: 'main.tex',
|
fileToRead: 'main.tex',
|
||||||
}).should('contain', 'Your introduction goes here')
|
}).should('contain', 'Your introduction goes here')
|
||||||
})
|
})
|
||||||
|
|
|
@ -55,8 +55,15 @@ describe('Project Sharing', function () {
|
||||||
|
|
||||||
function expectContentReadOnlyAccess() {
|
function expectContentReadOnlyAccess() {
|
||||||
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
||||||
cy.get('.cm-content').should('contain.text', '\\maketitle')
|
cy.findByRole('textbox', { name: /Source Editor editing/i }).should(
|
||||||
cy.get('.cm-content').should('have.attr', 'contenteditable', 'false')
|
'contain.text',
|
||||||
|
'\\maketitle'
|
||||||
|
)
|
||||||
|
cy.findByRole('textbox', { name: /Source Editor editing/i }).should(
|
||||||
|
'have.attr',
|
||||||
|
'contenteditable',
|
||||||
|
'false'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function expectContentWriteAccess() {
|
function expectContentWriteAccess() {
|
||||||
|
@ -64,13 +71,23 @@ describe('Project Sharing', function () {
|
||||||
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
||||||
const recompile = throttledRecompile()
|
const recompile = throttledRecompile()
|
||||||
// wait for the editor to finish loading
|
// wait for the editor to finish loading
|
||||||
cy.get('.cm-content').should('contain.text', '\\maketitle')
|
cy.findByRole('textbox', { name: /Source Editor editing/i }).should(
|
||||||
|
'contain.text',
|
||||||
|
'\\maketitle'
|
||||||
|
)
|
||||||
// the editor should be writable
|
// the editor should be writable
|
||||||
cy.get('.cm-content').should('have.attr', 'contenteditable', 'true')
|
cy.findByRole('textbox', { name: /Source Editor editing/i }).should(
|
||||||
|
'have.attr',
|
||||||
|
'contenteditable',
|
||||||
|
'true'
|
||||||
|
)
|
||||||
cy.findByText('\\maketitle').parent().click()
|
cy.findByText('\\maketitle').parent().click()
|
||||||
cy.findByText('\\maketitle').parent().type(`\n\\section{{}${section}}`)
|
cy.findByText('\\maketitle').parent().type(`\n\\section{{}${section}}`)
|
||||||
// should have written
|
// should have written
|
||||||
cy.get('.cm-content').should('contain.text', `\\section{${section}}`)
|
cy.findByRole('textbox', { name: /Source Editor editing/i }).should(
|
||||||
|
'contain.text',
|
||||||
|
`\\section{${section}}`
|
||||||
|
)
|
||||||
// check PDF
|
// check PDF
|
||||||
recompile()
|
recompile()
|
||||||
cy.get('.pdf-viewer').should('contain.text', projectName)
|
cy.get('.pdf-viewer').should('contain.text', projectName)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ensureUserExists, login } from './helpers/login'
|
import { ensureUserExists, login } from './helpers/login'
|
||||||
import { createProject } from './helpers/project'
|
import { createProject } from './helpers/project'
|
||||||
import { isExcludedBySharding, startWith } from './helpers/config'
|
import { isExcludedBySharding, startWith } from './helpers/config'
|
||||||
import { throttledRecompile } from './helpers/compile'
|
import { throttledRecompile, stopCompile } from './helpers/compile'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import { waitUntilScrollingFinished } from './helpers/waitUntilScrollingFinished'
|
import { waitUntilScrollingFinished } from './helpers/waitUntilScrollingFinished'
|
||||||
import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
|
import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
|
||||||
|
@ -56,8 +56,40 @@ describe('SandboxedCompiles', function () {
|
||||||
checkSyncTeX()
|
checkSyncTeX()
|
||||||
checkXeTeX()
|
checkXeTeX()
|
||||||
checkRecompilesAfterErrors()
|
checkRecompilesAfterErrors()
|
||||||
|
checkStopCompile()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function checkStopCompile() {
|
||||||
|
it('users can stop a running compile', function () {
|
||||||
|
login('user@example.com')
|
||||||
|
createProject('test-project')
|
||||||
|
// create an infinite loop in the main document
|
||||||
|
// this will cause the compile to run indefinitely
|
||||||
|
cy.findByText('\\maketitle').parent().click()
|
||||||
|
cy.findByText('\\maketitle')
|
||||||
|
.parent()
|
||||||
|
.type('\n\\def\\x{{}Hello!\\par\\x}\\x')
|
||||||
|
cy.log('Start compile')
|
||||||
|
// We need to start the compile manually because we do not want to wait for it to finish
|
||||||
|
cy.findByText('Recompile').click()
|
||||||
|
// Now stop the compile and kill the latex process
|
||||||
|
stopCompile({ delay: 1000 })
|
||||||
|
cy.get('.logs-pane')
|
||||||
|
.invoke('text')
|
||||||
|
.should('match', /PDF Rendering Error|Compilation cancelled/)
|
||||||
|
// Check that the previous compile is not running in the background by
|
||||||
|
// disabling the infinite loop and recompiling
|
||||||
|
cy.findByText('\\def').parent().click()
|
||||||
|
cy.findByText('\\def').parent().type('{home}disabled loop% ')
|
||||||
|
cy.findByText('Recompile').click()
|
||||||
|
cy.get('.pdf-viewer').should('contain.text', 'disabled loop')
|
||||||
|
cy.get('.logs-pane').should(
|
||||||
|
'not.contain.text',
|
||||||
|
'A previous compile is still running'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function checkSyncTeX() {
|
function checkSyncTeX() {
|
||||||
// TODO(25342): re-enable
|
// TODO(25342): re-enable
|
||||||
// eslint-disable-next-line mocha/no-skipped-tests
|
// eslint-disable-next-line mocha/no-skipped-tests
|
||||||
|
@ -129,7 +161,9 @@ describe('SandboxedCompiles', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.log('navigate to Section A')
|
cy.log('navigate to Section A')
|
||||||
cy.get('.cm-content').within(() => cy.findByText('Section A').click())
|
cy.findByRole('textbox', { name: /Source Editor editing/i }).within(
|
||||||
|
() => cy.findByText('Section A').click()
|
||||||
|
)
|
||||||
cy.get('[aria-label="Go to code location in PDF"]').click()
|
cy.get('[aria-label="Go to code location in PDF"]').click()
|
||||||
cy.get('@title').then((title: any) => {
|
cy.get('@title').then((title: any) => {
|
||||||
waitUntilScrollingFinished('.pdfjs-viewer-inner', title)
|
waitUntilScrollingFinished('.pdfjs-viewer-inner', title)
|
||||||
|
@ -138,7 +172,9 @@ describe('SandboxedCompiles', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.log('navigate to Section B')
|
cy.log('navigate to Section B')
|
||||||
cy.get('.cm-content').within(() => cy.findByText('Section B').click())
|
cy.findByRole('textbox', { name: /Source Editor editing/i }).within(
|
||||||
|
() => cy.findByText('Section B').click()
|
||||||
|
)
|
||||||
cy.get('[aria-label="Go to code location in PDF"]').click()
|
cy.get('[aria-label="Go to code location in PDF"]').click()
|
||||||
cy.get('@sectionA').then((title: any) => {
|
cy.get('@sectionA').then((title: any) => {
|
||||||
waitUntilScrollingFinished('.pdfjs-viewer-inner', title)
|
waitUntilScrollingFinished('.pdfjs-viewer-inner', title)
|
||||||
|
@ -227,6 +263,7 @@ describe('SandboxedCompiles', function () {
|
||||||
checkSyncTeX()
|
checkSyncTeX()
|
||||||
checkXeTeX()
|
checkXeTeX()
|
||||||
checkRecompilesAfterErrors()
|
checkRecompilesAfterErrors()
|
||||||
|
checkStopCompile()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.skip('unavailable in CE', function () {
|
describe.skip('unavailable in CE', function () {
|
||||||
|
@ -241,5 +278,6 @@ describe('SandboxedCompiles', function () {
|
||||||
checkSyncTeX()
|
checkSyncTeX()
|
||||||
checkXeTeX()
|
checkXeTeX()
|
||||||
checkRecompilesAfterErrors()
|
checkRecompilesAfterErrors()
|
||||||
|
checkStopCompile()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# Instead run bin/update_build_scripts from
|
# Instead run bin/update_build_scripts from
|
||||||
# https://github.com/overleaf/internal/
|
# https://github.com/overleaf/internal/
|
||||||
|
|
||||||
FROM node:22.15.1 AS base
|
FROM node:22.17.0 AS base
|
||||||
|
|
||||||
WORKDIR /overleaf/services/chat
|
WORKDIR /overleaf/services/chat
|
||||||
|
|
||||||
|
|
|
@ -32,12 +32,12 @@ HERE=$(shell pwd)
|
||||||
MONOREPO=$(shell cd ../../ && pwd)
|
MONOREPO=$(shell cd ../../ && pwd)
|
||||||
# Run the linting commands in the scope of the monorepo.
|
# Run the linting commands in the scope of the monorepo.
|
||||||
# Eslint and prettier (plus some configs) are on the root.
|
# Eslint and prettier (plus some configs) are on the root.
|
||||||
RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 npm run --silent
|
RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.17.0 npm run --silent
|
||||||
|
|
||||||
RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/.eslintignore:/overleaf/.eslintignore --volume $(MONOREPO)/.eslintrc:/overleaf/.eslintrc --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc:/overleaf/.prettierrc --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent
|
RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/.eslintignore:/overleaf/.eslintignore --volume $(MONOREPO)/.eslintrc:/overleaf/.eslintrc --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc:/overleaf/.prettierrc --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent
|
||||||
|
|
||||||
# Same but from the top of the monorepo
|
# Same but from the top of the monorepo
|
||||||
RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent
|
RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.17.0 npm run --silent
|
||||||
|
|
||||||
SHELLCHECK_OPTS = \
|
SHELLCHECK_OPTS = \
|
||||||
--shell=bash \
|
--shell=bash \
|
||||||
|
|
|
@ -4,6 +4,6 @@ chat
|
||||||
--env-add=
|
--env-add=
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -6,7 +6,7 @@ version: "2.3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
test_unit:
|
test_unit:
|
||||||
image: node:22.15.1
|
image: node:22.17.0
|
||||||
volumes:
|
volumes:
|
||||||
- .:/overleaf/services/chat
|
- .:/overleaf/services/chat
|
||||||
- ../../node_modules:/overleaf/node_modules
|
- ../../node_modules:/overleaf/node_modules
|
||||||
|
@ -21,7 +21,7 @@ services:
|
||||||
user: node
|
user: node
|
||||||
|
|
||||||
test_acceptance:
|
test_acceptance:
|
||||||
image: node:22.15.1
|
image: node:22.17.0
|
||||||
volumes:
|
volumes:
|
||||||
- .:/overleaf/services/chat
|
- .:/overleaf/services/chat
|
||||||
- ../../node_modules:/overleaf/node_modules
|
- ../../node_modules:/overleaf/node_modules
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# Instead run bin/update_build_scripts from
|
# Instead run bin/update_build_scripts from
|
||||||
# https://github.com/overleaf/internal/
|
# https://github.com/overleaf/internal/
|
||||||
|
|
||||||
FROM node:22.15.1 AS base
|
FROM node:22.17.0 AS base
|
||||||
|
|
||||||
WORKDIR /overleaf/services/clsi
|
WORKDIR /overleaf/services/clsi
|
||||||
COPY services/clsi/install_deps.sh /overleaf/services/clsi/
|
COPY services/clsi/install_deps.sh /overleaf/services/clsi/
|
||||||
|
|
|
@ -32,12 +32,12 @@ HERE=$(shell pwd)
|
||||||
MONOREPO=$(shell cd ../../ && pwd)
|
MONOREPO=$(shell cd ../../ && pwd)
|
||||||
# Run the linting commands in the scope of the monorepo.
|
# Run the linting commands in the scope of the monorepo.
|
||||||
# Eslint and prettier (plus some configs) are on the root.
|
# Eslint and prettier (plus some configs) are on the root.
|
||||||
RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 npm run --silent
|
RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.17.0 npm run --silent
|
||||||
|
|
||||||
RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/.eslintignore:/overleaf/.eslintignore --volume $(MONOREPO)/.eslintrc:/overleaf/.eslintrc --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc:/overleaf/.prettierrc --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent
|
RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/.eslintignore:/overleaf/.eslintignore --volume $(MONOREPO)/.eslintrc:/overleaf/.eslintrc --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc:/overleaf/.prettierrc --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent
|
||||||
|
|
||||||
# Same but from the top of the monorepo
|
# Same but from the top of the monorepo
|
||||||
RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent
|
RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.17.0 npm run --silent
|
||||||
|
|
||||||
SHELLCHECK_OPTS = \
|
SHELLCHECK_OPTS = \
|
||||||
--shell=bash \
|
--shell=bash \
|
||||||
|
|
|
@ -13,6 +13,7 @@ const {
|
||||||
const logger = require('@overleaf/logger')
|
const logger = require('@overleaf/logger')
|
||||||
const Metrics = require('@overleaf/metrics')
|
const Metrics = require('@overleaf/metrics')
|
||||||
const Settings = require('@overleaf/settings')
|
const Settings = require('@overleaf/settings')
|
||||||
|
const { MeteredStream } = require('@overleaf/stream-utils')
|
||||||
const { CACHE_SUBDIR } = require('./OutputCacheManager')
|
const { CACHE_SUBDIR } = require('./OutputCacheManager')
|
||||||
const { isExtraneousFile } = require('./ResourceWriter')
|
const { isExtraneousFile } = require('./ResourceWriter')
|
||||||
|
|
||||||
|
@ -204,7 +205,13 @@ async function downloadOutputDotSynctexFromCompileCache(
|
||||||
const dst = Path.join(outputDir, 'output.synctex.gz')
|
const dst = Path.join(outputDir, 'output.synctex.gz')
|
||||||
const tmp = dst + crypto.randomUUID()
|
const tmp = dst + crypto.randomUUID()
|
||||||
try {
|
try {
|
||||||
await pipeline(stream, fs.createWriteStream(tmp))
|
await pipeline(
|
||||||
|
stream,
|
||||||
|
new MeteredStream(Metrics, 'clsi_cache_egress', {
|
||||||
|
path: 'output.synctex.gz',
|
||||||
|
}),
|
||||||
|
fs.createWriteStream(tmp)
|
||||||
|
)
|
||||||
await fs.promises.rename(tmp, dst)
|
await fs.promises.rename(tmp, dst)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
try {
|
try {
|
||||||
|
@ -253,6 +260,7 @@ async function downloadLatestCompileCache(projectId, userId, compileDir) {
|
||||||
let abort = false
|
let abort = false
|
||||||
await pipeline(
|
await pipeline(
|
||||||
stream,
|
stream,
|
||||||
|
new MeteredStream(Metrics, 'clsi_cache_egress', { path: 'output.tar.gz' }),
|
||||||
createGunzip(),
|
createGunzip(),
|
||||||
tarFs.extract(compileDir, {
|
tarFs.extract(compileDir, {
|
||||||
// use ignore hook for counting entries (files+folders) and validation.
|
// use ignore hook for counting entries (files+folders) and validation.
|
||||||
|
|
|
@ -232,8 +232,8 @@ const DockerRunner = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// set the path based on the image year
|
// set the path based on the image year
|
||||||
const match = image.match(/:([0-9]+)\.[0-9]+/)
|
const match = image.match(/:([0-9]+)\.[0-9]+|:TL([0-9]+)/)
|
||||||
const year = match ? match[1] : '2014'
|
const year = match ? match[1] || match[2] : '2014'
|
||||||
env.PATH = `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/${year}/bin/x86_64-linux/`
|
env.PATH = `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/${year}/bin/x86_64-linux/`
|
||||||
const options = {
|
const options = {
|
||||||
Cmd: command,
|
Cmd: command,
|
||||||
|
|
|
@ -54,6 +54,7 @@ module.exports = CommandRunner = {
|
||||||
cwd: directory,
|
cwd: directory,
|
||||||
env,
|
env,
|
||||||
stdio: ['pipe', 'pipe', 'ignore'],
|
stdio: ['pipe', 'pipe', 'ignore'],
|
||||||
|
detached: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
let stdout = ''
|
let stdout = ''
|
||||||
|
|
|
@ -5,7 +5,7 @@ clsi
|
||||||
--env-add=ENABLE_PDF_CACHING="true",PDF_CACHING_ENABLE_WORKER_POOL="true",ALLOWED_IMAGES=quay.io/sharelatex/texlive-full:2017.1,TEXLIVE_IMAGE=quay.io/sharelatex/texlive-full:2017.1,TEX_LIVE_IMAGE_NAME_OVERRIDE=us-east1-docker.pkg.dev/overleaf-ops/ol-docker,TEXLIVE_IMAGE_USER="tex",SANDBOXED_COMPILES="true",SANDBOXED_COMPILES_HOST_DIR_COMPILES=$PWD/compiles,SANDBOXED_COMPILES_HOST_DIR_OUTPUT=$PWD/output
|
--env-add=ENABLE_PDF_CACHING="true",PDF_CACHING_ENABLE_WORKER_POOL="true",ALLOWED_IMAGES=quay.io/sharelatex/texlive-full:2017.1,TEXLIVE_IMAGE=quay.io/sharelatex/texlive-full:2017.1,TEX_LIVE_IMAGE_NAME_OVERRIDE=us-east1-docker.pkg.dev/overleaf-ops/ol-docker,TEXLIVE_IMAGE_USER="tex",SANDBOXED_COMPILES="true",SANDBOXED_COMPILES_HOST_DIR_COMPILES=$PWD/compiles,SANDBOXED_COMPILES_HOST_DIR_OUTPUT=$PWD/output
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=True
|
--public-repo=True
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
--use-large-ci-runner=True
|
--use-large-ci-runner=True
|
||||||
|
|
|
@ -107,7 +107,7 @@ if ((process.env.DOCKER_RUNNER || process.env.SANDBOXED_COMPILES) === 'true') {
|
||||||
CLSI: 1,
|
CLSI: 1,
|
||||||
},
|
},
|
||||||
socketPath: '/var/run/docker.sock',
|
socketPath: '/var/run/docker.sock',
|
||||||
user: process.env.TEXLIVE_IMAGE_USER || 'tex',
|
user: process.env.TEXLIVE_IMAGE_USER || 'www-data',
|
||||||
},
|
},
|
||||||
optimiseInDocker: true,
|
optimiseInDocker: true,
|
||||||
expireProjectAfterIdleMs: 24 * 60 * 60 * 1000,
|
expireProjectAfterIdleMs: 24 * 60 * 60 * 1000,
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"@overleaf/o-error": "*",
|
"@overleaf/o-error": "*",
|
||||||
"@overleaf/promise-utils": "*",
|
"@overleaf/promise-utils": "*",
|
||||||
"@overleaf/settings": "*",
|
"@overleaf/settings": "*",
|
||||||
|
"@overleaf/stream-utils": "*",
|
||||||
"archiver": "5.3.2",
|
"archiver": "5.3.2",
|
||||||
"async": "^3.2.5",
|
"async": "^3.2.5",
|
||||||
"body-parser": "^1.20.3",
|
"body-parser": "^1.20.3",
|
||||||
|
@ -37,7 +38,6 @@
|
||||||
"workerpool": "^6.1.5"
|
"workerpool": "^6.1.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/workerpool": "^6.1.0",
|
|
||||||
"chai": "^4.3.6",
|
"chai": "^4.3.6",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
"mocha": "^11.1.0",
|
"mocha": "^11.1.0",
|
||||||
|
|
|
@ -829,13 +829,19 @@
|
||||||
"args": []
|
"args": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "gettimeofday",
|
"name": "gettimeofday",
|
||||||
"action": "SCMP_ACT_ALLOW",
|
"action": "SCMP_ACT_ALLOW",
|
||||||
"args": []
|
"args": []
|
||||||
}, {
|
},
|
||||||
"name": "epoll_pwait",
|
{
|
||||||
"action": "SCMP_ACT_ALLOW",
|
"name": "epoll_pwait",
|
||||||
"args": []
|
"action": "SCMP_ACT_ALLOW",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "poll",
|
||||||
|
"action": "SCMP_ACT_ALLOW",
|
||||||
|
"args": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
47
services/clsi/test/acceptance/js/StopCompile.js
Normal file
47
services/clsi/test/acceptance/js/StopCompile.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
const Client = require('./helpers/Client')
|
||||||
|
const ClsiApp = require('./helpers/ClsiApp')
|
||||||
|
const { expect } = require('chai')
|
||||||
|
|
||||||
|
describe('Stop compile', function () {
|
||||||
|
before(function (done) {
|
||||||
|
this.request = {
|
||||||
|
options: {
|
||||||
|
timeout: 100,
|
||||||
|
}, // seconds
|
||||||
|
resources: [
|
||||||
|
{
|
||||||
|
path: 'main.tex',
|
||||||
|
content: `\
|
||||||
|
\\documentclass{article}
|
||||||
|
\\begin{document}
|
||||||
|
\\def\\x{Hello!\\par\\x}
|
||||||
|
\\x
|
||||||
|
\\end{document}\
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
this.project_id = Client.randomId()
|
||||||
|
ClsiApp.ensureRunning(() => {
|
||||||
|
// start the compile in the background
|
||||||
|
Client.compile(this.project_id, this.request, (error, res, body) => {
|
||||||
|
this.compileResult = { error, res, body }
|
||||||
|
})
|
||||||
|
// wait for 1 second before stopping the compile
|
||||||
|
setTimeout(() => {
|
||||||
|
Client.stopCompile(this.project_id, (error, res, body) => {
|
||||||
|
this.stopResult = { error, res, body }
|
||||||
|
setTimeout(done, 1000) // allow time for the compile request to terminate
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should force a compile response with an error status', function () {
|
||||||
|
expect(this.stopResult.error).to.be.null
|
||||||
|
expect(this.stopResult.res.statusCode).to.equal(204)
|
||||||
|
expect(this.compileResult.res.statusCode).to.equal(200)
|
||||||
|
expect(this.compileResult.body.compile.status).to.equal('terminated')
|
||||||
|
expect(this.compileResult.body.compile.error).to.equal('terminated')
|
||||||
|
})
|
||||||
|
})
|
|
@ -42,6 +42,16 @@ module.exports = Client = {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
stopCompile(projectId, callback) {
|
||||||
|
if (callback == null) {
|
||||||
|
callback = function () {}
|
||||||
|
}
|
||||||
|
return request.post(
|
||||||
|
{ url: `${this.host}/project/${projectId}/compile/stop` },
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
clearCache(projectId, callback) {
|
clearCache(projectId, callback) {
|
||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
callback = function () {}
|
callback = function () {}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# Instead run bin/update_build_scripts from
|
# Instead run bin/update_build_scripts from
|
||||||
# https://github.com/overleaf/internal/
|
# https://github.com/overleaf/internal/
|
||||||
|
|
||||||
FROM node:22.15.1 AS base
|
FROM node:22.17.0 AS base
|
||||||
|
|
||||||
WORKDIR /overleaf/services/contacts
|
WORKDIR /overleaf/services/contacts
|
||||||
|
|
||||||
|
|
|
@ -32,12 +32,12 @@ HERE=$(shell pwd)
|
||||||
MONOREPO=$(shell cd ../../ && pwd)
|
MONOREPO=$(shell cd ../../ && pwd)
|
||||||
# Run the linting commands in the scope of the monorepo.
|
# Run the linting commands in the scope of the monorepo.
|
||||||
# Eslint and prettier (plus some configs) are on the root.
|
# Eslint and prettier (plus some configs) are on the root.
|
||||||
RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 npm run --silent
|
RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.17.0 npm run --silent
|
||||||
|
|
||||||
RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/.eslintignore:/overleaf/.eslintignore --volume $(MONOREPO)/.eslintrc:/overleaf/.eslintrc --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc:/overleaf/.prettierrc --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent
|
RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/.eslintignore:/overleaf/.eslintignore --volume $(MONOREPO)/.eslintrc:/overleaf/.eslintrc --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc:/overleaf/.prettierrc --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent
|
||||||
|
|
||||||
# Same but from the top of the monorepo
|
# Same but from the top of the monorepo
|
||||||
RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent
|
RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.17.0 npm run --silent
|
||||||
|
|
||||||
SHELLCHECK_OPTS = \
|
SHELLCHECK_OPTS = \
|
||||||
--shell=bash \
|
--shell=bash \
|
||||||
|
|
|
@ -4,6 +4,6 @@ contacts
|
||||||
--env-add=
|
--env-add=
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=True
|
--esmock-loader=True
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -6,7 +6,7 @@ version: "2.3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
test_unit:
|
test_unit:
|
||||||
image: node:22.15.1
|
image: node:22.17.0
|
||||||
volumes:
|
volumes:
|
||||||
- .:/overleaf/services/contacts
|
- .:/overleaf/services/contacts
|
||||||
- ../../node_modules:/overleaf/node_modules
|
- ../../node_modules:/overleaf/node_modules
|
||||||
|
@ -21,7 +21,7 @@ services:
|
||||||
user: node
|
user: node
|
||||||
|
|
||||||
test_acceptance:
|
test_acceptance:
|
||||||
image: node:22.15.1
|
image: node:22.17.0
|
||||||
volumes:
|
volumes:
|
||||||
- .:/overleaf/services/contacts
|
- .:/overleaf/services/contacts
|
||||||
- ../../node_modules:/overleaf/node_modules
|
- ../../node_modules:/overleaf/node_modules
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# Instead run bin/update_build_scripts from
|
# Instead run bin/update_build_scripts from
|
||||||
# https://github.com/overleaf/internal/
|
# https://github.com/overleaf/internal/
|
||||||
|
|
||||||
FROM node:22.15.1 AS base
|
FROM node:22.17.0 AS base
|
||||||
|
|
||||||
WORKDIR /overleaf/services/docstore
|
WORKDIR /overleaf/services/docstore
|
||||||
|
|
||||||
|
|
|
@ -32,12 +32,12 @@ HERE=$(shell pwd)
|
||||||
MONOREPO=$(shell cd ../../ && pwd)
|
MONOREPO=$(shell cd ../../ && pwd)
|
||||||
# Run the linting commands in the scope of the monorepo.
|
# Run the linting commands in the scope of the monorepo.
|
||||||
# Eslint and prettier (plus some configs) are on the root.
|
# Eslint and prettier (plus some configs) are on the root.
|
||||||
RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 npm run --silent
|
RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.17.0 npm run --silent
|
||||||
|
|
||||||
RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/.eslintignore:/overleaf/.eslintignore --volume $(MONOREPO)/.eslintrc:/overleaf/.eslintrc --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc:/overleaf/.prettierrc --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent
|
RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/.eslintignore:/overleaf/.eslintignore --volume $(MONOREPO)/.eslintrc:/overleaf/.eslintrc --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc:/overleaf/.prettierrc --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent
|
||||||
|
|
||||||
# Same but from the top of the monorepo
|
# Same but from the top of the monorepo
|
||||||
RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent
|
RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.17.0 npm run --silent
|
||||||
|
|
||||||
SHELLCHECK_OPTS = \
|
SHELLCHECK_OPTS = \
|
||||||
--shell=bash \
|
--shell=bash \
|
||||||
|
|
|
@ -4,6 +4,6 @@ docstore
|
||||||
--env-add=
|
--env-add=
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=True
|
--public-repo=True
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -6,7 +6,7 @@ version: "2.3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
test_unit:
|
test_unit:
|
||||||
image: node:22.15.1
|
image: node:22.17.0
|
||||||
volumes:
|
volumes:
|
||||||
- .:/overleaf/services/docstore
|
- .:/overleaf/services/docstore
|
||||||
- ../../node_modules:/overleaf/node_modules
|
- ../../node_modules:/overleaf/node_modules
|
||||||
|
@ -21,7 +21,7 @@ services:
|
||||||
user: node
|
user: node
|
||||||
|
|
||||||
test_acceptance:
|
test_acceptance:
|
||||||
image: node:22.15.1
|
image: node:22.17.0
|
||||||
volumes:
|
volumes:
|
||||||
- .:/overleaf/services/docstore
|
- .:/overleaf/services/docstore
|
||||||
- ../../node_modules:/overleaf/node_modules
|
- ../../node_modules:/overleaf/node_modules
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# Instead run bin/update_build_scripts from
|
# Instead run bin/update_build_scripts from
|
||||||
# https://github.com/overleaf/internal/
|
# https://github.com/overleaf/internal/
|
||||||
|
|
||||||
FROM node:22.15.1 AS base
|
FROM node:22.17.0 AS base
|
||||||
|
|
||||||
WORKDIR /overleaf/services/document-updater
|
WORKDIR /overleaf/services/document-updater
|
||||||
|
|
||||||
|
|
|
@ -32,12 +32,12 @@ HERE=$(shell pwd)
|
||||||
MONOREPO=$(shell cd ../../ && pwd)
|
MONOREPO=$(shell cd ../../ && pwd)
|
||||||
# Run the linting commands in the scope of the monorepo.
|
# Run the linting commands in the scope of the monorepo.
|
||||||
# Eslint and prettier (plus some configs) are on the root.
|
# Eslint and prettier (plus some configs) are on the root.
|
||||||
RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 npm run --silent
|
RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.17.0 npm run --silent
|
||||||
|
|
||||||
RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/.eslintignore:/overleaf/.eslintignore --volume $(MONOREPO)/.eslintrc:/overleaf/.eslintrc --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc:/overleaf/.prettierrc --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent
|
RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/.eslintignore:/overleaf/.eslintignore --volume $(MONOREPO)/.eslintrc:/overleaf/.eslintrc --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc:/overleaf/.prettierrc --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent
|
||||||
|
|
||||||
# Same but from the top of the monorepo
|
# Same but from the top of the monorepo
|
||||||
RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent
|
RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.17.0 npm run --silent
|
||||||
|
|
||||||
SHELLCHECK_OPTS = \
|
SHELLCHECK_OPTS = \
|
||||||
--shell=bash \
|
--shell=bash \
|
||||||
|
|
|
@ -4,6 +4,6 @@ document-updater
|
||||||
--env-add=
|
--env-add=
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=True
|
--public-repo=True
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -6,7 +6,7 @@ version: "2.3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
test_unit:
|
test_unit:
|
||||||
image: node:22.15.1
|
image: node:22.17.0
|
||||||
volumes:
|
volumes:
|
||||||
- .:/overleaf/services/document-updater
|
- .:/overleaf/services/document-updater
|
||||||
- ../../node_modules:/overleaf/node_modules
|
- ../../node_modules:/overleaf/node_modules
|
||||||
|
@ -21,7 +21,7 @@ services:
|
||||||
user: node
|
user: node
|
||||||
|
|
||||||
test_acceptance:
|
test_acceptance:
|
||||||
image: node:22.15.1
|
image: node:22.17.0
|
||||||
volumes:
|
volumes:
|
||||||
- .:/overleaf/services/document-updater
|
- .:/overleaf/services/document-updater
|
||||||
- ../../node_modules:/overleaf/node_modules
|
- ../../node_modules:/overleaf/node_modules
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
22.15.1
|
22.17.0
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# Instead run bin/update_build_scripts from
|
# Instead run bin/update_build_scripts from
|
||||||
# https://github.com/overleaf/internal/
|
# https://github.com/overleaf/internal/
|
||||||
|
|
||||||
FROM node:22.15.1 AS base
|
FROM node:22.17.0 AS base
|
||||||
|
|
||||||
WORKDIR /overleaf/services/filestore
|
WORKDIR /overleaf/services/filestore
|
||||||
COPY services/filestore/install_deps.sh /overleaf/services/filestore/
|
COPY services/filestore/install_deps.sh /overleaf/services/filestore/
|
||||||
|
|
|
@ -32,12 +32,12 @@ HERE=$(shell pwd)
|
||||||
MONOREPO=$(shell cd ../../ && pwd)
|
MONOREPO=$(shell cd ../../ && pwd)
|
||||||
# Run the linting commands in the scope of the monorepo.
|
# Run the linting commands in the scope of the monorepo.
|
||||||
# Eslint and prettier (plus some configs) are on the root.
|
# Eslint and prettier (plus some configs) are on the root.
|
||||||
RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 npm run --silent
|
RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.17.0 npm run --silent
|
||||||
|
|
||||||
RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/.eslintignore:/overleaf/.eslintignore --volume $(MONOREPO)/.eslintrc:/overleaf/.eslintrc --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc:/overleaf/.prettierrc --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent
|
RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/.eslintignore:/overleaf/.eslintignore --volume $(MONOREPO)/.eslintrc:/overleaf/.eslintrc --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc:/overleaf/.prettierrc --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent
|
||||||
|
|
||||||
# Same but from the top of the monorepo
|
# Same but from the top of the monorepo
|
||||||
RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent
|
RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.17.0 npm run --silent
|
||||||
|
|
||||||
SHELLCHECK_OPTS = \
|
SHELLCHECK_OPTS = \
|
||||||
--shell=bash \
|
--shell=bash \
|
||||||
|
|
|
@ -5,7 +5,7 @@ filestore
|
||||||
--env-add=ENABLE_CONVERSIONS="true",USE_PROM_METRICS="true",AWS_S3_USER_FILES_STORAGE_CLASS=REDUCED_REDUNDANCY,AWS_S3_USER_FILES_BUCKET_NAME=fake-user-files,AWS_S3_USER_FILES_DEK_BUCKET_NAME=fake-user-files-dek,AWS_S3_TEMPLATE_FILES_BUCKET_NAME=fake-template-files,GCS_USER_FILES_BUCKET_NAME=fake-gcs-user-files,GCS_TEMPLATE_FILES_BUCKET_NAME=fake-gcs-template-files
|
--env-add=ENABLE_CONVERSIONS="true",USE_PROM_METRICS="true",AWS_S3_USER_FILES_STORAGE_CLASS=REDUCED_REDUNDANCY,AWS_S3_USER_FILES_BUCKET_NAME=fake-user-files,AWS_S3_USER_FILES_DEK_BUCKET_NAME=fake-user-files-dek,AWS_S3_TEMPLATE_FILES_BUCKET_NAME=fake-template-files,GCS_USER_FILES_BUCKET_NAME=fake-gcs-user-files,GCS_TEMPLATE_FILES_BUCKET_NAME=fake-gcs-template-files
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--node-version=22.15.1
|
--node-version=22.17.0
|
||||||
--public-repo=True
|
--public-repo=True
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
--test-acceptance-shards=SHARD_01_,SHARD_02_,SHARD_03_
|
--test-acceptance-shards=SHARD_01_,SHARD_02_,SHARD_03_
|
||||||
|
|
|
@ -64,7 +64,7 @@ services:
|
||||||
command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
|
command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
|
||||||
user: root
|
user: root
|
||||||
certs:
|
certs:
|
||||||
image: node:22.15.1
|
image: node:22.17.0
|
||||||
volumes:
|
volumes:
|
||||||
- ./test/acceptance/certs:/certs
|
- ./test/acceptance/certs:/certs
|
||||||
working_dir: /certs
|
working_dir: /certs
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue