23 Commits

Author SHA1 Message Date
Miguel Serrano
6ca1a9e472 [git-bridge] Update dependency versions (#29484)
GitOrigin-RevId: 1640ea71798f650196f20f656f8656f3abeee7e5
2025-11-04 09:06:15 +00:00
Maria Florencia Besteiro Gonzalez
8cd2f6ac46 Merge pull request #29485 from overleaf/mfb-from-joi-to-zod-clsi-cache-revert
Reapply "migrate from joi to zod CLSI-CACHE service"

GitOrigin-RevId: 638b9e21b8cf18c1733f8a01375dab4099fe5d73
2025-11-04 09:06:10 +00:00
Alf Eaton
c3881084be Merge pull request #29444 from overleaf/ds-remove-brazil-discount-banner
Removing GEO Price Banner from Brazil

GitOrigin-RevId: 85968cb546083bb3d3a526061daf8e6c1149867d
2025-11-04 09:06:06 +00:00
Alf Eaton
1d3d81e67d Allow enabling EditContext in CodeMirror (#29114)
GitOrigin-RevId: 15d70fd2d5da68d43f441bb0c65ff0cd2d785306
2025-11-04 09:06:01 +00:00
Alf Eaton
ad3d68ece0 Record when the word count modal is opened (#29134)
GitOrigin-RevId: d49778199e793297c49447f56060dedb31750c34
2025-11-04 09:05:57 +00:00
Alf Eaton
632f7c85ed Remove content class names from token access page (#29212)
GitOrigin-RevId: 6e3e8ab9b445eed698f6d019841d12cfc24d0454
2025-11-04 09:05:52 +00:00
Miguel Serrano
1a3ce3f192 Revert "[web] Add User logs to Group Audit Logs view (#29155)" (#29479)
This reverts commit c455edc5a700ba24e73a16b2a66b50cb92f24176.

GitOrigin-RevId: 40a1516ab9cec690d0487a0a870b9fab17598d60
2025-11-04 09:05:47 +00:00
Miguel Serrano
3522923d4e [web] Add User logs to Group Audit Logs view (#29155)
* [web] Add User logs to Group Audit Logs view

GitOrigin-RevId: c455edc5a700ba24e73a16b2a66b50cb92f24176
2025-11-04 09:05:35 +00:00
Borja
85848b927e fix: improve regex escaping in HighlightSearchTerm component (#29477)
GitOrigin-RevId: ceb9c461a22b0170d6275ff31247e0eab6b22197
2025-11-04 09:05:23 +00:00
Jimmy Domagala-Tang
42a80a1ca3 feat: moving to grouping by primarily recipient, then by project
GitOrigin-RevId: 31e3420d6f5834a9da405eea99f2f353a39a0f11
2025-11-04 09:05:19 +00:00
Jimmy Domagala-Tang
618445722a fix: renaming consts for min and max notification age
GitOrigin-RevId: 7d36d073301ff02d79e2296af7bfc946ea1ad72a
2025-11-04 09:05:14 +00:00
Jimmy Domagala-Tang
d77be2c94e adding test for processNotifications
GitOrigin-RevId: 35825755344e9cc10f9e571a253a2eae982b306a
2025-11-04 09:05:10 +00:00
Jimmy Domagala-Tang
bfa70bf43b feat: handle determining delay and when to send at notification processing instead of managing scheduledAt. this removes the need for scheduledAt as we will do the scheduling calculations at processing time
GitOrigin-RevId: d0bbd5adc29ab58a797c171b55aabfda1cec39ea
2025-11-04 09:05:05 +00:00
Domagoj Kriskovic
23fc714d93 Replace dependency on "mmmagic" (#29272)
* Replace dependency on "mmmagic"

* Refactor file type checks in OpenInOverleafHelper

GitOrigin-RevId: fc5876b75955cb4981f084df777caa294907c8f9
2025-11-04 09:05:00 +00:00
Borja
b1b638dd90 Add modal to request language (#29441)
GitOrigin-RevId: a928f6c1eb12f1e47fc6f295b8cbb5c8740df5ac
2025-11-03 09:06:21 +00:00
Maria Florencia Besteiro Gonzalez
a88c307962 Merge pull request #29471 from overleaf/revert-29369-mfb-from-joi-to-zod-clsi-cache
Revert "migrate from joi to zod CLSI-CACHE service"

GitOrigin-RevId: 1846e5a1d990f7ff22982fc32277e24d69e9d1e0
2025-11-03 09:06:10 +00:00
Maria Florencia Besteiro Gonzalez
1720314726 Merge pull request #29369 from overleaf/mfb-from-joi-to-zod-clsi-cache
migrate from joi to zod CLSI-CACHE service

GitOrigin-RevId: b583431a902a1183235cb91a270f4123a5a7e547
2025-11-03 09:06:05 +00:00
Antoine Clausse
5a242cd4ac [web] Add font Inter (#29440)
* Add font Inter

Files downloaded from https://rsms.me/inter/
Kept only the woff2 files and CSS definitions for Inter-Regular and Inter-SemiBold

* Delete inter folder

* Add inter to build-fonts.mjs and run it

* Add inter.css

* Update latofonts archive URL

GitOrigin-RevId: 24cd8a48f4566d8a55443eb2cd938ee129dff5f8
2025-11-03 09:05:57 +00:00
Antoine Clausse
4d1e316bea [web] Conditionally render copies of login/register pages (Pug) depending of a uniaccessphase1 split-test (#29390)
* Conditionally render a CIAM copy of login.pug depending on `ciam` split-test

* Conditionally render a CIAM copy of register.pug depending on `ciam` split-test

* Conditionally render a CIAM copy of institutional_login.pug depending on `ciam` split-test

* Rename the test to `uniaccessphase1`

* Fixup ciam paths (underscores, not hyphens)

* Escape translations in `log_in_with_existing_institution_email`

See https://github.com/overleaf/internal/pull/29390#discussion_r2479318609

GitOrigin-RevId: fff0e154d1e1faad4842a2c49fa5889f9cc189e6
2025-11-03 09:05:53 +00:00
Rebeka Dekany
ec5a283e9e Handle autoplay failures by showing video controls (#29412)
GitOrigin-RevId: afe5d70d00bfda97cde7e3479875b9cd7be5c46f
2025-11-03 09:05:49 +00:00
Thomas
c48bc282f1 Merge pull request #29066 from overleaf/copilot/fix-broken-links-manage-users
Hide Members tab for non-group subscriptions in admin panel

GitOrigin-RevId: 138566a6e3d89d3009d1b6683cbe04212d279969
2025-11-03 09:05:34 +00:00
Jakob Ackermann
a80b69abe5 [web] move free compiles up one bracket (#29411)
GitOrigin-RevId: 613b703d677d91110cd6dcf085e9291008e05f5c
2025-11-03 09:05:23 +00:00
Simon Gardner
41976072e5 Update UX copy for group member management options
GitOrigin-RevId: 33fcc1da1fae77f57ca4833cbaf314f497ef0ab7
2025-11-03 09:05:11 +00:00
51 changed files with 523 additions and 348 deletions

View File

@@ -0,0 +1,17 @@
const { isZodErrorLike, fromError } = require('zod-validation-error')
/**
* @typedef {import('express').ErrorRequestHandler} ErrorRequestHandler
*/
const handleValidationError = [
/** @type {ErrorRequestHandler} */
(err, req, res, next) => {
if (!isZodErrorLike(err)) {
return next(err)
}
res.status(400).json({ ...fromError(err), statusCode: 400 })
},
]
module.exports = { handleValidationError }

View File

@@ -2,10 +2,12 @@ const { ParamsError } = require('./Errors')
const { z } = require('zod')
const { zz } = require('./zodHelpers')
const { validateReq } = require('./validateReq')
const { handleValidationError } = require('./handleValidationError')
module.exports = {
z,
zz,
validateReq,
handleValidationError,
ParamsError,
}

View File

@@ -22,7 +22,8 @@
"dependencies": {
"@overleaf/o-error": "*",
"mongodb": "^6.12.0",
"zod": "^4.1.8"
"zod": "^4.1.8",
"zod-validation-error": "^4.0.1"
},
"devDependencies": {
"typescript": "^5.0.4",

207
package-lock.json generated
View File

@@ -541,7 +541,8 @@
"dependencies": {
"@overleaf/o-error": "*",
"mongodb": "^6.12.0",
"zod": "^4.1.8"
"zod": "^4.1.8",
"zod-validation-error": "^4.0.1"
},
"devDependencies": {
"typescript": "^5.0.4",
@@ -9304,6 +9305,16 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true
},
"node_modules/@borewit/text-codec": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz",
"integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@bugsnag/browser": {
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@bugsnag/browser/-/browser-7.25.0.tgz",
@@ -10341,26 +10352,6 @@
"ieee754": "^1.2.1"
}
},
"node_modules/@customerio/cdp-analytics-node/node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/@customerio/cdp-analytics-node/node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
@@ -19126,6 +19117,47 @@
"@testing-library/dom": ">=7.21.4"
}
},
"node_modules/@tokenizer/inflate": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz",
"integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"fflate": "^0.8.2",
"token-types": "^6.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@tokenizer/inflate/node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/@tokenizer/token": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
"license": "MIT"
},
"node_modules/@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -30400,6 +30432,12 @@
"node": ">=4.0.0"
}
},
"node_modules/fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
"license": "MIT"
},
"node_modules/figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@@ -30468,6 +30506,24 @@
"node": ">= 12"
}
},
"node_modules/file-type": {
"version": "21.0.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-21.0.0.tgz",
"integrity": "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==",
"license": "MIT",
"dependencies": {
"@tokenizer/inflate": "^0.2.7",
"strtok3": "^10.2.2",
"token-types": "^6.0.0",
"uint8array-extras": "^1.4.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
}
},
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -33121,9 +33177,24 @@
"license": "ISC"
},
"node_modules/ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/ignore": {
"version": "5.3.1",
@@ -46121,6 +46192,22 @@
"optional": true,
"peer": true
},
"node_modules/strtok3": {
"version": "10.3.4",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz",
"integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==",
"license": "MIT",
"dependencies": {
"@tokenizer/token": "^0.3.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/stubs": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz",
@@ -48051,6 +48138,24 @@
"resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz",
"integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ="
},
"node_modules/token-types": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz",
"integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==",
"license": "MIT",
"dependencies": {
"@borewit/text-codec": "^0.1.0",
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/toposort": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
@@ -48499,6 +48604,18 @@
"resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz",
"integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA=="
},
"node_modules/uint8array-extras": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz",
"integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ultron": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz",
@@ -49136,26 +49253,6 @@
"ieee754": "^1.2.1"
}
},
"node_modules/vinyl-contents/node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/vinyl-fs": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.0.tgz",
@@ -51424,9 +51521,9 @@
"@overleaf/promise-utils": "*",
"@overleaf/settings": "*",
"@overleaf/stream-utils": "*",
"@overleaf/validation-tools": "*",
"body-parser": "^1.20.3",
"bunyan": "^1.8.15",
"celebrate": "^15.0.3",
"express": "^4.21.2",
"p-limit": "^3.1.0"
},
@@ -53194,6 +53291,7 @@
"express-bearer-token": "^2.4.0",
"express-http-proxy": "^1.6.0",
"express-session": "^1.17.1",
"file-type": "^21.0.0",
"globby": "^5.0.0",
"helmet": "^6.0.1",
"https-proxy-agent": "^7.0.6",
@@ -54194,25 +54292,6 @@
"node": ">= 14"
}
},
"services/web/node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"services/web/node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",

View File

@@ -15,27 +15,27 @@
<maven.assembly.plugin.version>3.1.0</maven.assembly.plugin.version>
<fmt.plugin.version>2.23</fmt.plugin.version>
<junit.version>4.13.2</junit.version>
<jmock.junit4.version>2.8.4</jmock.junit4.version>
<jmock.junit4.version>2.13.1</jmock.junit4.version>
<jetty.servlet.version>9.4.57.v20241219</jetty.servlet.version>
<gson.version>2.9.0</gson.version>
<async.http.client.version>3.0.2</async.http.client.version>
<gson.version>2.13.2</gson.version>
<async.http.client.version>3.0.3</async.http.client.version>
<jgit.version>6.10.1.202505221210-r</jgit.version>
<sqlite.jdbc.version>3.41.2.2</sqlite.jdbc.version>
<joda.time.version>2.9.9</joda.time.version>
<google.oauth.client.version>1.37.0</google.oauth.client.version>
<google.oauth.client.version>1.39.0</google.oauth.client.version>
<google.http.client.version>1.23.0</google.http.client.version>
<commons.lang3.version>3.18.0</commons.lang3.version>
<logback.classic.version>1.5.18</logback.classic.version>
<mockserver.version>5.12.0</mockserver.version>
<mockito.version>5.12.0</mockito.version>
<aws.java.sdk.version>1.12.780</aws.java.sdk.version>
<commons.lang3.version>3.19.0</commons.lang3.version>
<logback.classic.version>1.5.20</logback.classic.version>
<mockserver.version>5.15.0</mockserver.version>
<mockito.version>5.20.0</mockito.version>
<aws.java.sdk.version>1.12.793</aws.java.sdk.version>
<jakarta.xml.bind.api.version>${jaxb.runtime.version}</jakarta.xml.bind.api.version>
<jaxb.runtime.version>2.3.2</jaxb.runtime.version>
<httpclient.version>4.5.14</httpclient.version>
<commons.io.version>2.18.0</commons.io.version>
<commons.compress.version>1.27.1</commons.compress.version>
<commons.io.version>2.20.0</commons.io.version>
<commons.compress.version>1.28.0</commons.compress.version>
<simpleclient.version>0.10.0</simpleclient.version>
<bouncycastle.crypto.version>1.81</bouncycastle.crypto.version>
<bouncycastle.crypto.version>1.82</bouncycastle.crypto.version>
</properties>
<build>
<plugins>

View File

@@ -3,5 +3,8 @@
"node_modules": true,
"data": true
},
"cSpell.words": ["docstore", "Tpds"]
"cSpell.words": [
"docstore",
"Tpds"
]
}

View File

@@ -2,6 +2,7 @@ DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
BUILD_NUMBER ?= local
export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/')
export COMMIT_SHA ?= $(shell git rev-parse HEAD)
PROJECT_NAME = web
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
@@ -16,7 +17,7 @@ CFG_SERVER_CE=/overleaf/services/web/test/acceptance/config/settings.test.server
CFG_SERVER_PRO=/overleaf/services/web/test/acceptance/config/settings.test.server-pro.js
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \
BRANCH_NAME=$(BRANCH_NAME) \
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}
@@ -426,7 +427,7 @@ ci:
#
ORG_PATH = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
RUN_LINT_FORMAT ?= \
docker run --rm --env BRANCH_NAME --env CI --env COMMIT_SHA --env MONOREPO --volume ${PWD}/data/reports:/overleaf/services/web/data/reports ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
docker run --rm --env BRANCH_NAME --env CI --env COMMIT_SHA --env MONOREPO --volume ${PWD}/data/reports:/overleaf/services/web/data/reports ${IMAGE_CI}
NODE_MODULES_PATH := ${PATH}:${PWD}/node_modules/.bin:/overleaf/services/web/node_modules/.bin
WITH_NODE_MODULES_PATH = \
@@ -549,9 +550,9 @@ shellcheck_fix:
# Build & publish
#
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_REPO ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
# A shared scratch tag that is used by all the pipelines for pushing the layers of the production image ASAP from a parallel step.
# We do not want to make the image available before all the tests have passed.
# Using a single tag avoids generating cruft in our docker repository / AR.

View File

@@ -52,6 +52,8 @@ function getNewCompileBackendClass(projectId, compileBackendClass) {
return 'n4'
case 'c2d':
return 'n4'
case 'c3d':
return 'n4'
case 'c4d':
return 'n4'
default:

View File

@@ -135,7 +135,7 @@ async function _getProjectCompileLimits(project) {
timeout:
ownerFeatures.compileTimeout || Settings.defaultFeatures.compileTimeout,
compileGroup,
compileBackendClass: compileGroup === 'standard' ? 'n2d' : 'c4d',
compileBackendClass: compileGroup === 'standard' ? 'c3d' : 'c4d',
ownerAnalyticsId: analyticsId,
}
return limits

View File

@@ -445,7 +445,6 @@ async function projectListPage(req, res, next) {
_.sample(['on-premise', 'FOMO', 'FOMO', 'FOMO'])
let showInrGeoBanner = false
let showBrlGeoBanner = false
let showLATAMBanner = false
let recommendedCurrency
@@ -459,7 +458,6 @@ async function projectListPage(req, res, next) {
if (countryCode === 'IN') {
showInrGeoBanner = true
}
showBrlGeoBanner = countryCode === 'BR'
showLATAMBanner = ['MX', 'CO', 'CL', 'PE'].includes(countryCode)
// LATAM Banner needs to know which currency to display
@@ -566,7 +564,6 @@ async function projectListPage(req, res, next) {
showLATAMBanner,
recommendedCurrency,
showInrGeoBanner,
showBrlGeoBanner,
projectDashboardReact: true, // used in navbar
groupSsoSetupSuccess,
joinedGroupName,

View File

@@ -2,7 +2,6 @@ import express from 'express'
import Settings from '@overleaf/settings'
import logger from '@overleaf/logger'
import metrics from '@overleaf/metrics'
import Validation from './Validation.js'
import csp, { removeCSPHeaders } from './CSP.mjs'
import Router from '../router.mjs'
import helmet from 'helmet'
@@ -38,6 +37,7 @@ import os from 'node:os'
import http from 'node:http'
import { fileURLToPath } from 'node:url'
import serveStaticWrapper from './ServeStaticWrapper.mjs'
import { handleValidationError } from '@overleaf/validation-tools'
const { hasAdminAccess } = AdminAuthorizationHelper
const sessionsRedisClient = UserSessionsRedis.client()
@@ -356,17 +356,17 @@ const server = http.createServer(app)
if (Settings.enabledServices.includes('api')) {
logger.debug({}, 'providing api router')
app.use(privateApiRouter)
app.use(Validation.errorMiddleware)
app.use(handleValidationError)
app.use(ErrorController.handleApiError)
}
if (Settings.enabledServices.includes('web')) {
logger.debug({}, 'providing web router')
app.use(publicApiRouter) // public API goes with web router for public access
app.use(Validation.errorMiddleware)
app.use(handleValidationError)
app.use(ErrorController.handleApiError)
app.use(webRouter)
app.use(Validation.errorMiddleware)
app.use(handleValidationError)
app.use(ErrorController.handleError)
}

View File

@@ -7,22 +7,6 @@ const {
zz,
ParamsError,
} = require('@overleaf/validation-tools')
const { isZodErrorLike, fromError } = require('zod-validation-error')
/**
* @typedef {import('express').ErrorRequestHandler} ErrorRequestHandler
*/
const errorMiddleware = [
/** @type {ErrorRequestHandler} */
(err, req, res, next) => {
if (!isZodErrorLike(err)) {
return next(err)
}
res.status(400).json({ ...fromError(err), statusCode: 400 })
},
]
const validateReqWeb = (req, schema) => {
try {
@@ -37,7 +21,6 @@ const validateReqWeb = (req, schema) => {
}
module.exports = {
errorMiddleware,
validateReq: validateReqWeb,
z,
zz,

View File

@@ -61,7 +61,6 @@ block append meta
content=groupsAndEnterpriseBannerVariant
)
meta(name='ol-showInrGeoBanner' data-type='boolean' content=showInrGeoBanner)
meta(name='ol-showBrlGeoBanner' data-type='boolean' content=showBrlGeoBanner)
meta(
name='ol-recommendedCurrency'
data-type='string'

View File

@@ -13,5 +13,4 @@ block append meta
meta(name='ol-user' data-type='json' content=user)
block content
#main-content.content.content-alt
#token-access-page
#token-access-page

View File

@@ -715,10 +715,14 @@ module.exports = {
parseInt(process.env.OVERLEAF_PROJECT_HARD_DELETION_DELAY, 10) ||
1000 * 60 * 60 * 24 * 90, // 90 days
// Delay before sending comment mention notifications
commentMentionDelay:
// Maximum Delay before sending comment mention notifications
notificationMaxDelay:
parseInt(process.env.COMMENT_MENTION_DELAY_MINUTES) || 30 * 60 * 1000, // 30 minutes
// Comment mention notifications will wait at least this long before being sent
notificationMinDelay:
parseInt(process.env.COMMENT_MENTION_DELAY_MINUTES) || 10 * 60 * 1000, // 10 minutes
// Maximum JSON size in HTTP requests
// We should be able to process twice the max doc length, to allow for
// - the doc content

View File

@@ -4,7 +4,6 @@ import localesPromise from '@/i18n'
import './shared/commands'
import './shared/exceptions'
import './ct/commands'
import './ct/codemirror'
import '../../test/frontend/helpers/bootstrap'
beforeEach(function () {

View File

@@ -1,4 +0,0 @@
import { EditorView } from '@codemirror/view'
// @ts-ignore (disable EditContext-based editing until stable)
EditorView.EDIT_CONTEXT = false

View File

@@ -412,6 +412,7 @@
"delete_figure": "",
"delete_message": "",
"delete_message_confirmation": "",
"delete_permanently": "",
"delete_projects": "",
"delete_row_or_column": "",
"delete_sso_config": "",
@@ -2037,6 +2038,7 @@
"unlink_all_users_explanation": "",
"unlink_dropbox_folder": "",
"unlink_dropbox_warning": "",
"unlink_from_sso": "",
"unlink_github_repository": "",
"unlink_github_warning": "",
"unlink_linked_accounts": "",
@@ -2045,7 +2047,6 @@
"unlink_provider_account_warning": "",
"unlink_reference": "",
"unlink_the_project_from_the_current_github_repo": "",
"unlink_user": "",
"unlink_user_explanation": "",
"unlink_users": "",
"unlink_warning_reference": "",

View File

@@ -2,6 +2,7 @@ This directory contains fonts used by the Overleaf web application.
* [DM Mono](https://github.com/googlefonts/dm-mono)
* [Font Awesome](https://fontawesome.com/v4/)
* [Inter](https://rsms.me/inter/)
* [Lato](https://www.latofonts.com/)
* [Material Symbols](https://github.com/google/material-design-icons)
* [Merriweather](https://github.com/SorkinType/Merriweather)

View File

@@ -24,10 +24,19 @@ const families = [
archive: 'https://fontawesome.com/v4/assets/font-awesome-4.7.0.zip',
fonts: ['font-awesome-4.7.0/fonts/fontawesome-webfont.woff2'],
},
{
folder: 'inter',
url: 'https://github.com/rsms/inter/releases/tag/v4.1',
archive:
'https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip',
fonts: ['web/Inter-Regular.woff2', 'web/Inter-SemiBold.woff2'],
license:
'https://raw.githubusercontent.com/rsms/inter/refs/heads/master/LICENSE.txt',
},
{
folder: 'lato',
url: 'https://www.latofonts.com/',
archive: 'https://www.latofonts.com/download/lato2oflweb-zip/',
archive: 'https://www.latofonts.com/files/Lato2OFLWeb.zip',
fonts: [
'Lato2OFLWeb/LatoLatin/fonts/LatoLatin-Bold.woff2',
'Lato2OFLWeb/LatoLatin/fonts/LatoLatin-BoldItalic.woff2',

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,92 @@
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION AND CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,14 @@
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('Inter-Regular.woff2') format('woff2');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('Inter-SemiBold.woff2') format('woff2');
}

View File

@@ -1,5 +1,12 @@
import { debugConsole } from '@/utils/debugging'
let hasHandledAutoplayFailure = false
function enableControls(videoEl, reason) {
debugConsole.log(`Enabling video controls: ${reason}`)
videoEl.setAttribute('controls', '')
}
function setup(videoEl) {
const reducedMotionReduce = window.matchMedia(
'(prefers-reduced-motion: reduce)'
@@ -10,7 +17,7 @@ function setup(videoEl) {
// in console, if user seeks the control seek bar relatively fast
// AbortError: The fetching process for the media resource was aborted by the user agent at the user's request.
// this is only a problem in firefox (tested in macOS), chrome and safari is fine
videoEl.setAttribute('controls', '')
enableControls(videoEl, 'reduced motion preference')
return
}
@@ -21,11 +28,29 @@ function setup(videoEl) {
let videoIsVisible
function handleAutoplayFailure(error) {
// Possible HTMLMediaElement.play() errors
// Only show controls for errors where manual play would work
// NotAllowedError: autoplay blocked by browser/device settings
// AbortError: play attempt interrupted by browser
if (error.name === 'NotAllowedError' || error.name === 'AbortError') {
if (!hasHandledAutoplayFailure) {
hasHandledAutoplayFailure = true
document.querySelectorAll('[data-ol-autoplay-video]').forEach(video => {
enableControls(video, `autoplay blocked (${error.name})`)
})
}
} else {
debugConsole.error('Video playback error:', error)
}
}
videoEl.addEventListener('ended', () => {
setTimeout(() => {
videoEl.currentTime = 0
if (videoIsVisible) {
videoEl.play()
if (videoIsVisible && !hasHandledAutoplayFailure) {
videoEl.play().catch(handleAutoplayFailure)
}
}, DELAY_BEFORE_REPLAY)
})
@@ -35,23 +60,18 @@ function setup(videoEl) {
for (const change of changes) {
if (change.isIntersecting) {
videoIsVisible = true
if (videoEl.readyState >= videoEl.HAVE_FUTURE_DATA) {
if (!videoEl.ended) {
videoEl
.play()
.catch(error =>
debugConsole.error('Video autoplay failed:', error)
)
} else {
videoEl
.play()
.catch(error =>
debugConsole.error('Video autoplay failed:', error)
)
}
if (
!hasHandledAutoplayFailure &&
videoEl.readyState >= videoEl.HAVE_FUTURE_DATA
) {
videoEl.play().catch(handleAutoplayFailure)
}
} else {
videoIsVisible = false
// Pause video when it leaves viewport to save resources
if (!videoEl.paused) {
videoEl.pause()
}
}
}
},

View File

@@ -223,7 +223,7 @@ export default function DropdownButton({
key="unlink-user-action"
data-testid="unlink-user-action"
>
{t('unlink_user')}
{t('unlink_from_sso')}
</MenuItemButton>
)
}
@@ -250,7 +250,7 @@ export default function DropdownButton({
data-testid="delete-user-action"
onClick={onDeleteUserClick}
>
{t('delete_user')}
{t('delete_permanently')}
</MenuItemButton>
)
buttons.push(
@@ -259,7 +259,7 @@ export default function DropdownButton({
data-testid="release-user-action"
onClick={onReleaseUserClick}
>
{t('remove_user')}
{t('remove_from_group')}
</MenuItemButton>
)
} else if (!isUserManaged) {

View File

@@ -85,7 +85,7 @@ export default function UnlinkUserModal({
return (
<OLModal show onHide={onClose}>
<OLModalHeader>
<OLModalTitle>{t('unlink_user')}</OLModalTitle>
<OLModalTitle>{t('unlink_from_sso')}</OLModalTitle>
</OLModalHeader>
<OLModalBody>
{hasError && (
@@ -121,7 +121,7 @@ export default function UnlinkUserModal({
onClick={e => handleUnlink(e)}
disabled={unlinkInFlight}
>
{t('unlink_user')}
{t('unlink_from_sso')}
</OLButton>
</OLModalFooter>
</OLModal>

View File

@@ -1,124 +0,0 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import bannerImage from '../../../images/brl-banner.png'
import usePersistedState from '../../../../../shared/hooks/use-persisted-state'
import * as eventTracking from '../../../../../infrastructure/event-tracking'
import {
OLModal,
OLModalBody,
OLModalFooter,
OLModalHeader,
OLModalTitle,
} from '@/shared/components/ol/ol-modal'
import OLButton from '@/shared/components/ol/ol-button'
import { useTranslation } from 'react-i18next'
export default function BRLBanner() {
const { t } = useTranslation()
const [dismissedUntil, setDismissedUntil] = usePersistedState<
Date | undefined
>(`has_dismissed_brl_banner_until`)
const viewEventSent = useRef<boolean>(false)
const [showModal, setShowModal] = useState(true)
useEffect(() => {
if (dismissedUntil && new Date(dismissedUntil) > new Date()) {
return
}
if (!viewEventSent.current) {
eventTracking.sendMB('promo-prompt', {
location: 'dashboard-modal',
name: 'geo-pricing',
page: '/project',
content: 'modal',
country: 'BR',
})
viewEventSent.current = true
}
}, [dismissedUntil])
const handleClick = useCallback(() => {
eventTracking.sendMB('promo-click', {
location: 'dashboard-modal',
name: 'geo-pricing',
page: '/project',
content: 'modal',
country: 'BR',
type: 'click',
})
setShowModal(false)
window.open('/user/subscription/plans')
}, [])
const bannerDismissed = useCallback(() => {
eventTracking.sendMB('promo-dismiss', {
location: 'dashboard-modal',
name: 'geo-pricing',
page: '/project',
content: 'modal',
country: 'BR',
})
const until = new Date()
until.setDate(until.getDate() + 30) // 30 days
setDismissedUntil(until)
}, [setDismissedUntil])
const handleHide = useCallback(() => {
setShowModal(false)
bannerDismissed()
}, [bannerDismissed])
const handleMaybeLater = useCallback(() => {
eventTracking.sendMB('promo-click', {
location: 'dashboard-modal',
name: 'geo-pricing',
page: '/project',
content: 'modal',
country: 'BR',
type: 'pause',
})
setShowModal(false)
const until = new Date()
until.setDate(until.getDate() + 1) // 1 day
setDismissedUntil(until)
}, [setDismissedUntil])
if (dismissedUntil && new Date(dismissedUntil) > new Date()) {
return null
}
return (
<OLModal show={showModal} onHide={handleHide}>
<OLModalHeader>
<OLModalTitle>{t('latam_discount_modal_title')}</OLModalTitle>
</OLModalHeader>
<OLModalBody>
<p>
<img
alt={t('latam_discount_modal_title')}
src={bannerImage}
style={{
width: '100%',
}}
/>
</p>
<p>
{t('latam_discount_modal_info', {
discount: '50',
currencyName: 'Brazilian Reais',
})}
</p>
</OLModalBody>
<OLModalFooter>
<OLButton variant="secondary" onClick={handleMaybeLater}>
{t('maybe_later')}
</OLButton>
<OLButton variant="primary" onClick={handleClick}>
{t('get_discounted_plan')}
</OLButton>
</OLModalFooter>
</OLModal>
)
}

View File

@@ -1,15 +1,12 @@
import getMeta from '@/utils/meta'
import BRLBanner from './ads/brl-banner'
import INRBanner from './ads/inr-banner'
import LATAMBanner from './ads/latam-banner'
function GeoBanners() {
const showInrGeoBanner = getMeta('ol-showInrGeoBanner')
const showBrlGeoBanner = getMeta('ol-showBrlGeoBanner')
const showLATAMBanner = getMeta('ol-showLATAMBanner')
return (
<>
{showBrlGeoBanner && <BRLBanner />}
{showLATAMBanner && <LATAMBanner />}
{showInrGeoBanner && <INRBanner />}
</>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -19,6 +19,7 @@ import {
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'
import { useFeatureFlag } from '@/shared/context/split-test-context'
// TODO: remove this when definitely no longer used
export * from './codemirror-context'
@@ -34,12 +35,15 @@ function CodeMirrorEditor() {
})
const isMounted = useIsMounted()
const editContextEnabled = useFeatureFlag('edit-context')
// create the view using the initial state and intercept transactions
const viewRef = useRef<EditorView | null>(null)
if (viewRef.current === null) {
// @ts-ignore (disable EditContext-based editing until stable)
EditorView.EDIT_CONTEXT = false
if (!editContextEnabled) {
// @ts-expect-error (disable EditContext-based editing until stable)
EditorView.EDIT_CONTEXT = false
}
const view = new EditorView({
state,

View File

@@ -10,6 +10,8 @@ import { WordCountServer } from './word-count-server'
import { WordCountClient } from './word-count-client'
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
import SplitTestBadge from '@/shared/components/split-test-badge'
import { useEffect } from 'react'
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
// NOTE: this component is only mounted when the modal is open
export default function WordCountModalContent({
@@ -19,6 +21,15 @@ export default function WordCountModalContent({
}) {
const { t } = useTranslation()
const { sendEvent } = useEditorAnalytics()
useEffect(() => {
// record when the word count modal is opened
sendEvent('word-count-opened', {
mode: isSplitTestEnabled('word-count-client') ? 'client' : 'server',
})
}, [sendEvent])
return (
<>
<OLModalHeader>

View File

@@ -28,7 +28,8 @@ const en = {
'ask-texgpt': 'Ask TeXGPT',
'synonyms-in-context': 'Synonyms in Context',
translate: 'Translate',
'translate.zh': 'Chinese',
'translate.zh-CN': 'Chinese (Simplified)',
'translate.zh-HANT': 'Chinese (Traditional)',
'translate.en': 'English',
'translate.fr': 'French',
'translate.hi': 'Hindi',
@@ -46,7 +47,7 @@ const en = {
'translate.hu': 'Hungarian',
'translate.it': 'Italian',
'translate.ms': 'Malay',
'translate.fa': 'Persian(Farsi)',
'translate.fa': 'Persian (Farsi)',
'translate.pl': 'Polish',
'translate.ro': 'Romanian',
'translate.ru': 'Russian',
@@ -55,11 +56,22 @@ const en = {
'translate.tr': 'Turkish',
'translate.uk': 'Ukrainian',
'translate.vi': 'Vietnamese',
'translate.pt': 'Portuguese',
'translate.ko': 'Korean',
'translate.id': 'Indonesian',
'translate.other': 'Other',
'translate.not-listed': 'Language not listed?',
'translate.more-languages-coming-soon.title': 'More languages coming soon',
'translate.more-languages-coming-soon.body':
'Sorry, we dont currently offer any other languages for Translate. We will be adding more throughout November so watch this space!',
'translate.request-new-language.title': 'Request a Language',
'translate.request-new-language.body':
"Sorry, we don't currently offer any other languages. Tell us what language you wanted to use and we will review whether we can add it in the future.",
'translate.request-new-language.submit': 'Request',
'translate.request-new-language.thanks.title':
'Thank you for your language request',
'translate.request-new-language.thanks.body':
'Well review your request and consider adding it in the future',
'blocked-suggestion-signpost.question':
'Do you want to permanently stop this suggestion from appearing again?',
'blocked-suggestion-signpost.tooltip': 'You can block a suggestion here.',
@@ -390,7 +402,8 @@ const es = {
'ask-texgpt': 'Preguntar a TeXGPT',
'synonyms-in-context': 'Sinónimos en Contexto',
translate: 'Traducir',
'translate.zh': 'Chino',
'translate.zh-CN': 'Chino (Simplificado)',
'translate.zh-HANT': 'Chino (Tradicional)',
'translate.en': 'Inglés',
'translate.fr': 'Francés',
'translate.hi': 'Hindi',
@@ -417,11 +430,22 @@ const es = {
'translate.tr': 'Turco',
'translate.uk': 'Ucraniano',
'translate.vi': 'Vietnamita',
'translate.pt': 'Portugués',
'translate.ko': 'Coreano',
'translate.id': 'Indonesio',
'translate.other': 'Otro',
'translate.not-listed': '¿Idioma no listado?',
'translate.more-languages-coming-soon.title': 'Más idiomas próximamente',
'translate.more-languages-coming-soon.body':
'Lo sentimos, actualmente no ofrecemos otros idiomas para la traducción. Agregaremos más a lo largo de noviembre, ¡así que mantente atento!',
'translate.request-new-language.title': 'Solicitar un nuevo idioma',
'translate.request-new-language.body':
'Lo sentimos, actualmente no ofrecemos otros idiomas. Dinos qué idioma querías usar y revisaremos si podemos agregarlo en el futuro.',
'translate.request-new-language.submit': 'Solicitar',
'translate.request-new-language.thanks.title':
'Gracias por tu solicitud de idioma',
'translate.request-new-language.thanks.body':
'Revisaremos tu solicitud y consideraremos agregarlo en el futuro',
'blocked-suggestion-signpost.question':
'¿Quieres dejar de ver esta sugerencia permanentemente? Puedes bloquear esta sugerencia aquí.',
'blocked-suggestion-signpost.tooltip':

View File

@@ -253,7 +253,6 @@ export interface Meta {
'ol-shouldAllowEditingDetails': boolean
'ol-shouldLoadHotjar': boolean
'ol-showAiErrorAssistant': boolean
'ol-showBrlGeoBanner': boolean
'ol-showCouponField': boolean
'ol-showGroupDiscount': boolean
'ol-showGroupsAndEnterpriseBanner': boolean

View File

@@ -1,5 +1,6 @@
// Web fonts
@import '../fonts/noto-sans/noto-sans.css';
@import '../fonts/inter/inter.css';
@import '../fonts/dm-mono/dm-mono.css';
@import '../fonts/merriweather/merriweather.css';
@import '../fonts/source-code-pro/source-code-pro.css';

View File

@@ -1016,7 +1016,7 @@
"log_in_with": "Log ind med __provider__",
"log_in_with_a_different_account": "Log ind med en anden konto",
"log_in_with_email": "Log ind med __email__",
"log_in_with_existing_institution_email": "For at forbinde din <b>__appName__</b>- og din <b>__institutionName__</b>-konto, er du nødt til først at logge ind med din eksisterende <b>__appName__</b>-konto.",
"log_in_with_existing_institution_email": "For at forbinde din <0>__appName__</0>- og din <0>__institutionName__</0>-konto, er du nødt til først at logge ind med din eksisterende <0>__appName__</0>-konto.",
"log_in_with_primary_email_address": "Dette bliver e-mailaddressen du skal bruge hvis du logger ind med e-mailaddresse og kode. Vigtige __appName__ beskeder vil blive sendt til denne adresse.",
"log_in_with_sso": "Log ind med SSO",
"log_in_with_sso_email": "Arbejde- eller universitets-e-mailaddresse",
@@ -2003,7 +2003,6 @@
"unlink_provider_account_warning": "Advarsel: Når du afkobler din konto fra __provider__ kan du ikke længere logge ind igennem __provider__.",
"unlink_reference": "Fjern link til reference udbyder",
"unlink_the_project_from_the_current_github_repo": "Afkobl projektet fra det nuværende GitHub repository og opret en forbindelse til et repository du ejer. (Der kræves et aktivt __appName__ abonnement for at sætte GitHub synkronisering op).",
"unlink_user": "Afkobl bruger",
"unlink_users": "Afkobl brugere",
"unlink_warning_reference": "Advarsel: Når du fjerner linket til denne udbyder fra din konto, vil du ikke længere have mulighed for at importere referencer ind i dine projekter.",
"unlinking": "Fjerner forbindelse",

View File

@@ -744,7 +744,7 @@
"log_in_first_to_proceed": "Du musst dich zuerst <b>anmelden</b>, um fortzufahren.",
"log_in_with": "Einloggen mit __provider__",
"log_in_with_email": "Melde dich mit __email__ an",
"log_in_with_existing_institution_email": "Bitte melde dich mit deinem bestehenden <b>__appName__</b>-Konto an, um deine institutionellen Konten <b>__appName__</b> und <b>__institutionName__</b> zu verknüpfen.",
"log_in_with_existing_institution_email": "Bitte melde dich mit deinem bestehenden <0>__appName__</0>-Konto an, um deine institutionellen Konten <0>__appName__</0> und <0>__institutionName__</0> zu verknüpfen.",
"log_out": "Abmelden",
"log_out_from": "Von __email__ abmelden",
"log_viewer_error": "Beim Anzeigen der Kompilierfehler und -protokolle dieses Projekts ist ein Problem aufgetreten.",

View File

@@ -252,7 +252,6 @@
"bold": "Bold",
"booktabs": "Booktabs",
"breadcrumbs": "Breadcrumbs",
"brl_discount_offer_plans_page_banner": "__flag__ <b>Great news!</b> Weve applied a 50% discount to premium plans on this page for our users in Brazil. Check out the new lower prices.",
"browser": "Browser",
"built_in": "Built-In",
"bullet_list": "Bullet list",
@@ -535,6 +534,7 @@
"delete_figure": "Delete figure",
"delete_message": "Delete message",
"delete_message_confirmation": "Are you sure you want to delete this message? This cant be undone.",
"delete_permanently": "Delete permanently",
"delete_projects": "Delete Projects",
"delete_row_or_column": "Delete row or column",
"delete_sso_config": "Delete SSO configuration",
@@ -1304,7 +1304,7 @@
"log_in_with": "Log in with __provider__",
"log_in_with_a_different_account": "Log in with a different account",
"log_in_with_email": "Log in with __email__",
"log_in_with_existing_institution_email": "Please log in with your existing <b>__appName__</b> account in order to get your <b>__appName__</b> and <b>__institutionName__</b> institutional accounts linked.",
"log_in_with_existing_institution_email": "Please log in with your existing <0>__appName__</0> account in order to get your <0>__appName__</0> and <0>__institutionName__</0> institutional accounts linked.",
"log_in_with_primary_email_address": "This will be the email address to use if you log in with an email address and password. Important __appName__ notifications will be sent to this email address.",
"log_in_with_sso": "Log in with SSO",
"log_in_with_sso_email": "Work or university email address",
@@ -2565,6 +2565,7 @@
"unlink_all_users_explanation": "Youre about to remove the SSO login option for all users in your group. If SSO is enabled, this will force users to reauthenticate their Overleaf accounts with your IdP. Theyll receive an email asking them to do this.",
"unlink_dropbox_folder": "Unlink Dropbox Account",
"unlink_dropbox_warning": "Any projects that you have synced with Dropbox will be disconnected and no longer kept in sync with Dropbox. Are you sure you want to unlink your Dropbox account?",
"unlink_from_sso": "Unlink from SSO",
"unlink_github_repository": "Unlink GitHub repository",
"unlink_github_warning": "Any projects that you have synced with GitHub will be disconnected and no longer kept in sync with GitHub. Are you sure you want to unlink your GitHub account?",
"unlink_linked_accounts": "Unlink any linked accounts (such as ORCID ID, IEEE). <0>Remove them in Account settings (under Linked Accounts).</0>",
@@ -2573,7 +2574,6 @@
"unlink_provider_account_warning": "Warning: When you unlink your account from __provider__ you will not be able to sign in using __provider__ anymore.",
"unlink_reference": "Unlink References Provider",
"unlink_the_project_from_the_current_github_repo": "Unlink the project from the current GitHub repository and create a connection to a repository you own. (You need an active __appName__ subscription to set up a GitHub Sync).",
"unlink_user": "Unlink user",
"unlink_user_explanation": "Youre about to remove the SSO login option for <0>__email__</0>. This will force them to reauthenticate their Overleaf account with your IdP. Theyll receive an email asking them to do this.",
"unlink_users": "Unlink users",
"unlink_warning_reference": "Warning: When you unlink your account from this provider you will not be able to import references into your projects.",

View File

@@ -170,7 +170,6 @@
"blank_project": "Proyecto vacío",
"blocked_filename": "Este nombre de archivo está bloqueado.",
"blog": "Blog",
"brl_discount_offer_plans_page_banner": "__flag__ <b>¡Grandes noticias!</b> Hemos aplicado un 50% de descuento a los planes premium de esta página para nuestros usuarios en Brasil. Echa un vistazo a los nuevos precios más bajos.",
"browser": "Navegador",
"built_in": "Integrado",
"buy_now_no_exclamation_mark": "Comprar ahora",

View File

@@ -688,7 +688,7 @@
"log_in_first_to_proceed": "Vous aurez besoin de <b>vous connecter</b> avant de poursuivre.",
"log_in_with": "Se connecter avec __provider__",
"log_in_with_email": "Se connecter avec __email__",
"log_in_with_existing_institution_email": "Veuillez vous connecter sur votre compte <b>__appName__</b> existant afin de lier vos comptes institutionnels <b>__appName__</b> et <b>__institutionName__</b>.",
"log_in_with_existing_institution_email": "Veuillez vous connecter sur votre compte <0>__appName__</0> existant afin de lier vos comptes institutionnels <0>__appName__</0> et <0>__institutionName__</0>.",
"log_out": "Déconnexion",
"log_out_from": "Se déconnecter de __email__",
"logged_in_with_email": "Vous êtes actuellement connecté à <b>__appName__</b> avec ladresse <b>__email__</b>.",

View File

@@ -72,7 +72,6 @@
"bibliographies": "Bibliografia",
"blank_project": "Projeto Em Branco",
"blog": "Blog",
"brl_discount_offer_plans_page_banner": "__flag__ Aplicamos um desconto de 50% aos planos premium nesta página para nossos usuários no Brasil. Confira os novos preços mais baixos.",
"built_in": "Embutido",
"by": "por",
"cancel": "Cancelar",

View File

@@ -451,7 +451,7 @@
"log_in_first_to_proceed": "Du måste först <b>logga in</b> för att fortsätta.",
"log_in_with": "Logga in med __provider__",
"log_in_with_email": "Logga in med __email__",
"log_in_with_existing_institution_email": "Vänligen logga in med ditt befintliga <b>__appName__</b>-konto för att länka kontot <b>__appName__</b> och det institutionella kontot <b>__institutionName__ </b>.",
"log_in_with_existing_institution_email": "Vänligen logga in med ditt befintliga <0>__appName__</0>-konto för att länka kontot <0>__appName__</0> och det institutionella kontot <0>__institutionName__ </0>.",
"log_out": "Logga ut",
"log_out_from": "Logga ut från __email__",
"logged_in_with_email": "Du är för närvarande inloggad i <b>__appName__</b> med e-postadressen <b>__email__</b>.",

View File

@@ -231,7 +231,6 @@
"blocked_filename": "此文件名被阻止。",
"blog": "博客",
"bold": "粗体",
"brl_discount_offer_plans_page_banner": "__flag__<b>好消息</b> 我们为巴西用户在本页面上的高级计划提供了50%的折扣。看看新的低价。",
"browser": "浏览器",
"built_in": "内嵌",
"bullet_list": "项目符号列表",
@@ -1214,7 +1213,7 @@
"log_in_with": "用 __provider__ 账户登陆",
"log_in_with_a_different_account": "以另外一个账户登录",
"log_in_with_email": "使用 __email__ 登录",
"log_in_with_existing_institution_email": "请使用您现有的 <b>__appName__</b> 帐户登录,以便将您的<b>__appName__</b>和<b>__institutionName__</b> 机构帐户关联起来。",
"log_in_with_existing_institution_email": "请使用您现有的 <0>__appName__</0> 帐户登录,以便将您的<0>__appName__</0>和<0>__institutionName__</0> 机构帐户关联起来。",
"log_in_with_primary_email_address": "如果您使用电子邮件地址和密码登录,这将是要使用的电子邮件地址。 重要的 __appName__ 通知将发送到此电子邮件地址。",
"log_in_with_sso": "通过 SSO 登录",
"log_in_with_sso_email": "工作或大学电子邮件账户",
@@ -2396,7 +2395,6 @@
"unlink_provider_account_warning": "警告:当您取消帐户与 __provider__ 的链接后,您将无法再使用 __provider__ 登录。",
"unlink_reference": "取消关联参考文献提供者",
"unlink_the_project_from_the_current_github_repo": "取消项目与当前 GitHub 存储库的链接,并创建与您拥有的存储库的连接。 (您需要有效的 __appName__ 订阅才能设置 GitHub 同步)。",
"unlink_user": "取消链接用户",
"unlink_user_explanation": "您即将删除 <0>__email__</0> 的 SSO 登录选项。 这将迫使他们向您的 IdP 重新验证其 Overleaf 帐户。 他们会收到一封电子邮件,要求他们这样做。",
"unlink_users": "取消用户链接",
"unlink_warning_reference": "警告:如果将账户与此提供者取消关联,您将无法把参考文献导入到项目中。",

View File

@@ -132,6 +132,7 @@
"express-bearer-token": "^2.4.0",
"express-http-proxy": "^1.6.0",
"express-session": "^1.17.1",
"file-type": "^21.0.0",
"globby": "^5.0.0",
"helmet": "^6.0.1",
"https-proxy-agent": "^7.0.6",

View File

@@ -274,7 +274,7 @@ describe('MembersList', function () {
it('should show successs notification and update the user row after unlinking', function () {
cy.findByRole('dialog').within(() => {
cy.findByRole('button', { name: /unlink user/i }).click()
cy.findByRole('button', { name: /unlink from sso/i }).click()
})
cy.findByRole('alert').should(
'contain.text',
@@ -314,7 +314,7 @@ describe('MembersList', function () {
it('should show successs notification and update the user row after unlinking', function () {
cy.findByRole('dialog').within(() => {
cy.findByRole('button', { name: /unlink user/i }).click()
cy.findByRole('button', { name: /unlink from sso/i }).click()
})
cy.findByRole('alert').should(
'contain.text',
@@ -347,7 +347,7 @@ describe('MembersList', function () {
it('should show successs notification and update the user row after unlinking', function () {
cy.findByRole('dialog').within(() => {
cy.findByRole('button', { name: /unlink user/i }).click()
cy.findByRole('button', { name: /unlink from sso/i }).click()
})
cy.findByRole('alert').should(
'contain.text',

View File

@@ -35,7 +35,7 @@ describe('<UnlinkUserModal />', function () {
it('displays the modal', async function () {
renderWithContext(<UnlinkUserModal {...defaultProps} />)
await screen.findByRole('heading', {
name: 'Unlink user',
name: 'Unlink from SSO',
})
screen.getByText('Youre about to remove the SSO login option for', {
exact: false,
@@ -47,10 +47,12 @@ describe('<UnlinkUserModal />', function () {
renderWithContext(<UnlinkUserModal {...defaultProps} />)
await screen.findByRole('heading', {
name: 'Unlink user',
name: 'Unlink from SSO',
})
const confirmButton = screen.getByRole('button', { name: 'Unlink user' })
const confirmButton = screen.getByRole('button', {
name: 'Unlink from SSO',
})
fireEvent.click(confirmButton)
await waitFor(() => expect(defaultProps.onClose).to.have.been.called)
@@ -70,10 +72,12 @@ describe('<UnlinkUserModal />', function () {
renderWithContext(<UnlinkUserModal {...defaultProps} />)
await screen.findByRole('heading', {
name: 'Unlink user',
name: 'Unlink from SSO',
})
const confirmButton = screen.getByRole('button', { name: 'Unlink user' })
const confirmButton = screen.getByRole('button', {
name: 'Unlink from SSO',
})
fireEvent.click(confirmButton)
await waitFor(() => screen.findByText('Sorry, something went wrong'))

View File

@@ -55,17 +55,17 @@ describe('ClsiCookieManager', function () {
ctx.project_id,
ctx.user_id,
'',
'n2d'
'c3d'
)
ctx.redis.get
.calledWith(`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`)
.calledWith(`clsiserver:c3d:${ctx.project_id}:${ctx.user_id}`)
.should.equal(true)
serverId.should.equal('clsi-7')
})
it('should fallback to old key', async function (ctx) {
ctx.redis.get
.withArgs(`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`)
.withArgs(`clsiserver:c3d:${ctx.project_id}:${ctx.user_id}`)
.resolves(null)
ctx.redis.get
.withArgs(`clsiserver:${ctx.project_id}:${ctx.user_id}`)
@@ -74,10 +74,10 @@ describe('ClsiCookieManager', function () {
ctx.project_id,
ctx.user_id,
'',
'n2d'
'c3d'
)
ctx.redis.get
.calledWith(`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`)
.calledWith(`clsiserver:c3d:${ctx.project_id}:${ctx.user_id}`)
.should.equal(true)
ctx.redis.get
.calledWith(`clsiserver:${ctx.project_id}:${ctx.user_id}`)
@@ -109,7 +109,7 @@ describe('ClsiCookieManager', function () {
ctx.project_id,
ctx.user_id,
'',
'n2d'
'c3d'
)
ctx.ClsiCookieManager.promises._populateServerIdViaRequest
.calledWith(ctx.project_id, ctx.user_id)
@@ -140,13 +140,13 @@ describe('ClsiCookieManager', function () {
ctx.project_id,
ctx.user_id,
'standard',
'n2d'
'c3d'
)
const args = ctx.ClsiCookieManager.promises.setServerId.args[0]
args[0].should.equal(ctx.project_id)
args[1].should.equal(ctx.user_id)
args[2].should.equal('standard')
args[3].should.equal('n2d')
args[3].should.equal('c3d')
args[4].should.deep.equal(ctx.clsiServerId)
})
@@ -156,7 +156,7 @@ describe('ClsiCookieManager', function () {
ctx.project_id,
ctx.user_id,
'',
'n2d'
'c3d'
)
serverId.should.equal(ctx.clsiServerId)
})
@@ -175,7 +175,7 @@ describe('ClsiCookieManager', function () {
ctx.project_id,
ctx.user_id,
'standard',
'n2d',
'c3d',
ctx.clsiServerId,
null
)
@@ -189,10 +189,10 @@ describe('ClsiCookieManager', function () {
await ctx.ClsiCookieManager.promises.clearServerId(
ctx.project_id,
ctx.user_id,
'n2d'
'c3d'
)
ctx.redis.del.should.have.been.calledWith(
`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`,
`clsiserver:c3d:${ctx.project_id}:${ctx.user_id}`,
`clsiserver:${ctx.project_id}:${ctx.user_id}`
)
})
@@ -211,12 +211,12 @@ describe('ClsiCookieManager', function () {
ctx.project_id,
ctx.user_id,
'standard',
'n2d',
'c3d',
ctx.clsiServerId,
null
)
ctx.redis.setex.should.have.been.calledWith(
`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`,
`clsiserver:c3d:${ctx.project_id}:${ctx.user_id}`,
ctx.settings.clsiCookie.ttlInSeconds,
ctx.clsiServerId
)
@@ -228,12 +228,12 @@ describe('ClsiCookieManager', function () {
ctx.project_id,
ctx.user_id,
'standard',
'n2d',
'c3d',
ctx.clsiServerId,
null
)
expect(ctx.redis.setex).to.have.been.calledWith(
`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`,
`clsiserver:c3d:${ctx.project_id}:${ctx.user_id}`,
ctx.settings.clsiCookie.ttlInSecondsRegular,
ctx.clsiServerId
)
@@ -256,7 +256,7 @@ describe('ClsiCookieManager', function () {
ctx.project_id,
ctx.user_id,
'standard',
'n2d',
'c3d',
ctx.clsiServerId,
null
)
@@ -281,12 +281,12 @@ describe('ClsiCookieManager', function () {
ctx.project_id,
ctx.user_id,
'standard',
'n2d',
'c3d',
ctx.clsiServerId,
null
)
ctx.redis_secondary.setex.should.have.been.calledWith(
`clsiserver:n2d:${ctx.project_id}:${ctx.user_id}`,
`clsiserver:c3d:${ctx.project_id}:${ctx.user_id}`,
ctx.settings.clsiCookie.ttlInSeconds,
ctx.clsiServerId
)
@@ -300,14 +300,14 @@ describe('ClsiCookieManager', function () {
ctx.project_id,
ctx.user_id,
'standard',
'n2d',
'c3d',
ctx.clsiServerId,
'previous-clsi-server-id'
)
expect(
ctx.fetchUtils.fetchStringWithResponse
).to.have.been.calledWith(
`${ctx.settings.apis.clsi.url}/instance-state?clsiserverid=previous-clsi-server-id&compileGroup=standard&compileBackendClass=n2d`,
`${ctx.settings.apis.clsi.url}/instance-state?clsiserverid=previous-clsi-server-id&compileGroup=standard&compileBackendClass=c3d`,
{ method: 'GET', signal: sinon.match.instanceOf(AbortSignal) }
)
}

View File

@@ -149,7 +149,7 @@ describe('ClsiManager', function () {
},
clsi: {
url: `http://${CLSI_HOST}`,
submissionBackendClass: 'n2d',
submissionBackendClass: 'c3d',
},
clsi_new: {
sample: 100,
@@ -280,7 +280,7 @@ describe('ClsiManager', function () {
ctx.project._id,
ctx.user_id,
{
compileBackendClass: 'n2d',
compileBackendClass: 'c3d',
compileGroup: 'standard',
timeout: ctx.timeout,
}
@@ -294,7 +294,7 @@ describe('ClsiManager', function () {
url.host === CLSI_HOST &&
url.pathname ===
`/project/${ctx.project._id}/user/${ctx.user_id}/compile` &&
url.searchParams.get('compileBackendClass') === 'n2d' &&
url.searchParams.get('compileBackendClass') === 'c3d' &&
url.searchParams.get('compileGroup') === 'standard'
),
{
@@ -374,7 +374,7 @@ describe('ClsiManager', function () {
ctx.project._id,
ctx.user_id,
'standard',
'n2d',
'c3d',
ctx.newClsiServerId
)
})
@@ -417,7 +417,7 @@ describe('ClsiManager', function () {
ctx.result = await ctx.ClsiManager.promises.sendRequest(
ctx.project._id,
ctx.user_id,
{ compileBackendClass: 'n2d', compileGroup: 'standard' }
{ compileBackendClass: 'c3d', compileGroup: 'standard' }
)
})
@@ -452,7 +452,7 @@ describe('ClsiManager', function () {
{
timeout: 100,
incrementalCompilesEnabled: true,
compileBackendClass: 'n2d',
compileBackendClass: 'c3d',
compileGroup: 'priority',
compileFromClsiCache: true,
populateClsiCache: true,
@@ -499,7 +499,7 @@ describe('ClsiManager', function () {
url.hostname === CLSI_HOST &&
url.pathname ===
`/project/${ctx.project._id}/user/${ctx.user_id}/compile` &&
url.searchParams.get('compileBackendClass') === 'n2d' &&
url.searchParams.get('compileBackendClass') === 'c3d' &&
url.searchParams.get('compileGroup') === 'priority'
),
{
@@ -798,7 +798,7 @@ describe('ClsiManager', function () {
ctx.result = await ctx.ClsiManager.promises.sendRequest(
ctx.project._id,
ctx.user_id,
{ compileBackendClass: 'n2d' }
{ compileBackendClass: 'c3d' }
)
})
@@ -823,7 +823,7 @@ describe('ClsiManager', function () {
it('should clear the CLSI server id cookie', function (ctx) {
expect(
ctx.ClsiCookieManager.promises.clearServerId
).to.have.been.calledWith(ctx.project._id, ctx.user_id, 'n2d')
).to.have.been.calledWith(ctx.project._id, ctx.user_id, 'c3d')
})
it('should return a success status', function (ctx) {
@@ -930,7 +930,7 @@ describe('ClsiManager', function () {
ctx.result = await ctx.ClsiManager.promises.sendExternalRequest(
ctx.submissionId,
ctx.clsiRequest,
{ compileBackendClass: 'n2d', compileGroup: 'standard' }
{ compileBackendClass: 'c3d', compileGroup: 'standard' }
)
})
@@ -940,7 +940,7 @@ describe('ClsiManager', function () {
url =>
url.host === CLSI_HOST &&
url.pathname === `/project/${ctx.submissionId}/compile` &&
url.searchParams.get('compileBackendClass') === 'n2d' &&
url.searchParams.get('compileBackendClass') === 'c3d' &&
url.searchParams.get('compileGroup') === 'standard'
),
{
@@ -1005,7 +1005,7 @@ describe('ClsiManager', function () {
await ctx.ClsiManager.promises.deleteAuxFiles(
ctx.project._id,
ctx.user_id,
{ compileBackendClass: 'n2d', compileGroup: 'standard' },
{ compileBackendClass: 'c3d', compileGroup: 'standard' },
'node-1'
)
})
@@ -1017,7 +1017,7 @@ describe('ClsiManager', function () {
url.host === CLSI_HOST &&
url.pathname ===
`/project/${ctx.project._id}/user/${ctx.user_id}` &&
url.searchParams.get('compileBackendClass') === 'n2d' &&
url.searchParams.get('compileBackendClass') === 'c3d' &&
url.searchParams.get('compileGroup') === 'standard' &&
url.searchParams.get('clsiserverid') === 'node-1'
),
@@ -1039,7 +1039,7 @@ describe('ClsiManager', function () {
it('should clear the clsi persistance', function (ctx) {
ctx.ClsiCookieManager.promises.clearServerId
.calledWith(ctx.project._id, ctx.user_id, 'n2d')
.calledWith(ctx.project._id, ctx.user_id, 'c3d')
.should.equal(true)
})
@@ -1107,7 +1107,7 @@ describe('ClsiManager', function () {
ctx.project._id,
ctx.user_id,
false,
{ compileBackendClass: 'n2d', compileGroup: 'standard' },
{ compileBackendClass: 'c3d', compileGroup: 'standard' },
'node-1'
)
})
@@ -1117,7 +1117,7 @@ describe('ClsiManager', function () {
sinon.match(
url =>
url.toString() ===
`http://clsi.example.com/project/${ctx.project._id}/user/${ctx.user_id}/wordcount?compileBackendClass=n2d&compileGroup=standard&file=main.tex&image=mock-image-name&clsiserverid=node-1`
`http://clsi.example.com/project/${ctx.project._id}/user/${ctx.user_id}/wordcount?compileBackendClass=c3d&compileGroup=standard&file=main.tex&image=mock-image-name&clsiserverid=node-1`
)
)
})
@@ -1134,7 +1134,7 @@ describe('ClsiManager', function () {
ctx.project._id,
ctx.user_id,
'other.tex',
{ compileBackendClass: 'n2d', compileGroup: 'standard' },
{ compileBackendClass: 'c3d', compileGroup: 'standard' },
'node-2'
)
})
@@ -1146,7 +1146,7 @@ describe('ClsiManager', function () {
url.host === CLSI_HOST &&
url.pathname ===
`/project/${ctx.project._id}/user/${ctx.user_id}/wordcount` &&
url.searchParams.get('compileBackendClass') === 'n2d' &&
url.searchParams.get('compileBackendClass') === 'c3d' &&
url.searchParams.get('compileGroup') === 'standard' &&
url.searchParams.get('clsiserverid') === 'node-2' &&
url.searchParams.get('file') === 'other.tex' &&

View File

@@ -40,7 +40,7 @@ describe('CompileController', function () {
apis: {
clsi: {
url: 'http://clsi.example.com',
submissionBackendClass: 'n2d',
submissionBackendClass: 'c3d',
},
clsi_priority: {
url: 'http://clsi-priority.example.com',
@@ -442,7 +442,7 @@ describe('CompileController', function () {
ctx.ClsiManager.promises.sendExternalRequest.should.have.been.calledWith(
ctx.submission_id,
{ compileGroup: 'special', timeout: 600 },
{ compileGroup: 'special', compileBackendClass: 'n2d', timeout: 600 }
{ compileGroup: 'special', compileBackendClass: 'c3d', timeout: 600 }
)
})
})
@@ -473,7 +473,7 @@ describe('CompileController', function () {
draft: true,
check: 'validate',
compileGroup: 'standard',
compileBackendClass: 'n2d',
compileBackendClass: 'c3d',
timeout: 60,
}
)
@@ -606,7 +606,7 @@ describe('CompileController', function () {
'output-file',
ctx.expected_url,
{},
{ compileGroup: 'standard', compileBackendClass: 'n2d' }
{ compileGroup: 'standard', compileBackendClass: 'c3d' }
)
})
})
@@ -629,7 +629,7 @@ describe('CompileController', function () {
{},
{
compileGroup: 'special',
compileBackendClass: 'n2d',
compileBackendClass: 'c3d',
}
)
})
@@ -753,7 +753,7 @@ describe('CompileController', function () {
.stub()
.resolves({
compileGroup: 'standard',
compileBackendClass: 'n2d',
compileBackendClass: 'c3d',
})
await ctx.CompileController._proxyToClsi(
ctx.projectId,
@@ -768,7 +768,7 @@ describe('CompileController', function () {
it('should open a request to the CLSI', function (ctx) {
ctx.fetchUtils.fetchStreamWithResponse.should.have.been.calledWith(
`${ctx.settings.apis.clsi.url}${ctx.url}?compileGroup=standard&compileBackendClass=n2d&query=foo`
`${ctx.settings.apis.clsi.url}${ctx.url}?compileGroup=standard&compileBackendClass=c3d&query=foo`
)
})
@@ -810,7 +810,7 @@ describe('CompileController', function () {
.stub()
.resolves({
compileGroup: 'standard',
compileBackendClass: 'n2d',
compileBackendClass: 'c3d',
})
await ctx.CompileController._proxyToClsi(
ctx.projectId,
@@ -825,7 +825,7 @@ describe('CompileController', function () {
it('should open a request to the CLSI', function (ctx) {
ctx.fetchUtils.fetchStreamWithResponse.should.have.been.calledWith(
`${ctx.settings.apis.clsi.url}${ctx.url}?compileGroup=standard&compileBackendClass=n2d`
`${ctx.settings.apis.clsi.url}${ctx.url}?compileGroup=standard&compileBackendClass=c3d`
)
})
@@ -841,7 +841,7 @@ describe('CompileController', function () {
.stub()
.resolves({
compileGroup: 'standard',
compileBackendClass: 'n2d',
compileBackendClass: 'c3d',
})
await ctx.CompileController._proxyToClsi(
ctx.projectId,
@@ -856,7 +856,7 @@ describe('CompileController', function () {
it('should proxy to the standard url', function (ctx) {
ctx.fetchUtils.fetchStreamWithResponse.should.have.been.calledWith(
`${ctx.settings.apis.clsi.url}${ctx.url}?compileGroup=standard&compileBackendClass=n2d`
`${ctx.settings.apis.clsi.url}${ctx.url}?compileGroup=standard&compileBackendClass=c3d`
)
})
})
@@ -867,7 +867,7 @@ describe('CompileController', function () {
.stub()
.resolves({
compileGroup: 'standard',
compileBackendClass: 'n2d',
compileBackendClass: 'c3d',
})
ctx.req.query = { build: 1234 }
await ctx.CompileController._proxyToClsi(
@@ -883,7 +883,7 @@ describe('CompileController', function () {
it('should proxy to the standard url without the build parameter', function (ctx) {
ctx.fetchUtils.fetchStreamWithResponse.should.have.been.calledWith(
`${ctx.settings.apis.clsi.url}${ctx.url}?compileGroup=standard&compileBackendClass=n2d`
`${ctx.settings.apis.clsi.url}${ctx.url}?compileGroup=standard&compileBackendClass=c3d`
)
})
})

View File

@@ -19,7 +19,7 @@ describe('CompileManager', function () {
vi.doMock('@overleaf/settings', () => ({
default: (ctx.settings = {
apis: {
clsi: { submissionBackendClass: 'n2d' },
clsi: { submissionBackendClass: 'c3d' },
},
redis: { web: { host: '127.0.0.1', port: 42 } },
rateLimit: { autoCompile: {} },

View File

@@ -0,0 +1,44 @@
/* eslint-disable no-unused-vars */
import Helpers from './lib/helpers.mjs'
import { getCollectionInternal } from './lib/mongodb.mjs'
const tags = ['server-pro', 'saas']
const oldIndexes = [
{
key: {
scheduledAt: 1,
},
name: 'scheduledAt_1',
expireAfterSeconds: 60 * 60 * 24, // expire after 24 hours
},
]
const newIndexes = [
{
key: {
recipient_id: 1,
},
name: 'recipientId_1',
expireAfterSeconds: 60 * 60 * 24, // expire after 24 hours
},
]
const migrate = async client => {
const emailNotifications = await getCollectionInternal('emailNotifications')
await Helpers.dropIndexesFromCollection(emailNotifications, oldIndexes)
await Helpers.addIndexesToCollection(emailNotifications, newIndexes)
}
const rollback = async client => {
const emailNotifications = await getCollectionInternal('emailNotifications')
await Helpers.dropIndexesFromCollection(emailNotifications, newIndexes)
await Helpers.addIndexesToCollection(emailNotifications, oldIndexes)
}
export default {
tags,
migrate,
rollback,
}